Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/anvil/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions crates/anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ pub struct NodeConfig {
pub max_transactions: usize,
/// don't print anything on startup
pub silent: bool,
/// print console.logs as they are generated
pub inline_logs: bool,
/// url of the rpc server that should be used for any rpc calls
pub eth_rpc_url: Option<String>,
/// pins the block number for the state fork
Expand Down Expand Up @@ -379,6 +381,7 @@ impl Default for NodeConfig {
// TODO make this something dependent on block capacity
max_transactions: 1_000,
silent: false,
inline_logs: false,
eth_rpc_url: None,
fork_block_number: None,
account_generator: None,
Expand Down Expand Up @@ -608,6 +611,15 @@ impl NodeConfig {
self
}

/// If set, console.log() calls and emit events will be logged to the console as they are
/// executed This can be helpful when debugging anvil itself, or when something terminates
/// anvil early
#[must_use]
pub fn set_inline_logs(mut self, inline_logs: bool) -> Self {
self.inline_logs = inline_logs;
self
}

/// Sets the ipc path to use
///
/// Note: this is a double Option for
Expand Down Expand Up @@ -1006,6 +1018,7 @@ latest block number: {latest_block}"
fees,
fork,
self.enable_steps_tracing,
self.inline_logs,
self.prune_history,
self.transaction_block_keeper,
self.block_time,
Expand Down
5 changes: 5 additions & 0 deletions crates/anvil/src/eth/backend/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator>
/// Cumulative gas used by all executed transactions
pub gas_used: U256,
pub enable_steps_tracing: bool,
pub inline_logs: bool,
}

impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'a, DB, Validator> {
Expand Down Expand Up @@ -262,6 +263,10 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator
inspector = inspector.with_steps_tracing();
}

if self.inline_logs {
inspector = inspector.with_inline_logs();
}

