From 25bd2753d34ea060abdf26349b1851af2e786db4 Mon Sep 17 00:00:00 2001 From: highonhopium Date: Thu, 29 Aug 2024 15:04:44 +0100 Subject: [PATCH] replays --- crates/eval/src/error.rs | 19 ++++++ crates/eval/src/fork.rs | 141 ++++++++++++++++++++++++++++++++++++++- crates/eval/src/trace.rs | 31 ++++++--- 3 files changed, 180 insertions(+), 11 deletions(-) diff --git a/crates/eval/src/error.rs b/crates/eval/src/error.rs index b2a6d2d40..f9230c7b9 100644 --- a/crates/eval/src/error.rs +++ b/crates/eval/src/error.rs @@ -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; @@ -23,6 +25,23 @@ pub enum ForkCallError { U64FromUint256(#[from] FromUintError), #[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"))] diff --git a/crates/eval/src/fork.rs b/crates/eval/src/fork.rs index 357b11260..e50ab2ecb 100644 --- a/crates/eval/src/fork.rs +++ b/crates/eval/src/fork.rs @@ -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::{ @@ -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}, @@ -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 { + 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, }; @@ -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::() + .unwrap(); + + let replay_result = forker.replay_transaction(tx_hash).await.unwrap(); + + assert!( + replay_result.env.tx.caller + == "0x8924274F5304277FFDdd29fad5181D98D5F65eF6" + .parse::
() + .unwrap() + ); + assert!(replay_result.exit_reason.is_ok()); + } } diff --git a/crates/eval/src/trace.rs b/crates/eval/src/trace.rs index 22c453f96..95428ca19 100644 --- a/crates/eval/src/trace.rs +++ b/crates/eval/src/trace.rs @@ -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"; @@ -56,13 +56,11 @@ pub struct RainEvalResult { pub traces: Vec, } -impl From> for RainEvalResult { - fn from(typed_return: ForkTypedReturn) -> Self { - let eval3Return { stack, writes } = typed_return.typed_return; - +impl From for RainEvalResult { + fn from(raw_call_result: RawCallResult) -> Self { let tracer_address = RAIN_TRACER_ADDRESS.parse::
().unwrap(); - let mut traces: Vec = typed_return - .raw + + let mut traces: Vec = raw_call_result .traces .unwrap() .to_owned() @@ -79,10 +77,25 @@ impl From> for RainEvalResult { traces.reverse(); RainEvalResult { - reverted: typed_return.raw.reverted, + reverted: raw_call_result.reverted, + stack: vec![], + writes: vec![], + traces, + } + } +} + +impl From> for RainEvalResult { + fn from(typed_return: ForkTypedReturn) -> 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, } } }