Skip to content

Commit

Permalink
Merge pull request #25 from zsluedem/entry-point-api
Browse files Browse the repository at this point in the history
Entry point api
  • Loading branch information
zsluedem authored Dec 20, 2022
2 parents d301382 + d618302 commit b4ed6c5
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 29 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
230 changes: 230 additions & 0 deletions src/contracts/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -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<M: Middleware> {
provider: Arc<M>,
entry_point_address: Address,
api: EntryPointAPI<M>,
}

impl<M: Middleware + 'static> EntryPoint<M> {
pub fn new(provider: Arc<M>, 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<M> {
self.provider.clone()
}

pub fn entry_point_address(&self) -> Address {
self.entry_point_address
}

fn deserialize_error_msg(
err_msg: &str,
) -> Result<entry_point_api::EntryPointAPIErrors, EntryPointErr> {
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<U: Into<UserOperation>>(
&self,
user_operation: U,
) -> Result<SimulateValidationResult, EntryPointErr> {
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<U: Into<UserOperation>>(
&self,
ops: Vec<U>,
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<U: Into<UserOperation>>(
&self,
initcode: Bytes,
) -> Result<entry_point_api::SenderAddressResult, EntryPointErr> {
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<U: Into<UserOperation>>(
&self,
ops_per_aggregator: Vec<U>,
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<String>,
}

impl FromStr for JsonRpcError {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let re = Regex::new(
r###"code: (\d+), message: ([^,]*), data: (None|Some\(String\("([^)]*)"\))"###,
)?;
let captures = re.captures(s).unwrap();
let code = captures[1].parse::<u64>().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
}
);
}
}
43 changes: 43 additions & 0 deletions src/contracts/gen.rs
Original file line number Diff line number Diff line change
@@ -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<UserOp> 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,
}
}
}
31 changes: 3 additions & 28 deletions src/contracts/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;

0 comments on commit b4ed6c5

Please sign in to comment.