Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check for opcode CREATE2, fix RPC eth_estimateUserOperationGas #69

Merged
merged 5 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed bundler-spec-test/keys/.gitkeep
Empty file.
41 changes: 40 additions & 1 deletion src/contracts/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ethers::abi::AbiDecode;
use ethers::providers::{FromErr, Middleware, ProviderError};
use ethers::types::{
Address, Bytes, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions,
GethTrace,
GethTrace, TransactionRequest, U256,
};
use regex::Regex;
use serde::Deserialize;
Expand Down Expand Up @@ -199,6 +199,44 @@ where
}
}

pub async fn estimate_call_gas<U: Into<UserOperation>>(
&self,
user_operation: U,
) -> Result<U256, EntryPointErr<M>> {
let user_operation = user_operation.into();

if user_operation.call_data.is_empty() {
Ok(U256::zero())
} else {
let result = self
.provider
.estimate_gas(
&TransactionRequest::new()
.from(self.address)
.to(user_operation.sender)
.data(user_operation.call_data.clone())
.into(),
None,
)
.await;

match result {
Ok(gas) => Ok(gas),
Err(e) => {
let err_msg = e.to_string();

JsonRpcError::from_str(&err_msg)
.map_err(|_| {
EntryPointErr::DecodeErr(format!(
"{err_msg:?} is not a valid JsonRpcError message"
))
})
.and_then(|json_error| Err(EntryPointErr::JsonRpcError(json_error)))
}
}
}
}

