diff --git a/crates/pool/src/mempool/paymaster.rs b/crates/pool/src/mempool/paymaster.rs index 8fbc73b28..28599459d 100644 --- a/crates/pool/src/mempool/paymaster.rs +++ b/crates/pool/src/mempool/paymaster.rs @@ -523,9 +523,8 @@ impl PaymasterBalance { #[cfg(test)] mod tests { use ethers::types::{Address, H256, U256}; - use rundler_provider::MockEntryPointV0_6; + use rundler_provider::{DepositInfo, MockEntryPointV0_6}; use rundler_types::{ - contracts::v0_6::verifying_paymaster::DepositInfo, pool::{PaymasterMetadata, PoolOperation}, v0_6::UserOperation, EntityInfos, UserOperation as UserOperationTrait, UserOperationId, ValidTimeRange, @@ -971,7 +970,7 @@ mod tests { entrypoint.expect_get_deposit_info().returning(|_| { Ok(DepositInfo { - deposit: 1000, + deposit: 1000.into(), staked: true, stake: 10000, unstake_delay_sec: 100, diff --git a/crates/pool/src/mempool/uo_pool.rs b/crates/pool/src/mempool/uo_pool.rs index 9af9c3794..23622ceb6 100644 --- a/crates/pool/src/mempool/uo_pool.rs +++ b/crates/pool/src/mempool/uo_pool.rs @@ -681,13 +681,12 @@ mod tests { use std::collections::HashMap; use ethers::types::{Bytes, H160}; - use rundler_provider::MockEntryPointV0_6; + use rundler_provider::{DepositInfo, MockEntryPointV0_6}; use rundler_sim::{ MockPrechecker, MockSimulator, PrecheckError, PrecheckSettings, SimulationError, SimulationResult, SimulationSettings, ViolationError, }; use rundler_types::{ - contracts::v0_6::verifying_paymaster::DepositInfo, pool::{PrecheckViolation, SimulationViolation}, v0_6::UserOperation, EntityInfo, EntityInfos, EntityType, EntryPointVersion, GasFees, @@ -1428,7 +1427,7 @@ mod tests { let mut entrypoint = MockEntryPointV0_6::new(); entrypoint.expect_get_deposit_info().returning(|_| { Ok(DepositInfo { - deposit: 1000, + deposit: 1000.into(), staked: true, stake: 10000, unstake_delay_sec: 100, diff --git a/crates/provider/src/ethers/entry_point/mod.rs b/crates/provider/src/ethers/entry_point/mod.rs index b93f2790a..d90460131 100644 --- a/crates/provider/src/ethers/entry_point/mod.rs +++ b/crates/provider/src/ethers/entry_point/mod.rs @@ -11,4 +11,48 @@ // 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::Middleware, + types::{Address, Bytes, Eip1559TransactionRequest, U256, U64}, +}; +use rundler_types::contracts::{ + arbitrum::node_interface::NodeInterface, optimism::gas_price_oracle::GasPriceOracle, +}; + pub(crate) mod v0_6; +pub(crate) mod v0_7; + +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 index 1738c69a4..9d9ce615b 100644 --- a/crates/provider/src/ethers/entry_point/v0_6.rs +++ b/crates/provider/src/ethers/entry_point/v0_6.rs @@ -20,7 +20,7 @@ use ethers::{ providers::{spoof, Middleware, RawCall}, types::{ transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Eip1559TransactionRequest, - H160, H256, U256, U64, + H160, H256, U256, }, utils::hex, }; @@ -31,18 +31,22 @@ use rundler_types::{ v0_6::{ get_balances::{GetBalancesResult, GETBALANCES_BYTECODE}, i_aggregator::IAggregator, - i_entry_point::{ExecutionResult, FailedOp, IEntryPoint, SignatureValidationFailed}, + i_entry_point::{ + DepositInfo as DepositInfoV0_6, ExecutionResult as ExecutionResultV0_6, FailedOp, + IEntryPoint, SignatureValidationFailed, + }, shared_types::UserOpsPerAggregator as UserOpsPerAggregatorV0_6, }, }, v0_6::UserOperation, - DepositInfoV0_6, GasFees, UserOpsPerAggregator, ValidationOutput, + GasFees, UserOpsPerAggregator, ValidationOutput, }; use rundler_utils::eth::{self, ContractRevertError}; use crate::{ - traits::HandleOpsOut, AggregatorOut, AggregatorSimOut, BundleHandler, EntryPointProvider, - L1GasProvider, Provider, SignatureAggregator, SimulationProvider, + traits::HandleOpsOut, AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, + EntryPointProvider, ExecutionResult, L1GasProvider, Provider, SignatureAggregator, + SimulationProvider, }; const ARBITRUM_NITRO_NODE_INTERFACE_ADDRESS: Address = H160([ @@ -119,12 +123,13 @@ where .context("entry point should return balance") } - async fn get_deposit_info(&self, address: Address) -> anyhow::Result { + 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")?) + .context("should get deposit info")? + .into()) } async fn get_balances(&self, addresses: Vec

) -> anyhow::Result> { @@ -265,12 +270,7 @@ where .calldata() .context("should get calldata for entry point handle ops")?; - let gas = self - .arb_node - .gas_estimate_l1_component(entry_point_address, false, data) - .call() - .await?; - Ok(U256::from(gas.0)) + super::estimate_arbitrum_l1_gas(&self.arb_node, entry_point_address, data).await } async fn calc_optimism_l1_gas( @@ -285,21 +285,8 @@ where .calldata() .context("should get calldata for entry point handle ops")?; - // construct an unsigned transaction with default values just for L1 gas estimation - let tx = Eip1559TransactionRequest::new() - .from(Address::random()) - .to(entry_point_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 = self.opt_gas_oracle.get_l1_fee(tx).call().await?; - Ok(l1_fee.checked_div(gas_price).unwrap_or(U256::MAX)) + super::estimate_optimism_l1_gas(&self.opt_gas_oracle, entry_point_address, data, gas_price) + .await } } @@ -310,18 +297,18 @@ where { type UO = UserOperation; - async fn get_simulate_validation_call( + fn get_tracer_simulate_validation_call( &self, user_op: UserOperation, max_validation_gas: u64, - ) -> anyhow::Result { + ) -> (TypedTransaction, spoof::State) { let pvg = user_op.pre_verification_gas; - let tx = self + let call = self .i_entry_point .simulate_validation(user_op) .gas(U256::from(max_validation_gas) + pvg) .tx; - Ok(tx) + (call, spoof::State::default()) } async fn call_simulate_validation( @@ -338,7 +325,7 @@ where .await { Ok(()) => anyhow::bail!("simulateValidation should always revert"), - Err(ContractError::Revert(revert_data)) => ValidationOutput::decode(revert_data) + Err(ContractError::Revert(revert_data)) => ValidationOutput::decode_v0_6(revert_data) .context("entry point should return validation output"), Err(error) => Err(error).context("call simulation RPC failed")?, } @@ -348,8 +335,8 @@ where &self, revert_data: Bytes, ) -> Result { - if let Ok(result) = ExecutionResult::decode(&revert_data) { - Ok(result) + if let Ok(result) = ExecutionResultV0_6::decode(&revert_data) { + Ok(result.into()) } else if let Ok(failed_op) = FailedOp::decode(&revert_data) { Err(failed_op.reason) } else if let Ok(err) = ContractRevertError::decode(&revert_data) { @@ -411,3 +398,28 @@ fn get_handle_ops_call( }; 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 new file mode 100644 index 000000000..ef08cc44f --- /dev/null +++ b/crates/provider/src/ethers/entry_point/v0_7.rs @@ -0,0 +1,436 @@ +// 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, FunctionCall}, + providers::{Middleware, RawCall}, + types::{ + spoof, transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, + Eip1559TransactionRequest, H160, H256, U256, + }, + utils::hex, +}; +use rundler_types::{ + contracts::{ + arbitrum::node_interface::NodeInterface, + optimism::gas_price_oracle::GasPriceOracle, + v0_7::{ + entry_point_simulations::{ + EntryPointSimulations, ExecutionResult as ExecutionResultV0_7, + ENTRYPOINTSIMULATIONS_BYTECODE, + }, + get_balances::{GetBalancesResult, GETBALANCES_BYTECODE}, + i_aggregator::IAggregator, + i_entry_point::{ + DepositInfo as DepositInfoV0_7, FailedOp, IEntryPoint, SignatureValidationFailed, + UserOpsPerAggregator as UserOpsPerAggregatorV0_7, + }, + }, + }, + v0_7::UserOperation, + GasFees, UserOpsPerAggregator, ValidationOutput, +}; +use rundler_utils::eth::{self, ContractRevertError}; + +use crate::{ + AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, EntryPointProvider, + ExecutionResult, HandleOpsOut, L1GasProvider, Provider, SignatureAggregator, + SimulationProvider, +}; + +// From v0.7 EP contract +const REVERT_REASON_MAX_LEN: usize = 2048; + +// TODO(danc): These should be configurable from chain spec +const ARBITRUM_NITRO_NODE_INTERFACE_ADDRESS: Address = H160([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc8, +]); +const OPTIMISM_BEDROCK_GAS_ORACLE_ADDRESS: Address = H160([ + 0x42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0F, +]); + +/// Entry point for the v0.7 contract. +#[derive(Debug, Clone)] +pub struct EntryPoint

{ + i_entry_point: IEntryPoint

, + provider: Arc

, + arb_node: NodeInterface

, + opt_gas_oracle: GasPriceOracle

, +} + +impl

EntryPoint

+where + P: Middleware, +{ + /// Create a new `EntryPoint` instance for v0.7 + pub fn new(entry_point_address: Address, provider: Arc

) -> Self { + Self { + i_entry_point: IEntryPoint::new(entry_point_address, Arc::clone(&provider)), + provider: Arc::clone(&provider), + arb_node: NodeInterface::new( + ARBITRUM_NITRO_NODE_INTERFACE_ADDRESS, + Arc::clone(&provider), + ), + opt_gas_oracle: GasPriceOracle::new(OPTIMISM_BEDROCK_GAS_ORACLE_ADDRESS, provider), + } + } +} + +#[async_trait::async_trait] +impl

