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

draft: allow simulations within a block #15

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
24 changes: 24 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<impl Reply, Infallible> {
let code;
let message: String;
Expand Down Expand Up @@ -78,6 +93,9 @@ pub async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible>
} else if let Some(_e) = err.find::<OverrideError>() {
code = StatusCode::INTERNAL_SERVER_ERROR;
message = "OVERRIDE_ERROR".to_string();
} else if let Some(_e) = err.find::<InvalidIndexError>() {
code = StatusCode::BAD_REQUEST;
message = "INVALID_TRANSACTION_INDEX".to_string();
} else if let Some(_e) = err.find::<EvmError>() {
if _e.0.to_string().contains("CallGasCostMoreThanGasLimit") {
code = StatusCode::BAD_REQUEST;
Expand All @@ -95,6 +113,12 @@ pub async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible>
None => "BAD_REQUEST".to_string(),
};
code = StatusCode::BAD_REQUEST;
} else if let Some(_e) = err.find::<InvalidRpcError>() {
code = StatusCode::BAD_REQUEST;
message = "INVALID_RPC".to_string();
} else if let Some(_e) = err.find::<RpcError>() {
code = StatusCode::INTERNAL_SERVER_ERROR;
message = "RPC_ERROR".to_string();
} else if err.find::<warp::reject::MethodNotAllowed>().is_some() {
// We can handle a specific error, here METHOD_NOT_ALLOWED,
// and render it however we want
Expand Down
5 changes: 5 additions & 0 deletions src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -289,6 +290,10 @@ impl Evm {
self.executor.env().cfg.chain_id.into()
}

pub fn get_fork_url(&self) -> Option<String> {
self.executor.backend().active_fork_url()
}

fn set_access_list(&mut self, access_list: Option<AccessList>) {
self.executor.env_mut().tx.access_list = access_list
.unwrap_or_default()
Expand Down
112 changes: 106 additions & 6 deletions src/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,7 +20,7 @@ use warp::Rejection;

use crate::errors::{
IncorrectChainIdError, InvalidBlockNumbersError, MultipleChainIdsError, NoURLForChainIdError,
StateNotFound,
StateNotFound, RpcError
};
use crate::evm::StorageOverride;
use crate::SharedSimulationState;
Expand All @@ -39,6 +42,8 @@ pub struct SimulationRequest {
pub block_timestamp: Option<u64>,
pub state_overrides: Option<HashMap<Address, StateOverride>>,
pub format_trace: Option<bool>,
#[serde(rename = "transactionBlockIndex")]
pub transaction_block_index: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -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,
Expand All @@ -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))
}
Expand All @@ -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,
Expand Down Expand Up @@ -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<SimulationResponse>,
) -> Result<(), Rejection> {
let provider = Provider::<Http>::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::<Vec<_>>();
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,
Expand Down Expand Up @@ -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))
Expand Down
Loading