From 8b31d0b5a9b7a25236ec76cc34f4c131006f9460 Mon Sep 17 00:00:00 2001 From: AA Date: Fri, 28 Jul 2023 20:18:52 +0000 Subject: [PATCH 01/18] logs to logger when referring to LogCollector inspector --- evm/src/executor/inspector/mod.rs | 2 +- evm/src/executor/inspector/stack.rs | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/evm/src/executor/inspector/mod.rs b/evm/src/executor/inspector/mod.rs index 74ab7da036d0c..bee0b3e9b5854 100644 --- a/evm/src/executor/inspector/mod.rs +++ b/evm/src/executor/inspector/mod.rs @@ -75,7 +75,7 @@ impl InspectorStackConfig { /// See also [`revm::Evm::inspect_ref`] and [`revm::Evm::commit_ref`] pub fn stack(&self) -> InspectorStack { let mut stack = - InspectorStack { logs: Some(LogCollector::default()), ..Default::default() }; + InspectorStack { logger: Some(LogCollector::default()), ..Default::default() }; stack.cheatcodes = self.create_cheatcodes(); if let Some(ref mut cheatcodes) = stack.cheatcodes { diff --git a/evm/src/executor/inspector/stack.rs b/evm/src/executor/inspector/stack.rs index 898ca459737fc..0006e666b4430 100644 --- a/evm/src/executor/inspector/stack.rs +++ b/evm/src/executor/inspector/stack.rs @@ -52,7 +52,7 @@ pub struct InspectorData { #[derive(Default)] pub struct InspectorStack { pub tracer: Option, - pub logs: Option, + pub logger: Option, pub cheatcodes: Option, pub gas: Option>>, pub debugger: Option, @@ -65,7 +65,7 @@ pub struct InspectorStack { impl InspectorStack { pub fn collect_inspector_states(self) -> InspectorData { InspectorData { - logs: self.logs.map(|logs| logs.logs).unwrap_or_default(), + logs: self.logger.map(|logs| logs.logs).unwrap_or_default(), labels: self .cheatcodes .as_ref() @@ -102,7 +102,7 @@ impl InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -147,7 +147,7 @@ where &mut self.debugger, &mut self.coverage, &mut self.tracer, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -178,7 +178,7 @@ where &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -204,7 +204,7 @@ where ) { call_inspectors!( inspector, - [&mut self.tracer, &mut self.logs, &mut self.cheatcodes, &mut self.printer], + [&mut self.tracer, &mut self.logger, &mut self.cheatcodes, &mut self.printer], { inspector.log(evm_data, address, topics, data); } @@ -224,7 +224,7 @@ where &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.debugger, &mut self.tracer, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer, &mut self.chisel_state @@ -256,7 +256,7 @@ where &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -308,7 +308,7 @@ where &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -341,7 +341,7 @@ where &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer ], @@ -370,7 +370,7 @@ where [ &mut self.debugger, &mut self.tracer, - &mut self.logs, + &mut self.logger, &mut self.cheatcodes, &mut self.printer, &mut self.chisel_state From 2a1c6668b84175e2ac48480fc5e0eb41ac000993 Mon Sep 17 00:00:00 2001 From: AA Date: Sat, 29 Jul 2023 16:52:43 +0000 Subject: [PATCH 02/18] missing file --- anvil/src/eth/backend/mem/inspector.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/anvil/src/eth/backend/mem/inspector.rs b/anvil/src/eth/backend/mem/inspector.rs index d515b945c699f..2cfe759b54545 100644 --- a/anvil/src/eth/backend/mem/inspector.rs +++ b/anvil/src/eth/backend/mem/inspector.rs @@ -23,7 +23,7 @@ pub struct Inspector { pub gas: Option>>, pub tracer: Option, /// collects all `console.sol` logs - pub logs: LogCollector, + pub logger: LogCollector, } // === impl Inspector === @@ -33,7 +33,7 @@ impl Inspector { /// /// This will log all `console.sol` logs pub fn print_logs(&self) { - print_logs(&self.logs.logs) + print_logs(&self.logger.logs) } /// Configures the `Tracer` [`revm::Inspector`] @@ -99,7 +99,7 @@ impl revm::Inspector for Inspector { [ &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer, - Some(&mut self.logs) + Some(&mut self.logger) ], { inspector.log(evm_data, address, topics, data); @@ -135,7 +135,7 @@ impl revm::Inspector for Inspector { [ &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer, - Some(&mut self.logs) + Some(&mut self.logger) ], { inspector.call(data, call, is_static); From ab6a4ee1b13de7d346ca106d4c23e45e3a576f64 Mon Sep 17 00:00:00 2001 From: AA Date: Sun, 30 Jul 2023 16:42:55 +0000 Subject: [PATCH 03/18] ONLOG typevar, --inline-logs --- anvil/src/cmd.rs | 5 +++ anvil/src/config.rs | 10 +++++ anvil/src/eth/backend/executor.rs | 5 +++ anvil/src/eth/backend/mem/inspector.rs | 38 +++++++++++++++++-- anvil/src/eth/backend/mem/mod.rs | 21 ++++++++++- chisel/src/runner.rs | 4 +- cli/src/cmd/cast/run.rs | 2 +- cli/src/cmd/forge/script/runner.rs | 4 +- evm/src/executor/builder.rs | 4 +- evm/src/executor/inspector/logs.rs | 49 ++++++++++++++++++++----- evm/src/executor/inspector/mod.rs | 10 +++-- evm/src/executor/inspector/stack.rs | 29 ++++++++++++--- evm/src/executor/mod.rs | 41 +++++++++++++++------ evm/src/fuzz/invariant/call_override.rs | 7 +++- evm/src/fuzz/invariant/error.rs | 36 ++++++++++-------- evm/src/fuzz/invariant/executor.rs | 14 +++---- evm/src/fuzz/invariant/mod.rs | 4 +- evm/src/fuzz/mod.rs | 10 ++--- forge/src/multi_runner.rs | 8 ++-- forge/src/runner.rs | 30 +++++++++++---- forge/tests/it/test_helpers.rs | 6 ++- 21 files changed, 249 insertions(+), 88 deletions(-) diff --git a/anvil/src/cmd.rs b/anvil/src/cmd.rs index 98c6b0d851570..fd0602f3025cd 100644 --- a/anvil/src/cmd.rs +++ b/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/anvil/src/config.rs b/anvil/src/config.rs index b4254208cdb69..387b03bfb0679 100644 --- a/anvil/src/config.rs +++ b/anvil/src/config.rs @@ -118,6 +118,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 @@ -375,6 +377,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, @@ -603,6 +606,12 @@ impl NodeConfig { self } + #[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 @@ -1000,6 +1009,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/anvil/src/eth/backend/executor.rs b/anvil/src/eth/backend/executor.rs index 8b3e8e2987688..77b45813f4fc5 100644 --- a/anvil/src/eth/backend/executor.rs +++ b/anvil/src/eth/backend/executor.rs @@ -107,6 +107,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> { @@ -265,6 +266,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/anvil/src/eth/backend/mem/inspector.rs b/anvil/src/eth/backend/mem/inspector.rs index 2cfe759b54545..3f8b608679fcd 100644 --- a/anvil/src/eth/backend/mem/inspector.rs +++ b/anvil/src/eth/backend/mem/inspector.rs @@ -6,8 +6,8 @@ use ethers::types::Log; use forge::revm::primitives::{B160, B256}; use foundry_evm::{ call_inspectors, - decode::decode_console_logs, - executor::inspector::{LogCollector, Tracer}, + decode::{decode_console_log, decode_console_logs}, + executor::inspector::{EvmEventLogger, OnLog, Tracer}, revm, revm::{ inspectors::GasInspector, @@ -15,7 +15,7 @@ use foundry_evm::{ EVMData, }, }; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, fmt::Debug, rc::Rc}; /// The [`revm::Inspector`] used when transacting in the evm #[derive(Debug, Clone, Default)] @@ -23,11 +23,35 @@ pub struct Inspector { pub gas: Option>>, pub tracer: Option, /// collects all `console.sol` logs - pub logger: LogCollector, + pub logger: EvmEventLogger, } +/* +impl std::fmt::Debug for Inspector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Inspector") + .field("gas", &self.gas) + .field("tracer", &self.tracer) + .field("logger", &self.logger) + .finish() + } +} +*/ + // === 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 /// @@ -42,6 +66,12 @@ impl Inspector { self } + /// Configures the `Tracer` [`revm::Inspector`] + 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 pub fn with_steps_tracing(mut self) -> Self { diff --git a/anvil/src/eth/backend/mem/mod.rs b/anvil/src/eth/backend/mem/mod.rs index 1f7e1516c0043..d9afbe4731b67 100644 --- a/anvil/src/eth/backend/mem/mod.rs +++ b/anvil/src/eth/backend/mem/mod.rs @@ -167,6 +167,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 @@ -183,6 +184,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, @@ -226,6 +228,7 @@ impl Backend { genesis, active_snapshots: Arc::new(Mutex::new(Default::default())), enable_steps_tracing, + inline_logs, prune_state_history_config, transaction_block_keeper, }; @@ -699,6 +702,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); @@ -760,6 +767,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 @@ -819,6 +827,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(); @@ -885,13 +894,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) ); } @@ -1053,6 +1062,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); @@ -1093,6 +1107,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/chisel/src/runner.rs b/chisel/src/runner.rs index 1a8291a397372..0f4e93d92e9d1 100644 --- a/chisel/src/runner.rs +++ b/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/cli/src/cmd/cast/run.rs b/cli/src/cmd/cast/run.rs index eb39ab43c7f88..b7a048f46c4ed 100644 --- a/cli/src/cmd/cast/run.rs +++ b/cli/src/cmd/cast/run.rs @@ -114,7 +114,7 @@ impl RunArgs { .with_config(env) .with_spec(evm_spec(&self.evm_version.unwrap_or(config.evm_version))); - let mut executor = builder.build(db); + let mut executor = builder.build::<()>(db); let mut env = executor.env().clone(); env.block.number = rU256::from(tx_block_number); diff --git a/cli/src/cmd/forge/script/runner.rs b/cli/src/cmd/forge/script/runner.rs index 761d967a9d427..e4590815d7eed 100644 --- a/cli/src/cmd/forge/script/runner.rs +++ b/cli/src/cmd/forge/script/runner.rs @@ -17,13 +17,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/evm/src/executor/builder.rs b/evm/src/executor/builder.rs index 5e021c7658c65..9d938cadaa4d1 100644 --- a/evm/src/executor/builder.rs +++ b/evm/src/executor/builder.rs @@ -1,5 +1,5 @@ use super::{ - inspector::{Cheatcodes, Fuzzer, InspectorStackConfig}, + inspector::{Cheatcodes, Fuzzer, InspectorStackConfig, OnLog}, Executor, }; use crate::{ @@ -105,7 +105,7 @@ impl ExecutorBuilder { } /// Builds the executor as configured. - pub fn build(self, db: Backend) -> Executor { + pub fn build(self, db: Backend) -> Executor { let gas_limit = self.gas_limit.unwrap_or(self.env.block.gas_limit.into()); Executor::new(db, self.env, self.inspector_config, gas_limit) } diff --git a/evm/src/executor/inspector/logs.rs b/evm/src/executor/inspector/logs.rs index 5c92d2a355a53..21247283268e2 100644 --- a/evm/src/executor/inspector/logs.rs +++ b/evm/src/executor/inspector/logs.rs @@ -7,22 +7,48 @@ 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; + 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 { +#[derive(Clone)] +pub struct EvmEventLogger +where + ONLOG::OnLogState: Default, +{ pub logs: Vec, + pub on_log_state: ONLOG::OnLogState, +} + +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,24 +62,29 @@ 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 where DB: Database, { 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( @@ -82,7 +113,7 @@ 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()); let token = Token::String(fmt); let data = ethers::abi::encode(&[token]).into(); Log { topics: vec![TOPIC], data, ..Default::default() } diff --git a/evm/src/executor/inspector/mod.rs b/evm/src/executor/inspector/mod.rs index bee0b3e9b5854..2b005fe321250 100644 --- a/evm/src/executor/inspector/mod.rs +++ b/evm/src/executor/inspector/mod.rs @@ -3,7 +3,7 @@ mod utils; mod logs; -pub use logs::LogCollector; +pub use logs::{EvmEventLogger, OnLog}; use std::{cell::RefCell, rc::Rc}; mod access_list; @@ -73,9 +73,11 @@ impl InspectorStackConfig { /// Returns 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 stack(&self) -> InspectorStack { - let mut stack = - InspectorStack { logger: Some(LogCollector::default()), ..Default::default() }; + pub fn stack(&self) -> InspectorStack { + let mut stack = InspectorStack { + logger: Some(EvmEventLogger::::default()), + ..Default::default() + }; stack.cheatcodes = self.create_cheatcodes(); if let Some(ref mut cheatcodes) = stack.cheatcodes { diff --git a/evm/src/executor/inspector/stack.rs b/evm/src/executor/inspector/stack.rs index 0006e666b4430..f195e2cd363e1 100644 --- a/evm/src/executor/inspector/stack.rs +++ b/evm/src/executor/inspector/stack.rs @@ -1,4 +1,6 @@ -use super::{Cheatcodes, ChiselState, Debugger, Fuzzer, LogCollector, TracePrinter, Tracer}; +use super::{ + Cheatcodes, ChiselState, Debugger, EvmEventLogger, Fuzzer, OnLog, TracePrinter, Tracer, +}; use crate::{ coverage::HitMaps, debug::DebugArena, @@ -49,10 +51,9 @@ 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(Default)] -pub struct InspectorStack { +pub struct InspectorStack { pub tracer: Option, - pub logger: Option, + pub logger: Option>, pub cheatcodes: Option, pub gas: Option>>, pub debugger: Option, @@ -62,7 +63,23 @@ pub struct InspectorStack { pub chisel_state: Option, } -impl InspectorStack { +impl Default for InspectorStack { + fn default() -> Self { + Self { + tracer: Option::::default(), + logger: Option::>::default(), + cheatcodes: Option::::default(), + gas: Option::>>::default(), + debugger: Option::::default(), + fuzzer: Option::::default(), + coverage: Option::::default(), + printer: Option::::default(), + chisel_state: Option::::default(), + } + } +} + +impl InspectorStack { pub fn collect_inspector_states(self) -> InspectorData { InspectorData { logs: self.logger.map(|logs| logs.logs).unwrap_or_default(), @@ -130,7 +147,7 @@ impl InspectorStack { } } -impl Inspector for InspectorStack +impl Inspector for InspectorStack where DB: DatabaseExt, { diff --git a/evm/src/executor/mod.rs b/evm/src/executor/mod.rs index c8b4591c71f9d..9d677f72e2ce0 100644 --- a/evm/src/executor/mod.rs +++ b/evm/src/executor/mod.rs @@ -1,3 +1,4 @@ +pub use self::inspector::OnLog; use self::inspector::{ cheatcodes::util::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStackConfig, }; @@ -33,7 +34,7 @@ pub use revm::{ B160, U256 as rU256, }, }; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, marker::PhantomData}; /// ABIs used internally in the executor pub mod abi; @@ -75,8 +76,8 @@ pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f3 /// - `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 { +#[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 @@ -89,11 +90,25 @@ pub struct Executor { /// 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 { +// the Clone 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_config: self.inspector_config.clone(), + gas_limit: self.gas_limit.clone(), + _marker: PhantomData, + } + } +} + +impl Executor { pub fn new( mut backend: Backend, env: Env, @@ -110,7 +125,7 @@ impl Executor { }, ); - Executor { backend, env, inspector_config, gas_limit } + Executor { backend, env, inspector_config, gas_limit, _marker: PhantomData } } /// Returns a reference to the Env @@ -352,7 +367,7 @@ impl Executor { value: U256, ) -> eyre::Result { // execute the call - let mut inspector = self.inspector_config.stack(); + let mut inspector = self.inspector_config.stack::(); // Build VM let mut env = self.build_test_env(from, TransactTo::Call(h160_to_b160(to)), calldata, value); @@ -377,7 +392,7 @@ impl Executor { /// Execute the transaction configured in `env.tx` pub fn call_raw_with_env(&mut self, mut env: Env) -> eyre::Result { // execute the call - let mut inspector = self.inspector_config.stack(); + let mut inspector = self.inspector_config.stack::(); let result = self.backend_mut().inspect_ref(&mut env, &mut inspector)?; convert_executed_result(env, inspector, result) } @@ -562,8 +577,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_config.clone(), self.gas_limit); + let executor = Executor::::new( + backend, + self.env.clone(), + self.inspector_config.clone(), + self.gas_limit, + ); let mut success = !reverted; if success { @@ -783,9 +802,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/evm/src/fuzz/invariant/call_override.rs b/evm/src/fuzz/invariant/call_override.rs index 9a935e2e751a1..54897e42de45b 100644 --- a/evm/src/fuzz/invariant/call_override.rs +++ b/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(ref mut fuzzer) = executor.inspector_config_mut().fuzzer { if let Some(ref mut call_generator) = fuzzer.call_generator { call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); diff --git a/evm/src/fuzz/invariant/error.rs b/evm/src/fuzz/invariant/error.rs index d3d0f6de5d5e8..538ac300181ad 100644 --- a/evm/src/fuzz/invariant/error.rs +++ b/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, @@ -87,9 +87,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, @@ -156,9 +156,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], @@ -206,10 +206,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![]; @@ -218,16 +218,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/evm/src/fuzz/invariant/executor.rs b/evm/src/fuzz/invariant/executor.rs index 2c46528767626..bcff4e95f7a4d 100644 --- a/evm/src/fuzz/invariant/executor.rs +++ b/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::{ @@ -44,8 +44,8 @@ type InvariantPreparation = /// 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: &'a mut Executor, +pub struct InvariantExecutor<'a, ONLOG: OnLog> { + pub executor: &'a mut Executor, /// Proptest runner. runner: TestRunner, /// The invariant configuration @@ -59,10 +59,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: &'a mut Executor, + executor: &'a mut Executor, runner: TestRunner, config: InvariantConfig, setup_contracts: &'a ContractsByAddress, @@ -555,10 +555,10 @@ fn collect_data( /// Verifies that the invariant run execution can continue. /// Returns the mapping of (Invariant Function Name -> Call Result) 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/evm/src/fuzz/invariant/mod.rs b/evm/src/fuzz/invariant/mod.rs index 79e428f87b2bf..5caa95a30f62b 100644 --- a/evm/src/fuzz/invariant/mod.rs +++ b/evm/src/fuzz/invariant/mod.rs @@ -37,9 +37,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, ) -> eyre::Result> { diff --git a/evm/src/fuzz/mod.rs b/evm/src/fuzz/mod.rs index d8d4edbe64daa..ef82b1507da70 100644 --- a/evm/src/fuzz/mod.rs +++ b/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/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 72094ac69cf65..45ca1145f2b77 100644 --- a/forge/src/multi_runner.rs +++ b/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, }; @@ -146,7 +146,7 @@ impl MultiContractRunner { .with_gas_limit(self.evm_opts.gas_limit()) .set_tracing(self.evm_opts.verbosity >= 3) .set_coverage(self.coverage) - .build(db.clone()); + .build::<()>(db.clone()); let identifier = id.identifier(); trace!(contract=%identifier, "start executing all tests in contract"); @@ -172,11 +172,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/forge/src/runner.rs b/forge/src/runner.rs index f489811c16b98..de1ff063445af 100644 --- a/forge/src/runner.rs +++ b/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::{ InvariantContract, InvariantExecutor, InvariantFuzzError, InvariantFuzzTestResult, @@ -28,15 +28,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 @@ -52,11 +53,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.clone(), + sender: self.sender.clone(), + } + } +} + +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, @@ -77,7 +93,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/forge/tests/it/test_helpers.rs b/forge/tests/it/test_helpers.rs index 3cf63af33c61c..4cbe4809f2686 100644 --- a/forge/tests/it/test_helpers.rs +++ b/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( From 289388a9e7252b0a6b1a50be4c4e7dc8e2eea95d Mon Sep 17 00:00:00 2001 From: AA Date: Sun, 30 Jul 2023 17:05:08 +0000 Subject: [PATCH 04/18] clippy fix to gas_limit.clone() --- evm/src/executor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/executor/mod.rs b/evm/src/executor/mod.rs index 9d677f72e2ce0..cb55a40297026 100644 --- a/evm/src/executor/mod.rs +++ b/evm/src/executor/mod.rs @@ -102,7 +102,7 @@ impl Clone for Executor { backend: self.backend.clone(), env: self.env.clone(), inspector_config: self.inspector_config.clone(), - gas_limit: self.gas_limit.clone(), + gas_limit: self.gas_limit, _marker: PhantomData, } } From 11302dfde73256b61dd0efd0f354038799056f11 Mon Sep 17 00:00:00 2001 From: AA Date: Sun, 30 Jul 2023 17:15:17 +0000 Subject: [PATCH 05/18] additional clippy fix --- forge/src/runner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge/src/runner.rs b/forge/src/runner.rs index de1ff063445af..9332e16969040 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -62,8 +62,8 @@ impl<'a, ONLOG: OnLog> Clone for ContractRunner<'a, ONLOG> { code: self.code.clone(), contract: self.contract, errors: self.errors, - initial_balance: self.initial_balance.clone(), - sender: self.sender.clone(), + initial_balance: self.initial_balance, + sender: self.sender, } } } From 8d41b1b90fdb65d1a044eb5a5cda353e9d5cd5a4 Mon Sep 17 00:00:00 2001 From: sam bacha Date: Sat, 29 Jul 2023 06:17:47 -0700 Subject: [PATCH 06/18] =?UTF-8?q?chore(update=20constants):=20increment=20?= =?UTF-8?q?solc=20versions=20for=20install=5Fcommonly=E2=80=A6=20(#5504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(update constants): increment solc versions for install_commonly_used_solc function `install_commonly_used_solc` pre-installs commonly used solc versions increment based of latest solc version update * rename vars --------- Co-authored-by: Matthias Seitz --- cli/test-utils/src/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/test-utils/src/util.rs b/cli/test-utils/src/util.rs index b7a2699afb92b..886847ff9c149 100644 --- a/cli/test-utils/src/util.rs +++ b/cli/test-utils/src/util.rs @@ -191,9 +191,9 @@ pub fn setup_cast_project(test: TestProject) -> (TestProject, TestCommand) { fn install_commonly_used_solc() { let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock(); if !*is_preinstalled { - let v0_8_18 = std::thread::spawn(|| Solc::blocking_install(&"0.8.18".parse().unwrap())); let v0_8_19 = std::thread::spawn(|| Solc::blocking_install(&"0.8.19".parse().unwrap())); let v0_8_20 = std::thread::spawn(|| Solc::blocking_install(&"0.8.20".parse().unwrap())); + let v0_8_21 = std::thread::spawn(|| Solc::blocking_install(&"0.8.21".parse().unwrap())); let wait = |res: std::thread::JoinHandle<_>| -> Result<(), ()> { if let Err(err) = res.join().unwrap() { @@ -208,7 +208,7 @@ fn install_commonly_used_solc() { }; // only set to installed if succeeded - *is_preinstalled = wait(v0_8_18).and(wait(v0_8_19)).and(wait(v0_8_20)).is_ok(); + *is_preinstalled = wait(v0_8_19).and(wait(v0_8_20)).and(wait(v0_8_21)).is_ok(); } } From df40cd7742fde3081eeb5b234504467eb6eae23d Mon Sep 17 00:00:00 2001 From: evalir Date: Mon, 31 Jul 2023 12:45:07 -0400 Subject: [PATCH 07/18] chore(`forge`): handle fork instantiating failures more gracefully (#5507) * fix: make evm_env() return a result to force more graceful handling of failed forks * chore: fix related handling errors (and just panic when needed) --- chisel/src/executor.rs | 3 ++- cli/src/cmd/cast/run.rs | 2 +- cli/src/cmd/forge/coverage.rs | 2 +- cli/src/cmd/forge/script/executor.rs | 3 ++- cli/src/cmd/forge/test/mod.rs | 2 +- evm/src/executor/opts.rs | 6 +++--- forge/tests/it/config.rs | 16 +++++++++++++--- 7 files changed, 23 insertions(+), 11 deletions(-) diff --git a/chisel/src/executor.rs b/chisel/src/executor.rs index c78e479495932..f4bc0a7dd550c 100644 --- a/chisel/src/executor.rs +++ b/chisel/src/executor.rs @@ -236,7 +236,8 @@ impl SessionSource { /// /// A configured [ChiselRunner] async fn prepare_runner(&mut self, final_pc: usize) -> ChiselRunner { - let env = self.config.evm_opts.evm_env().await; + let env = + self.config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); // Create an in-memory backend let backend = match self.config.backend.take() { diff --git a/cli/src/cmd/cast/run.rs b/cli/src/cmd/cast/run.rs index b7a048f46c4ed..fde2da267a240 100644 --- a/cli/src/cmd/cast/run.rs +++ b/cli/src/cmd/cast/run.rs @@ -102,7 +102,7 @@ impl RunArgs { evm_opts.fork_block_number = Some(tx_block_number - 1); // Set up the execution environment - let mut env = evm_opts.evm_env().await; + let mut env = evm_opts.evm_env().await?; // can safely disable base fee checks on replaying txs because can // assume those checks already passed on confirmed txs env.cfg.disable_base_fee = true; diff --git a/cli/src/cmd/forge/coverage.rs b/cli/src/cmd/forge/coverage.rs index 69a6ed3a82c4d..36cc7075019ea 100644 --- a/cli/src/cmd/forge/coverage.rs +++ b/cli/src/cmd/forge/coverage.rs @@ -301,7 +301,7 @@ impl CoverageArgs { // Build the contract runner let evm_spec = evm_spec(&config.evm_version); - let env = evm_opts.evm_env().await; + let env = evm_opts.evm_env().await?; let mut runner = MultiContractRunnerBuilder::default() .initial_balance(evm_opts.initial_balance) .evm_spec(evm_spec) diff --git a/cli/src/cmd/forge/script/executor.rs b/cli/src/cmd/forge/script/executor.rs index 2ad610482ff2c..c803a8d37201f 100644 --- a/cli/src/cmd/forge/script/executor.rs +++ b/cli/src/cmd/forge/script/executor.rs @@ -282,7 +282,8 @@ impl ScriptArgs { stage: SimulationStage, ) -> ScriptRunner { trace!("preparing script runner"); - let env = script_config.evm_opts.evm_env().await; + let env = + script_config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); // The db backend that serves all the data. let db = match &script_config.evm_opts.fork_url { diff --git a/cli/src/cmd/forge/test/mod.rs b/cli/src/cmd/forge/test/mod.rs index 106bd4ebcddbc..82bb5d77dd0bb 100644 --- a/cli/src/cmd/forge/test/mod.rs +++ b/cli/src/cmd/forge/test/mod.rs @@ -176,7 +176,7 @@ impl TestArgs { evm_opts.verbosity = 3; } - let env = evm_opts.evm_env().await; + let env = evm_opts.evm_env().await?; // Prepare the test builder let evm_spec = evm_spec(&config.evm_version); diff --git a/evm/src/executor/opts.rs b/evm/src/executor/opts.rs index 99410d9403489..944a46206dfe0 100644 --- a/evm/src/executor/opts.rs +++ b/evm/src/executor/opts.rs @@ -59,11 +59,11 @@ impl EvmOpts { /// /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain /// id, ) - pub async fn evm_env(&self) -> revm::primitives::Env { + pub async fn evm_env(&self) -> eyre::Result { if let Some(ref fork_url) = self.fork_url { - self.fork_evm_env(fork_url).await.expect("Could not instantiate forked environment").0 + Ok(self.fork_evm_env(fork_url).await?.0) } else { - self.local_evm_env() + Ok(self.local_evm_env()) } } diff --git a/forge/tests/it/config.rs b/forge/tests/it/config.rs index 58b70e5dcc474..f81472045d69a 100644 --- a/forge/tests/it/config.rs +++ b/forge/tests/it/config.rs @@ -161,7 +161,12 @@ pub async fn runner_with_config(mut config: Config) -> MultiContractRunner { base_runner() .with_cheats_config(CheatsConfig::new(&config, &EVM_OPTS)) .sender(config.sender) - .build(&PROJECT.paths.root, (*COMPILED).clone(), EVM_OPTS.evm_env().await, EVM_OPTS.clone()) + .build( + &PROJECT.paths.root, + (*COMPILED).clone(), + EVM_OPTS.evm_env().await.expect("Could not instantiate fork environment"), + EVM_OPTS.clone(), + ) .unwrap() } @@ -170,7 +175,12 @@ pub async fn tracing_runner() -> MultiContractRunner { let mut opts = EVM_OPTS.clone(); opts.verbosity = 5; base_runner() - .build(&PROJECT.paths.root, (*COMPILED).clone(), EVM_OPTS.evm_env().await, opts) + .build( + &PROJECT.paths.root, + (*COMPILED).clone(), + EVM_OPTS.evm_env().await.expect("Could not instantiate fork environment"), + opts, + ) .unwrap() } @@ -181,7 +191,7 @@ pub async fn forked_runner(rpc: &str) -> MultiContractRunner { opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC opts.fork_url = Some(rpc.to_string()); - let env = opts.evm_env().await; + let env = opts.evm_env().await.expect("Could not instantiate fork environment"); let fork = opts.get_fork(&Default::default(), env.clone()); base_runner() From 1dea8dd950c6730d4ab834200e7f68d628b62bdb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 31 Jul 2023 19:53:55 +0200 Subject: [PATCH 08/18] Add mapping slot API to cheatcodes (rebased) (#5123) --- abi/abi/HEVM.sol | 5 + abi/src/bindings/hevm.rs | 317 ++++++++++++++++++ evm/src/executor/abi/mod.rs | 5 +- evm/src/executor/inspector/cheatcodes/env.rs | 18 + .../executor/inspector/cheatcodes/mapping.rs | 120 +++++++ evm/src/executor/inspector/cheatcodes/mod.rs | 12 + testdata/cheats/Mapping.t.sol | 68 ++++ testdata/cheats/Vm.sol | 15 + 8 files changed, 557 insertions(+), 3 deletions(-) create mode 100644 evm/src/executor/inspector/cheatcodes/mapping.rs create mode 100644 testdata/cheats/Mapping.t.sol diff --git a/abi/abi/HEVM.sol b/abi/abi/HEVM.sol index 937785c064849..74f16b076a197 100644 --- a/abi/abi/HEVM.sol +++ b/abi/abi/HEVM.sol @@ -212,3 +212,8 @@ keyExists(string,string)(bool) pauseGasMetering() resumeGasMetering() +startMappingRecording() +stopMappingRecording() +getMappingLength(address,bytes32) +getMappingSlotAt(address,bytes32,uint256) +getMappingKeyAndParentOf(address,bytes32) diff --git a/abi/src/bindings/hevm.rs b/abi/src/bindings/hevm.rs index 19235b396b539..b4d64d74f0b37 100644 --- a/abi/src/bindings/hevm.rs +++ b/abi/src/bindings/hevm.rs @@ -1966,6 +1966,88 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("getMappingKeyAndParentOf"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned( + "getMappingKeyAndParentOf", + ), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Address, + internal_type: ::core::option::Option::None, + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::None, + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("getMappingLength"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("getMappingLength"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Address, + internal_type: ::core::option::Option::None, + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::None, + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), + ( + ::std::borrow::ToOwned::to_owned("getMappingSlotAt"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("getMappingSlotAt"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Address, + internal_type: ::core::option::Option::None, + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( + 32usize, + ), + internal_type: ::core::option::Option::None, + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + internal_type: ::core::option::Option::None, + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), ( ::std::borrow::ToOwned::to_owned("getNonce"), ::std::vec![ @@ -4313,6 +4395,20 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("startMappingRecording"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned( + "startMappingRecording", + ), + inputs: ::std::vec![], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), ( ::std::borrow::ToOwned::to_owned("startPrank"), ::std::vec![ @@ -4361,6 +4457,20 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("stopMappingRecording"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned( + "stopMappingRecording", + ), + inputs: ::std::vec![], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), ( ::std::borrow::ToOwned::to_owned("stopPrank"), ::std::vec![ @@ -5559,6 +5669,37 @@ pub mod hevm { .method_hash([40, 162, 73, 176], p0) .expect("method not found (this should never happen)") } + ///Calls the contract's `getMappingKeyAndParentOf` (0x876e24e6) function + pub fn get_mapping_key_and_parent_of( + &self, + p0: ::ethers_core::types::Address, + p1: [u8; 32], + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([135, 110, 36, 230], (p0, p1)) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `getMappingLength` (0x2f2fd63f) function + pub fn get_mapping_length( + &self, + p0: ::ethers_core::types::Address, + p1: [u8; 32], + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([47, 47, 214, 63], (p0, p1)) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `getMappingSlotAt` (0xebc73ab4) function + pub fn get_mapping_slot_at( + &self, + p0: ::ethers_core::types::Address, + p1: [u8; 32], + p2: ::ethers_core::types::U256, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([235, 199, 58, 180], (p0, p1, p2)) + .expect("method not found (this should never happen)") + } ///Calls the contract's `getNonce` (0x2d0335ab) function pub fn get_nonce( &self, @@ -6497,6 +6638,14 @@ pub mod hevm { .method_hash([206, 129, 125, 71], p0) .expect("method not found (this should never happen)") } + ///Calls the contract's `startMappingRecording` (0x3e9705c0) function + pub fn start_mapping_recording( + &self, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([62, 151, 5, 192], ()) + .expect("method not found (this should never happen)") + } ///Calls the contract's `startPrank` (0x06447d56) function pub fn start_prank_0( &self, @@ -6524,6 +6673,14 @@ pub mod hevm { .method_hash([118, 234, 221, 54], ()) .expect("method not found (this should never happen)") } + ///Calls the contract's `stopMappingRecording` (0x0d4aae9b) function + pub fn stop_mapping_recording( + &self, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([13, 74, 174, 155], ()) + .expect("method not found (this should never happen)") + } ///Calls the contract's `stopPrank` (0x90c5013b) function pub fn stop_prank(&self) -> ::ethers_contract::builders::ContractCall { self.0 @@ -7826,6 +7983,58 @@ pub mod hevm { )] #[ethcall(name = "getLabel", abi = "getLabel(address)")] pub struct GetLabelCall(pub ::ethers_core::types::Address); + ///Container type for all input parameters for the `getMappingKeyAndParentOf` function with signature `getMappingKeyAndParentOf(address,bytes32)` and selector `0x876e24e6` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall( + name = "getMappingKeyAndParentOf", + abi = "getMappingKeyAndParentOf(address,bytes32)" + )] + pub struct GetMappingKeyAndParentOfCall( + pub ::ethers_core::types::Address, + pub [u8; 32], + ); + ///Container type for all input parameters for the `getMappingLength` function with signature `getMappingLength(address,bytes32)` and selector `0x2f2fd63f` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "getMappingLength", abi = "getMappingLength(address,bytes32)")] + pub struct GetMappingLengthCall(pub ::ethers_core::types::Address, pub [u8; 32]); + ///Container type for all input parameters for the `getMappingSlotAt` function with signature `getMappingSlotAt(address,bytes32,uint256)` and selector `0xebc73ab4` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall( + name = "getMappingSlotAt", + abi = "getMappingSlotAt(address,bytes32,uint256)" + )] + pub struct GetMappingSlotAtCall( + pub ::ethers_core::types::Address, + pub [u8; 32], + pub ::ethers_core::types::U256, + ); ///Container type for all input parameters for the `getNonce` function with signature `getNonce(address)` and selector `0x2d0335ab` #[derive( Clone, @@ -9160,6 +9369,19 @@ pub mod hevm { )] #[ethcall(name = "startBroadcast", abi = "startBroadcast(uint256)")] pub struct StartBroadcast2Call(pub ::ethers_core::types::U256); + ///Container type for all input parameters for the `startMappingRecording` function with signature `startMappingRecording()` and selector `0x3e9705c0` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "startMappingRecording", abi = "startMappingRecording()")] + pub struct StartMappingRecordingCall; ///Container type for all input parameters for the `startPrank` function with signature `startPrank(address)` and selector `0x06447d56` #[derive( Clone, @@ -9202,6 +9424,19 @@ pub mod hevm { )] #[ethcall(name = "stopBroadcast", abi = "stopBroadcast()")] pub struct StopBroadcastCall; + ///Container type for all input parameters for the `stopMappingRecording` function with signature `stopMappingRecording()` and selector `0x0d4aae9b` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "stopMappingRecording", abi = "stopMappingRecording()")] + pub struct StopMappingRecordingCall; ///Container type for all input parameters for the `stopPrank` function with signature `stopPrank()` and selector `0x90c5013b` #[derive( Clone, @@ -9512,6 +9747,9 @@ pub mod hevm { GetCode(GetCodeCall), GetDeployedCode(GetDeployedCodeCall), GetLabel(GetLabelCall), + GetMappingKeyAndParentOf(GetMappingKeyAndParentOfCall), + GetMappingLength(GetMappingLengthCall), + GetMappingSlotAt(GetMappingSlotAtCall), GetNonce(GetNonceCall), GetRecordedLogs(GetRecordedLogsCall), IsPersistent(IsPersistentCall), @@ -9604,9 +9842,11 @@ pub mod hevm { StartBroadcast0(StartBroadcast0Call), StartBroadcast1(StartBroadcast1Call), StartBroadcast2(StartBroadcast2Call), + StartMappingRecording(StartMappingRecordingCall), StartPrank0(StartPrank0Call), StartPrank1(StartPrank1Call), StopBroadcast(StopBroadcastCall), + StopMappingRecording(StopMappingRecordingCall), StopPrank(StopPrankCall), Store(StoreCall), ToString0(ToString0Call), @@ -9958,6 +10198,20 @@ pub mod hevm { = ::decode(data) { return Ok(Self::GetLabel(decoded)); } + if let Ok(decoded) + = ::decode( + data, + ) { + return Ok(Self::GetMappingKeyAndParentOf(decoded)); + } + if let Ok(decoded) + = ::decode(data) { + return Ok(Self::GetMappingLength(decoded)); + } + if let Ok(decoded) + = ::decode(data) { + return Ok(Self::GetMappingSlotAt(decoded)); + } if let Ok(decoded) = ::decode(data) { return Ok(Self::GetNonce(decoded)); @@ -10354,6 +10608,12 @@ pub mod hevm { = ::decode(data) { return Ok(Self::StartBroadcast2(decoded)); } + if let Ok(decoded) + = ::decode( + data, + ) { + return Ok(Self::StartMappingRecording(decoded)); + } if let Ok(decoded) = ::decode(data) { return Ok(Self::StartPrank0(decoded)); @@ -10366,6 +10626,12 @@ pub mod hevm { = ::decode(data) { return Ok(Self::StopBroadcast(decoded)); } + if let Ok(decoded) + = ::decode( + data, + ) { + return Ok(Self::StopMappingRecording(decoded)); + } if let Ok(decoded) = ::decode(data) { return Ok(Self::StopPrank(decoded)); @@ -10615,6 +10881,15 @@ pub mod hevm { ::ethers_core::abi::AbiEncode::encode(element) } Self::GetLabel(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::GetMappingKeyAndParentOf(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::GetMappingLength(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::GetMappingSlotAt(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } Self::GetNonce(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::GetRecordedLogs(element) => { ::ethers_core::abi::AbiEncode::encode(element) @@ -10845,6 +11120,9 @@ pub mod hevm { Self::StartBroadcast2(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::StartMappingRecording(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } Self::StartPrank0(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -10854,6 +11132,9 @@ pub mod hevm { Self::StopBroadcast(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::StopMappingRecording(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } Self::StopPrank(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -10988,6 +11269,11 @@ pub mod hevm { Self::GetCode(element) => ::core::fmt::Display::fmt(element, f), Self::GetDeployedCode(element) => ::core::fmt::Display::fmt(element, f), Self::GetLabel(element) => ::core::fmt::Display::fmt(element, f), + Self::GetMappingKeyAndParentOf(element) => { + ::core::fmt::Display::fmt(element, f) + } + Self::GetMappingLength(element) => ::core::fmt::Display::fmt(element, f), + Self::GetMappingSlotAt(element) => ::core::fmt::Display::fmt(element, f), Self::GetNonce(element) => ::core::fmt::Display::fmt(element, f), Self::GetRecordedLogs(element) => ::core::fmt::Display::fmt(element, f), Self::IsPersistent(element) => ::core::fmt::Display::fmt(element, f), @@ -11092,9 +11378,15 @@ pub mod hevm { Self::StartBroadcast0(element) => ::core::fmt::Display::fmt(element, f), Self::StartBroadcast1(element) => ::core::fmt::Display::fmt(element, f), Self::StartBroadcast2(element) => ::core::fmt::Display::fmt(element, f), + Self::StartMappingRecording(element) => { + ::core::fmt::Display::fmt(element, f) + } Self::StartPrank0(element) => ::core::fmt::Display::fmt(element, f), Self::StartPrank1(element) => ::core::fmt::Display::fmt(element, f), Self::StopBroadcast(element) => ::core::fmt::Display::fmt(element, f), + Self::StopMappingRecording(element) => { + ::core::fmt::Display::fmt(element, f) + } Self::StopPrank(element) => ::core::fmt::Display::fmt(element, f), Self::Store(element) => ::core::fmt::Display::fmt(element, f), Self::ToString0(element) => ::core::fmt::Display::fmt(element, f), @@ -11510,6 +11802,21 @@ pub mod hevm { Self::GetLabel(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: GetMappingKeyAndParentOfCall) -> Self { + Self::GetMappingKeyAndParentOf(value) + } + } + impl ::core::convert::From for HEVMCalls { + fn from(value: GetMappingLengthCall) -> Self { + Self::GetMappingLength(value) + } + } + impl ::core::convert::From for HEVMCalls { + fn from(value: GetMappingSlotAtCall) -> Self { + Self::GetMappingSlotAt(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: GetNonceCall) -> Self { Self::GetNonce(value) @@ -11970,6 +12277,11 @@ pub mod hevm { Self::StartBroadcast2(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: StartMappingRecordingCall) -> Self { + Self::StartMappingRecording(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: StartPrank0Call) -> Self { Self::StartPrank0(value) @@ -11985,6 +12297,11 @@ pub mod hevm { Self::StopBroadcast(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: StopMappingRecordingCall) -> Self { + Self::StopMappingRecording(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: StopPrankCall) -> Self { Self::StopPrank(value) diff --git a/evm/src/executor/abi/mod.rs b/evm/src/executor/abi/mod.rs index 14c2a66a4e1df..276fd27d917da 100644 --- a/evm/src/executor/abi/mod.rs +++ b/evm/src/executor/abi/mod.rs @@ -1,12 +1,11 @@ use ethers::types::{Address, Selector, H160}; -use once_cell::sync::Lazy; -use std::collections::HashMap; - pub use foundry_abi::{ console::{self, ConsoleEvents, CONSOLE_ABI}, hardhat_console::{self, HardhatConsoleCalls, HARDHATCONSOLE_ABI as HARDHAT_CONSOLE_ABI}, hevm::{self, HEVMCalls, HEVM_ABI}, }; +use once_cell::sync::Lazy; +use std::collections::HashMap; /// The cheatcode handler address (0x7109709ECfa91a80626fF3989D68f67F5b1DD12D). /// diff --git a/evm/src/executor/inspector/cheatcodes/env.rs b/evm/src/executor/inspector/cheatcodes/env.rs index 00501c2574a82..0746bd407db30 100644 --- a/evm/src/executor/inspector/cheatcodes/env.rs +++ b/evm/src/executor/inspector/cheatcodes/env.rs @@ -4,6 +4,7 @@ use crate::{ executor::{ backend::DatabaseExt, inspector::cheatcodes::{ + mapping::{get_mapping_key_and_parent, get_mapping_length, get_mapping_slot_at}, util::{is_potential_precompile, with_journaled_account}, DealRecord, }, @@ -641,6 +642,23 @@ pub fn apply( state.gas_metering = None; Bytes::new() } + HEVMCalls::StartMappingRecording(_) => { + if state.mapping_slots.is_none() { + state.mapping_slots = Some(Default::default()); + } + Bytes::new() + } + HEVMCalls::StopMappingRecording(_) => { + state.mapping_slots = None; + Bytes::new() + } + HEVMCalls::GetMappingLength(inner) => get_mapping_length(state, inner.0, inner.1.into()), + HEVMCalls::GetMappingSlotAt(inner) => { + get_mapping_slot_at(state, inner.0, inner.1.into(), inner.2) + } + HEVMCalls::GetMappingKeyAndParentOf(inner) => { + get_mapping_key_and_parent(state, inner.0, inner.1.into()) + } _ => return Ok(None), }; Ok(Some(result)) diff --git a/evm/src/executor/inspector/cheatcodes/mapping.rs b/evm/src/executor/inspector/cheatcodes/mapping.rs new file mode 100644 index 0000000000000..cf81dc447eb1c --- /dev/null +++ b/evm/src/executor/inspector/cheatcodes/mapping.rs @@ -0,0 +1,120 @@ +use super::Cheatcodes; +use crate::utils::{b160_to_h160, ru256_to_u256}; +use ethers::{ + abi::{self, Token}, + types::{Address, Bytes, U256}, + utils::keccak256, +}; +use revm::{ + interpreter::{opcode, Interpreter}, + Database, EVMData, +}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, Default)] +pub struct MappingSlots { + /// Holds mapping parent (slots => slots) + pub parent_slots: BTreeMap, + + /// Holds mapping key (slots => key) + pub keys: BTreeMap, + + /// Holds mapping child (slots => slots[]) + pub children: BTreeMap>, + + /// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record + /// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in + /// `data_low`, higher 256 bits in `data_high`. + /// This is needed for mapping_key detect if the slot is for some mapping and record that. + pub seen_sha3: BTreeMap, +} + +impl MappingSlots { + pub fn insert(&mut self, slot: U256) -> bool { + match self.seen_sha3.get(&slot).copied() { + Some((key, parent)) => { + if self.keys.contains_key(&slot) { + return false + } + self.keys.insert(slot, key); + self.parent_slots.insert(slot, parent); + self.children.entry(parent).or_default().push(slot); + self.insert(parent); + true + } + None => false, + } + } +} + +pub fn get_mapping_length(state: &Cheatcodes, address: Address, slot: U256) -> Bytes { + let result = match state.mapping_slots.as_ref().and_then(|dict| dict.get(&address)) { + Some(mapping_slots) => { + mapping_slots.children.get(&slot).map(|set| set.len()).unwrap_or_default() + } + None => 0, + }; + abi::encode(&[Token::Uint(result.into())]).into() +} + +pub fn get_mapping_slot_at(state: &Cheatcodes, address: Address, slot: U256, index: U256) -> Bytes { + let result = match state.mapping_slots.as_ref().and_then(|dict| dict.get(&address)) { + Some(mapping_slots) => mapping_slots + .children + .get(&slot) + .and_then(|set| set.get(index.as_usize())) + .copied() + .unwrap_or_default(), + None => 0.into(), + }; + abi::encode(&[Token::Uint(result)]).into() +} + +pub fn get_mapping_key_and_parent(state: &Cheatcodes, address: Address, slot: U256) -> Bytes { + let (found, key, parent) = + match state.mapping_slots.as_ref().and_then(|dict| dict.get(&address)) { + Some(mapping_slots) => match mapping_slots.keys.get(&slot) { + Some(key) => (true, *key, mapping_slots.parent_slots[&slot]), + None => match mapping_slots.seen_sha3.get(&slot).copied() { + Some(maybe_info) => (true, maybe_info.0, maybe_info.1), + None => (false, U256::zero(), U256::zero()), + }, + }, + None => (false, U256::zero(), U256::zero()), + }; + abi::encode(&[Token::Bool(found), Token::Uint(key), Token::Uint(parent)]).into() +} + +pub fn on_evm_step( + mapping_slots: &mut BTreeMap, + interpreter: &Interpreter, + _data: &mut EVMData<'_, DB>, +) { + match interpreter.contract.bytecode.bytecode()[interpreter.program_counter()] { + opcode::SHA3 => { + if interpreter.stack.peek(1) == Ok(revm::primitives::U256::from(0x40)) { + let address = interpreter.contract.address; + let offset = interpreter.stack.peek(0).expect("stack size > 1").to::(); + let low = U256::from(interpreter.memory.get_slice(offset, 0x20)); + let high = U256::from(interpreter.memory.get_slice(offset + 0x20, 0x20)); + let result = U256::from(keccak256(interpreter.memory.get_slice(offset, 0x40))); + + mapping_slots + .entry(b160_to_h160(address)) + .or_default() + .seen_sha3 + .insert(result, (low, high)); + } + } + opcode::SSTORE => { + if let Some(mapping_slots) = + mapping_slots.get_mut(&b160_to_h160(interpreter.contract.address)) + { + if let Ok(slot) = interpreter.stack.peek(0) { + mapping_slots.insert(ru256_to_u256(slot)); + } + } + } + _ => {} + } +} diff --git a/evm/src/executor/inspector/cheatcodes/mod.rs b/evm/src/executor/inspector/cheatcodes/mod.rs index 70a4b9b1ecab6..afc14138d9a44 100644 --- a/evm/src/executor/inspector/cheatcodes/mod.rs +++ b/evm/src/executor/inspector/cheatcodes/mod.rs @@ -1,6 +1,7 @@ use self::{ env::Broadcast, expect::{handle_expect_emit, handle_expect_revert, ExpectedCallType}, + mapping::MappingSlots, util::{check_if_fixed_gas_limit, process_create, BroadcastableTransactions, MAGIC_SKIP_BYTES}, }; use crate::{ @@ -54,6 +55,8 @@ mod fork; mod fs; /// Cheatcodes that configure the fuzzer mod fuzz; +/// Mapping related cheatcodes +mod mapping; /// Snapshot related cheatcodes mod snapshot; /// Utility cheatcodes (`sign` etc.) @@ -182,6 +185,10 @@ pub struct Cheatcodes { /// CREATE / CREATE2 frames. This is needed to make gas meter pausing work correctly when /// paused and creating new contracts. pub gas_metering_create: Option>, + + /// Holds mapping slots info + pub mapping_slots: Option>, + /// current program counter pub pc: usize, /// Breakpoints supplied by the `vm.breakpoint("")` cheatcode @@ -528,6 +535,11 @@ where ]) } + // Record writes with sstore (and sha3) if `StartMappingRecording` has been called + if let Some(mapping_slots) = &mut self.mapping_slots { + mapping::on_evm_step(mapping_slots, interpreter, data) + } + InstructionResult::Continue } diff --git a/testdata/cheats/Mapping.t.sol b/testdata/cheats/Mapping.t.sol new file mode 100644 index 0000000000000..36678b413113b --- /dev/null +++ b/testdata/cheats/Mapping.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; +import "./Vm.sol"; + +contract RecordMapping { + int256 length; + mapping(address => int256) data; + mapping(int256 => mapping(int256 => int256)) nestedData; + + function setData(address addr, int256 value) public { + data[addr] = value; + } + + function setNestedData(int256 i, int256 j) public { + nestedData[i][j] = i * j; + } +} + +contract RecordMappingTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRecordMapping() public { + RecordMapping target = new RecordMapping(); + + // Start recording + vm.startMappingRecording(); + + // Verify Records + target.setData(address(this), 100); + target.setNestedData(99, 10); + target.setNestedData(98, 10); + bool found; + bytes32 key; + bytes32 parent; + + bytes32 dataSlot = bytes32(uint256(1)); + bytes32 nestDataSlot = bytes32(uint256(2)); + assertEq(uint256(vm.getMappingLength(address(target), dataSlot)), 1, "number of data is incorrect"); + assertEq(uint256(vm.getMappingLength(address(this), dataSlot)), 0, "number of data is incorrect"); + assertEq(uint256(vm.getMappingLength(address(target), nestDataSlot)), 2, "number of nestedData is incorrect"); + + bytes32 dataValueSlot = vm.getMappingSlotAt(address(target), dataSlot, 0); + (found, key, parent) = vm.getMappingKeyAndParentOf(address(target), dataValueSlot); + assert(found); + assertEq(address(uint160(uint256(key))), address(this), "key of data[i] is incorrect"); + assertEq(parent, dataSlot, "parent of data[i] is incorrect"); + assertGt(uint256(dataValueSlot), 0); + assertEq(uint256(vm.load(address(target), dataValueSlot)), 100); + + for (uint256 k; k < vm.getMappingLength(address(target), nestDataSlot); k++) { + bytes32 subSlot = vm.getMappingSlotAt(address(target), nestDataSlot, k); + (found, key, parent) = vm.getMappingKeyAndParentOf(address(target), subSlot); + uint256 i = uint256(key); + assertEq(parent, nestDataSlot, "parent of nestedData[i][j] is incorrect"); + assertEq(uint256(vm.getMappingLength(address(target), subSlot)), 1, "number of nestedData[i] is incorrect"); + bytes32 leafSlot = vm.getMappingSlotAt(address(target), subSlot, 0); + (found, key, parent) = vm.getMappingKeyAndParentOf(address(target), leafSlot); + uint256 j = uint256(key); + assertEq(parent, subSlot, "parent of nestedData[i][j] is incorrect"); + assertEq(j, 10); + assertEq(uint256(vm.load(address(target), leafSlot)), i * j, "value of nestedData[i][j] is incorrect"); + } + vm.stopMappingRecording(); + assertEq(uint256(vm.getMappingLength(address(target), dataSlot)), 0, "number of data is incorrect"); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index e05ea43bb7aa7..9a176ca6f5cf3 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -590,4 +590,19 @@ interface Vm { // Resumes gas metering from where it left off function resumeGasMetering() external; + + // Starts recording all map SSTOREs for later retrieval. + function startMappingRecording() external; + + // Stops recording all map SSTOREs for later retrieval and clears the recorded data. + function stopMappingRecording() external; + + // Gets the length of a mapping at a given slot, for a given address. + function getMappingLength(address target, bytes32 slot) external returns (uint256); + + // Gets the element at index idx of a mapping at a given slot, for a given address. + function getMappingSlotAt(address target, bytes32 slot, uint256 idx) external returns (bytes32); + + // Gets the map key and parent of a mapping at a given slot, for a given address. + function getMappingKeyAndParentOf(address target, bytes32 slot) external returns (bool, bytes32, bytes32); } From e874072988f5b481d4e72b504c282bade0e0418f Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 31 Jul 2023 22:56:28 +0200 Subject: [PATCH 09/18] ci: add weekly cargo update workflow (#5497) --- .github/workflows/dependencies.yml | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/dependencies.yml diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 0000000000000..e731a7a56923f --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,61 @@ +# Automatically run `cargo update` periodically + +name: Update Dependencies + +on: + schedule: + # Run weekly + - cron: "0 0 * * SUN" + workflow_dispatch: + # Needed so we can run it manually + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: cargo-update + TITLE: "chore(deps): weekly `cargo update`" + BODY: | + Automation to keep dependencies in `Cargo.lock` current. + +
cargo update log +

+ + ```log + $cargo_update_log + ``` + +

+
+ +jobs: + update: + name: Update + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + + - name: cargo update + # Remove first line that always just says "Updating crates.io index" + run: cargo update --color never 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log + + - name: craft commit message and PR body + id: msg + run: | + export cargo_update_log="$(cat cargo_update.log)" + + echo "commit_message<> $GITHUB_OUTPUT + printf "$TITLE\n\n$cargo_update_log\n" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "body<> $GITHUB_OUTPUT + echo "$BODY" | envsubst >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + add-paths: ./Cargo.lock + commit-message: ${{ steps.msg.outputs.commit_message }} + title: ${{ env.TITLE }} + body: ${{ steps.msg.outputs.body }} + branch: ${{ env.BRANCH }} From 0c4dd5dc9592369709f6b88321272b1fdec2b05a Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov <62447812+klkvr@users.noreply.github.com> Date: Tue, 1 Aug 2023 04:54:20 +0300 Subject: [PATCH 10/18] Add correct processing for non-existent json-keys (#5511) * Add correct processing for non-existent keys * Fix clippy error * chore: include changes in changelog --------- Co-authored-by: Enrique Ortiz --- CHANGELOG.md | 1 + evm/src/executor/inspector/cheatcodes/ext.rs | 4 +++- testdata/cheats/Json.t.sol | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 106dc496a9e89..2d9a4e1a92665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,3 +34,4 @@ To use the latest pinned nightly on your CI, modify your Foundry installation st - [precompiles will not be compatible with all cheatcodes](https://github.com/foundry-rs/foundry/pull/4905). - The difficulty and prevrandao cheatcodes now [fail if not used with the correct EVM version](https://github.com/foundry-rs/foundry/pull/4904). - The default EVM version will be Shanghai. If you're using an EVM chain which is not compatible with [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855) you need to change your EVM version. See [Matt Solomon's thread](https://twitter.com/msolomon44/status/1656411871635972096) for more information. +- Non-existent JSON keys are now processed correctly, and `parseJson` returns non-decodable empty bytes if they do not exist. https://github.com/foundry-rs/foundry/pull/5511 diff --git a/evm/src/executor/inspector/cheatcodes/ext.rs b/evm/src/executor/inspector/cheatcodes/ext.rs index 73d9420df0f1e..d4871cf01d743 100644 --- a/evm/src/executor/inspector/cheatcodes/ext.rs +++ b/evm/src/executor/inspector/cheatcodes/ext.rs @@ -246,7 +246,9 @@ fn canonicalize_json_key(key: &str) -> String { /// Encodes a vector of [`Token`] into a vector of bytes. fn encode_abi_values(values: Vec) -> Vec { - if values.len() == 1 { + if values.is_empty() { + abi::encode(&[Token::Bytes(Vec::new())]) + } else if values.len() == 1 { abi::encode(&[Token::Bytes(abi::encode(&values))]) } else { abi::encode(&[Token::Bytes(abi::encode(&[Token::Array(values)]))]) diff --git a/testdata/cheats/Json.t.sol b/testdata/cheats/Json.t.sol index b4c350135b634..e19bb37db573b 100644 --- a/testdata/cheats/Json.t.sol +++ b/testdata/cheats/Json.t.sol @@ -148,6 +148,17 @@ contract ParseJsonTest is DSTest { string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } + + function test_nonExistentKey() public { + bytes memory data = vm.parseJson(json, ".thisKeyDoesNotExist"); + assertEq(0, data.length); + + data = vm.parseJson(json, ".this.path.does.n.0.t.exist"); + assertEq(0, data.length); + + data = vm.parseJson("", "."); + assertEq(0, data.length); + } } contract WriteJsonTest is DSTest { From 833d69e5519b2d1ee48af4761423adfb146fa799 Mon Sep 17 00:00:00 2001 From: Andrew Athan <24279435+aathan@users.noreply.github.com> Date: Tue, 1 Aug 2023 11:42:19 -0700 Subject: [PATCH 11/18] Pass details on GasTooHigh (#5489) * Pass details on GasTooHigh * Update anvil/src/eth/backend/mem/mod.rs * chore: fmt/clippy --------- Co-authored-by: AA Co-authored-by: evalir --- anvil/src/eth/api.rs | 7 ++++--- anvil/src/eth/backend/mem/mod.rs | 6 ++++-- anvil/src/eth/error.rs | 31 ++++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/anvil/src/eth/api.rs b/anvil/src/eth/api.rs index 02f45dbf24b5a..be95b7c2f87da 100644 --- a/anvil/src/eth/api.rs +++ b/anvil/src/eth/api.rs @@ -2053,7 +2053,7 @@ impl EthApi { // Exceptional case: init used too much gas, we need to increase the gas limit and try // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh)) = + if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) = ethres { // if price or limit was included in the request then we can execute the request @@ -2125,8 +2125,9 @@ impl EthApi { // Exceptional case: init used too much gas, we need to increase the gas limit and try // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh)) = - ethres + if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh( + _, + ))) = ethres { // increase the lowest gas limit lowest_gas_limit = mid_gas_limit; diff --git a/anvil/src/eth/backend/mem/mod.rs b/anvil/src/eth/backend/mem/mod.rs index d9afbe4731b67..b9e969863fb7d 100644 --- a/anvil/src/eth/backend/mem/mod.rs +++ b/anvil/src/eth/backend/mem/mod.rs @@ -13,7 +13,7 @@ use crate::{ time::{utc_from_secs, TimeManager}, validate::TransactionValidator, }, - error::{BlockchainError, InvalidTransactionError}, + error::{BlockchainError, ErrDetail, InvalidTransactionError}, fees::{FeeDetails, FeeManager}, macros::node_info, pool::transactions::PoolTransaction, @@ -2197,7 +2197,9 @@ impl TransactionValidator for Backend { // Check gas limit, iff block gas limit is set. if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.into() { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); - return Err(InvalidTransactionError::GasTooHigh) + return Err(InvalidTransactionError::GasTooHigh(ErrDetail { + detail: String::from("tx.gas_limit > env.block.gas_limit"), + })) } // check nonce diff --git a/anvil/src/eth/error.rs b/anvil/src/eth/error.rs index 3dbb0fe7f9e06..c45d20bb4b591 100644 --- a/anvil/src/eth/error.rs +++ b/anvil/src/eth/error.rs @@ -119,6 +119,11 @@ pub enum FeeHistoryError { InvalidBlockRange, } +#[derive(Debug)] +pub struct ErrDetail { + pub detail: String, +} + /// An error due to invalid transaction #[derive(thiserror::Error, Debug)] pub enum InvalidTransactionError { @@ -150,8 +155,8 @@ pub enum InvalidTransactionError { #[error("intrinsic gas too low")] GasTooLow, /// returned if the transaction gas exceeds the limit - #[error("intrinsic gas too high")] - GasTooHigh, + #[error("intrinsic gas too high -- {}",.0.detail)] + GasTooHigh(ErrDetail), /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total /// fee cap. #[error("max priority fee per gas higher than max fee per gas")] @@ -185,8 +190,16 @@ impl From for InvalidTransactionError { InvalidTransactionError::TipAboveFeeCap } InvalidTransaction::GasPriceLessThanBasefee => InvalidTransactionError::FeeCapTooLow, - InvalidTransaction::CallerGasLimitMoreThanBlock => InvalidTransactionError::GasTooHigh, - InvalidTransaction::CallGasCostMoreThanGasLimit => InvalidTransactionError::GasTooHigh, + InvalidTransaction::CallerGasLimitMoreThanBlock => { + InvalidTransactionError::GasTooHigh(ErrDetail { + detail: String::from("CallerGasLimitMoreThanBlock"), + }) + } + InvalidTransaction::CallGasCostMoreThanGasLimit => { + InvalidTransactionError::GasTooHigh(ErrDetail { + detail: String::from("CallGasCostMoreThanGasLimit"), + }) + } InvalidTransaction::RejectCallerWithCode => InvalidTransactionError::SenderNoEOA, InvalidTransaction::LackOfFundForGasLimit { .. } => { InvalidTransactionError::InsufficientFunds @@ -272,7 +285,15 @@ impl ToRpcResponseResult for Result { data: serde_json::to_value(data).ok(), } } - InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh => { + InvalidTransactionError::GasTooLow => { + // + RpcError { + code: ErrorCode::ServerError(-32000), + message: err.to_string().into(), + data: None, + } + } + InvalidTransactionError::GasTooHigh(_) => { // RpcError { code: ErrorCode::ServerError(-32000), From 2ce80bd019274428a34eadb438117b935aa60a18 Mon Sep 17 00:00:00 2001 From: Rahul Ravindran <10168946+ravindranrahul@users.noreply.github.com> Date: Wed, 2 Aug 2023 00:13:32 +0530 Subject: [PATCH 12/18] feat(`forge`) - Test scaffolding (#5495) * feat: #5466 - Test scaffolding * reafactor: removed return * chore: fmt * refactor: named imports * refactor: std::fs -> foundry_common::fs --------- Co-authored-by: Rahul Ravindran Co-authored-by: Enrique Ortiz --- cli/assets/generated/TestTemplate.t.sol | 13 +++++ cli/src/cmd/forge/generate/mod.rs | 71 +++++++++++++++++++++++++ cli/src/cmd/forge/mod.rs | 1 + cli/src/forge.rs | 5 +- cli/src/opts/forge.rs | 5 +- 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 cli/assets/generated/TestTemplate.t.sol create mode 100644 cli/src/cmd/forge/generate/mod.rs diff --git a/cli/assets/generated/TestTemplate.t.sol b/cli/assets/generated/TestTemplate.t.sol new file mode 100644 index 0000000000000..468acba01069a --- /dev/null +++ b/cli/assets/generated/TestTemplate.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2} from "forge-std/Test.sol"; +import {{contract_name}} from "../src/{contract_name}.sol"; + +contract {contract_name}Test is Test { + {contract_name} public {instance_name}; + + function setUp() public { + {instance_name} = new {contract_name}(); + } +} \ No newline at end of file diff --git a/cli/src/cmd/forge/generate/mod.rs b/cli/src/cmd/forge/generate/mod.rs new file mode 100644 index 0000000000000..22929f421e562 --- /dev/null +++ b/cli/src/cmd/forge/generate/mod.rs @@ -0,0 +1,71 @@ +//! generate command + +use clap::{Parser, Subcommand}; +use foundry_common::fs; +use std::path::Path; +use yansi::Paint; + +/// CLI arguments for `forge generate`. +#[derive(Debug, Parser)] +pub struct GenerateArgs { + #[clap(subcommand)] + pub sub: GenerateSubcommands, +} + +#[derive(Debug, Subcommand)] +pub enum GenerateSubcommands { + /// Scaffolds test file for given contract. + Test(GenerateTestArgs), +} + +#[derive(Debug, Parser)] +pub struct GenerateTestArgs { + /// Contract name for test generation. + #[clap(long, short, value_name = "CONTRACT_NAME")] + pub contract_name: String, +} + +impl GenerateTestArgs { + pub fn run(self) -> eyre::Result<()> { + let contract_name = format_identifier(&self.contract_name, true); + let instance_name = format_identifier(&self.contract_name, false); + + // Create the test file content. + let test_content = include_str!("../../../../assets/generated/TestTemplate.t.sol"); + let test_content = test_content + .replace("{contract_name}", &contract_name) + .replace("{instance_name}", &instance_name); + + // Create the test directory if it doesn't exist. + fs::create_dir_all("test")?; + + // Define the test file path + let test_file_path = Path::new("test").join(format!("{}.t.sol", contract_name)); + + // Write the test content to the test file. + fs::write(&test_file_path, test_content)?; + + println!("{} test file: {}", Paint::green("Generated"), test_file_path.to_str().unwrap()); + Ok(()) + } +} + +/// Utility function to convert an identifier to pascal or camel case. +fn format_identifier(input: &str, is_pascal_case: bool) -> String { + let mut result = String::new(); + let mut capitalize_next = is_pascal_case; + + for word in input.split_whitespace() { + if !word.is_empty() { + let (first, rest) = word.split_at(1); + let formatted_word = if capitalize_next { + format!("{}{}", first.to_uppercase(), rest) + } else { + format!("{}{}", first.to_lowercase(), rest) + }; + capitalize_next = true; + result.push_str(&formatted_word); + } + } + result +} diff --git a/cli/src/cmd/forge/mod.rs b/cli/src/cmd/forge/mod.rs index 73e694d8bc334..5a1f508e04412 100644 --- a/cli/src/cmd/forge/mod.rs +++ b/cli/src/cmd/forge/mod.rs @@ -51,6 +51,7 @@ pub mod flatten; pub mod fmt; pub mod fourbyte; pub mod geiger; +pub mod generate; pub mod init; pub mod inspect; pub mod install; diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 22f3cfec43a14..efcabad7dc368 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -2,7 +2,7 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use foundry_cli::{ cmd::{ - forge::{cache::CacheSubcommands, watch}, + forge::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}, Cmd, }, handler, @@ -97,5 +97,8 @@ fn main() -> eyre::Result<()> { } Subcommands::Doc(cmd) => cmd.run(), Subcommands::Selectors { command } => utils::block_on(command.run()), + Subcommands::Generate(cmd) => match cmd.sub { + GenerateSubcommands::Test(cmd) => cmd.run(), + }, } } diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index a7adf214f6111..391c6b783c1e4 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -9,7 +9,7 @@ use crate::cmd::forge::{ flatten, fmt::FmtArgs, fourbyte::UploadSelectorsArgs, - geiger, + geiger, generate, init::InitArgs, inspect, install::InstallArgs, @@ -163,6 +163,9 @@ pub enum Subcommands { #[clap(subcommand)] command: SelectorsSubcommands, }, + + /// Generate scaffold files. + Generate(generate::GenerateArgs), } // A set of solc compiler settings that can be set via command line arguments, which are intended From 6a21b1aac1d5683c83ed8eb31492811bfea3b8bd Mon Sep 17 00:00:00 2001 From: evalir Date: Tue, 1 Aug 2023 16:12:07 -0400 Subject: [PATCH 13/18] fix(`cheatcodes`): disallow using `vm.prank` after `vm.startPrank` (#5520) * chore: disallow using vm.prank after vm.startprank * chore: rename state single call bool * Update evm/src/executor/inspector/cheatcodes/env.rs Co-authored-by: Matt Solomon --------- Co-authored-by: Matt Solomon --- evm/src/executor/inspector/cheatcodes/env.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/evm/src/executor/inspector/cheatcodes/env.rs b/evm/src/executor/inspector/cheatcodes/env.rs index 0746bd407db30..17cd37e7e9b0a 100644 --- a/evm/src/executor/inspector/cheatcodes/env.rs +++ b/evm/src/executor/inspector/cheatcodes/env.rs @@ -161,8 +161,11 @@ fn prank( ) -> Result { let prank = Prank::new(prank_caller, prank_origin, new_caller, new_origin, depth, single_call); - if let Some(Prank { used, .. }) = state.prank { + if let Some(Prank { used, single_call: current_single_call, .. }) = state.prank { ensure!(used, "You cannot overwrite `prank` until it is applied at least once"); + // This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on. + // This should not be possible without first calling `stopPrank` + ensure!(single_call == current_single_call, "You cannot override an ongoing prank with a single vm.prank. Use vm.startPrank to override the current prank."); } ensure!( From 64eca91cf0fab63f1cc5596e7610e8ef4a94d238 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Wed, 2 Aug 2023 07:13:12 -0700 Subject: [PATCH 14/18] Invariant testing: read shrink sequence config when assert invariants that aren't broken yet (#5323) * Read shrink sequence config when assert invariants that aren't broken yet * fmt --------- Co-authored-by: evalir --- evm/src/fuzz/invariant/executor.rs | 5 ++++- evm/src/fuzz/invariant/mod.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/evm/src/fuzz/invariant/executor.rs b/evm/src/fuzz/invariant/executor.rs index bcff4e95f7a4d..109ac1d7956de 100644 --- a/evm/src/fuzz/invariant/executor.rs +++ b/evm/src/fuzz/invariant/executor.rs @@ -101,6 +101,7 @@ impl<'a, ONLOG: OnLog> InvariantExecutor<'a, ONLOG> { &blank_executor.borrow(), &[], &mut failures.borrow_mut(), + self.config.shrink_sequence, ) .ok(), ); @@ -576,7 +577,9 @@ fn can_continue( // Assert invariants IFF the call did not revert and the handlers did not fail. if !call_result.reverted && !handlers_failed { - call_results = assert_invariants(invariant_contract, executor, calldata, failures).ok(); + call_results = + assert_invariants(invariant_contract, executor, calldata, failures, shrink_sequence) + .ok(); if call_results.is_none() { return (false, None) } diff --git a/evm/src/fuzz/invariant/mod.rs b/evm/src/fuzz/invariant/mod.rs index 5caa95a30f62b..10eb7b435468b 100644 --- a/evm/src/fuzz/invariant/mod.rs +++ b/evm/src/fuzz/invariant/mod.rs @@ -42,6 +42,7 @@ pub fn assert_invariants( executor: &Executor, calldata: &[BasicTxDetails], invariant_failures: &mut InvariantFailures, + shrink_sequence: bool, ) -> eyre::Result> { let mut found_case = false; let mut inner_sequence = vec![]; @@ -95,7 +96,7 @@ pub fn assert_invariants( calldata, call_result, &inner_sequence, - true, + shrink_sequence, )), ); found_case = true; From faf0b546b91138c2c8b2425e81a7425d32158455 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 2 Aug 2023 18:12:44 +0300 Subject: [PATCH 15/18] fix(cast): continue execution after preceding reverted transaction (#5523) * fix(cast): continue execution of preceding transactions after revert * chore: clippy * chore: clippy * chore: fmt * chore: clippy --------- Co-authored-by: Enrique Ortiz --- anvil/src/eth/backend/mem/mod.rs | 1 - cli/src/cmd/cast/run.rs | 14 +++++++++++--- cli/src/cmd/forge/install.rs | 4 ++-- cli/src/cmd/forge/script/broadcast.rs | 8 ++++---- cli/src/cmd/forge/script/cmd.rs | 10 ++-------- cli/src/cmd/forge/script/executor.rs | 9 +++------ evm/src/trace/decoder.rs | 6 +----- forge/src/gas_report.rs | 3 +-- 8 files changed, 24 insertions(+), 31 deletions(-) diff --git a/anvil/src/eth/backend/mem/mod.rs b/anvil/src/eth/backend/mem/mod.rs index b9e969863fb7d..72a6d085e72da 100644 --- a/anvil/src/eth/backend/mem/mod.rs +++ b/anvil/src/eth/backend/mem/mod.rs @@ -1209,7 +1209,6 @@ impl Backend { } if let Some(fork) = self.get_fork() { - let filter = filter; return Ok(fork.logs(&filter).await?) } diff --git a/cli/src/cmd/cast/run.rs b/cli/src/cmd/cast/run.rs index fde2da267a240..082ef9c1e3afc 100644 --- a/cli/src/cmd/cast/run.rs +++ b/cli/src/cmd/cast/run.rs @@ -157,9 +157,17 @@ impl RunArgs { })?; } else { trace!(tx=?tx.hash, "executing previous create transaction"); - executor.deploy_with_env(env.clone(), None).wrap_err_with(|| { - format!("Failed to deploy transaction: {:?}", tx.hash()) - })?; + if let Err(error) = executor.deploy_with_env(env.clone(), None) { + match error { + // Reverted transactions should be skipped + EvmError::Execution(_) => (), + error => { + return Err(error).wrap_err_with(|| { + format!("Failed to deploy transaction: {:?}", tx.hash()) + }) + } + } + } } update_progress!(pb, index); diff --git a/cli/src/cmd/forge/install.rs b/cli/src/cmd/forge/install.rs index 728740b32baef..29b25fbe2cad1 100644 --- a/cli/src/cmd/forge/install.rs +++ b/cli/src/cmd/forge/install.rs @@ -402,7 +402,7 @@ impl Installer<'_> { let n = if s.is_empty() { Ok(1) } else { s.parse() }; // match user input, 0 indicates skipping and use original tag match n { - Ok(i) if i == 0 => return Ok(tag.into()), + Ok(0) => return Ok(tag.into()), Ok(i) if (1..=n_candidates).contains(&i) => { let c = &candidates[i]; println!("[{i}] {c} selected"); @@ -470,7 +470,7 @@ impl Installer<'_> { // match user input, 0 indicates skipping and use original tag match input.parse::() { - Ok(i) if i == 0 => Ok(Some(tag.into())), + Ok(0) => Ok(Some(tag.into())), Ok(i) if (1..=n_candidates).contains(&i) => { let c = &candidates[i]; println!("[{i}] {c} selected"); diff --git a/cli/src/cmd/forge/script/broadcast.rs b/cli/src/cmd/forge/script/broadcast.rs index 320eeb308ec94..462f3d72e849f 100644 --- a/cli/src/cmd/forge/script/broadcast.rs +++ b/cli/src/cmd/forge/script/broadcast.rs @@ -263,7 +263,7 @@ impl ScriptArgs { &self, mut result: ScriptResult, libraries: Libraries, - decoder: &mut CallTraceDecoder, + decoder: &CallTraceDecoder, mut script_config: ScriptConfig, verify: VerifyBundle, ) -> Result<()> { @@ -360,7 +360,7 @@ impl ScriptArgs { txs: BroadcastableTransactions, script_result: &ScriptResult, script_config: &mut ScriptConfig, - decoder: &mut CallTraceDecoder, + decoder: &CallTraceDecoder, known_contracts: &ContractsByArtifact, ) -> Result> { if !txs.is_empty() { @@ -391,8 +391,8 @@ impl ScriptArgs { async fn fills_transactions_with_gas( &self, txs: BroadcastableTransactions, - script_config: &mut ScriptConfig, - decoder: &mut CallTraceDecoder, + script_config: &ScriptConfig, + decoder: &CallTraceDecoder, known_contracts: &ContractsByArtifact, ) -> Result> { let gas_filled_txs = if self.skip_simulation { diff --git a/cli/src/cmd/forge/script/cmd.rs b/cli/src/cmd/forge/script/cmd.rs index 2b5dd924b77ce..df447a12a60a7 100644 --- a/cli/src/cmd/forge/script/cmd.rs +++ b/cli/src/cmd/forge/script/cmd.rs @@ -119,14 +119,8 @@ impl ScriptArgs { verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); self.check_contract_sizes(&result, &highlevel_known_contracts)?; - self.handle_broadcastable_transactions( - result, - libraries, - &mut decoder, - script_config, - verify, - ) - .await + self.handle_broadcastable_transactions(result, libraries, &decoder, script_config, verify) + .await } // In case there are libraries to be deployed, it makes sure that these are added to the list of diff --git a/cli/src/cmd/forge/script/executor.rs b/cli/src/cmd/forge/script/executor.rs index c803a8d37201f..974ce9b516bd6 100644 --- a/cli/src/cmd/forge/script/executor.rs +++ b/cli/src/cmd/forge/script/executor.rs @@ -95,8 +95,8 @@ impl ScriptArgs { pub async fn onchain_simulation( &self, transactions: BroadcastableTransactions, - script_config: &mut ScriptConfig, - decoder: &mut CallTraceDecoder, + script_config: &ScriptConfig, + decoder: &CallTraceDecoder, contracts: &ContractsByArtifact, ) -> eyre::Result> { trace!(target: "script", "executing onchain simulation"); @@ -247,10 +247,7 @@ impl ScriptArgs { } /// Build the multiple runners from different forks. - async fn build_runners( - &self, - script_config: &mut ScriptConfig, - ) -> HashMap { + async fn build_runners(&self, script_config: &ScriptConfig) -> HashMap { let sender = script_config.evm_opts.sender; if !shell::verbosity().is_silent() { diff --git a/evm/src/trace/decoder.rs b/evm/src/trace/decoder.rs index db90cd3c00020..f8e8ae651a1c3 100644 --- a/evm/src/trace/decoder.rs +++ b/evm/src/trace/decoder.rs @@ -202,11 +202,7 @@ impl CallTraceDecoder { // Flatten errors from all ABIs abi.errors().for_each(|error| { - let entry = self - .errors - .errors - .entry(error.name.clone()) - .or_insert_with(Default::default); + let entry = self.errors.errors.entry(error.name.clone()).or_default(); entry.push(error.clone()); }); diff --git a/forge/src/gas_report.rs b/forge/src/gas_report.rs index 9ac5eae695e2a..d610a19190a68 100644 --- a/forge/src/gas_report.rs +++ b/forge/src/gas_report.rs @@ -69,8 +69,7 @@ impl GasReport { (!self.ignore.contains(&contract_name) && self.report_for.is_empty()) || (self.report_for.contains(&contract_name)); if report_contract { - let contract_report = - self.contracts.entry(name.to_string()).or_insert_with(Default::default); + let contract_report = self.contracts.entry(name.to_string()).or_default(); match &trace.data { RawOrDecodedCall::Raw(bytes) if trace.created() => { From 422f71469e91a4b5795e3a9b79b4ff643ae31747 Mon Sep 17 00:00:00 2001 From: Matthew Alexander Date: Thu, 3 Aug 2023 22:41:41 +0800 Subject: [PATCH 16/18] chore: removed `is_eip1559` boolean flag (#5534) * chore: removed is_eip1559 flag * chore: set eip1559 fields to "None" if not EIP1559 type --- anvil/src/eth/api.rs | 3 --- anvil/src/eth/backend/mem/mod.rs | 7 ++----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/anvil/src/eth/api.rs b/anvil/src/eth/api.rs index be95b7c2f87da..4cab9194c4710 100644 --- a/anvil/src/eth/api.rs +++ b/anvil/src/eth/api.rs @@ -981,7 +981,6 @@ impl EthApi { pending.transaction, None, None, - true, Some(self.backend.base_fee()), ); // we set the from field here explicitly to the set sender of the pending transaction, @@ -1907,7 +1906,6 @@ impl EthApi { tx.pending_transaction.transaction.clone(), None, None, - true, None, ); @@ -2256,7 +2254,6 @@ impl EthApi { tx, Some(&block), Some(info), - true, Some(base_fee), ); block_transactions.push(tx); diff --git a/anvil/src/eth/backend/mem/mod.rs b/anvil/src/eth/backend/mem/mod.rs index 72a6d085e72da..c669421ceaae6 100644 --- a/anvil/src/eth/backend/mem/mod.rs +++ b/anvil/src/eth/backend/mem/mod.rs @@ -1381,7 +1381,7 @@ impl Backend { let info = storage.transactions.get(&hash)?.info.clone(); let tx = block.transactions.get(info.transaction_index as usize)?.clone(); - let tx = transaction_build(Some(hash), tx, Some(block), Some(info), true, base_fee); + let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee); transactions.push(tx); } Some(transactions) @@ -1999,7 +1999,6 @@ impl Backend { tx, Some(&block), Some(info), - true, block.header.base_fee_per_gas, )) } @@ -2035,7 +2034,6 @@ impl Backend { tx, Some(&block), Some(info), - true, block.header.base_fee_per_gas, )) } @@ -2262,7 +2260,6 @@ pub fn transaction_build( eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, - is_eip1559: bool, base_fee: Option, ) -> Transaction { let mut transaction: Transaction = eth_transaction.clone().into(); @@ -2281,7 +2278,7 @@ pub fn transaction_build( base_fee.checked_add(max_priority_fee_per_gas).unwrap_or_else(U256::max_value), ); } - } else if !is_eip1559 { + } else { transaction.max_fee_per_gas = None; transaction.max_priority_fee_per_gas = None; transaction.transaction_type = None; From 4a59a0546bc6cd23b8b0026e8892e8c593c5fd0c Mon Sep 17 00:00:00 2001 From: Andrew Athan <24279435+aathan@users.noreply.github.com> Date: Wed, 2 Aug 2023 09:04:31 -0700 Subject: [PATCH 17/18] logs member name changed to logger when referring to LogCollector inspector (#5498) * logs to logger when referring to LogCollector inspector * missing file * chore: rename to log collector --------- Co-authored-by: AA Co-authored-by: Enrique Ortiz --- anvil/src/eth/backend/mem/inspector.rs | 48 +++++++++++++++----------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/anvil/src/eth/backend/mem/inspector.rs b/anvil/src/eth/backend/mem/inspector.rs index 3f8b608679fcd..03cf6b00b61bf 100644 --- a/anvil/src/eth/backend/mem/inspector.rs +++ b/anvil/src/eth/backend/mem/inspector.rs @@ -125,16 +125,20 @@ impl revm::Inspector for Inspector { data: &Bytes, ) { call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.tracer, - Some(&mut self.logger) - ], - { - inspector.log(evm_data, address, topics, data); - } - ); + inspector, + [ + &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), + &mut self.tracer, + <<<<<<< HEAD + Some(&mut self.logger) + ======= + Some(&mut self.log_collector) + >>>>>>> e05b9c75 (logs member name changed to logger when referring to LogCollector inspector (#5498)) + ], + { + inspector.log(evm_data, address, topics, data); + } + ); } fn step_end( @@ -161,16 +165,20 @@ impl revm::Inspector for Inspector { is_static: bool, ) -> (InstructionResult, Gas, Bytes) { call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.tracer, - Some(&mut self.logger) - ], - { - inspector.call(data, call, is_static); - } - ); + inspector, + [ + &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), + &mut self.tracer, + <<<<<<< HEAD + Some(&mut self.logger) + ======= + Some(&mut self.log_collector) + >>>>>>> e05b9c75 (logs member name changed to logger when referring to LogCollector inspector (#5498)) + ], + { + inspector.call(data, call, is_static); + } + ); (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) } From c4848dd579cd5a16c7ba853d2d868c4eface38bf Mon Sep 17 00:00:00 2001 From: AA Date: Fri, 28 Jul 2023 20:18:52 +0000 Subject: [PATCH 18/18] logs to logger when referring to LogCollector inspector --- anvil/src/eth/backend/mem/inspector.rs | 48 +++++++++++--------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/anvil/src/eth/backend/mem/inspector.rs b/anvil/src/eth/backend/mem/inspector.rs index 03cf6b00b61bf..3f8b608679fcd 100644 --- a/anvil/src/eth/backend/mem/inspector.rs +++ b/anvil/src/eth/backend/mem/inspector.rs @@ -125,20 +125,16 @@ impl revm::Inspector for Inspector { data: &Bytes, ) { call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.tracer, - <<<<<<< HEAD - Some(&mut self.logger) - ======= - Some(&mut self.log_collector) - >>>>>>> e05b9c75 (logs member name changed to logger when referring to LogCollector inspector (#5498)) - ], - { - inspector.log(evm_data, address, topics, data); - } - ); + inspector, + [ + &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), + &mut self.tracer, + Some(&mut self.logger) + ], + { + inspector.log(evm_data, address, topics, data); + } + ); } fn step_end( @@ -165,20 +161,16 @@ impl revm::Inspector for Inspector { is_static: bool, ) -> (InstructionResult, Gas, Bytes) { call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.tracer, - <<<<<<< HEAD - Some(&mut self.logger) - ======= - Some(&mut self.log_collector) - >>>>>>> e05b9c75 (logs member name changed to logger when referring to LogCollector inspector (#5498)) - ], - { - inspector.call(data, call, is_static); - } - ); + inspector, + [ + &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), + &mut self.tracer, + Some(&mut self.logger) + ], + { + inspector.call(data, call, is_static); + } + ); (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) }