crate::traits::EntryPoint 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(); + + // TODO: Cap the gas here. + let result = aggregator.aggregate_signatures(packed_ops).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_arbitrum_l1_gas( + &self, + entry_point_address: Address, + user_op: UserOperation, + ) -> 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")?; + + super::estimate_arbitrum_l1_gas(&self.arb_node, entry_point_address, data).await + } + + async fn calc_optimism_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")?; + + super::estimate_optimism_l1_gas(&self.opt_gas_oracle, 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_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, + ) -> anyhow::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_BYTECODE.clone()); + + let ep_simulations = EntryPointSimulations::new(addr, Arc::clone(&self.provider)); + let result = ep_simulations + .simulate_validation(user_op.pack()) + .gas(U256::from(max_validation_gas) + pvg) + .call_raw() + .state(&spoof_ep) + .await + .context("should simulate validation")?; + Ok(result.into()) + } + + fn decode_simulate_handle_ops_revert( + &self, + revert_data: Bytes, + ) -> Result { + if let Ok(result) = ExecutionResultV0_7::decode(&revert_data) { + Ok(result.into()) + } else if let Ok(failed_op) = FailedOp::decode(&revert_data) { + Err(failed_op.reason) + } else if let Ok(err) = ContractRevertError::decode(&revert_data) { + Err(err.reason) + } else { + Err(hex::encode(&revert_data[..REVERT_REASON_MAX_LEN])) + } + } + + // 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 mut spoof_ep = spoofed_state.clone(); + spoof_ep + .account(addr) + .code(ENTRYPOINTSIMULATIONS_BYTECODE.clone()); + 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 + .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)); + } +} + +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_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/mod.rs b/crates/provider/src/ethers/mod.rs index 8ddcb7d21..ad0f669b0 100644 --- a/crates/provider/src/ethers/mod.rs +++ b/crates/provider/src/ethers/mod.rs @@ -14,6 +14,6 @@ //! Provider implementations using [ethers-rs](https://github.com/gakonst/ethers-rs) mod entry_point; -pub use entry_point::v0_6::EntryPoint as EntryPointV0_6; +pub use entry_point::{v0_6::EntryPoint as EntryPointV0_6, v0_7::EntryPoint as EntryPointV0_7}; mod metrics_middleware; pub(crate) mod provider; diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index f997e56b7..9501c6dd7 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -22,7 +22,10 @@ //! 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}; +pub use ethers::{ + provider::new_provider, EntryPointV0_6 as EthersEntryPointV0_6, + EntryPointV0_7 as EthersEntryPointV0_7, +}; mod traits; #[cfg(any(test, feature = "test-utils"))] @@ -30,7 +33,7 @@ pub use traits::test_utils::*; #[cfg(any(test, feature = "test-utils"))] pub use traits::MockProvider; pub use traits::{ - AggregatorOut, AggregatorSimOut, BundleHandler, EntryPoint, EntryPointProvider, HandleOpsOut, - L1GasProvider, Provider, ProviderError, ProviderResult, SignatureAggregator, - SimulationProvider, + AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, EntryPoint, EntryPointProvider, + ExecutionResult, HandleOpsOut, L1GasProvider, Provider, ProviderError, ProviderResult, + SignatureAggregator, SimulationProvider, }; diff --git a/crates/provider/src/traits/entry_point.rs b/crates/provider/src/traits/entry_point.rs index fc50916dd..e2554cf89 100644 --- a/crates/provider/src/traits/entry_point.rs +++ b/crates/provider/src/traits/entry_point.rs @@ -14,10 +14,7 @@ use ethers::types::{ spoof, transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, H256, U256, }; -use rundler_types::{ - contracts::v0_6::i_entry_point::ExecutionResult, DepositInfoV0_6, GasFees, UserOperation, - UserOpsPerAggregator, ValidationOutput, -}; +use rundler_types::{GasFees, Timestamp, UserOperation, UserOpsPerAggregator, ValidationOutput}; /// Output of a successful signature aggregator simulation call #[derive(Clone, Debug, Default)] @@ -53,6 +50,38 @@ pub enum HandleOpsOut { PostOpRevert, } +/// Deposit info for an address from the entry point contract +#[derive(Clone, Debug, Default)] +pub struct DepositInfo { + /// Amount deposited on the entry point + pub deposit: U256, + /// Whether the address has staked + pub staked: bool, + /// Amount staked on the entry point + pub stake: u128, + /// The amount of time in sections that must pass before the stake can be withdrawn + pub unstake_delay_sec: u32, + /// The time at which the stake can be withdrawn + pub withdraw_time: u64, +} + +/// Result of an execution +#[derive(Clone, Debug, Default)] +pub struct ExecutionResult { + /// Gas used before the operation execution + pub pre_op_gas: U256, + /// Amount paid by the operation + pub paid: U256, + /// Time which the operation is valid after + pub valid_after: Timestamp, + /// Time which the operation is valid until + pub valid_until: Timestamp, + /// True if the operation execution succeeded + pub target_success: bool, + /// Result of the operation execution + pub target_result: Bytes, +} + /// Trait for interacting with an entry point contract. #[async_trait::async_trait] #[auto_impl::auto_impl(&, Arc)] @@ -65,7 +94,7 @@ pub trait EntryPoint: Send + Sync + 'static { -> anyhow::Result; /// 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) -> anyhow::Result; /// Get the balances of a list of addresses in order async fn get_balances(&self, addresses: Vec

) -> anyhow::Result>; @@ -152,11 +181,11 @@ pub trait SimulationProvider: Send + Sync + 'static { type UO: UserOperation; /// Construct a call for the entry point contract's `simulateValidation` function - async fn get_simulate_validation_call( + fn get_tracer_simulate_validation_call( &self, user_op: Self::UO, max_validation_gas: u64, - ) -> anyhow::Result; + ) -> (TypedTransaction, spoof::State); /// Call the entry point contract's `simulateValidation` function. async fn call_simulate_validation( diff --git a/crates/provider/src/traits/mod.rs b/crates/provider/src/traits/mod.rs index ed4f1e4da..be618c09e 100644 --- a/crates/provider/src/traits/mod.rs +++ b/crates/provider/src/traits/mod.rs @@ -18,8 +18,8 @@ pub use error::ProviderError; mod entry_point; pub use entry_point::{ - AggregatorOut, AggregatorSimOut, BundleHandler, EntryPoint, EntryPointProvider, HandleOpsOut, - L1GasProvider, SignatureAggregator, SimulationProvider, + AggregatorOut, AggregatorSimOut, BundleHandler, DepositInfo, EntryPoint, EntryPointProvider, + ExecutionResult, HandleOpsOut, L1GasProvider, SignatureAggregator, SimulationProvider, }; mod provider; diff --git a/crates/provider/src/traits/test_utils.rs b/crates/provider/src/traits/test_utils.rs index 7b5492e01..a49ae96cd 100644 --- a/crates/provider/src/traits/test_utils.rs +++ b/crates/provider/src/traits/test_utils.rs @@ -14,14 +14,11 @@ use ethers::types::{ spoof, transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, H256, U256, }; -use rundler_types::{ - contracts::v0_6::i_entry_point::ExecutionResult, v0_6, DepositInfoV0_6, GasFees, - UserOpsPerAggregator, ValidationOutput, -}; +use rundler_types::{v0_6, GasFees, UserOpsPerAggregator, ValidationOutput}; use crate::{ - AggregatorOut, BundleHandler, EntryPoint, HandleOpsOut, L1GasProvider, SignatureAggregator, - SimulationProvider, + AggregatorOut, BundleHandler, DepositInfo, EntryPoint, ExecutionResult, HandleOpsOut, + L1GasProvider, SignatureAggregator, SimulationProvider, }; mockall::mock! { @@ -32,7 +29,7 @@ mockall::mock! { 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_deposit_info(&self, address: Address) -> anyhow::Result; async fn get_balances(&self, addresses: Vec
) -> anyhow::Result>; } @@ -55,11 +52,11 @@ mockall::mock! { #[async_trait::async_trait] impl SimulationProvider for EntryPointV0_6 { type UO = v0_6::UserOperation; - async fn get_simulate_validation_call( + fn get_tracer_simulate_validation_call( &self, user_op: v0_6::UserOperation, max_validation_gas: u64, - ) -> anyhow::Result; + ) -> (TypedTransaction, spoof::State); async fn call_simulate_validation( &self, user_op: v0_6::UserOperation, diff --git a/crates/rpc/src/eth/router.rs b/crates/rpc/src/eth/router.rs index 285c44608..b4959907c 100644 --- a/crates/rpc/src/eth/router.rs +++ b/crates/rpc/src/eth/router.rs @@ -289,7 +289,7 @@ where .call_simulate_validation(uo.into(), max_verification_gas) .await?; - Ok(!output.return_info.sig_failed) + Ok(!output.return_info.account_sig_failed) } } diff --git a/crates/sim/src/estimation/v0_6.rs b/crates/sim/src/estimation/v0_6.rs index d0cbf7e6e..16f1ab737 100644 --- a/crates/sim/src/estimation/v0_6.rs +++ b/crates/sim/src/estimation/v0_6.rs @@ -439,10 +439,10 @@ mod tests { types::U64, utils::hex, }; - use rundler_provider::{MockEntryPointV0_6, MockProvider}; + use rundler_provider::{ExecutionResult, MockEntryPointV0_6, MockProvider}; use rundler_types::{ chain::L1GasOracleContractType, - contracts::{utils::get_gas_used::GasUsedResult, v0_6::i_entry_point::ExecutionResult}, + contracts::utils::get_gas_used::GasUsedResult, v0_6::{UserOperation, UserOperationOptionalGas}, UserOperation as UserOperationTrait, }; @@ -715,8 +715,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) @@ -772,8 +772,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) @@ -829,8 +829,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) @@ -925,8 +925,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) @@ -967,8 +967,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) @@ -1167,8 +1167,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) @@ -1250,8 +1250,8 @@ mod tests { Ok(ExecutionResult { pre_op_gas: U256::from(10000), paid: U256::from(100000), - valid_after: 100000000000, - valid_until: 100000000001, + valid_after: 100000000000.into(), + valid_until: 100000000001.into(), target_success: true, target_result: Bytes::new(), }) diff --git a/crates/sim/src/simulation/v0_6/simulator.rs b/crates/sim/src/simulation/v0_6/simulator.rs index 6c1575aae..515be71a6 100644 --- a/crates/sim/src/simulation/v0_6/simulator.rs +++ b/crates/sim/src/simulation/v0_6/simulator.rs @@ -175,7 +175,7 @@ where entity_infos: None, })? } - let Ok(entry_point_out) = ValidationOutput::decode_hex(revert_data) else { + let Ok(entry_point_out) = ValidationOutput::decode_v0_6_hex(revert_data) else { let entity_addr = match last_entity_type { EntityType::Factory => factory_address, EntityType::Paymaster => paymaster_address, @@ -256,7 +256,11 @@ where let mut violations = vec![]; - if entry_point_out.return_info.sig_failed { + // v0.6 doesn't distinguish between the different types of signature failures + // both of these will be set to true if the signature failed. + if entry_point_out.return_info.account_sig_failed + || entry_point_out.return_info.paymaster_sig_failed + { violations.push(SimulationViolation::InvalidSignature); } diff --git a/crates/sim/src/simulation/v0_6/tracer.rs b/crates/sim/src/simulation/v0_6/tracer.rs index 15854408a..27af6b1e3 100644 --- a/crates/sim/src/simulation/v0_6/tracer.rs +++ b/crates/sim/src/simulation/v0_6/tracer.rs @@ -101,10 +101,9 @@ where block_id: BlockId, max_validation_gas: u64, ) -> anyhow::Result { - let tx = self + let (tx, state_override) = self .entry_point - .get_simulate_validation_call(op, max_validation_gas) - .await?; + .get_tracer_simulate_validation_call(op, max_validation_gas); SimulationTracerOutput::try_from( self.provider @@ -118,7 +117,7 @@ where )), ..Default::default() }, - ..Default::default() + state_overrides: Some(state_override), }, ) .await?, diff --git a/crates/types/build.rs b/crates/types/build.rs index e91b29a36..1230901c4 100644 --- a/crates/types/build.rs +++ b/crates/types/build.rs @@ -81,6 +81,8 @@ fn generate_v0_7_bindings() -> Result<(), Box> { abigen_of("v0_7", "EntryPoint")?, abigen_of("v0_7", "IAggregator")?, abigen_of("v0_7", "IStakeManager")?, + abigen_of("v0_7", "GetBalances")?, + abigen_of("v0_7", "EntryPointSimulations")?, ]) .build()? .write_to_module("src/contracts/v0_7", false)?; diff --git a/crates/types/contracts/src/v0_6/imports.sol b/crates/types/contracts/src/v0_6/imports.sol index fbb9f4ab1..625263d49 100644 --- a/crates/types/contracts/src/v0_6/imports.sol +++ b/crates/types/contracts/src/v0_6/imports.sol @@ -6,6 +6,6 @@ pragma solidity ^0.8.13; import "account-abstraction/v0_6/samples/SimpleAccount.sol"; import "account-abstraction/v0_6/samples/SimpleAccountFactory.sol"; import "account-abstraction/v0_6/samples/VerifyingPaymaster.sol"; -import "account-abstraction/v0_6/core/EntryPoint.sol"; +import "account-abstraction/v0_6/interfaces/IEntryPoint.sol"; import "account-abstraction/v0_6/interfaces/IAggregator.sol"; import "account-abstraction/v0_6/interfaces/IStakeManager.sol"; diff --git a/crates/types/contracts/src/v0_7/GetBalances.sol b/crates/types/contracts/src/v0_7/GetBalances.sol new file mode 100644 index 000000000..bda81addc --- /dev/null +++ b/crates/types/contracts/src/v0_7/GetBalances.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.12; + +import "account-abstraction/v0_7/interfaces/IStakeManager.sol"; + +contract GetBalances { + error GetBalancesResult(uint256[] balances); + + constructor(address stakeManager, address[] memory addresses) { + revert GetBalancesResult(getBalancesHelper(stakeManager, addresses)); + } + + function getBalancesHelper(address stakeManager, address[] memory addresses) public view returns (uint256[] memory) { + uint256[] memory balances = new uint256[](addresses.length); + IStakeManager istakeManager = IStakeManager(stakeManager); + + for (uint256 i = 0; i < addresses.length; i++) { + balances[i] = istakeManager.balanceOf(addresses[i]); + } + + return balances; + } +} diff --git a/crates/types/contracts/src/v0_7/imports.sol b/crates/types/contracts/src/v0_7/imports.sol index 72be598ea..592dddd3a 100644 --- a/crates/types/contracts/src/v0_7/imports.sol +++ b/crates/types/contracts/src/v0_7/imports.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; // Simply importing a dependency is enough for Forge to include it in builds. -import "account-abstraction/v0_7/core/EntryPoint.sol"; +import "account-abstraction/v0_7/interfaces/IEntryPoint.sol"; import "account-abstraction/v0_7/interfaces/IAggregator.sol"; import "account-abstraction/v0_7/interfaces/IStakeManager.sol"; +import "account-abstraction/v0_7/core/EntryPointSimulations.sol"; diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 7aebebed8..febe53299 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -26,7 +26,6 @@ pub mod chain; #[rustfmt::skip] pub mod contracts; -pub use contracts::v0_6::shared_types::DepositInfo as DepositInfoV0_6; mod entity; pub use entity::{Entity, EntityInfo, EntityInfos, EntityType, EntityUpdate, EntityUpdateType}; @@ -49,4 +48,6 @@ mod storage; pub use storage::StorageSlot; mod validation_results; -pub use validation_results::{AggregatorInfo, StakeInfo, ValidationOutput, ValidationReturnInfo}; +pub use validation_results::{ + parse_validation_data, AggregatorInfo, StakeInfo, ValidationOutput, ValidationReturnInfo, +}; diff --git a/crates/types/src/timestamp.rs b/crates/types/src/timestamp.rs index 78d6bb14f..5e5187d24 100644 --- a/crates/types/src/timestamp.rs +++ b/crates/types/src/timestamp.rs @@ -192,6 +192,14 @@ impl ValidTimeRange { pub fn contains(self, timestamp: Timestamp, buffer: Duration) -> bool { self.valid_after <= timestamp && (timestamp + buffer) <= self.valid_until } + + /// Intersect two time ranges into a single time range that is valid whenever both are valid + pub fn intersect(self, other: Self) -> Self { + Self { + valid_after: self.valid_after.max(other.valid_after), + valid_until: self.valid_until.min(other.valid_until), + } + } } #[cfg(test)] @@ -286,6 +294,15 @@ mod test { assert_eq!(json, "\"0x64\""); } + #[test] + fn test_merge_time_ranges() { + let range1 = ValidTimeRange::new(Timestamp::new(100), Timestamp::new(200)); + let range2 = ValidTimeRange::new(Timestamp::new(150), Timestamp::new(250)); + let intersect = range1.intersect(range2); + assert_eq!(intersect.valid_after, Timestamp::new(150)); + assert_eq!(intersect.valid_until, Timestamp::new(200)); + } + fn get_timestamp_out_of_bounds_for_datetime() -> Timestamp { // This is just a bit further in the future than the maximum allowed // DateTime, which is just before the start of year 2^18 = 262144. diff --git a/crates/types/src/validation_results.rs b/crates/types/src/validation_results.rs index dad3659be..4317b9a4d 100644 --- a/crates/types/src/validation_results.rs +++ b/crates/types/src/validation_results.rs @@ -12,16 +12,29 @@ // If not, see https://www.gnu.org/licenses/. use ethers::{ - abi, - abi::{AbiDecode, AbiError}, - types::{Address, Bytes, U256}, + abi::{self, AbiDecode, AbiError}, + types::{Address, Bytes, H160, U256}, }; use crate::{ - contracts::v0_6::entry_point::{ValidationResult, ValidationResultWithAggregation}, - Timestamp, + contracts::{ + v0_6::i_entry_point::{ + ValidationResult as ValidationResultV0_6, + ValidationResultWithAggregation as ValidationResultWithAggregationV0_6, + }, + v0_7::entry_point_simulations::{ + AggregatorStakeInfo as AggregatorStakeInfoV0_7, ReturnInfo as ReturnInfoV0_7, + StakeInfo as StakeInfoV0_7, ValidationResult as ValidationResultV0_7, + }, + }, + Timestamp, ValidTimeRange, }; +/// Both v0.6 and v0.7 contracts use this aggregator address to indicate that the signature validation failed +/// Zero is also used to indicate that no aggregator is used AND that the signature validation failed. +const SIG_VALIDATION_FAILED: Address = + H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + /// Equivalent to the generated `ValidationResult` or /// `ValidationResultWithAggregation` from `EntryPoint`, but with named structs /// instead of tuples and with a helper for deserializing. @@ -39,21 +52,42 @@ pub struct ValidationOutput { pub aggregator_info: Option, } -impl AbiDecode for ValidationOutput { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - if let Ok(result) = ValidationResult::decode(bytes.as_ref()) { +impl ValidationOutput { + /// Decode a v0.6 validation result from bytes. + pub fn decode_v0_6(bytes: impl AsRef<[u8]>) -> Result { + if let Ok(result) = ValidationResultV0_6::decode(bytes.as_ref()) { + return Ok(result.into()); + } + if let Ok(result) = ValidationResultWithAggregationV0_6::decode(bytes) { return Ok(result.into()); } - if let Ok(result) = ValidationResultWithAggregation::decode(bytes) { + Err(AbiError::DecodingError(abi::Error::InvalidData)) + } + + /// Decode a v0.6 validation result from hex. + pub fn decode_v0_6_hex(hex: impl AsRef) -> Result { + let bytes: Bytes = hex.as_ref().parse()?; + Self::decode_v0_6(&bytes) + } + + /// Decode a v0.7 validation result from bytes. + pub fn decode_v0_7(bytes: impl AsRef<[u8]>) -> Result { + if let Ok(result) = ValidationResultV0_7::decode(bytes.as_ref()) { return Ok(result.into()); } Err(AbiError::DecodingError(abi::Error::InvalidData)) } + + /// Decode a v0.7 validation result from hex. + pub fn decode_v0_7_hex(hex: impl AsRef) -> Result { + let bytes: Bytes = hex.as_ref().parse()?; + Self::decode_v0_7(&bytes) + } } -impl From for ValidationOutput { - fn from(value: ValidationResult) -> Self { - let ValidationResult { +impl From for ValidationOutput { + fn from(value: ValidationResultV0_6) -> Self { + let ValidationResultV0_6 { return_info, sender_info, factory_info, @@ -69,9 +103,9 @@ impl From for ValidationOutput { } } -impl From for ValidationOutput { - fn from(value: ValidationResultWithAggregation) -> Self { - let ValidationResultWithAggregation { +impl From for ValidationOutput { + fn from(value: ValidationResultWithAggregationV0_6) -> Self { + let ValidationResultWithAggregationV0_6 { return_info, sender_info, factory_info, @@ -88,13 +122,41 @@ impl From for ValidationOutput { } } +impl From for ValidationOutput { + fn from(value: ValidationResultV0_7) -> Self { + let ValidationResultV0_7 { + return_info, + sender_info, + factory_info, + paymaster_info, + aggregator_info, + } = value; + + let aggregator_info = if aggregator_info.aggregator.is_zero() { + None + } else { + Some(aggregator_info.into()) + }; + + Self { + return_info: return_info.into(), + sender_info: sender_info.into(), + factory_info: factory_info.into(), + paymaster_info: paymaster_info.into(), + aggregator_info, + } + } +} + /// ValidationReturnInfo from EntryPoint contract #[derive(Debug)] pub struct ValidationReturnInfo { /// The amount of gas used before the op was executed (pre verification gas and validation gas) pub pre_op_gas: U256, - /// Whether the signature verification failed - pub sig_failed: bool, + /// Whether the account signature verification failed + pub account_sig_failed: bool, + /// Whether the paymaster signature verification failed + pub paymaster_sig_failed: bool, /// The time after which the op is valid pub valid_after: Timestamp, /// The time until which the op is valid @@ -103,6 +165,7 @@ pub struct ValidationReturnInfo { pub paymaster_context: Bytes, } +// Conversion for v0.6 impl From<(U256, U256, bool, u64, u64, Bytes)> for ValidationReturnInfo { fn from(value: (U256, U256, bool, u64, u64, Bytes)) -> Self { let ( @@ -113,9 +176,11 @@ impl From<(U256, U256, bool, u64, u64, Bytes)> for ValidationReturnInfo { valid_until, paymaster_context, ) = value; + // In v0.6 if one signature fails both do Self { pre_op_gas, - sig_failed, + account_sig_failed: sig_failed, + paymaster_sig_failed: sig_failed, valid_after: valid_after.into(), valid_until: valid_until.into(), paymaster_context, @@ -123,6 +188,84 @@ impl From<(U256, U256, bool, u64, u64, Bytes)> for ValidationReturnInfo { } } +impl From for ValidationReturnInfo { + fn from(value: ReturnInfoV0_7) -> Self { + let ReturnInfoV0_7 { + pre_op_gas, + prefund: _, + account_validation_data, + paymaster_validation_data, + paymaster_context, + } = value; + + let account = parse_validation_data(account_validation_data); + let paymaster = parse_validation_data(paymaster_validation_data); + + let intersect_range = account + .valid_time_range() + .intersect(paymaster.valid_time_range()); + + Self { + pre_op_gas, + account_sig_failed: !account.signature_valid(), + paymaster_sig_failed: !paymaster.signature_valid(), + valid_after: intersect_range.valid_after, + valid_until: intersect_range.valid_until, + paymaster_context, + } + } +} + +/// ValidationData from EntryPoint contract +pub struct ValidationData { + aggregator: Address, + valid_after: u64, + valid_until: u64, +} + +impl ValidationData { + /// Valid time range for the validation data + pub fn valid_time_range(&self) -> ValidTimeRange { + ValidTimeRange::new(self.valid_after.into(), self.valid_until.into()) + } + + /// Whether the signature is valid + pub fn signature_valid(&self) -> bool { + self.aggregator != SIG_VALIDATION_FAILED + } + + /// The aggregator address, if any + pub fn aggregator(&self) -> Option
{ + if self.aggregator == SIG_VALIDATION_FAILED || self.aggregator.is_zero() { + None + } else { + Some(self.aggregator) + } + } +} + +/// Parse the validation data from a U256 +/// +/// Works for both v0.6 and v0.7 validation data +pub fn parse_validation_data(data: U256) -> ValidationData { + let slice: [u8; 32] = data.into(); + let aggregator = Address::from_slice(&slice[0..20]); + + let mut buf = [0_u8; 8]; + buf[..6].copy_from_slice(&slice[20..26]); + let valid_after = u64::from_le_bytes(buf); + + let mut buf = [0_u8; 8]; + buf[..6].copy_from_slice(&slice[26..32]); + let valid_until = u64::from_le_bytes(buf); + + ValidationData { + aggregator, + valid_after, + valid_until, + } +} + /// StakeInfo from EntryPoint contract #[derive(Clone, Copy, Debug)] pub struct StakeInfo { @@ -141,6 +284,19 @@ impl From<(U256, U256)> for StakeInfo { } } +impl From for StakeInfo { + fn from(value: StakeInfoV0_7) -> Self { + let StakeInfoV0_7 { + stake, + unstake_delay_sec, + } = value; + Self { + stake, + unstake_delay_sec, + } + } +} + /// AggregatorInfo from EntryPoint contract #[derive(Clone, Copy, Debug)] pub struct AggregatorInfo { @@ -158,3 +314,35 @@ impl From<(Address, (U256, U256))> for AggregatorInfo { } } } + +impl From for AggregatorInfo { + fn from(value: AggregatorStakeInfoV0_7) -> Self { + let AggregatorStakeInfoV0_7 { + aggregator, + stake_info, + } = value; + Self { + address: aggregator, + stake_info: stake_info.into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::parse_validation_data; + + #[test] + fn test_parse_validation_data() { + let data = "0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"; + let parsed = parse_validation_data(data.into()); + assert_eq!( + parsed.aggregator, + "0x00112233445566778899aabbccddeeff00112233" + .parse() + .unwrap() + ); + assert_eq!(parsed.valid_after, 0x998877665544); // solidity is LE + assert_eq!(parsed.valid_until, 0xffeeddccbbaa); + } +}