trace!(target: "backend", "[{:?}] executing", transaction.hash());
// transact and commit the transaction
let exec_result = match evm.inspect_commit(&mut inspector) {
Expand Down
33 changes: 27 additions & 6 deletions crates/anvil/src/eth/backend/mem/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use bytes::Bytes;
use ethers::types::Log;
use foundry_evm::{
call_inspectors,
decode::decode_console_logs,
executor::inspector::{LogCollector, Tracer},
decode::{decode_console_log, decode_console_logs},
executor::{EvmEventLogger, OnLog, Tracer},
revm,
revm::{
interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter},
Expand All @@ -20,17 +20,29 @@ use foundry_evm::{
pub struct Inspector {
pub tracer: Option<Tracer>,
/// collects all `console.sol` logs
pub log_collector: LogCollector,
pub logger: EvmEventLogger<InlineLogs>,
}

// === impl Inspector ===

#[derive(Debug, Clone, Default)]
pub struct InlineLogs;

impl OnLog for InlineLogs {
type OnLogState = bool;
fn on_log(inline_logs_enabled: &mut Self::OnLogState, log_entry: &Log) {
if *inline_logs_enabled {
node_info!("{}", decode_console_log(log_entry).unwrap_or(format!("{:?}", log_entry)));
}
}
}

impl Inspector {
/// Called after the inspecting the evm
///
/// This will log all `console.sol` logs
pub fn print_logs(&self) {
print_logs(&self.log_collector.logs)
print_logs(&self.logger.logs)
}

/// Configures the `Tracer` [`revm::Inspector`]
Expand All @@ -39,6 +51,15 @@ impl Inspector {
self
}

/// Configures the `Executor` to emit logs and events as they are executed
pub fn with_inline_logs(mut self) -> Self {
self.logger.on_log_state = true;
self
}

/// Enables steps recording for `Tracer` and attaches `GasInspector` to it
/// If `Tracer` wasn't configured before, configures it automatically

/// Enables steps recording for `Tracer`.
pub fn with_steps_tracing(mut self) -> Self {
let tracer = self.tracer.get_or_insert_with(Default::default);
Expand Down Expand Up @@ -76,7 +97,7 @@ impl<DB: Database> revm::Inspector<DB> for Inspector {
topics: &[B256],
data: &Bytes,
) {
call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| {
call_inspectors!([&mut self.tracer, Some(&mut self.logger)], |inspector| {
inspector.log(evm_data, address, topics, data);
});
}
Expand All @@ -100,7 +121,7 @@ impl<DB: Database> revm::Inspector<DB> for Inspector {
data: &mut EVMData<'_, DB>,
call: &mut CallInputs,
) -> (InstructionResult, Gas, Bytes) {
call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| {
call_inspectors!([&mut self.tracer, Some(&mut self.logger)], |inspector| {
inspector.call(data, call);
});

Expand Down
21 changes: 19 additions & 2 deletions crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub struct Backend {
/// keeps track of active snapshots at a specific block
active_snapshots: Arc<Mutex<HashMap<U256, (u64, H256)>>>,
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
Expand All @@ -180,6 +181,7 @@ impl Backend {
fees: FeeManager,
fork: Option<ClientFork>,
enable_steps_tracing: bool,
inline_logs: bool,
prune_state_history_config: PruneStateHistoryConfig,
transaction_block_keeper: Option<usize>,
automine_block_time: Option<Duration>,
Expand Down Expand Up @@ -223,6 +225,7 @@ impl Backend {
genesis,
active_snapshots: Arc::new(Mutex::new(Default::default())),
enable_steps_tracing,
inline_logs,
prune_state_history_config,
transaction_block_keeper,
};
Expand Down Expand Up @@ -703,6 +706,10 @@ impl Backend {
let db = self.db.read().await;
let mut inspector = Inspector::default();

if self.inline_logs {
inspector = inspector.with_inline_logs();
}

let mut evm = revm::EVM::new();
evm.env = env;
evm.database(&*db);
Expand Down Expand Up @@ -764,6 +771,7 @@ impl Backend {
parent_hash: storage.best_hash,
gas_used: U256::zero(),
enable_steps_tracing: self.enable_steps_tracing,
inline_logs: self.inline_logs,
};

// create a new pending block
Expand Down Expand Up @@ -823,6 +831,7 @@ impl Backend {
parent_hash: best_hash,
gas_used: U256::zero(),
enable_steps_tracing: self.enable_steps_tracing,
inline_logs: self.inline_logs,
};
let executed_tx = executor.execute();

Expand Down Expand Up @@ -889,13 +898,13 @@ impl Backend {
// assuming max 5 args
Some(token) => {
node_info!(
" Error: reverted with custom error: {:?}",
" Error: reverted with custom error (1): {:?}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
" Error: reverted with custom error (1): {:?}",
" Error: reverted with custom error: {:?}",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI the point of this is to differentiate the source of the error. anvil's errors are sometimes formatted exactly the same but originate in different places.

token
);
}
None => {
node_info!(
" Error: reverted with custom error: {}",
" Error: reverted with custom error (2): {}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
" Error: reverted with custom error (2): {}",
" Error: reverted with custom error: {}",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI the point of this is to differentiate the source of the error. anvil's errors are sometimes formatted exactly the same but originate in different places.

hex::encode(r)
);
}
Expand Down Expand Up @@ -1057,6 +1066,11 @@ impl Backend {
D: DatabaseRef<Error = DatabaseError>,
{
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);
Expand Down Expand Up @@ -1097,6 +1111,9 @@ impl Backend {
) -> Result<DefaultFrame, BlockchainError> {
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);
Expand Down
4 changes: 2 additions & 2 deletions crates/chisel/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -71,7 +71,7 @@ impl ChiselRunner {
///
/// A new [ChiselRunner]
pub fn new(
executor: Executor,
executor: Executor<()>,
initial_balance: U256,
sender: Address,
input: Option<Vec<u8>>,
Expand Down
4 changes: 2 additions & 2 deletions crates/evm/src/executor/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{inspector::InspectorStackBuilder, Executor};
use super::{inspector::InspectorStackBuilder, Executor, OnLog};
use crate::executor::backend::Backend;
use ethers::types::U256;
use revm::primitives::{Env, SpecId};
Expand Down Expand Up @@ -63,7 +63,7 @@ impl ExecutorBuilder {

/// Builds the executor as configured.
#[inline]
pub fn build(self, mut env: Env, db: Backend) -> Executor {
pub fn build<ONLOG: OnLog>(self, mut env: Env, db: Backend) -> Executor<ONLOG> {
let Self { mut stack, gas_limit, spec_id } = self;
env.cfg.spec_id = spec_id;
stack.block = Some(env.block.clone());
Expand Down
67 changes: 58 additions & 9 deletions crates/evm/src/executor/inspector/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,53 @@ use ethers::{
abi::{AbiDecode, Token},
types::{Log, H256},
};
use foundry_macros::ConsoleFmt;
use revm::{
interpreter::{CallInputs, Gas, InstructionResult},
primitives::{B160, B256},
Database, EVMData, Inspector,
};
use std::fmt::Debug;

pub trait OnLog {
type OnLogState: Default + Clone + Sync;
fn on_log(ls: &mut Self::OnLogState, log: &Log);
}

/// An inspector that collects logs during execution.
///
/// The inspector collects logs from the LOG opcodes as well as Hardhat-style logs.
#[derive(Debug, Clone, Default)]
pub struct LogCollector {
pub struct EvmEventLogger<ONLOG: OnLog>
where
ONLOG::OnLogState: Default,
{
pub logs: Vec<Log>,
pub on_log_state: ONLOG::OnLogState,
}

impl<ONLOG: OnLog> Clone for EvmEventLogger<ONLOG> {
fn clone(&self) -> Self {
Self { logs: self.logs.clone(), on_log_state: self.on_log_state.clone() }
}
}

impl<ONLOG: OnLog> Default for EvmEventLogger<ONLOG> {
fn default() -> EvmEventLogger<ONLOG> {
Self { logs: Vec::<Log>::default(), on_log_state: ONLOG::OnLogState::default() }
}
}

impl LogCollector {
impl OnLog for () {
type OnLogState = ();
fn on_log(_: &mut Self::OnLogState, _: &Log) {}
}

impl<ONLOG: OnLog> Debug for EvmEventLogger<ONLOG> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EvmEventLogger").field("logs", &self.logs).finish()
}
}

impl<ONLOG: OnLog> EvmEventLogger<ONLOG> {
fn hardhat_log(&mut self, mut input: Vec<u8>) -> (InstructionResult, Bytes) {
// Patch the Hardhat-style selectors
patch_hardhat_console_selector(&mut input);
Expand All @@ -36,21 +67,26 @@ impl LogCollector {
}
};

let log_entry = convert_hh_log_to_event(decoded);
ONLOG::on_log(&mut self.on_log_state, &log_entry);

// Convert it to a DS-style `emit log(string)` event
self.logs.push(convert_hh_log_to_event(decoded));
self.logs.push(log_entry);

(InstructionResult::Continue, Bytes::new())
}
}

impl<DB: Database> Inspector<DB> for LogCollector {
impl<DB: Database, ONLOG: OnLog> Inspector<DB> for EvmEventLogger<ONLOG> {
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(
Expand Down Expand Up @@ -78,7 +114,20 @@ const TOPIC: H256 = H256([
/// Converts a call to Hardhat's `console.log` to a DSTest `log(string)` event.
fn convert_hh_log_to_event(call: HardhatConsoleCalls) -> Log {
// Convert the parameters of the call to their string representation using `ConsoleFmt`.
let fmt = call.fmt(Default::default());
let fmt = foundry_macros::ConsoleFmt::fmt(&call, Default::default());
/* NOTE: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the master branch this is call.fmt(Default::default())
However, if I change that here, I get the below error.
I don't understand why, as the change seems self contained, and if I'm getting an error whereas no such
error is generated here, in the master branch ... that implies there's some incompatible diff happeneing
elsewhere ... a diff that shouldn't exist! Yikes.

error[E0277]: the trait bound `&mut std::fmt::Formatter<'_>: std::default::Default` is not satisfied
--> crates/evm/src/executor/inspector/logs.rs:117:24
|
117 | let fmt = call.fmt(Default::default());
| ^^^^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `&mut std::fmt::Formatter<'_>`
*/
let token = Token::String(fmt);
let data = ethers::abi::encode(&[token]).into();
Log { topics: vec![TOPIC], data, ..Default::default() }
Expand Down
Loading