From 8199e602b83e65917a1067ad71f26e0c39895969 Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Mon, 19 Dec 2022 11:36:46 +0800 Subject: [PATCH 1/3] update account abstraction --- thirdparty/account-abstraction | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ac02fef3f6c25233ee65ac267296b53997ad9fc9 Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Mon, 19 Dec 2022 12:03:21 +0800 Subject: [PATCH 2/3] add entry point api --- Cargo.lock | 1 + Cargo.toml | 1 + src/contracts/entrypoint.rs | 238 ++++++++++++++++++++++++++++++++++++ src/contracts/gen.rs | 47 +++++++ src/contracts/mod.rs | 31 +---- 5 files changed, 290 insertions(+), 28 deletions(-) create mode 100644 src/contracts/entrypoint.rs create mode 100644 src/contracts/gen.rs 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..90160654 --- /dev/null +++ b/src/contracts/entrypoint.rs @@ -0,0 +1,238 @@ +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 get_user_op_hash>( + &self, + user_operation: U, + ) -> Result<[u8; 32], EntryPointErr> { + let res: Result<[u8; 32], ContractError> = + self.api.get_user_op_hash(user_operation.into()).await; + res.map_err(|e| EntryPointErr::UnknownErr(format!("Get user op hash error with {:?}", e))) + } + + 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(SimulateResult::SimulationResult(res)) + } + entry_point_api::EntryPointAPIErrors::SimulationResultWithAggregation(res) => { + Ok(SimulateResult::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 SimulateResult { + 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 test { + use ethers::types::Bytes; + + use super::JsonRpcError; + use std::str::FromStr; + + #[test] + fn json_rpc_errpr_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..b3f5ec8e --- /dev/null +++ b/src/contracts/gen.rs @@ -0,0 +1,47 @@ +use crate::types::user_operation::UserOperation as UserOp; +use ethers::contract::abigen; +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!(EntryPointAPI, "$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" +); + +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::*; From d6183027aa5d81ce4240756a62dce58fd67cd1f9 Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Mon, 19 Dec 2022 20:08:21 +0800 Subject: [PATCH 3/3] fix up comments --- src/contracts/entrypoint.rs | 24 +++++++------------- src/contracts/gen.rs | 44 +++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/contracts/entrypoint.rs b/src/contracts/entrypoint.rs index 90160654..e6b1c4b3 100644 --- a/src/contracts/entrypoint.rs +++ b/src/contracts/entrypoint.rs @@ -61,19 +61,10 @@ impl EntryPoint { }) } - pub async fn get_user_op_hash>( - &self, - user_operation: U, - ) -> Result<[u8; 32], EntryPointErr> { - let res: Result<[u8; 32], ContractError> = - self.api.get_user_op_hash(user_operation.into()).await; - res.map_err(|e| EntryPointErr::UnknownErr(format!("Get user op hash error with {:?}", e))) - } - pub async fn simulate_validation>( &self, user_operation: U, - ) -> Result { + ) -> Result { let request_result = self.api.simulate_validation(user_operation.into()).await; match request_result { Ok(_) => Err(EntryPointErr::UnknownErr( @@ -86,10 +77,12 @@ impl EntryPoint { Err(EntryPointErr::FailedOp(failed_op)) } entry_point_api::EntryPointAPIErrors::SimulationResult(res) => { - Ok(SimulateResult::SimulationResult(res)) + Ok(SimulateValidationResult::SimulationResult(res)) } entry_point_api::EntryPointAPIErrors::SimulationResultWithAggregation(res) => { - Ok(SimulateResult::SimulationResultWithAggregation(res)) + Ok(SimulateValidationResult::SimulationResultWithAggregation( + res, + )) } _ => Err(EntryPointErr::UnknownErr(format!( "Simulate validation with invalid error: {:?}", @@ -166,7 +159,7 @@ pub enum EntryPointErr { } #[derive(Debug)] -pub enum SimulateResult { +pub enum SimulateValidationResult { SimulationResult(entry_point_api::SimulationResult), SimulationResultWithAggregation(entry_point_api::SimulationResultWithAggregation), } @@ -203,14 +196,13 @@ impl FromStr for JsonRpcError { } #[cfg(test)] -mod test { - use ethers::types::Bytes; +mod tests { use super::JsonRpcError; use std::str::FromStr; #[test] - fn json_rpc_errpr_parse() { + fn json_rpc_err_parse() { let some_data = "(code: 3, message: execution reverted: , data: Some(String(\"0x00fa072b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001941413230206163636f756e74206e6f74206465706c6f79656400000000000000\")))"; let err = JsonRpcError::from_str(some_data); diff --git a/src/contracts/gen.rs b/src/contracts/gen.rs index b3f5ec8e..0c543faf 100644 --- a/src/contracts/gen.rs +++ b/src/contracts/gen.rs @@ -1,32 +1,28 @@ use crate::types::user_operation::UserOperation as UserOp; use ethers::contract::abigen; -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!(EntryPointAPI, "$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" -); +// 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 {