Skip to content

Commit

Permalink
replays
Browse files Browse the repository at this point in the history
  • Loading branch information
hardyjosh committed Aug 29, 2024
1 parent a29afe6 commit 25bd275
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 11 deletions.
19 changes: 19 additions & 0 deletions crates/eval/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use alloy::primitives::ruint::FromUintError;
#[cfg(not(target_family = "wasm"))]
use foundry_evm::backend::DatabaseError;
#[cfg(not(target_family = "wasm"))]
use foundry_evm::executors::RawCallResult;
use rain_error_decoding::{AbiDecodeFailedErrors, AbiDecodedErrorType};
use thiserror::Error;
Expand All @@ -23,6 +25,23 @@ pub enum ForkCallError {
U64FromUint256(#[from] FromUintError<u64>),
#[error(transparent)]
Eyre(#[from] eyre::Report),
#[error("Replay transaction error: {:#?}", .0)]
ReplayTransactionError(ReplayTransactionError),
}

#[derive(Debug, Error)]
pub enum ReplayTransactionError {
#[error("Transaction not found for hash {0} and fork url {1}")]
TransactionNotFound(String, String),
#[error("No active fork found")]
NoActiveFork,
#[cfg(not(target_family = "wasm"))]
#[error("Database error for hash {0} and fork url {1}: {2}")]
DatabaseError(String, String, DatabaseError),
#[error("No block number found in transaction for hash {0} and fork url {1}")]
NoBlockNumberFound(String, String),
#[error("No from address found in transaction for hash {0} and fork url {1}")]
NoFromAddressFound(String, String),
}

#[cfg(not(target_family = "wasm"))]
Expand Down
141 changes: 139 additions & 2 deletions crates/eval/src/fork.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::error::ForkCallError;
use crate::error::{ForkCallError, ReplayTransactionError};
use alloy::primitives::{Address, BlockNumber, U256};
use alloy::sol_types::SolCall;
use foundry_evm::{
Expand All @@ -8,6 +8,7 @@ use foundry_evm::{
opts::EvmOpts,
};
use rain_error_decoding::AbiDecodedErrorType;
use revm::primitives::B256;
use revm::{
interpreter::InstructionResult,
primitives::{Address as Addr, Bytes, Env, HashSet, SpecId},
Expand Down Expand Up @@ -388,12 +389,120 @@ impl Forker {
)
.map_err(|v| ForkCallError::ExecutorError(v.to_string()))
}

/// Replays a transaction from the forked EVM.
/// # Arguments
/// * `tx_hash` - The transaction hash.
/// # Returns
/// A result containing the raw call result.
pub async fn replay_transaction(
&mut self,
tx_hash: B256,
) -> Result<RawCallResult, ForkCallError> {
let fork_url = self
.executor
.backend()
.active_fork_url()
.unwrap_or("No fork url found".to_string());

// get the transaction
let shared_backend = &self
.executor
.backend()
.active_fork_db()
.ok_or(ForkCallError::ReplayTransactionError(
ReplayTransactionError::NoActiveFork,
))?
.db;
let full_tx = shared_backend.get_transaction(tx_hash).map_err(|e| {
ForkCallError::ReplayTransactionError(ReplayTransactionError::DatabaseError(
tx_hash.to_string(),
fork_url.clone(),
e,
))
})?;

// get the block number from the transaction
let block_number = full_tx
.block_number
.ok_or(ForkCallError::ReplayTransactionError(
ReplayTransactionError::NoBlockNumberFound(tx_hash.to_string(), fork_url.clone()),
))?;

// get the block
let block = shared_backend.get_full_block(block_number).map_err(|e| {
ForkCallError::ReplayTransactionError(ReplayTransactionError::DatabaseError(
block_number.to_string(),
fork_url.clone(),
e,
))
})?;

// matching env to the env from the block the transaction is in
self.executor.env_mut().block.number = U256::from(block_number);
self.executor.env_mut().block.timestamp = U256::from(block.header.timestamp);
self.executor.env_mut().block.coinbase = block.header.miner;
self.executor.env_mut().block.difficulty = block.header.difficulty;
self.executor.env_mut().block.prevrandao = Some(block.header.mix_hash.unwrap_or_default());
self.executor.env_mut().block.basefee =
U256::from(block.header.base_fee_per_gas.unwrap_or_default());
self.executor.env_mut().block.gas_limit = U256::from(block.header.gas_limit);

let _ = &self
.add_or_select(
NewForkedEvm {
fork_url: fork_url.clone(),
fork_block_number: Some(block_number - 1),
},
None,
)
.await;

let active_fork_local_id = self
.executor
.backend()
.active_fork_id()
.ok_or(ForkCallError::ExecutorError("no active fork!".to_owned()))?;

let mut journaled_state = JournaledState::new(SpecId::LATEST, HashSet::new());

let env = self.executor.env().clone();

// replay all transactions that came before
let tx = self.executor.backend_mut().replay_until(
active_fork_local_id,
env,
tx_hash,
&mut journaled_state,
)?;

let res = match tx {
Some(tx) => match tx.to {
Some(to) => self.call(tx.from.as_slice(), to.as_slice(), &tx.input)?,
None => {
return Err(ForkCallError::ReplayTransactionError(
ReplayTransactionError::NoFromAddressFound(tx_hash.to_string(), fork_url),
))
}
},
None => {
return Err(ForkCallError::ReplayTransactionError(
ReplayTransactionError::TransactionNotFound(tx_hash.to_string(), fork_url),
))
}
};

Ok(res)
}
}

#[cfg(test)]

mod tests {
use crate::namespace::CreateNamespace;
use crate::{
namespace::CreateNamespace,
trace::{RainEvalResult, RainSourceTrace},
};
use rain_interpreter_env::{
CI_DEPLOY_SEPOLIA_RPC_URL, CI_FORK_SEPOLIA_BLOCK_NUMBER, CI_FORK_SEPOLIA_DEPLOYER_ADDRESS,
};
Expand Down Expand Up @@ -663,4 +772,32 @@ mod tests {
U256::from(POLYGON_FORK_NUMBER + 1)
);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fork_replay() {
let mut forker = Forker::new_with_fork(
NewForkedEvm {
fork_url: CI_DEPLOY_SEPOLIA_RPC_URL.to_string(),
fork_block_number: None,
},
None,
None,
)
.await
.unwrap();

let tx_hash = "0xcbfff7d9369afcc7a851dff42ca2769f32d77c3b9066023b887583ee9cd0809d"
.parse::<B256>()
.unwrap();

let replay_result = forker.replay_transaction(tx_hash).await.unwrap();

assert!(
replay_result.env.tx.caller
== "0x8924274F5304277FFDdd29fad5181D98D5F65eF6"
.parse::<Address>()
.unwrap()
);
assert!(replay_result.exit_reason.is_ok());
}
}
31 changes: 22 additions & 9 deletions crates/eval/src/trace.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::fork::ForkTypedReturn;
use alloy::primitives::{Address, U256};
use foundry_evm::executors::RawCallResult;
use rain_interpreter_bindings::IInterpreterV3::{eval3Call, eval3Return};

use thiserror::Error;

pub const RAIN_TRACER_ADDRESS: &str = "0xF06Cd48c98d7321649dB7D8b2C396A81A2046555";
Expand Down Expand Up @@ -56,13 +56,11 @@ pub struct RainEvalResult {
pub traces: Vec<RainSourceTrace>,
}

impl From<ForkTypedReturn<eval3Call>> for RainEvalResult {
fn from(typed_return: ForkTypedReturn<eval3Call>) -> Self {
let eval3Return { stack, writes } = typed_return.typed_return;

impl From<RawCallResult> for RainEvalResult {
fn from(raw_call_result: RawCallResult) -> Self {
let tracer_address = RAIN_TRACER_ADDRESS.parse::<Address>().unwrap();
let mut traces: Vec<RainSourceTrace> = typed_return
.raw

let mut traces: Vec<RainSourceTrace> = raw_call_result
.traces
.unwrap()
.to_owned()
Expand All @@ -79,10 +77,25 @@ impl From<ForkTypedReturn<eval3Call>> for RainEvalResult {
traces.reverse();

RainEvalResult {
reverted: typed_return.raw.reverted,
reverted: raw_call_result.reverted,
stack: vec![],
writes: vec![],
traces,
}
}
}

impl From<ForkTypedReturn<eval3Call>> for RainEvalResult {
fn from(typed_return: ForkTypedReturn<eval3Call>) -> Self {
let eval3Return { stack, writes } = typed_return.typed_return;

let res: RainEvalResult = typed_return.raw.into();

RainEvalResult {
reverted: res.reverted,
stack,
writes,
traces,
traces: res.traces,
}
}
}
Expand Down

0 comments on commit 25bd275

Please sign in to comment.