diff --git a/Cargo.lock b/Cargo.lock index 350c4496..42c682bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "prost", "prost-build", "protobuf-src", + "regex", "ron", "serde", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 9812ac37..b6adc91c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ tonic = { version = "0.8", default-features = false, features = [ ] } tracing = "0.1" tracing-subscriber = "0.3" +regex = "1" [build-dependencies] anyhow = "1" diff --git a/src/contracts/entrypoint.rs b/src/contracts/entrypoint.rs new file mode 100644 index 00000000..e6b1c4b3 --- /dev/null +++ b/src/contracts/entrypoint.rs @@ -0,0 +1,230 @@ +use std::str::FromStr; +use std::sync::Arc; + +use anyhow; +use ethers::abi::AbiDecode; +use ethers::prelude::ContractError; +use ethers::providers::Middleware; +use ethers::types::{Address, Bytes}; +use regex::Regex; +use serde::Deserialize; + +use super::gen::entry_point_api::{self, UserOperation}; +use super::gen::EntryPointAPI; + +pub struct EntryPoint { + provider: Arc, + entry_point_address: Address, + api: EntryPointAPI, +} + +impl EntryPoint { + pub fn new(provider: Arc, entry_point_address: Address) -> Self { + let api = EntryPointAPI::new(entry_point_address, provider.clone()); + Self { + provider, + entry_point_address: entry_point_address, + api, + } + } + + pub fn provider(&self) -> Arc { + self.provider.clone() + } + + pub fn entry_point_address(&self) -> Address { + self.entry_point_address + } + + fn deserialize_error_msg( + err_msg: &str, + ) -> Result { + JsonRpcError::from_str(&err_msg) + .map_err(|_| { + EntryPointErr::DecodeErr(format!( + "{:?} is not a valid JsonRpcError message", + err_msg + )) + }) + .and_then(|json_error| { + json_error.data.ok_or_else(|| { + EntryPointErr::DecodeErr("{:?} doesn't have valid data field".to_string()) + }) + }) + .and_then(|data: String| { + AbiDecode::decode_hex(data).map_err(|_| { + EntryPointErr::DecodeErr(format!( + "{:?} data field could not be deserialize to EntryPointAPIErrors", + err_msg + )) + }) + }) + } + + pub async fn simulate_validation>( + &self, + user_operation: U, + ) -> Result { + let request_result = self.api.simulate_validation(user_operation.into()).await; + match request_result { + Ok(_) => Err(EntryPointErr::UnknownErr( + "Simulate validation should expect revert".to_string(), + )), + Err(e) => { + let err_msg = e.to_string(); + Self::deserialize_error_msg(&err_msg).and_then(|op| match op { + entry_point_api::EntryPointAPIErrors::FailedOp(failed_op) => { + Err(EntryPointErr::FailedOp(failed_op)) + } + entry_point_api::EntryPointAPIErrors::SimulationResult(res) => { + Ok(SimulateValidationResult::SimulationResult(res)) + } + entry_point_api::EntryPointAPIErrors::SimulationResultWithAggregation(res) => { + Ok(SimulateValidationResult::SimulationResultWithAggregation( + res, + )) + } + _ => Err(EntryPointErr::UnknownErr(format!( + "Simulate validation with invalid error: {:?}", + op + ))), + }) + } + } + } + + pub async fn handle_ops>( + &self, + ops: Vec, + beneficiary: Address, + ) -> Result<(), EntryPointErr> { + self.api + .handle_ops(ops.into_iter().map(|u| u.into()).collect(), beneficiary) + .await + .or_else(|e| { + let err_msg = e.to_string(); + Self::deserialize_error_msg(&err_msg).and_then(|op| match op { + entry_point_api::EntryPointAPIErrors::FailedOp(failed_op) => { + Err(EntryPointErr::FailedOp(failed_op)) + } + _ => Err(EntryPointErr::UnknownErr(format!( + "Handle ops with invalid error: {:?}", + op + ))), + }) + }) + } + + async fn get_sender_address>( + &self, + initcode: Bytes, + ) -> Result { + let result = self.api.get_sender_address(initcode).await; + + match result { + Ok(_) => Err(EntryPointErr::UnknownErr( + "Get sender address should expect revert".to_string(), + )), + Err(e) => { + let err_msg = e.to_string(); + Self::deserialize_error_msg(&err_msg).and_then(|op| match op { + entry_point_api::EntryPointAPIErrors::SenderAddressResult(res) => Ok(res), + entry_point_api::EntryPointAPIErrors::FailedOp(failed_op) => { + Err(EntryPointErr::FailedOp(failed_op)) + } + _ => Err(EntryPointErr::UnknownErr(format!( + "Simulate validation with invalid error: {:?}", + op + ))), + }) + } + } + } + + async fn handle_aggregated_ops>( + &self, + ops_per_aggregator: Vec, + beneficiary: Address, + ) -> Result<(), EntryPointErr> { + todo!() + } +} + +#[derive(Debug)] +pub enum EntryPointErr { + FailedOp(entry_point_api::FailedOp), + NetworkErr, // TODO + DecodeErr(String), + UnknownErr(String), // describe impossible error. We should fix the codes here(or contract codes) if this occurs. +} + +#[derive(Debug)] +pub enum SimulateValidationResult { + SimulationResult(entry_point_api::SimulationResult), + SimulationResultWithAggregation(entry_point_api::SimulationResultWithAggregation), +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct JsonRpcError { + /// The error code + pub code: u64, + /// The error message + pub message: String, + /// Additional data + pub data: Option, +} + +impl FromStr for JsonRpcError { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + let re = Regex::new( + r###"code: (\d+), message: ([^,]*), data: (None|Some\(String\("([^)]*)"\))"###, + )?; + let captures = re.captures(s).unwrap(); + let code = captures[1].parse::().unwrap(); + let message = &captures[2]; + let data = match &captures[3] { + "None" => None, + _ => Some(captures[4].to_string()), + }; + Ok(JsonRpcError { + code, + message: message.to_string(), + data, + }) + } +} + +#[cfg(test)] +mod tests { + + use super::JsonRpcError; + use std::str::FromStr; + + #[test] + fn json_rpc_err_parse() { + let some_data = + "(code: 3, message: execution reverted: , data: Some(String(\"0x00fa072b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001941413230206163636f756e74206e6f74206465706c6f79656400000000000000\")))"; + let err = JsonRpcError::from_str(some_data); + + assert_eq!( + err.unwrap(), + JsonRpcError { + code: 3, + message: "execution reverted: ".to_string(), + data: Some("0x00fa072b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001941413230206163636f756e74206e6f74206465706c6f79656400000000000000".to_string()) + } + ); + + let none_data = "(code: 3, message: execution reverted, data: None)"; + let err2 = JsonRpcError::from_str(none_data); + assert_eq!( + err2.unwrap(), + JsonRpcError { + code: 3, + message: "execution reverted".to_string(), + data: None + } + ); + } +} diff --git a/src/contracts/gen.rs b/src/contracts/gen.rs new file mode 100644 index 00000000..0c543faf --- /dev/null +++ b/src/contracts/gen.rs @@ -0,0 +1,43 @@ +use crate::types::user_operation::UserOperation as UserOp; +use ethers::contract::abigen; + +abigen!(EntryPointAPI, "$OUT_DIR/IEntryPoint.sol/IEntryPoint.json"); + +// The below generations are not used now. So we comment them out for now. +// abigen!( +// AggregatedAccount, +// "$OUT_DIR/IAggregatedAccount.sol/IAggregatedAccount.json" +// ); +// abigen!(Aggregator, "$OUT_DIR/IAggregator.sol/IAggregator.json"); +// abigen!( +// Create2Deployer, +// "$OUT_DIR/ICreate2Deployer.sol/ICreate2Deployer.json" +// ); +// abigen!(Paymaster, "$OUT_DIR/IPaymaster.sol/IPaymaster.json"); +// abigen!( +// StakeManager, +// "$OUT_DIR/IStakeManager.sol/IStakeManager.json" +// ); +// abigen!(Account, "$OUT_DIR/IAccount.sol/IAccount.json"); +// abigen!( +// UserOperation, +// "$OUT_DIR/UserOperation.sol/UserOperationLib.json" +// ); + +impl From for entry_point_api::UserOperation { + fn from(value: UserOp) -> Self { + Self { + sender: value.sender, + nonce: value.nonce, + init_code: value.init_code, + call_data: value.call_data, + call_gas_limit: value.call_gas_limit, + verification_gas_limit: value.verification_gas_limit, + pre_verification_gas: value.pre_verification_gas, + max_fee_per_gas: value.max_fee_per_gas, + max_priority_fee_per_gas: value.max_priority_fee_per_gas, + paymaster_and_data: value.paymaster_and_data, + signature: value.signature, + } + } +} diff --git a/src/contracts/mod.rs b/src/contracts/mod.rs index e9c0dfea..76713363 100644 --- a/src/contracts/mod.rs +++ b/src/contracts/mod.rs @@ -1,29 +1,4 @@ -use ethers::contract::abigen; +mod entrypoint; +mod gen; -abigen!( - AggregatedAccount, - "$OUT_DIR/IAggregatedAccount.sol/IAggregatedAccount.json" -); - -abigen!(Aggregator, "$OUT_DIR/IAggregator.sol/IAggregator.json"); - -abigen!( - Create2Deployer, - "$OUT_DIR/ICreate2Deployer.sol/ICreate2Deployer.json" -); - -abigen!(EntryPoint, "$OUT_DIR/IEntryPoint.sol/IEntryPoint.json"); - -abigen!(Paymaster, "$OUT_DIR/IPaymaster.sol/IPaymaster.json"); - -abigen!( - StakeManager, - "$OUT_DIR/IStakeManager.sol/IStakeManager.json" -); - -abigen!(Account, "$OUT_DIR/IAccount.sol/IAccount.json"); - -abigen!( - UserOperation, - "$OUT_DIR/UserOperation.sol/UserOperationLib.json" -); +pub use entrypoint::*; diff --git a/thirdparty/account-abstraction b/thirdparty/account-abstraction index 695e4904..35b16cdd 160000 --- a/thirdparty/account-abstraction +++ b/thirdparty/account-abstraction @@ -1 +1 @@ -Subproject commit 695e490484dbf380b9135d30b57037a0428514aa +Subproject commit 35b16cdd21faeaad4f20f15a132dda7d81bfa8b8