diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 98c6b0d851570..fd0602f3025cd 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -58,6 +58,10 @@ pub struct NodeArgs { #[clap(long)] pub silent: bool, + // Print hardhat console.logs as they are generated + #[clap(long)] + pub inline_logs: bool, + /// The EVM hardfork to use. /// /// Choose the hardfork by name, e.g. `shanghai`, `paris`, `london`, etc... @@ -210,6 +214,7 @@ impl NodeArgs { .set_pruned_history(self.prune_history) .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) + .set_inline_logs(self.inline_logs) } fn account_generator(&self) -> AccountGenerator { diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 653847839a279..2f27854c938da 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -120,6 +120,8 @@ pub struct NodeConfig { pub max_transactions: usize, /// don't print anything on startup pub silent: bool, + /// print console.logs as they are generated + pub inline_logs: bool, /// url of the rpc server that should be used for any rpc calls pub eth_rpc_url: Option, /// pins the block number for the state fork @@ -379,6 +381,7 @@ impl Default for NodeConfig { // TODO make this something dependent on block capacity max_transactions: 1_000, silent: false, + inline_logs: false, eth_rpc_url: None, fork_block_number: None, account_generator: None, @@ -608,6 +611,15 @@ impl NodeConfig { self } + /// If set, console.log() calls and emit events will be logged to the console as they are + /// executed This can be helpful when debugging anvil itself, or when something terminates + /// anvil early + #[must_use] + pub fn set_inline_logs(mut self, inline_logs: bool) -> Self { + self.inline_logs = inline_logs; + self + } + /// Sets the ipc path to use /// /// Note: this is a double Option for @@ -1006,6 +1018,7 @@ latest block number: {latest_block}" fees, fork, self.enable_steps_tracing, + self.inline_logs, self.prune_history, self.transaction_block_keeper, self.block_time, diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 7198e160cd0ab..c248600fe0c01 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -104,6 +104,7 @@ pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator> /// Cumulative gas used by all executed transactions pub gas_used: U256, pub enable_steps_tracing: bool, + pub inline_logs: bool, } impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'a, DB, Validator> { @@ -262,6 +263,10 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator inspector = inspector.with_steps_tracing(); } + if self.inline_logs { + inspector = inspector.with_inline_logs(); + } + trace!(target: "backend", "[{:?}] executing", transaction.hash()); // transact and commit the transaction let exec_result = match evm.inspect_commit(&mut inspector) { diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 9ca7686c618b5..54c48b545b298 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -5,8 +5,8 @@ use bytes::Bytes; use ethers::types::Log; use foundry_evm::{ call_inspectors, - decode::decode_console_logs, - executor::inspector::{LogCollector, Tracer}, + decode::{decode_console_log, decode_console_logs}, + executor::{EvmEventLogger, OnLog, Tracer}, revm, revm::{ interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, @@ -20,17 +20,29 @@ use foundry_evm::{ pub struct Inspector { pub tracer: Option, /// collects all `console.sol` logs - pub log_collector: LogCollector, + pub logger: EvmEventLogger, } // === impl Inspector === +#[derive(Debug, Clone, Default)] +pub struct InlineLogs; + +impl OnLog for InlineLogs { + type OnLogState = bool; + fn on_log(inline_logs_enabled: &mut Self::OnLogState, log_entry: &Log) { + if *inline_logs_enabled { + node_info!("{}", decode_console_log(log_entry).unwrap_or(format!("{:?}", log_entry))); + } + } +} + impl Inspector { /// Called after the inspecting the evm /// /// This will log all `console.sol` logs pub fn print_logs(&self) { - print_logs(&self.log_collector.logs) + print_logs(&self.logger.logs) } /// Configures the `Tracer` [`revm::Inspector`] @@ -39,6 +51,15 @@ impl Inspector { self } + /// Configures the `Executor` to emit logs and events as they are executed + pub fn with_inline_logs(mut self) -> Self { + self.logger.on_log_state = true; + self + } + + /// Enables steps recording for `Tracer` and attaches `GasInspector` to it + /// If `Tracer` wasn't configured before, configures it automatically + /// Enables steps recording for `Tracer`. pub fn with_steps_tracing(mut self) -> Self { let tracer = self.tracer.get_or_insert_with(Default::default); @@ -76,7 +97,7 @@ impl revm::Inspector for Inspector { topics: &[B256], data: &Bytes, ) { - call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { + call_inspectors!([&mut self.tracer, Some(&mut self.logger)], |inspector| { inspector.log(evm_data, address, topics, data); }); } @@ -100,7 +121,7 @@ impl revm::Inspector for Inspector { data: &mut EVMData<'_, DB>, call: &mut CallInputs, ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { + call_inspectors!([&mut self.tracer, Some(&mut self.logger)], |inspector| { inspector.call(data, call); }); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 3b9a6bf263745..973ef061cf5da 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -164,6 +164,7 @@ pub struct Backend { /// keeps track of active snapshots at a specific block active_snapshots: Arc>>, enable_steps_tracing: bool, + inline_logs: bool, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory @@ -180,6 +181,7 @@ impl Backend { fees: FeeManager, fork: Option, enable_steps_tracing: bool, + inline_logs: bool, prune_state_history_config: PruneStateHistoryConfig, transaction_block_keeper: Option, automine_block_time: Option, @@ -223,6 +225,7 @@ impl Backend { genesis, active_snapshots: Arc::new(Mutex::new(Default::default())), enable_steps_tracing, + inline_logs, prune_state_history_config, transaction_block_keeper, }; @@ -703,6 +706,10 @@ impl Backend { let db = self.db.read().await; let mut inspector = Inspector::default(); + if self.inline_logs { + inspector = inspector.with_inline_logs(); + } + let mut evm = revm::EVM::new(); evm.env = env; evm.database(&*db); @@ -764,6 +771,7 @@ impl Backend { parent_hash: storage.best_hash, gas_used: U256::zero(), enable_steps_tracing: self.enable_steps_tracing, + inline_logs: self.inline_logs, }; // create a new pending block @@ -823,6 +831,7 @@ impl Backend { parent_hash: best_hash, gas_used: U256::zero(), enable_steps_tracing: self.enable_steps_tracing, + inline_logs: self.inline_logs, }; let executed_tx = executor.execute(); @@ -889,13 +898,13 @@ impl Backend { // assuming max 5 args Some(token) => { node_info!( - " Error: reverted with custom error: {:?}", + " Error: reverted with custom error (1): {:?}", token ); } None => { node_info!( - " Error: reverted with custom error: {}", + " Error: reverted with custom error (2): {}", hex::encode(r) ); } @@ -1057,6 +1066,11 @@ impl Backend { D: DatabaseRef, { let mut inspector = Inspector::default(); + + if self.inline_logs { + inspector = inspector.with_inline_logs(); + } + let mut evm = revm::EVM::new(); evm.env = self.build_call_env(request, fee_details, block_env); evm.database(state); @@ -1097,6 +1111,9 @@ impl Backend { ) -> Result { self.with_database_at(block_request, |state, block| { let mut inspector = Inspector::default().with_steps_tracing(); + if self.inline_logs { + inspector = inspector.with_inline_logs(); + } let block_number = block.number; let mut evm = revm::EVM::new(); evm.env = self.build_call_env(request, fee_details, block); diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index e03a76e11e721..cf46e13728e3c 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -25,7 +25,7 @@ static RUN_SELECTOR: [u8; 4] = [0xc0, 0x40, 0x62, 0x26]; #[derive(Debug)] pub struct ChiselRunner { /// The Executor - pub executor: Executor, + pub executor: Executor<()>, /// An initial balance pub initial_balance: U256, /// The sender @@ -71,7 +71,7 @@ impl ChiselRunner { /// /// A new [ChiselRunner] pub fn new( - executor: Executor, + executor: Executor<()>, initial_balance: U256, sender: Address, input: Option>, diff --git a/crates/evm/src/executor/builder.rs b/crates/evm/src/executor/builder.rs index 9369bc4f2a0a2..109b968741e0b 100644 --- a/crates/evm/src/executor/builder.rs +++ b/crates/evm/src/executor/builder.rs @@ -1,4 +1,4 @@ -use super::{inspector::InspectorStackBuilder, Executor}; +use super::{inspector::InspectorStackBuilder, Executor, OnLog}; use crate::executor::backend::Backend; use ethers::types::U256; use revm::primitives::{Env, SpecId}; @@ -63,7 +63,7 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build(self, mut env: Env, db: Backend) -> Executor { + pub fn build(self, mut env: Env, db: Backend) -> Executor { let Self { mut stack, gas_limit, spec_id } = self; env.cfg.spec_id = spec_id; stack.block = Some(env.block.clone()); diff --git a/crates/evm/src/executor/inspector/logs.rs b/crates/evm/src/executor/inspector/logs.rs index a4124d4695a62..0a73949a04f7f 100644 --- a/crates/evm/src/executor/inspector/logs.rs +++ b/crates/evm/src/executor/inspector/logs.rs @@ -7,22 +7,53 @@ use ethers::{ abi::{AbiDecode, Token}, types::{Log, H256}, }; -use foundry_macros::ConsoleFmt; use revm::{ interpreter::{CallInputs, Gas, InstructionResult}, primitives::{B160, B256}, Database, EVMData, Inspector, }; +use std::fmt::Debug; + +pub trait OnLog { + type OnLogState: Default + Clone + Sync; + fn on_log(ls: &mut Self::OnLogState, log: &Log); +} /// An inspector that collects logs during execution. /// /// The inspector collects logs from the LOG opcodes as well as Hardhat-style logs. -#[derive(Debug, Clone, Default)] -pub struct LogCollector { +pub struct EvmEventLogger +where + ONLOG::OnLogState: Default, +{ pub logs: Vec, + pub on_log_state: ONLOG::OnLogState, +} + +impl Clone for EvmEventLogger { + fn clone(&self) -> Self { + Self { logs: self.logs.clone(), on_log_state: self.on_log_state.clone() } + } +} + +impl Default for EvmEventLogger { + fn default() -> EvmEventLogger { + Self { logs: Vec::::default(), on_log_state: ONLOG::OnLogState::default() } + } } -impl LogCollector { +impl OnLog for () { + type OnLogState = (); + fn on_log(_: &mut Self::OnLogState, _: &Log) {} +} + +impl Debug for EvmEventLogger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EvmEventLogger").field("logs", &self.logs).finish() + } +} + +impl EvmEventLogger { fn hardhat_log(&mut self, mut input: Vec) -> (InstructionResult, Bytes) { // Patch the Hardhat-style selectors patch_hardhat_console_selector(&mut input); @@ -36,21 +67,26 @@ impl LogCollector { } }; + let log_entry = convert_hh_log_to_event(decoded); + ONLOG::on_log(&mut self.on_log_state, &log_entry); + // Convert it to a DS-style `emit log(string)` event - self.logs.push(convert_hh_log_to_event(decoded)); + self.logs.push(log_entry); (InstructionResult::Continue, Bytes::new()) } } -impl Inspector for LogCollector { +impl Inspector for EvmEventLogger { fn log(&mut self, _: &mut EVMData<'_, DB>, address: &B160, topics: &[B256], data: &Bytes) { - self.logs.push(Log { + let log_entry = Log { address: b160_to_h160(*address), topics: topics.iter().copied().map(b256_to_h256).collect(), data: data.clone().into(), ..Default::default() - }); + }; + ONLOG::on_log(&mut self.on_log_state, &log_entry); + self.logs.push(log_entry); } fn call( @@ -78,7 +114,20 @@ const TOPIC: H256 = H256([ /// Converts a call to Hardhat's `console.log` to a DSTest `log(string)` event. fn convert_hh_log_to_event(call: HardhatConsoleCalls) -> Log { // Convert the parameters of the call to their string representation using `ConsoleFmt`. - let fmt = call.fmt(Default::default()); + let fmt = foundry_macros::ConsoleFmt::fmt(&call, Default::default()); + /* NOTE: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + In the master branch this is call.fmt(Default::default()) + However, if I change that here, I get the below error. + I don't understand why, as the change seems self contained, and if I'm getting an error whereas no such + error is generated here, in the master branch ... that implies there's some incompatible diff happeneing + elsewhere ... a diff that shouldn't exist! Yikes. + + error[E0277]: the trait bound `&mut std::fmt::Formatter<'_>: std::default::Default` is not satisfied + --> crates/evm/src/executor/inspector/logs.rs:117:24 + | + 117 | let fmt = call.fmt(Default::default()); + | ^^^^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `&mut std::fmt::Formatter<'_>` + */ let token = Token::String(fmt); let data = ethers::abi::encode(&[token]).into(); Log { topics: vec![TOPIC], data, ..Default::default() } diff --git a/crates/evm/src/executor/inspector/mod.rs b/crates/evm/src/executor/inspector/mod.rs index f25a6ab434b9b..839c663965c9c 100644 --- a/crates/evm/src/executor/inspector/mod.rs +++ b/crates/evm/src/executor/inspector/mod.rs @@ -1,6 +1,9 @@ #[macro_use] mod utils; +mod logs; +pub use logs::{EvmEventLogger, OnLog}; + mod access_list; pub use access_list::AccessListTracer; @@ -19,9 +22,6 @@ pub use debugger::Debugger; mod fuzzer; pub use fuzzer::Fuzzer; -mod logs; -pub use logs::LogCollector; - mod printer; pub use printer::TracePrinter; diff --git a/crates/evm/src/executor/inspector/stack.rs b/crates/evm/src/executor/inspector/stack.rs index d626fc3d4e33a..0c867f512f031 100644 --- a/crates/evm/src/executor/inspector/stack.rs +++ b/crates/evm/src/executor/inspector/stack.rs @@ -1,5 +1,6 @@ use super::{ - Cheatcodes, CheatsConfig, ChiselState, Debugger, Fuzzer, LogCollector, TracePrinter, Tracer, + Cheatcodes, CheatsConfig, ChiselState, Debugger, EvmEventLogger, Fuzzer, OnLog, TracePrinter, + Tracer, }; use crate::{ coverage::HitMaps, @@ -42,7 +43,7 @@ pub struct InspectorStackBuilder { pub trace: Option, /// Whether to enable the debugger. pub debug: Option, - /// Whether logs should be collected. + /// Whether logs should be inspected. pub logs: Option, /// Whether coverage info should be collected. pub coverage: Option, @@ -132,7 +133,7 @@ impl InspectorStackBuilder { /// Builds the stack of inspectors to use when transacting/committing on the EVM. /// /// See also [`revm::Evm::inspect_ref`] and [`revm::Evm::commit_ref`]. - pub fn build(self) -> InspectorStack { + pub fn build(self) -> InspectorStack { let Self { block, gas_price, @@ -202,19 +203,48 @@ pub struct InspectorData { /// /// If a call to an inspector returns a value other than [InstructionResult::Continue] (or /// equivalent) the remaining inspectors are not called. -#[derive(Debug, Clone, Default)] -pub struct InspectorStack { +#[derive(Debug)] +pub struct InspectorStack { pub cheatcodes: Option, pub chisel_state: Option, pub coverage: Option, pub debugger: Option, pub fuzzer: Option, - pub log_collector: Option, + pub logger: Option>, pub printer: Option, pub tracer: Option, } -impl InspectorStack { +impl Clone for InspectorStack { + fn clone(&self) -> Self { + Self { + cheatcodes: self.cheatcodes.clone(), + chisel_state: self.chisel_state.clone(), + coverage: self.coverage.clone(), + debugger: self.debugger.clone(), + fuzzer: self.fuzzer.clone(), + logger: self.logger.clone(), + printer: self.printer.clone(), + tracer: self.tracer.clone(), + } + } +} +impl Default for InspectorStack { + fn default() -> Self { + Self { + cheatcodes: Option::::default(), + chisel_state: Option::::default(), + coverage: Option::::default(), + debugger: Option::::default(), + fuzzer: Option::::default(), + logger: Option::>::default(), + printer: Option::::default(), + tracer: Option::::default(), + } + } +} + +impl InspectorStack { /// Creates a new inspector stack. /// /// Note that the stack is empty by default, and you must add inspectors to it. @@ -281,7 +311,7 @@ impl InspectorStack { /// Set whether to enable the log collector. #[inline] pub fn collect_logs(&mut self, yes: bool) { - self.log_collector = yes.then(Default::default); + self.logger = yes.then(Default::default); } /// Set whether to enable the trace printer. @@ -300,7 +330,7 @@ impl InspectorStack { #[inline] pub fn collect(self) -> InspectorData { InspectorData { - logs: self.log_collector.map(|logs| logs.logs).unwrap_or_default(), + logs: self.logger.map(|logs| logs.logs).unwrap_or_default(), labels: self .cheatcodes .as_ref() @@ -333,7 +363,7 @@ impl InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -355,7 +385,7 @@ impl InspectorStack { } } -impl Inspector for InspectorStack { +impl Inspector for InspectorStack { fn initialize_interp( &mut self, interpreter: &mut Interpreter, @@ -366,7 +396,7 @@ impl Inspector for InspectorStack { &mut self.debugger, &mut self.coverage, &mut self.tracer, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -394,7 +424,7 @@ impl Inspector for InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -419,7 +449,7 @@ impl Inspector for InspectorStack { data: &Bytes, ) { call_inspectors!( - [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], + [&mut self.tracer, &mut self.logger, &mut self.cheatcodes, &mut self.printer], |inspector| { inspector.log(evm_data, address, topics, data); } @@ -436,7 +466,7 @@ impl Inspector for InspectorStack { [ &mut self.debugger, &mut self.tracer, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer, &mut self.chisel_state @@ -465,7 +495,7 @@ impl Inspector for InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -514,7 +544,7 @@ impl Inspector for InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -545,7 +575,7 @@ impl Inspector for InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -573,7 +603,7 @@ impl Inspector for InspectorStack { [ &mut self.debugger, &mut self.tracer, - &mut self.log_collector, + &mut self.logger, &mut self.cheatcodes, &mut self.printer, &mut self.chisel_state diff --git a/crates/evm/src/executor/mod.rs b/crates/evm/src/executor/mod.rs index d07d1bd112c79..24deaf94985c8 100644 --- a/crates/evm/src/executor/mod.rs +++ b/crates/evm/src/executor/mod.rs @@ -1,4 +1,5 @@ use self::inspector::{cheatcodes::util::BroadcastableTransactions, Cheatcodes, InspectorData}; +pub use self::inspector::{EvmEventLogger, OnLog, Tracer}; use crate::{ debug::DebugArena, decode, @@ -30,7 +31,7 @@ pub use revm::{ TxEnv, B160, U256 as rU256, }, }; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, marker::PhantomData}; /// ABIs used internally in the executor pub mod abi; @@ -75,9 +76,9 @@ pub const DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE: &[u8] = &hex!("7fffffffffffffff /// - `committing`: any state changes made during the call are recorded and are persisting /// - `raw`: state changes only exist for the duration of the call and are discarded afterwards, in /// other words: the state of the underlying database remains unchanged. -#[derive(Debug, Clone)] -pub struct Executor { - /// The underlying `revm::Database` that contains the EVM storage. +#[derive(Debug)] +pub struct Executor { + /// The underlying `revm::Database` that contains the EVM storage // Note: We do not store an EVM here, since we are really // only interested in the database. REVM's `EVM` is a thin // wrapper around spawning a new EVM on every call anyway, @@ -86,18 +87,38 @@ pub struct Executor { /// The EVM environment. pub env: Env, /// The Revm inspector stack. - pub inspector: InspectorStack, + pub inspector: InspectorStack, /// The gas limit for calls and deployments. This is different from the gas limit imposed by /// the passed in environment, as those limits are used by the EVM for certain opcodes like /// `gaslimit`. gas_limit: U256, + _marker: std::marker::PhantomData, } // === impl Executor === -impl Executor { +// Clone is coded by hand (no macro) because otherwise the macro derivation ends up producing errors +// elsewhere: "expected Executor but got &Executor" +impl Clone for Executor { + fn clone(&self) -> Self { + Executor { + backend: self.backend.clone(), + env: self.env.clone(), + inspector: self.inspector.clone(), + gas_limit: self.gas_limit, + _marker: PhantomData, + } + } +} + +impl Executor { #[inline] - pub fn new(mut backend: Backend, env: Env, inspector: InspectorStack, gas_limit: U256) -> Self { + pub fn new( + mut backend: Backend, + env: Env, + inspector: InspectorStack, + gas_limit: U256, + ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // does not fail backend.insert_account_info( @@ -108,7 +129,26 @@ impl Executor { }, ); - Executor { backend, env, inspector, gas_limit } + Executor { backend, env, inspector, gas_limit, _marker: PhantomData } + } + + /// Returns a reference to the Env + pub fn env(&self) -> &Env { + &self.env + } + + /// Returns a mutable reference to the Env + pub fn env_mut(&mut self) -> &mut Env { + &mut self.env + } + + /// Returns a mutable reference to the Backend + pub fn backend_mut(&mut self) -> &mut Backend { + &mut self.backend + } + + pub fn backend(&self) -> &Backend { + &self.backend } /// Creates the default CREATE2 Contract Deployer for local tests and scripts. @@ -535,8 +575,12 @@ impl Executor { // for the contract's `failed` variable and the `globalFailure` flag in the state of the // cheatcode address which are both read when call `"failed()(bool)"` in the next step backend.commit(state_changeset); - let executor = - Executor::new(backend, self.env.clone(), self.inspector.clone(), self.gas_limit); + let executor = Executor::::new( + backend, + self.env.clone(), + self.inspector.clone(), + self.gas_limit, + ); let mut success = !reverted; if success { @@ -756,9 +800,9 @@ fn calc_stipend(calldata: &[u8], spec: SpecId) -> u64 { } /// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` -fn convert_executed_result( +fn convert_executed_result( env: Env, - inspector: InspectorStack, + inspector: InspectorStack, result: ResultAndState, ) -> eyre::Result { let ResultAndState { result: exec_result, state: state_changeset } = result; diff --git a/crates/evm/src/fuzz/invariant/call_override.rs b/crates/evm/src/fuzz/invariant/call_override.rs index c843848bbd423..efaa85636bb4b 100644 --- a/crates/evm/src/fuzz/invariant/call_override.rs +++ b/crates/evm/src/fuzz/invariant/call_override.rs @@ -1,5 +1,5 @@ use super::BasicTxDetails; -use crate::executor::Executor; +use crate::executor::{Executor, OnLog}; use ethers::types::{Address, Bytes}; use parking_lot::{Mutex, RwLock}; use proptest::{ @@ -92,7 +92,10 @@ impl RandomCallGenerator { } /// Sets up the calls generated by the internal fuzzer, if they exist. -pub fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { +pub fn set_up_inner_replay( + executor: &mut Executor, + inner_sequence: &[Option], +) { if let Some(fuzzer) = &mut executor.inspector.fuzzer { if let Some(call_generator) = &mut fuzzer.call_generator { call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); diff --git a/crates/evm/src/fuzz/invariant/error.rs b/crates/evm/src/fuzz/invariant/error.rs index 0f6157d329655..4a1e9603d8621 100644 --- a/crates/evm/src/fuzz/invariant/error.rs +++ b/crates/evm/src/fuzz/invariant/error.rs @@ -1,7 +1,7 @@ use super::{BasicTxDetails, InvariantContract}; use crate::{ decode::decode_revert, - executor::{Executor, RawCallResult}, + executor::{Executor, OnLog, RawCallResult}, fuzz::{invariant::set_up_inner_replay, *}, trace::{load_contracts, TraceKind, Traces}, CALLER, @@ -79,9 +79,9 @@ impl InvariantFuzzError { } /// Replays the error case and collects all necessary traces. - pub fn replay( + pub fn replay( &self, - mut executor: Executor, + mut executor: Executor, known_contracts: Option<&ContractsByArtifact>, mut ided_contracts: ContractsByAddress, logs: &mut Vec, @@ -151,9 +151,9 @@ impl InvariantFuzzError { } /// Tests that the modified sequence of calls successfully reverts on the error function. - fn fails_successfully<'a>( + fn fails_successfully<'a, ONLOG: OnLog>( &self, - mut executor: Executor, + mut executor: Executor, calls: &'a [BasicTxDetails], anchor: usize, removed_calls: &[usize], @@ -198,10 +198,10 @@ impl InvariantFuzzError { /// same process again. /// /// Returns the smallest sequence found. - fn try_shrinking<'a>( + fn try_shrinking<'a, ONLOG: OnLog>( &self, calls: &'a [BasicTxDetails], - executor: &Executor, + executor: &Executor, ) -> Vec<&'a BasicTxDetails> { let mut anchor = 0; let mut removed_calls = vec![]; @@ -210,16 +210,20 @@ impl InvariantFuzzError { while anchor != calls.len() { // Get the latest removed element, so we know which one to remove next. - let removed = - match self.fails_successfully(executor.clone(), calls, anchor, &removed_calls) { - Ok(new_sequence) => { - if shrunk.len() > new_sequence.len() { - shrunk = new_sequence; - } - removed_calls.last().cloned() + let removed = match self.fails_successfully::( + executor.clone(), + calls, + anchor, + &removed_calls, + ) { + Ok(new_sequence) => { + if shrunk.len() > new_sequence.len() { + shrunk = new_sequence; } - Err(_) => removed_calls.pop(), - }; + removed_calls.last().cloned() + } + Err(_) => removed_calls.pop(), + }; if let Some(last_removed) = removed { // If we haven't reached the end of the sequence, then remove the next element. diff --git a/crates/evm/src/fuzz/invariant/executor.rs b/crates/evm/src/fuzz/invariant/executor.rs index 154bf729fb670..5f064748d4cb2 100644 --- a/crates/evm/src/fuzz/invariant/executor.rs +++ b/crates/evm/src/fuzz/invariant/executor.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::{ executor::{ - inspector::Fuzzer, Executor, RawCallResult, StateChangeset, CHEATCODE_ADDRESS, + inspector::Fuzzer, Executor, OnLog, RawCallResult, StateChangeset, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, }, fuzz::{ @@ -60,8 +60,8 @@ impl RichInvariantResults { /// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contracts with /// inputs, until it finds a counterexample sequence. The provided [`TestRunner`] contains all the /// configuration which can be overridden via [environment variables](https://docs.rs/proptest/1.0.0/proptest/test_runner/struct.Config.html) -pub struct InvariantExecutor<'a> { - pub executor: Executor, +pub struct InvariantExecutor<'a, ONLOG: OnLog> { + pub executor: Executor, /// Proptest runner. runner: TestRunner, /// The invariant configuration @@ -75,10 +75,10 @@ pub struct InvariantExecutor<'a> { artifact_filters: ArtifactFilters, } -impl<'a> InvariantExecutor<'a> { +impl<'a, ONLOG: OnLog> InvariantExecutor<'a, ONLOG> { /// Instantiates a fuzzed executor EVM given a testrunner pub fn new( - executor: Executor, + executor: Executor, runner: TestRunner, config: InvariantConfig, setup_contracts: &'a ContractsByAddress, @@ -563,10 +563,10 @@ fn collect_data( /// Returns the mapping of (Invariant Function Name -> Call Result, Logs, Traces) if invariants were /// asserted. #[allow(clippy::too_many_arguments)] -fn can_continue( +fn can_continue( invariant_contract: &InvariantContract, call_result: RawCallResult, - executor: &Executor, + executor: &Executor, calldata: &[BasicTxDetails], failures: &mut InvariantFailures, targeted_contracts: &FuzzRunIdentifiedContracts, diff --git a/crates/evm/src/fuzz/invariant/mod.rs b/crates/evm/src/fuzz/invariant/mod.rs index 9bd5a828f0190..b1c8e24e86e3f 100644 --- a/crates/evm/src/fuzz/invariant/mod.rs +++ b/crates/evm/src/fuzz/invariant/mod.rs @@ -1,7 +1,7 @@ //! Fuzzing support abstracted over the [`Evm`](crate::Evm) used use crate::{ - executor::Executor, + executor::{Executor, OnLog}, fuzz::*, trace::{load_contracts, TraceKind, Traces}, CALLER, @@ -48,9 +48,9 @@ pub struct InvariantContract<'a> { /// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the /// external `invariant_failures.failed_invariant` map and returns a generic error. /// Returns the mapping of (Invariant Function Name -> Call Result). -pub fn assert_invariants( +pub fn assert_invariants( invariant_contract: &InvariantContract, - executor: &Executor, + executor: &Executor, calldata: &[BasicTxDetails], invariant_failures: &mut InvariantFailures, shrink_sequence: bool, @@ -101,9 +101,9 @@ pub fn assert_invariants( /// Replays the provided invariant run for collecting the logs and traces from all depths. #[allow(clippy::too_many_arguments)] -pub fn replay_run( +pub fn replay_run( invariant_contract: &InvariantContract, - mut executor: Executor, + mut executor: Executor, known_contracts: Option<&ContractsByArtifact>, mut ided_contracts: ContractsByAddress, logs: &mut Vec, diff --git a/crates/evm/src/fuzz/mod.rs b/crates/evm/src/fuzz/mod.rs index 3d7f0c559aaad..36d803f823cd8 100644 --- a/crates/evm/src/fuzz/mod.rs +++ b/crates/evm/src/fuzz/mod.rs @@ -1,7 +1,7 @@ use crate::{ coverage::HitMaps, decode::{self, decode_console_logs}, - executor::{Executor, RawCallResult}, + executor::{Executor, OnLog, RawCallResult}, trace::CallTraceArena, }; use error::{FuzzError, ASSUME_MAGIC_RETURN_CODE}; @@ -30,9 +30,9 @@ pub mod strategies; /// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contract with /// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the /// configuration which can be overridden via [environment variables](https://docs.rs/proptest/1.0.0/proptest/test_runner/struct.Config.html) -pub struct FuzzedExecutor<'a> { +pub struct FuzzedExecutor<'a, ONLOG: OnLog> { /// The VM - executor: &'a Executor, + executor: &'a Executor, /// The fuzzer runner: TestRunner, /// The account that calls tests @@ -41,10 +41,10 @@ pub struct FuzzedExecutor<'a> { config: FuzzConfig, } -impl<'a> FuzzedExecutor<'a> { +impl<'a, ONLOG: OnLog> FuzzedExecutor<'a, ONLOG> { /// Instantiates a fuzzed executor given a testrunner pub fn new( - executor: &'a Executor, + executor: &'a Executor, runner: TestRunner, sender: Address, config: FuzzConfig, diff --git a/crates/evm/src/trace/executor.rs b/crates/evm/src/trace/executor.rs index 7a57f68c42fe0..4f279f97bfef1 100644 --- a/crates/evm/src/trace/executor.rs +++ b/crates/evm/src/trace/executor.rs @@ -1,5 +1,5 @@ use crate::{ - executor::{fork::CreateFork, opts::EvmOpts, Backend, Executor, ExecutorBuilder}, + executor::{fork::CreateFork, opts::EvmOpts, Backend, Executor, ExecutorBuilder, OnLog}, utils::evm_spec, }; use ethers::solc::EvmVersion; @@ -7,12 +7,14 @@ use foundry_config::Config; use revm::primitives::Env; use std::ops::{Deref, DerefMut}; +pub type TracingExecutor = GenTracingExecutor<()>; + /// A default executor with tracing enabled -pub struct TracingExecutor { - executor: Executor, +pub struct GenTracingExecutor { + executor: Executor, } -impl TracingExecutor { +impl GenTracingExecutor { pub async fn new( env: revm::primitives::Env, fork: Option, @@ -46,15 +48,15 @@ impl TracingExecutor { } } -impl Deref for TracingExecutor { - type Target = Executor; +impl Deref for GenTracingExecutor { + type Target = Executor; fn deref(&self) -> &Self::Target { &self.executor } } -impl DerefMut for TracingExecutor { +impl DerefMut for GenTracingExecutor { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.executor } diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/forge/bin/cmd/script/runner.rs index 8a50bb21eaf32..d3474b1df1f52 100644 --- a/crates/forge/bin/cmd/script/runner.rs +++ b/crates/forge/bin/cmd/script/runner.rs @@ -18,13 +18,13 @@ pub enum SimulationStage { /// Drives script execution #[derive(Debug)] pub struct ScriptRunner { - pub executor: Executor, + pub executor: Executor<()>, pub initial_balance: U256, pub sender: Address, } impl ScriptRunner { - pub fn new(executor: Executor, initial_balance: U256, sender: Address) -> Self { + pub fn new(executor: Executor<()>, initial_balance: U256, sender: Address) -> Self { Self { executor, initial_balance, sender } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 24b06d63f088d..b20f3e004dd69 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -10,7 +10,7 @@ use foundry_common::{ContractsByArtifact, TestFunctionExt}; use foundry_evm::{ executor::{ backend::Backend, fork::CreateFork, inspector::CheatsConfig, opts::EvmOpts, Executor, - ExecutorBuilder, + ExecutorBuilder, OnLog, }, revm, }; @@ -139,7 +139,7 @@ impl MultiContractRunner { }) .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) .map_with(stream_result, |stream_result, (id, (abi, deploy_code, libs))| { - let executor = ExecutorBuilder::new() + let executor: Executor<()> = ExecutorBuilder::new() .inspectors(|stack| { stack .cheatcodes(self.cheats_config.clone()) @@ -174,11 +174,11 @@ impl MultiContractRunner { #[instrument(skip_all, fields(name = %name))] #[allow(clippy::too_many_arguments)] - fn run_tests( + fn run_tests( &self, name: &str, contract: &Abi, - executor: Executor, + executor: Executor, deploy_code: Bytes, libs: &[Bytes], filter: impl TestFilter, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index b81ce598aca64..a305acffa7e71 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -14,7 +14,7 @@ use foundry_common::{ use foundry_config::{FuzzConfig, InvariantConfig}; use foundry_evm::{ decode::decode_console_logs, - executor::{CallResult, EvmError, ExecutionErr, Executor}, + executor::{CallResult, EvmError, ExecutionErr, Executor, OnLog}, fuzz::{ invariant::{ replay_run, InvariantContract, InvariantExecutor, InvariantFuzzError, @@ -29,15 +29,16 @@ use proptest::test_runner::{TestError, TestRunner}; use rayon::prelude::*; use std::{ collections::{BTreeMap, HashMap}, + marker::Sync, time::Instant, }; /// A type that executes all tests of a contract -#[derive(Debug, Clone)] -pub struct ContractRunner<'a> { +#[derive(Debug)] +pub struct ContractRunner<'a, ONLOG: OnLog> { pub name: &'a str, /// The executor used by the runner. - pub executor: Executor, + pub executor: Executor, /// Library contracts to be deployed before the test contract pub predeploy_libs: &'a [Bytes], /// The deployed contract's code @@ -53,11 +54,26 @@ pub struct ContractRunner<'a> { pub sender: Address, } -impl<'a> ContractRunner<'a> { +impl<'a, ONLOG: OnLog> Clone for ContractRunner<'a, ONLOG> { + fn clone(&self) -> Self { + Self { + name: self.name, + executor: self.executor.clone(), + predeploy_libs: self.predeploy_libs, + code: self.code.clone(), + contract: self.contract, + errors: self.errors, + initial_balance: self.initial_balance, + sender: self.sender, + } + } +} + +impl<'a, ONLOG: OnLog> ContractRunner<'a, ONLOG> { #[allow(clippy::too_many_arguments)] pub fn new( name: &'a str, - executor: Executor, + executor: Executor, contract: &'a Abi, code: Bytes, initial_balance: U256, @@ -78,7 +94,7 @@ impl<'a> ContractRunner<'a> { } } -impl<'a> ContractRunner<'a> { +impl<'a, ONLOG: OnLog + Sync> ContractRunner<'a, ONLOG> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, setup: bool) -> TestSetup { diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index b28a3cca0c958..bd2fe8e60a1f9 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -11,7 +11,7 @@ use foundry_evm::{ executor::{ backend::Backend, opts::{Env, EvmOpts}, - DatabaseRef, Executor, ExecutorBuilder, + DatabaseRef, Executor, ExecutorBuilder, OnLog, }, fuzz::FuzzedExecutor, CALLER, @@ -76,7 +76,9 @@ pub static EVM_OPTS: Lazy = Lazy::new(|| EvmOpts { ..Default::default() }); -pub fn fuzz_executor(executor: &Executor) -> FuzzedExecutor { +pub fn fuzz_executor( + executor: &Executor, +) -> FuzzedExecutor { let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; FuzzedExecutor::new(