pub async fn handle_aggregated_ops<U: Into<UserOperation>>(
&self,
_ops_per_aggregator: Vec<U>,
Expand All @@ -213,6 +251,7 @@ pub enum EntryPointErr<M: Middleware> {
FailedOp(FailedOp),
ProviderErr(ProviderError),
MiddlewareErr(M::Error),
JsonRpcError(JsonRpcError),
NetworkErr, // TODO
DecodeErr(String),
UnknownErr(String), // describe impossible error. We should fix the codes here(or contract codes) if this occurs.
Expand Down
5 changes: 3 additions & 2 deletions src/rpc/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ impl EthApiServer for EthApiServerImpl {
return Ok(user_operation_gas_estimation);
}

Err(jsonrpsee::core::Error::Call(CallError::Failed(
anyhow::format_err!("failed to gas estimation for user operation"),
Err(jsonrpsee::core::Error::Call(CallError::Custom(
serde_json::from_str::<ErrorObject>(&response.data)
.map_err(|err| format_err!("error parsing error object: {}", err))?,
)))
}

Expand Down
25 changes: 24 additions & 1 deletion src/types/sanity_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use ethers::{
use jsonrpsee::types::{error::ErrorCode, ErrorObject};

const SANITY_CHECK_ERROR_CODE: i32 = -32602;
const SANITY_CHECK_EXECUTION_ERROR_CODE: i32 = -32521;

pub type SanityCheckError = ErrorObject<'static>;

Expand Down Expand Up @@ -47,7 +48,13 @@ pub enum BadUserOperationError<M: Middleware> {
SenderVerification {
sender: Address,
},
UserOperationExecution {
message: String,
},
Middleware(M::Error),
UnknownError {
error: String,
},
}

impl<M: Middleware> From<BadUserOperationError<M>> for SanityCheckError {
Expand Down Expand Up @@ -141,7 +148,23 @@ impl<M: Middleware> From<BadUserOperationError<M>> for SanityCheckError {
format!("Sender {sender} is invalid (sender check)",),
None::<bool>,
),
BadUserOperationError::Middleware(_) => SanityCheckError::from(ErrorCode::InternalError),
BadUserOperationError::UserOperationExecution { message } => {
SanityCheckError::owned(
SANITY_CHECK_EXECUTION_ERROR_CODE,
message,
None::<bool>,
)
},
BadUserOperationError::Middleware(_) => {
SanityCheckError::from(ErrorCode::InternalError)
},
BadUserOperationError::UnknownError { error } => {
SanityCheckError::owned(
SANITY_CHECK_ERROR_CODE,
error,
None::<bool>,
)
},
}
}
}
15 changes: 14 additions & 1 deletion src/types/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::collections::{HashMap, HashSet};

const SIMULATE_VALIDATION_ERROR_CODE: i32 = -32500;
const OPCODE_VALIDATION_ERROR_CODE: i32 = -32502;
const SIMULATION_EXECUTION_ERROR_CODE: i32 = -32521;

pub type SimulationError = ErrorObject<'static>;

Expand Down Expand Up @@ -36,15 +37,21 @@ lazy_static! {
set.insert("CREATE".to_string());
set.insert("COINBASE".to_string());
set.insert("SELFDESTRUCT".to_string());
set.insert("RANDOM".to_string());
set.insert("PREVRANDAO".to_string());
set
};

pub static ref CREATE2_OPCODE: String = "CREATE2".to_string();
}

#[derive(Debug)]
pub enum SimulateValidationError<M: Middleware> {
UserOperationRejected { message: String },
OpcodeValidation { entity: String, opcode: String },
UserOperationExecution { message: String },
Middleware(M::Error),
UnknownError { error: String },
}

impl<M: Middleware> From<SimulateValidationError<M>> for SimulationError {
Expand All @@ -55,12 +62,18 @@ impl<M: Middleware> From<SimulateValidationError<M>> for SimulationError {
}
SimulateValidationError::OpcodeValidation { entity, opcode } => SimulationError::owned(
OPCODE_VALIDATION_ERROR_CODE,
format!("{entity} uses opcode {opcode}"),
format!("{entity} uses banned opcode: {opcode}"),
None::<bool>,
),
SimulateValidationError::UserOperationExecution { message } => {
SimulationError::owned(SIMULATION_EXECUTION_ERROR_CODE, message, None::<bool>)
}
SimulateValidationError::Middleware(_) => {
SimulationError::from(ErrorCode::InternalError)
}
SimulateValidationError::UnknownError { error } => {
SimulationError::owned(SIMULATE_VALIDATION_ERROR_CODE, error, None::<bool>)
}
}
}
}
1 change: 1 addition & 0 deletions src/types/user_operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ impl From<UserOperationPartial> for UserOperation {
#[serde(rename_all = "camelCase")]
pub struct UserOperationGasEstimation {
pub pre_verification_gas: U256,
#[serde(rename = "verificationGas")]
pub verification_gas_limit: U256,
pub call_gas_limit: U256,
}
Expand Down
6 changes: 4 additions & 2 deletions src/uopool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use ethers::{
abi::AbiEncode,
providers::{Http, Middleware, Provider},
types::{Address, H256, U256},
utils::keccak256,
utils::{keccak256, to_checksum},
};
use jsonrpsee::tracing::info;
use parking_lot::RwLock;
Expand All @@ -38,7 +38,9 @@ pub type MempoolBox<T> = Box<dyn Mempool<UserOperations = T, Error = anyhow::Err
pub type ReputationBox<T> = Box<dyn Reputation<ReputationEntries = T> + Send + Sync>;

pub fn mempool_id(entry_point: &Address, chain_id: &U256) -> MempoolId {
H256::from_slice(keccak256([entry_point.encode(), chain_id.encode()].concat()).as_slice())
H256::from_slice(
keccak256([to_checksum(entry_point, None).encode(), chain_id.encode()].concat()).as_slice(),
)
}

pub trait Mempool: Debug {
Expand Down
39 changes: 24 additions & 15 deletions src/uopool/services/sanity_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
};
use ethers::{
providers::Middleware,
types::{Address, TransactionRequest, U256},
types::{Address, U256},
};

impl<M: Middleware + 'static> UoPoolService<M>
Expand Down Expand Up @@ -116,27 +116,36 @@ where
user_operation: &UserOperation,
entry_point: &Address,
) -> Result<(), BadUserOperationError<M>> {
let call_gas_estimation = self
.eth_provider
.estimate_gas(
&TransactionRequest::new()
.from(*entry_point)
.to(user_operation.sender)
.data(user_operation.call_data.clone())
.into(),
None,
)
.await
.map_err(|error| BadUserOperationError::Middleware(error))?;
let mempool_id = mempool_id(entry_point, &self.chain_id);

if let Some(entry_point) = self.entry_points.get(&mempool_id) {
let call_gas_estimation = entry_point
.estimate_call_gas(user_operation.clone())
.await
.map_err(|error| match error {
EntryPointErr::JsonRpcError(err) => {
BadUserOperationError::UserOperationExecution {
message: err.message,
}
}
_ => BadUserOperationError::UnknownError {
error: format!("{error:?}"),
},
})?;

if user_operation.call_gas_limit >= call_gas_estimation {
return Ok(());
}

if user_operation.call_gas_limit < call_gas_estimation {
return Err(BadUserOperationError::LowCallGasLimit {
call_gas_limit: user_operation.call_gas_limit,
call_gas_estimation,
});
}

Ok(())
Err(BadUserOperationError::UnknownError {
error: format!("Unknown entry point: {entry_point}"),
})
}

async fn max_fee_per_gas(
Expand Down
16 changes: 13 additions & 3 deletions src/uopool/services/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ethers::{
use crate::{
contracts::{tracer::JsTracerFrame, EntryPointErr, SimulateValidationResult},
types::{
simulation::{SimulateValidationError, FORBIDDEN_OPCODES, LEVEL_TO_ENTITY},
simulation::{SimulateValidationError, CREATE2_OPCODE, FORBIDDEN_OPCODES, LEVEL_TO_ENTITY},
user_operation::UserOperation,
},
uopool::mempool_id,
Expand Down Expand Up @@ -117,6 +117,16 @@ where
});
}
}

if let Some(count) = trace.number_levels[index].opcodes.get(&*CREATE2_OPCODE) {
if LEVEL_TO_ENTITY[&index] == "factory" && *count == 1 {
continue;
}
return Err(SimulateValidationError::OpcodeValidation {
entity: LEVEL_TO_ENTITY[&index].to_string(),
opcode: CREATE2_OPCODE.to_string(),
});
}
}

Ok(())
Expand All @@ -126,7 +136,7 @@ where
&self,
user_operation: &UserOperation,
entry_point: &Address,
) -> Result<(), SimulateValidationError<M>> {
) -> Result<SimulateValidationResult, SimulateValidationError<M>> {
let simulate_validation_result = self
.simulate_validation(user_operation, entry_point)
.await?;
Expand All @@ -145,6 +155,6 @@ where
self.forbidden_opcodes(&simulate_validation_result, &js_trace)
.await?;

Ok(())
Ok(simulate_validation_result)
}
}
Loading