diff --git a/src/errors.rs b/src/errors.rs index 68b1ef4..8726809 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -50,6 +50,21 @@ pub struct EvmError(pub Report); impl Reject for EvmError {} +#[derive(Debug)] +pub struct InvalidRpcError(); + +impl Reject for InvalidRpcError {} + +#[derive(Debug)] +pub struct RpcError(); + +impl Reject for RpcError {} + +#[derive(Debug)] +pub struct InvalidIndexError(); + +impl Reject for InvalidIndexError {} + pub async fn handle_rejection(err: Rejection) -> Result { let code; let message: String; @@ -78,6 +93,9 @@ pub async fn handle_rejection(err: Rejection) -> Result } else if let Some(_e) = err.find::() { code = StatusCode::INTERNAL_SERVER_ERROR; message = "OVERRIDE_ERROR".to_string(); + } else if let Some(_e) = err.find::() { + code = StatusCode::BAD_REQUEST; + message = "INVALID_TRANSACTION_INDEX".to_string(); } else if let Some(_e) = err.find::() { if _e.0.to_string().contains("CallGasCostMoreThanGasLimit") { code = StatusCode::BAD_REQUEST; @@ -95,6 +113,12 @@ pub async fn handle_rejection(err: Rejection) -> Result None => "BAD_REQUEST".to_string(), }; code = StatusCode::BAD_REQUEST; + } else if let Some(_e) = err.find::() { + code = StatusCode::BAD_REQUEST; + message = "INVALID_RPC".to_string(); + } else if let Some(_e) = err.find::() { + code = StatusCode::INTERNAL_SERVER_ERROR; + message = "RPC_ERROR".to_string(); } else if err.find::().is_some() { // We can handle a specific error, here METHOD_NOT_ALLOWED, // and render it however we want diff --git a/src/evm.rs b/src/evm.rs index ba49c15..6e3d7ff 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -5,6 +5,7 @@ use ethers::core::types::Log; use ethers::types::transaction::eip2930::AccessList; use ethers::types::Bytes; use foundry_config::Chain; +use foundry_evm::executor::backend::DatabaseExt; use foundry_evm::executor::{fork::CreateFork, Executor}; use foundry_evm::executor::{opts::EvmOpts, Backend, ExecutorBuilder}; use foundry_evm::trace::identifier::{EtherscanIdentifier, SignaturesIdentifier}; @@ -289,6 +290,10 @@ impl Evm { self.executor.env().cfg.chain_id.into() } + pub fn get_fork_url(&self) -> Option { + self.executor.backend().active_fork_url() + } + fn set_access_list(&mut self, access_list: Option) { self.executor.env_mut().tx.access_list = access_list .unwrap_or_default() diff --git a/src/simulation.rs b/src/simulation.rs index 1595d5c..f357ff9 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -5,10 +5,13 @@ use std::sync::Arc; use dashmap::mapref::one::RefMut; use ethers::abi::{Address, Hash, Uint}; use ethers::core::types::Log; +use ethers::providers::{Http, Middleware, Provider}; use ethers::types::transaction::eip2930::AccessList; use ethers::types::Bytes; +use eyre::anyhow; use foundry_evm::CallKind; use revm::interpreter::InstructionResult; +use revm::primitives::bitvec::macros::internal::funty::Fundamental; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; use uuid::Uuid; @@ -17,7 +20,7 @@ use warp::Rejection; use crate::errors::{ IncorrectChainIdError, InvalidBlockNumbersError, MultipleChainIdsError, NoURLForChainIdError, - StateNotFound, + StateNotFound, RpcError }; use crate::evm::StorageOverride; use crate::SharedSimulationState; @@ -39,6 +42,8 @@ pub struct SimulationRequest { pub block_timestamp: Option, pub state_overrides: Option>, pub format_trace: Option, + #[serde(rename = "transactionBlockIndex")] + pub transaction_block_index: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -232,7 +237,7 @@ pub async fn simulate(transaction: SimulationRequest, config: Config) -> Result< .unwrap_or(chain_id_to_fork_url(transaction.chain_id)?); let mut evm = Evm::new( None, - fork_url, + fork_url.clone(), transaction.block_number, transaction.gas_limit, true, @@ -249,7 +254,16 @@ pub async fn simulate(transaction: SimulationRequest, config: Config) -> Result< .expect("failed to set block timestamp"); } - let response = run(&mut evm, transaction, false).await?; + let response: SimulationResponse = if transaction.transaction_block_index.is_some() { + let mut arr_resp = Vec::with_capacity(1); + apply_block_transactions(&fork_url, &transaction, &mut evm, &mut arr_resp).await?; + arr_resp + .pop() + .ok_or_else(|| anyhow!("No simulated transaction")) + .unwrap() + } else { + run(&mut evm, transaction, false).await? + }; Ok(warp::reply::json(&response)) } @@ -267,7 +281,7 @@ pub async fn simulate_bundle( .unwrap_or(chain_id_to_fork_url(first_chain_id)?); let mut evm = Evm::new( None, - fork_url, + fork_url.clone(), first_block_number, transactions[0].gas_limit, true, @@ -304,12 +318,88 @@ pub async fn simulate_bundle( .await .expect("Failed to set block timestamp"); } - response.push(run(&mut evm, transaction, true).await?); + + if transaction.clone().transaction_block_index.is_some() { + apply_block_transactions(&fork_url, &transaction, &mut evm, &mut response).await?; + } else { + response.push(run(&mut evm, transaction, true).await?); + } } Ok(warp::reply::json(&response)) } +async fn apply_block_transactions( + fork_url: &String, + transaction: &SimulationRequest, + evm: &mut Evm, + response: &mut Vec, +) -> Result<(), Rejection> { + let provider = Provider::::try_from(fork_url); + + if provider.is_err() { + return Err(warp::reject::custom(NoURLForChainIdError)); + } + + let block_transactions = provider + .unwrap() + .get_block_with_txs( + transaction + .clone() + .block_number + .expect("Transaction has no block number"), + ) + .await; + if block_transactions.is_err() { + return Err(warp::reject::custom(RpcError())); + } + let block_transactions = block_transactions.unwrap(); + + if block_transactions.is_none() { + response.push(run(evm, transaction.clone(), true).await?); + return Ok(()); + } + + let block_transactions = block_transactions.unwrap(); + + let simulation_transactions: Vec<_> = block_transactions + .transactions + .iter() + .map(|x| SimulationRequest { + chain_id: x.chain_id.unwrap().as_u64(), + from: x.from, + to: x.to.unwrap(), + value: Some(PermissiveUint(x.value)), + data: Some(x.input.clone()), + gas_limit: x.gas.as_u64(), + block_number: None, + format_trace: None, + transaction_block_index: None, + access_list: None, + block_timestamp: None, + state_overrides: None, + }) + .collect(); + let transaction_block_index = transaction.transaction_block_index.unwrap(); + let transactions_before_index = simulation_transactions + .iter() + .take(transaction_block_index.as_usize()) + .collect::>(); + let transactions_after_index = simulation_transactions + .iter() + .skip(transaction_block_index.as_usize()); + for before_tx in transactions_before_index { + let result = run(evm, before_tx.clone(), true).await; + result.expect("Failed to run transactions in block prior to transaction index"); + } + response.push(run(evm, transaction.clone(), true).await?); + for after_tx in transactions_after_index { + let result = run(evm, after_tx.clone(), true).await; + result.expect("Failed to run transactions in block after transaction index"); + } + Ok(()) +} + pub async fn simulate_stateful_new( stateful_simulation_request: StatefulSimulationRequest, config: Config, @@ -400,7 +490,17 @@ pub async fn simulate_stateful( .await .expect("Failed to set block timestamp"); } - response.push(run(&mut evm, transaction, true).await?); + if transaction.clone().transaction_block_index.is_some() { + apply_block_transactions( + &evm.get_fork_url().expect("No fork URL"), + &transaction, + &mut evm, + &mut response, + ) + .await?; + } else { + response.push(run(&mut evm, transaction, true).await?); + } } Ok(warp::reply::json(&response))