From 19f481006b95013b54457c59b19703c8c2ced8a3 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:33:45 +0100 Subject: [PATCH] chore: move revm-inspectors to a separate repo (#5992) --- Cargo.lock | 39 +- Cargo.toml | 18 +- bin/reth/Cargo.toml | 2 +- bin/reth/src/builder/mod.rs | 4 +- crates/revm/Cargo.toml | 3 +- crates/revm/revm-inspectors/Cargo.toml | 35 - .../revm/revm-inspectors/src/access_list.rs | 99 -- crates/revm/revm-inspectors/src/lib.rs | 24 - .../revm-inspectors/src/stack/maybe_owned.rs | 178 ---- crates/revm/revm-inspectors/src/stack/mod.rs | 215 ---- .../revm/revm-inspectors/src/tracing/arena.rs | 92 -- .../src/tracing/builder/geth.rs | 325 ------ .../src/tracing/builder/mod.rs | 10 - .../src/tracing/builder/parity.rs | 633 ------------ .../src/tracing/builder/walker.rs | 39 - .../revm-inspectors/src/tracing/config.rs | 225 ----- .../revm-inspectors/src/tracing/fourbyte.rs | 78 -- .../revm-inspectors/src/tracing/js/bigint.js | 1 - .../src/tracing/js/bindings.rs | 936 ------------------ .../src/tracing/js/builtins.rs | 258 ----- .../revm-inspectors/src/tracing/js/mod.rs | 583 ----------- .../revm/revm-inspectors/src/tracing/mod.rs | 563 ----------- .../revm-inspectors/src/tracing/opcount.rs | 29 - .../revm/revm-inspectors/src/tracing/types.rs | 684 ------------- .../revm/revm-inspectors/src/tracing/utils.rs | 91 -- crates/revm/src/lib.rs | 2 +- crates/rpc/rpc/Cargo.toml | 2 +- deny.toml | 6 +- 28 files changed, 40 insertions(+), 5134 deletions(-) delete mode 100644 crates/revm/revm-inspectors/Cargo.toml delete mode 100644 crates/revm/revm-inspectors/src/access_list.rs delete mode 100644 crates/revm/revm-inspectors/src/lib.rs delete mode 100644 crates/revm/revm-inspectors/src/stack/maybe_owned.rs delete mode 100644 crates/revm/revm-inspectors/src/stack/mod.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/arena.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/builder/geth.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/builder/mod.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/builder/parity.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/builder/walker.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/config.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/fourbyte.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/js/bigint.js delete mode 100644 crates/revm/revm-inspectors/src/tracing/js/bindings.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/js/builtins.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/js/mod.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/mod.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/opcount.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/types.rs delete mode 100644 crates/revm/revm-inspectors/src/tracing/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 06b6b987b..9a69f1a94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5692,7 +5692,6 @@ dependencies = [ "reth-provider", "reth-prune", "reth-revm", - "reth-revm-inspectors", "reth-rpc", "reth-rpc-api", "reth-rpc-builder", @@ -5705,6 +5704,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "reth-trie", + "revm-inspectors", "secp256k1 0.27.0", "serde", "serde_json", @@ -6446,29 +6446,12 @@ dependencies = [ "reth-interfaces", "reth-primitives", "reth-provider", - "reth-revm-inspectors", "reth-trie", "revm", + "revm-inspectors", "tracing", ] -[[package]] -name = "reth-revm-inspectors" -version = "0.1.0-alpha.14" -dependencies = [ - "alloy-primitives", - "alloy-rpc-trace-types", - "alloy-rpc-types", - "alloy-sol-types", - "boa_engine", - "boa_gc", - "revm", - "serde", - "serde_json", - "thiserror", - "tokio", -] - [[package]] name = "reth-rpc" version = "0.1.0-alpha.14" @@ -6805,6 +6788,24 @@ dependencies = [ "revm-precompile", ] +[[package]] +name = "revm-inspectors" +version = "0.1.0" +source = "git+https://github.com/paradigmxyz/evm-inspectors#4a3f82fa0d2010b3de69479b42fafe82339dae5e" +dependencies = [ + "alloy-primitives", + "alloy-rpc-trace-types", + "alloy-rpc-types", + "alloy-sol-types", + "boa_engine", + "boa_gc", + "revm", + "serde", + "serde_json", + "thiserror", + "tokio", +] + [[package]] name = "revm-interpreter" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index b37e03d4f..c91c1c51d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ members = [ "crates/primitives/", "crates/prune/", "crates/revm/", - "crates/revm/revm-inspectors/", "crates/rpc/ipc/", "crates/rpc/rpc/", "crates/rpc/rpc-api/", @@ -135,7 +134,6 @@ reth-primitives = { path = "crates/primitives" } reth-provider = { path = "crates/storage/provider" } reth-prune = { path = "crates/prune" } reth-revm = { path = "crates/revm" } -reth-revm-inspectors = { path = "crates/revm/revm-inspectors" } reth-rpc = { path = "crates/rpc/rpc" } reth-rpc-api = { path = "crates/rpc/rpc-api" } reth-rpc-api-testing-util = { path = "crates/rpc/rpc-testing-util" } @@ -152,8 +150,14 @@ reth-transaction-pool = { path = "crates/transaction-pool" } reth-trie = { path = "crates/trie" } # revm -revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze", features = ["std", "secp256k1"], default-features = false } -revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze", features = ["std"], default-features = false } +revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze", features = [ + "std", + "secp256k1", +], default-features = false } +revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze", features = [ + "std", +], default-features = false } +revm-inspectors = { git = "https://github.com/paradigmxyz/evm-inspectors" } # eth alloy-primitives = "0.5" @@ -161,7 +165,9 @@ alloy-dyn-abi = "0.5" alloy-sol-types = "0.5" alloy-rlp = "0.3" alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", features = ["jsonrpsee-types"] } -alloy-rpc-trace-types = { git = "https://github.com/alloy-rs/alloy", features = ["jsonrpsee-types"] } +alloy-rpc-trace-types = { git = "https://github.com/alloy-rs/alloy", features = [ + "jsonrpsee-types", +] } ethers-core = { version = "2.0", default-features = false } ethers-providers = { version = "2.0", default-features = false } ethers-signers = { version = "2.0", default-features = false } @@ -196,7 +202,6 @@ once_cell = "1.17" syn = "2.0" ahash = "0.8.6" - # proc-macros proc-macro2 = "1.0" quote = "1.0" @@ -241,7 +246,6 @@ proptest = "1.4" proptest-derive = "0.4" serial_test = "2" - [workspace.metadata.cargo-udeps.ignore] # ignored because this is mutually exclusive with the optimism payload builder via feature flags normal = ["reth-ethereum-payload-builder"] diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 0ac8e5303..39245bf48 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -26,7 +26,6 @@ reth-db = { workspace = true, features = ["mdbx", "test-utils"] } # TODO: Temporary use of the test-utils feature reth-provider = { workspace = true, features = ["test-utils"] } reth-revm.workspace = true -reth-revm-inspectors.workspace = true reth-stages.workspace = true reth-interfaces = { workspace = true, features = ["test-utils", "clap"] } reth-transaction-pool.workspace = true @@ -60,6 +59,7 @@ reth-nippy-jar.workspace = true # crypto alloy-rlp.workspace = true secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } +revm-inspectors.workspace = true # tracing tracing.workspace = true diff --git a/bin/reth/src/builder/mod.rs b/bin/reth/src/builder/mod.rs index 71361b83a..1163b9ebb 100644 --- a/bin/reth/src/builder/mod.rs +++ b/bin/reth/src/builder/mod.rs @@ -73,7 +73,6 @@ use reth_provider::{ }; use reth_prune::PrunerBuilder; use reth_revm::EvmProcessorFactory; -use reth_revm_inspectors::stack::Hook; use reth_rpc_engine_api::EngineApi; use reth_stages::{ prelude::*, @@ -89,6 +88,7 @@ use reth_transaction_pool::{ blobstore::InMemoryBlobStore, EthTransactionPool, TransactionPool, TransactionValidationTaskExecutor, }; +use revm_inspectors::stack::Hook; use secp256k1::SecretKey; use std::{ net::{SocketAddr, SocketAddrV4}, @@ -862,7 +862,7 @@ impl NodeConfig { } let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - use reth_revm_inspectors::stack::InspectorStackConfig; + use revm_inspectors::stack::InspectorStackConfig; let factory = reth_revm::EvmProcessorFactory::new(self.chain.clone()); let stack_config = InspectorStackConfig { diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 6d12f754a..e717ee22e 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -16,11 +16,11 @@ workspace = true reth-primitives.workspace = true reth-interfaces.workspace = true reth-provider.workspace = true -reth-revm-inspectors.workspace = true reth-consensus-common.workspace = true # revm revm.workspace = true +revm-inspectors.workspace = true # common tracing.workspace = true @@ -35,3 +35,4 @@ optimism = [ "reth-consensus-common/optimism", "reth-interfaces/optimism", ] +js-tracer = ["revm-inspectors/js-tracer"] diff --git a/crates/revm/revm-inspectors/Cargo.toml b/crates/revm/revm-inspectors/Cargo.toml deleted file mode 100644 index a6fcbda0f..000000000 --- a/crates/revm/revm-inspectors/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "reth-revm-inspectors" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -description = "revm inspector implementations used by reth" - -[lints] -workspace = true - -[dependencies] -# eth -alloy-sol-types.workspace = true -alloy-primitives.workspace = true -alloy-rpc-types.workspace = true -alloy-rpc-trace-types.workspace = true - -revm.workspace = true - -serde = { workspace = true, features = ["derive"] } -thiserror = { workspace = true, optional = true } -serde_json = { workspace = true, optional = true } - -# js-tracing-inspector -boa_engine = { workspace = true, optional = true } -boa_gc = { workspace = true, optional = true } - -tokio = { version = "1", features = ["sync"], optional = true } - -[features] -default = ["js-tracer"] -js-tracer = ["boa_engine", "boa_gc", "tokio", "thiserror", "serde_json"] diff --git a/crates/revm/revm-inspectors/src/access_list.rs b/crates/revm/revm-inspectors/src/access_list.rs deleted file mode 100644 index e591a4f1d..000000000 --- a/crates/revm/revm-inspectors/src/access_list.rs +++ /dev/null @@ -1,99 +0,0 @@ -use alloy_primitives::{Address, B256}; -use alloy_rpc_types::{AccessList, AccessListItem}; -use revm::{ - interpreter::{opcode, Interpreter}, - Database, EVMData, Inspector, -}; -use std::collections::{BTreeSet, HashMap, HashSet}; - -/// An [Inspector] that collects touched accounts and storage slots. -/// -/// This can be used to construct an [AccessList] for a transaction via `eth_createAccessList` -#[derive(Default, Debug)] -pub struct AccessListInspector { - /// All addresses that should be excluded from the final accesslist - excluded: HashSet
, - /// All addresses and touched slots - access_list: HashMap>, -} - -impl AccessListInspector { - /// Creates a new inspector instance - /// - /// The `access_list` is the provided access list from the call request - pub fn new( - access_list: AccessList, - from: Address, - to: Address, - precompiles: impl IntoIterator, - ) -> Self { - AccessListInspector { - excluded: [from, to].into_iter().chain(precompiles).collect(), - access_list: access_list - .0 - .into_iter() - .map(|v| (v.address, v.storage_keys.into_iter().collect())) - .collect(), - } - } - - /// Returns list of addresses and storage keys used by the transaction. It gives you the list of - /// addresses and storage keys that were touched during execution. - pub fn into_access_list(self) -> AccessList { - let items = self.access_list.into_iter().map(|(address, slots)| AccessListItem { - address, - storage_keys: slots.into_iter().collect(), - }); - AccessList(items.collect()) - } - - /// Returns list of addresses and storage keys used by the transaction. It gives you the list of - /// addresses and storage keys that were touched during execution. - pub fn access_list(&self) -> AccessList { - let items = self.access_list.iter().map(|(address, slots)| AccessListItem { - address: *address, - storage_keys: slots.iter().copied().collect(), - }); - AccessList(items.collect()) - } -} - -impl Inspector for AccessListInspector -where - DB: Database, -{ - fn step(&mut self, interpreter: &mut Interpreter<'_>, _data: &mut EVMData<'_, DB>) { - match interpreter.current_opcode() { - opcode::SLOAD | opcode::SSTORE => { - if let Ok(slot) = interpreter.stack().peek(0) { - let cur_contract = interpreter.contract.address; - self.access_list - .entry(cur_contract) - .or_default() - .insert(B256::from(slot.to_be_bytes())); - } - } - opcode::EXTCODECOPY | - opcode::EXTCODEHASH | - opcode::EXTCODESIZE | - opcode::BALANCE | - opcode::SELFDESTRUCT => { - if let Ok(slot) = interpreter.stack().peek(0) { - let addr = Address::from_word(B256::from(slot.to_be_bytes())); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - opcode::DELEGATECALL | opcode::CALL | opcode::STATICCALL | opcode::CALLCODE => { - if let Ok(slot) = interpreter.stack().peek(1) { - let addr = Address::from_word(B256::from(slot.to_be_bytes())); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - _ => (), - } - } -} diff --git a/crates/revm/revm-inspectors/src/lib.rs b/crates/revm/revm-inspectors/src/lib.rs deleted file mode 100644 index e524eb517..000000000 --- a/crates/revm/revm-inspectors/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! revm [Inspector](revm::Inspector) implementations, such as call tracers -//! -//! ## Feature Flags -//! -//! - `js-tracer` (default): Enables a JavaScript tracer implementation. This pulls in extra -//! dependencies (such as `boa`, `tokio` and `serde_json`). - -#![doc( - html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", - html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", - issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" -)] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -/// An inspector implementation for an EIP2930 Accesslist -pub mod access_list; - -/// An inspector stack abstracting the implementation details of -/// each inspector and allowing to hook on block/transaction execution, -/// used in the main RETH executor. -pub mod stack; - -/// An inspector for recording traces -pub mod tracing; diff --git a/crates/revm/revm-inspectors/src/stack/maybe_owned.rs b/crates/revm/revm-inspectors/src/stack/maybe_owned.rs deleted file mode 100644 index c02ce6e4b..000000000 --- a/crates/revm/revm-inspectors/src/stack/maybe_owned.rs +++ /dev/null @@ -1,178 +0,0 @@ -use alloy_primitives::U256; -use revm::{ - interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::{db::Database, Address, Bytes, B256}, - EVMData, Inspector, -}; -use std::{ - cell::{Ref, RefCell}, - rc::Rc, -}; - -/// An [Inspector] that is either owned by an individual [Inspector] or is shared as part of a -/// series of inspectors in a [InspectorStack](crate::stack::InspectorStack). -/// -/// Caution: if the [Inspector] is _stacked_ then it _must_ be called first. -#[derive(Debug)] -pub enum MaybeOwnedInspector { - /// Inspector is owned. - Owned(Rc>), - /// Inspector is shared and part of a stack - Stacked(Rc>), -} - -impl MaybeOwnedInspector { - /// Create a new _owned_ instance - pub fn new_owned(inspector: INSP) -> Self { - MaybeOwnedInspector::Owned(Rc::new(RefCell::new(inspector))) - } - - /// Creates a [MaybeOwnedInspector::Stacked] clone of this type. - pub fn clone_stacked(&self) -> Self { - match self { - MaybeOwnedInspector::Owned(gas) | MaybeOwnedInspector::Stacked(gas) => { - MaybeOwnedInspector::Stacked(Rc::clone(gas)) - } - } - } - - /// Returns a reference to the inspector. - pub fn as_ref(&self) -> Ref<'_, INSP> { - match self { - MaybeOwnedInspector::Owned(insp) => insp.borrow(), - MaybeOwnedInspector::Stacked(insp) => insp.borrow(), - } - } -} - -impl MaybeOwnedInspector { - /// Create a new _owned_ instance - pub fn owned() -> Self { - Self::new_owned(Default::default()) - } -} - -impl Default for MaybeOwnedInspector { - fn default() -> Self { - Self::owned() - } -} - -impl Clone for MaybeOwnedInspector { - fn clone(&self) -> Self { - self.clone_stacked() - } -} - -impl Inspector for MaybeOwnedInspector -where - DB: Database, - INSP: Inspector, -{ - fn initialize_interp(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - match self { - MaybeOwnedInspector::Owned(insp) => insp.borrow_mut().initialize_interp(interp, data), - MaybeOwnedInspector::Stacked(_) => {} - } - } - - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - match self { - MaybeOwnedInspector::Owned(insp) => insp.borrow_mut().step(interp, data), - MaybeOwnedInspector::Stacked(_) => {} - } - } - - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { - match self { - MaybeOwnedInspector::Owned(insp) => { - return insp.borrow_mut().log(evm_data, address, topics, data) - } - MaybeOwnedInspector::Stacked(_) => {} - } - } - - fn step_end(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - match self { - MaybeOwnedInspector::Owned(insp) => insp.borrow_mut().step_end(interp, data), - MaybeOwnedInspector::Stacked(_) => {} - } - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - match self { - MaybeOwnedInspector::Owned(insp) => return insp.borrow_mut().call(data, inputs), - MaybeOwnedInspector::Stacked(_) => {} - } - - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - match self { - MaybeOwnedInspector::Owned(insp) => { - return insp.borrow_mut().call_end(data, inputs, remaining_gas, ret, out) - } - MaybeOwnedInspector::Stacked(_) => {} - } - (ret, remaining_gas, out) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - match self { - MaybeOwnedInspector::Owned(insp) => return insp.borrow_mut().create(data, inputs), - MaybeOwnedInspector::Stacked(_) => {} - } - - (InstructionResult::Continue, None, Gas::new(0), Bytes::default()) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CreateInputs, - ret: InstructionResult, - address: Option
, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - match self { - MaybeOwnedInspector::Owned(insp) => { - return insp.borrow_mut().create_end(data, inputs, ret, address, remaining_gas, out) - } - MaybeOwnedInspector::Stacked(_) => {} - } - - (ret, address, remaining_gas, out) - } - - fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - match self { - MaybeOwnedInspector::Owned(insp) => { - return insp.borrow_mut().selfdestruct(contract, target, value) - } - MaybeOwnedInspector::Stacked(_) => {} - } - } -} diff --git a/crates/revm/revm-inspectors/src/stack/mod.rs b/crates/revm/revm-inspectors/src/stack/mod.rs deleted file mode 100644 index f8ea91794..000000000 --- a/crates/revm/revm-inspectors/src/stack/mod.rs +++ /dev/null @@ -1,215 +0,0 @@ -use alloy_primitives::{Address, Bytes, B256, U256}; -use revm::{ - inspectors::CustomPrintTracer, - interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::Env, - Database, EVMData, Inspector, -}; -use std::fmt::Debug; - -/// A wrapped [Inspector] that can be reused in the stack -mod maybe_owned; -pub use maybe_owned::MaybeOwnedInspector; - -/// One can hook on inspector execution in 3 ways: -/// - Block: Hook on block execution -/// - BlockWithIndex: Hook on block execution transaction index -/// - Transaction: Hook on a specific transaction hash -#[derive(Debug, Default, Clone)] -pub enum Hook { - #[default] - /// No hook. - None, - /// Hook on a specific block. - Block(u64), - /// Hook on a specific transaction hash. - Transaction(B256), - /// Hooks on every transaction in a block. - All, -} - -/// An inspector that calls multiple inspectors in sequence. -/// -/// If a call to an inspector returns a value other than [InstructionResult::Continue] (or -/// equivalent) the remaining inspectors are not called. -#[derive(Default, Clone)] -pub struct InspectorStack { - /// An inspector that prints the opcode traces to the console. - pub custom_print_tracer: Option, - /// The provided hook - pub hook: Hook, -} - -impl Debug for InspectorStack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("InspectorStack") - .field("custom_print_tracer", &self.custom_print_tracer.is_some()) - .field("hook", &self.hook) - .finish() - } -} - -impl InspectorStack { - /// Create a new inspector stack. - pub fn new(config: InspectorStackConfig) -> Self { - let mut stack = InspectorStack { hook: config.hook, ..Default::default() }; - - if config.use_printer_tracer { - stack.custom_print_tracer = Some(CustomPrintTracer::default()); - } - - stack - } - - /// Check if the inspector should be used. - pub fn should_inspect(&self, env: &Env, tx_hash: B256) -> bool { - match self.hook { - Hook::None => false, - Hook::Block(block) => env.block.number.to::() == block, - Hook::Transaction(hash) => hash == tx_hash, - Hook::All => true, - } - } -} - -/// Configuration for the inspectors. -#[derive(Debug, Default)] -pub struct InspectorStackConfig { - /// Enable revm inspector printer. - /// In execution this will print opcode level traces directly to console. - pub use_printer_tracer: bool, - - /// Hook on a specific block or transaction. - pub hook: Hook, -} - -/// Helper macro to call the same method on multiple inspectors without resorting to dynamic -/// dispatch -#[macro_export] -macro_rules! call_inspectors { - ($id:ident, [ $($inspector:expr),+ ], $call:block) => { - $({ - if let Some($id) = $inspector { - $call; - } - })+ - } -} - -impl Inspector for InspectorStack -where - DB: Database, -{ - fn initialize_interp(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - inspector.initialize_interp(interpreter, data); - }); - } - - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - inspector.step(interpreter, data); - }); - } - - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - inspector.log(evm_data, address, topics, data); - }); - } - - fn step_end(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - inspector.step_end(interpreter, data); - }); - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - let (status, gas, retdata) = inspector.call(data, inputs); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return (status, gas, retdata) - } - }); - - (InstructionResult::Continue, Gas::new(inputs.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - let (new_ret, new_gas, new_out) = - inspector.call_end(data, inputs, remaining_gas, ret, out.clone()); - - // If the inspector returns a different ret or a revert with a non-empty message, - // we assume it wants to tell us something - if new_ret != ret || (new_ret == InstructionResult::Revert && new_out != out) { - return (new_ret, new_gas, new_out) - } - }); - - (ret, remaining_gas, out) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - let (status, addr, gas, retdata) = inspector.create(data, inputs); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return (status, addr, gas, retdata) - } - }); - - (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::new()) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CreateInputs, - ret: InstructionResult, - address: Option
, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - let (new_ret, new_address, new_gas, new_retdata) = - inspector.create_end(data, inputs, ret, address, remaining_gas, out.clone()); - - if new_ret != ret { - return (new_ret, new_address, new_gas, new_retdata) - } - }); - - (ret, address, remaining_gas, out) - } - - fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - call_inspectors!(inspector, [&mut self.custom_print_tracer], { - Inspector::::selfdestruct(inspector, contract, target, value); - }); - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/arena.rs b/crates/revm/revm-inspectors/src/tracing/arena.rs deleted file mode 100644 index cb7c6b518..000000000 --- a/crates/revm/revm-inspectors/src/tracing/arena.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::tracing::types::{CallTrace, CallTraceNode, LogCallOrder}; - -/// An arena of recorded traces. -/// -/// This type will be populated via the [TracingInspector](crate::tracing::TracingInspector). -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CallTraceArena { - /// The arena of recorded trace nodes - pub(crate) arena: Vec, -} - -impl CallTraceArena { - /// Pushes a new trace into the arena, returning the trace ID - /// - /// This appends a new trace to the arena, and also inserts a new entry in the node's parent - /// node children set if `attach_to_parent` is `true`. E.g. if calls to precompiles should - /// not be included in the call graph this should be called with [PushTraceKind::PushOnly]. - pub(crate) fn push_trace( - &mut self, - mut entry: usize, - kind: PushTraceKind, - new_trace: CallTrace, - ) -> usize { - loop { - match new_trace.depth { - // The entry node, just update it - 0 => { - self.arena[0].trace = new_trace; - return 0 - } - // We found the parent node, add the new trace as a child - _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { - let id = self.arena.len(); - let node = CallTraceNode { - parent: Some(entry), - trace: new_trace, - idx: id, - ..Default::default() - }; - self.arena.push(node); - - // also track the child in the parent node - if kind.is_attach_to_parent() { - let parent = &mut self.arena[entry]; - let trace_location = parent.children.len(); - parent.ordering.push(LogCallOrder::Call(trace_location)); - parent.children.push(id); - } - - return id - } - _ => { - // We haven't found the parent node, go deeper - entry = *self.arena[entry].children.last().expect("Disconnected trace"); - } - } - } - } - - /// Returns the nodes in the arena - pub fn nodes(&self) -> &[CallTraceNode] { - &self.arena - } - - /// Consumes the arena and returns the nodes - pub fn into_nodes(self) -> Vec { - self.arena - } -} - -/// How to push a trace into the arena -pub(crate) enum PushTraceKind { - /// This will _only_ push the trace into the arena. - PushOnly, - /// This will push the trace into the arena, and also insert a new entry in the node's parent - /// node children set. - PushAndAttachToParent, -} - -impl PushTraceKind { - #[inline] - fn is_attach_to_parent(&self) -> bool { - matches!(self, PushTraceKind::PushAndAttachToParent) - } -} - -impl Default for CallTraceArena { - fn default() -> Self { - // The first node is the root node - CallTraceArena { arena: vec![Default::default()] } - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs deleted file mode 100644 index b4a6b6548..000000000 --- a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs +++ /dev/null @@ -1,325 +0,0 @@ -//! Geth trace builder - -use crate::tracing::{ - types::{CallTraceNode, CallTraceStepStackItem}, - utils::load_account_code, - TracingInspectorConfig, -}; -use alloy_primitives::{Address, Bytes, B256, U256}; -use alloy_rpc_trace_types::geth::{ - AccountChangeKind, AccountState, CallConfig, CallFrame, DefaultFrame, DiffMode, - GethDefaultTracingOptions, PreStateConfig, PreStateFrame, PreStateMode, StructLog, -}; -use revm::{db::DatabaseRef, primitives::ResultAndState}; -use std::collections::{btree_map::Entry, BTreeMap, HashMap, VecDeque}; - -/// A type for creating geth style traces -#[derive(Clone, Debug)] -pub struct GethTraceBuilder { - /// Recorded trace nodes. - nodes: Vec, - /// How the traces were recorded - _config: TracingInspectorConfig, -} - -impl GethTraceBuilder { - /// Returns a new instance of the builder - pub fn new(nodes: Vec, _config: TracingInspectorConfig) -> Self { - Self { nodes, _config } - } - - /// Fill in the geth trace with all steps of the trace and its children traces in the order they - /// appear in the transaction. - fn fill_geth_trace( - &self, - main_trace_node: &CallTraceNode, - opts: &GethDefaultTracingOptions, - storage: &mut HashMap>, - struct_logs: &mut Vec, - ) { - // A stack with all the steps of the trace and all its children's steps. - // This is used to process the steps in the order they appear in the transactions. - // Steps are grouped by their Call Trace Node, in order to process them all in the order - // they appear in the transaction, we need to process steps of call nodes when they appear. - // When we find a call step, we push all the steps of the child trace on the stack, so they - // are processed next. The very next step is the last item on the stack - let mut step_stack = VecDeque::with_capacity(main_trace_node.trace.steps.len()); - - main_trace_node.push_steps_on_stack(&mut step_stack); - - // Iterate over the steps inside the given trace - while let Some(CallTraceStepStackItem { trace_node, step, call_child_id }) = - step_stack.pop_back() - { - let mut log = step.convert_to_geth_struct_log(opts); - - // Fill in memory and storage depending on the options - if opts.is_storage_enabled() { - let contract_storage = storage.entry(step.contract).or_default(); - if let Some(change) = step.storage_change { - contract_storage.insert(change.key.into(), change.value.into()); - log.storage = Some(contract_storage.clone()); - } - } - - if opts.is_return_data_enabled() { - log.return_data = Some(trace_node.trace.output.clone()); - } - - // Add step to geth trace - struct_logs.push(log); - - // If the step is a call, we first push all the steps of the child trace on the stack, - // so they are processed next - if let Some(call_child_id) = call_child_id { - let child_trace = &self.nodes[call_child_id]; - child_trace.push_steps_on_stack(&mut step_stack); - } - } - } - - /// Generate a geth-style trace e.g. for `debug_traceTransaction` - /// - /// This expects the gas used and return value for the - /// [ExecutionResult](revm::primitives::ExecutionResult) of the executed transaction. - pub fn geth_traces( - &self, - receipt_gas_used: u64, - return_value: Bytes, - opts: GethDefaultTracingOptions, - ) -> DefaultFrame { - if self.nodes.is_empty() { - return Default::default() - } - // Fetch top-level trace - let main_trace_node = &self.nodes[0]; - let main_trace = &main_trace_node.trace; - - let mut struct_logs = Vec::new(); - let mut storage = HashMap::new(); - self.fill_geth_trace(main_trace_node, &opts, &mut storage, &mut struct_logs); - - DefaultFrame { - // If the top-level trace succeeded, then it was a success - failed: !main_trace.success, - gas: receipt_gas_used, - return_value, - struct_logs, - } - } - - /// Generate a geth-style traces for the call tracer. - /// - /// This decodes all call frames from the recorded traces. - /// - /// This expects the gas used and return value for the - /// [ExecutionResult](revm::primitives::ExecutionResult) of the executed transaction. - pub fn geth_call_traces(&self, opts: CallConfig, gas_used: u64) -> CallFrame { - if self.nodes.is_empty() { - return Default::default() - } - - let include_logs = opts.with_log.unwrap_or_default(); - // first fill up the root - let main_trace_node = &self.nodes[0]; - let mut root_call_frame = main_trace_node.geth_empty_call_frame(include_logs); - root_call_frame.gas_used = U256::from(gas_used); - - // selfdestructs are not recorded as individual call traces but are derived from - // the call trace and are added as additional `CallFrame` objects to the parent call - if let Some(selfdestruct) = main_trace_node.geth_selfdestruct_call_trace() { - root_call_frame.calls.push(selfdestruct); - } - - if opts.only_top_call.unwrap_or_default() { - return root_call_frame - } - - // fill all the call frames in the root call frame with the recorded traces. - // traces are identified by their index in the arena - // so we can populate the call frame tree by walking up the call tree - let mut call_frames = Vec::with_capacity(self.nodes.len()); - call_frames.push((0, root_call_frame)); - - for (idx, trace) in self.nodes.iter().enumerate().skip(1) { - // selfdestructs are not recorded as individual call traces but are derived from - // the call trace and are added as additional `CallFrame` objects to the parent call - if let Some(selfdestruct) = trace.geth_selfdestruct_call_trace() { - call_frames.last_mut().expect("not empty").1.calls.push(selfdestruct); - } - call_frames.push((idx, trace.geth_empty_call_frame(include_logs))); - } - - // pop the _children_ calls frame and move it to the parent - // this will roll up the child frames to their parent; this works because `child idx > - // parent idx` - loop { - let (idx, call) = call_frames.pop().expect("call frames not empty"); - let node = &self.nodes[idx]; - if let Some(parent) = node.parent { - let parent_frame = &mut call_frames[parent]; - // we need to ensure that calls are in order they are called: the last child node is - // the last call, but since we walk up the tree, we need to always - // insert at position 0 - parent_frame.1.calls.insert(0, call); - } else { - debug_assert!(call_frames.is_empty(), "only one root node has no parent"); - return call - } - } - } - - /// Returns the accounts necessary for transaction execution. - /// - /// The prestate mode returns the accounts necessary to execute a given transaction. - /// diff_mode returns the differences between the transaction's pre and post-state. - /// - /// * `state` - The state post-transaction execution. - /// * `diff_mode` - if prestate is in diff or prestate mode. - /// * `db` - The database to fetch state pre-transaction execution. - pub fn geth_prestate_traces( - &self, - ResultAndState { state, .. }: &ResultAndState, - prestate_config: PreStateConfig, - db: DB, - ) -> Result { - let account_diffs = state.into_iter().map(|(addr, acc)| (*addr, acc)); - - if prestate_config.is_default_mode() { - let mut prestate = PreStateMode::default(); - // in default mode we __only__ return the touched state - for node in self.nodes.iter() { - let addr = node.trace.address; - - let acc_state = match prestate.0.entry(addr) { - Entry::Vacant(entry) => { - let db_acc = db.basic_ref(addr)?.unwrap_or_default(); - let code = load_account_code(&db, &db_acc); - let acc_state = - AccountState::from_account_info(db_acc.nonce, db_acc.balance, code); - entry.insert(acc_state) - } - Entry::Occupied(entry) => entry.into_mut(), - }; - - for (key, value) in node.touched_slots() { - match acc_state.storage.entry(key.into()) { - Entry::Vacant(entry) => { - entry.insert(value.into()); - } - Entry::Occupied(_) => { - // we've already recorded this slot - } - } - } - } - - // also need to check changed accounts for things like balance changes etc - for (addr, changed_acc) in account_diffs { - let acc_state = match prestate.0.entry(addr) { - Entry::Vacant(entry) => { - let db_acc = db.basic_ref(addr)?.unwrap_or_default(); - let code = load_account_code(&db, &db_acc); - let acc_state = - AccountState::from_account_info(db_acc.nonce, db_acc.balance, code); - entry.insert(acc_state) - } - Entry::Occupied(entry) => { - // already recorded via touched accounts - entry.into_mut() - } - }; - - // in case we missed anything during the trace, we need to add the changed accounts - // storage - for (key, slot) in changed_acc.storage.iter() { - match acc_state.storage.entry((*key).into()) { - Entry::Vacant(entry) => { - entry.insert(slot.previous_or_original_value.into()); - } - Entry::Occupied(_) => { - // we've already recorded this slot - } - } - } - } - - Ok(PreStateFrame::Default(prestate)) - } else { - let mut state_diff = DiffMode::default(); - let mut account_change_kinds = HashMap::with_capacity(account_diffs.len()); - for (addr, changed_acc) in account_diffs { - let db_acc = db.basic_ref(addr)?.unwrap_or_default(); - - let pre_code = load_account_code(&db, &db_acc); - - let mut pre_state = - AccountState::from_account_info(db_acc.nonce, db_acc.balance, pre_code); - - let mut post_state = AccountState::from_account_info( - changed_acc.info.nonce, - changed_acc.info.balance, - changed_acc.info.code.as_ref().map(|code| code.original_bytes()), - ); - - // handle storage changes - for (key, slot) in changed_acc.storage.iter().filter(|(_, slot)| slot.is_changed()) - { - pre_state.storage.insert((*key).into(), slot.previous_or_original_value.into()); - post_state.storage.insert((*key).into(), slot.present_value.into()); - } - - state_diff.pre.insert(addr, pre_state); - state_diff.post.insert(addr, post_state); - - // determine the change type - let pre_change = if changed_acc.is_created() { - AccountChangeKind::Create - } else { - AccountChangeKind::Modify - }; - let post_change = if changed_acc.is_selfdestructed() { - AccountChangeKind::SelfDestruct - } else { - AccountChangeKind::Modify - }; - - account_change_kinds.insert(addr, (pre_change, post_change)); - } - - // ensure we're only keeping changed entries - state_diff.retain_changed().remove_zero_storage_values(); - - self.diff_traces(&mut state_diff.pre, &mut state_diff.post, account_change_kinds); - Ok(PreStateFrame::Diff(state_diff)) - } - } - - /// Returns the difference between the pre and post state of the transaction depending on the - /// kind of changes of that account (pre,post) - fn diff_traces( - &self, - pre: &mut BTreeMap, - post: &mut BTreeMap, - change_type: HashMap, - ) { - post.retain(|addr, post_state| { - // Don't keep destroyed accounts in the post state - if change_type.get(addr).map(|ty| ty.1.is_selfdestruct()).unwrap_or(false) { - return false - } - if let Some(pre_state) = pre.get(addr) { - // remove any unchanged account info - post_state.remove_matching_account_info(pre_state); - } - - true - }); - - // Don't keep created accounts the pre state - pre.retain(|addr, _pre_state| { - // only keep accounts that are not created - change_type.get(addr).map(|ty| !ty.0.is_created()).unwrap_or(true) - }); - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/builder/mod.rs b/crates/revm/revm-inspectors/src/tracing/builder/mod.rs deleted file mode 100644 index e6e58d8c2..000000000 --- a/crates/revm/revm-inspectors/src/tracing/builder/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Builder types for building traces - -/// Geth style trace builders for `debug_` namespace -pub mod geth; - -/// Parity style trace builders for `trace_` namespace -pub mod parity; - -/// Walker types used for traversing various callgraphs -mod walker; diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs deleted file mode 100644 index a36b155f1..000000000 --- a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs +++ /dev/null @@ -1,633 +0,0 @@ -use super::walker::CallTraceNodeWalkerBF; -use crate::tracing::{ - types::{CallTraceNode, CallTraceStep}, - utils::load_account_code, - TracingInspectorConfig, -}; -use alloy_primitives::{Address, U64}; -use alloy_rpc_trace_types::parity::*; -use alloy_rpc_types::TransactionInfo; -use revm::{ - db::DatabaseRef, - interpreter::{ - opcode::{self, spec_opcode_gas}, - OpCode, - }, - primitives::{Account, ExecutionResult, ResultAndState, SpecId, KECCAK_EMPTY}, -}; -use std::collections::{HashSet, VecDeque}; - -/// A type for creating parity style traces -/// -/// Note: Parity style traces always ignore calls to precompiles. -#[derive(Clone, Debug)] -pub struct ParityTraceBuilder { - /// Recorded trace nodes - nodes: Vec, - /// The spec id of the EVM. - spec_id: Option, - - /// How the traces were recorded - _config: TracingInspectorConfig, -} - -impl ParityTraceBuilder { - /// Returns a new instance of the builder - pub fn new( - nodes: Vec, - spec_id: Option, - _config: TracingInspectorConfig, - ) -> Self { - Self { nodes, spec_id, _config } - } - - /// Returns a list of all addresses that appeared as callers. - pub fn callers(&self) -> HashSet
{ - self.nodes.iter().map(|node| node.trace.caller).collect() - } - - /// Manually the gas used of the root trace. - /// - /// The root trace's gasUsed should mirror the actual gas used by the transaction. - /// - /// This allows setting it manually by consuming the execution result's gas for example. - #[inline] - pub fn set_transaction_gas_used(&mut self, gas_used: u64) { - if let Some(node) = self.nodes.first_mut() { - node.trace.gas_used = gas_used; - } - } - - /// Convenience function for [ParityTraceBuilder::set_transaction_gas_used] that consumes the - /// type. - #[inline] - pub fn with_transaction_gas_used(mut self, gas_used: u64) -> Self { - self.set_transaction_gas_used(gas_used); - self - } - - /// Returns the trace addresses of all call nodes in the set - /// - /// Each entry in the returned vector represents the [Self::trace_address] of the corresponding - /// node in the nodes set. - /// - /// CAUTION: This also includes precompiles, which have an empty trace address. - fn trace_addresses(&self) -> Vec> { - let mut all_addresses = Vec::with_capacity(self.nodes.len()); - for idx in 0..self.nodes.len() { - all_addresses.push(self.trace_address(idx)); - } - all_addresses - } - - /// Returns the `traceAddress` of the node in the arena - /// - /// The `traceAddress` field of all returned traces, gives the exact location in the call trace - /// [index in root, index in first CALL, index in second CALL, …]. - /// - /// # Panics - /// - /// if the `idx` does not belong to a node - /// - /// Note: if the call node of `idx` is a precompile, the returned trace address will be empty. - fn trace_address(&self, idx: usize) -> Vec { - if idx == 0 { - // root call has empty traceAddress - return vec![] - } - let mut graph = vec![]; - let mut node = &self.nodes[idx]; - if node.is_precompile() { - return graph - } - while let Some(parent) = node.parent { - // the index of the child call in the arena - let child_idx = node.idx; - node = &self.nodes[parent]; - // find the index of the child call in the parent node - let call_idx = node - .children - .iter() - .position(|child| *child == child_idx) - .expect("non precompile child call exists in parent"); - graph.push(call_idx); - } - graph.reverse(); - graph - } - - /// Returns an iterator over all nodes to trace - /// - /// This excludes nodes that represent calls to precompiles. - fn iter_traceable_nodes(&self) -> impl Iterator { - self.nodes.iter().filter(|node| !node.is_precompile()) - } - - /// Returns an iterator over all recorded traces for `trace_transaction` - pub fn into_localized_transaction_traces_iter( - self, - info: TransactionInfo, - ) -> impl Iterator { - self.into_transaction_traces_iter().map(move |trace| { - let TransactionInfo { hash, index, block_hash, block_number, .. } = info; - LocalizedTransactionTrace { - trace, - transaction_position: index, - transaction_hash: hash, - block_number, - block_hash, - } - }) - } - - /// Returns all recorded traces for `trace_transaction` - pub fn into_localized_transaction_traces( - self, - info: TransactionInfo, - ) -> Vec { - self.into_localized_transaction_traces_iter(info).collect() - } - - /// Consumes the inspector and returns the trace results according to the configured trace - /// types. - /// - /// Warning: If `trace_types` contains [TraceType::StateDiff] the returned [StateDiff] will not - /// be filled. Use [ParityTraceBuilder::into_trace_results_with_state] or - /// [populate_state_diff] to populate the balance and nonce changes for the [StateDiff] - /// using the [DatabaseRef]. - pub fn into_trace_results( - self, - res: &ExecutionResult, - trace_types: &HashSet, - ) -> TraceResults { - let gas_used = res.gas_used(); - let output = res.output().cloned().unwrap_or_default(); - - let (trace, vm_trace, state_diff) = self.into_trace_type_traces(trace_types); - - let mut trace = - TraceResults { output, trace: trace.unwrap_or_default(), vm_trace, state_diff }; - - // we're setting the gas used of the root trace explicitly to the gas used of the execution - // result - trace.set_root_trace_gas_used(gas_used); - - trace - } - - /// Consumes the inspector and returns the trace results according to the configured trace - /// types. - /// - /// This also takes the [DatabaseRef] to populate the balance and nonce changes for the - /// [StateDiff]. - /// - /// Note: this is considered a convenience method that takes the state map of - /// [ResultAndState] after inspecting a transaction - /// with the [TracingInspector](crate::tracing::TracingInspector). - pub fn into_trace_results_with_state( - self, - res: &ResultAndState, - trace_types: &HashSet, - db: DB, - ) -> Result { - let ResultAndState { ref result, ref state } = res; - - let breadth_first_addresses = if trace_types.contains(&TraceType::VmTrace) { - CallTraceNodeWalkerBF::new(&self.nodes) - .map(|node| node.trace.address) - .collect::>() - } else { - vec![] - }; - - let mut trace_res = self.into_trace_results(result, trace_types); - - // check the state diff case - if let Some(ref mut state_diff) = trace_res.state_diff { - populate_state_diff(state_diff, &db, state.iter())?; - } - - // check the vm trace case - if let Some(ref mut vm_trace) = trace_res.vm_trace { - populate_vm_trace_bytecodes(&db, vm_trace, breadth_first_addresses)?; - } - - Ok(trace_res) - } - - /// Returns the tracing types that are configured in the set. - /// - /// Warning: if [TraceType::StateDiff] is provided this does __not__ fill the state diff, since - /// this requires access to the account diffs. - /// - /// See [Self::into_trace_results_with_state] and [populate_state_diff]. - pub fn into_trace_type_traces( - self, - trace_types: &HashSet, - ) -> (Option>, Option, Option) { - if trace_types.is_empty() || self.nodes.is_empty() { - return (None, None, None) - } - - let with_traces = trace_types.contains(&TraceType::Trace); - let with_diff = trace_types.contains(&TraceType::StateDiff); - - let vm_trace = - if trace_types.contains(&TraceType::VmTrace) { Some(self.vm_trace()) } else { None }; - - let mut traces = Vec::with_capacity(if with_traces { self.nodes.len() } else { 0 }); - - for node in self.iter_traceable_nodes() { - let trace_address = self.trace_address(node.idx); - - if with_traces { - let trace = node.parity_transaction_trace(trace_address); - traces.push(trace); - - // check if the trace node is a selfdestruct - if node.is_selfdestruct() { - // selfdestructs are not recorded as individual call traces but are derived from - // the call trace and are added as additional `TransactionTrace` objects in the - // trace array - let addr = { - let last = traces.last_mut().expect("exists"); - let mut addr = last.trace_address.clone(); - addr.push(last.subtraces); - // need to account for the additional selfdestruct trace - last.subtraces += 1; - addr - }; - - if let Some(trace) = node.parity_selfdestruct_trace(addr) { - traces.push(trace); - } - } - } - } - - let traces = with_traces.then_some(traces); - let diff = with_diff.then_some(StateDiff::default()); - - (traces, vm_trace, diff) - } - - /// Returns an iterator over all recorded traces for `trace_transaction` - pub fn into_transaction_traces_iter(self) -> impl Iterator { - let trace_addresses = self.trace_addresses(); - TransactionTraceIter { - next_selfdestruct: None, - iter: self - .nodes - .into_iter() - .zip(trace_addresses) - .filter(|(node, _)| !node.is_precompile()) - .map(|(node, trace_address)| (node.parity_transaction_trace(trace_address), node)), - } - } - - /// Returns the raw traces of the transaction - pub fn into_transaction_traces(self) -> Vec { - self.into_transaction_traces_iter().collect() - } - - /// Returns the last recorded step - #[inline] - fn last_step(&self) -> Option<&CallTraceStep> { - self.nodes.last().and_then(|node| node.trace.steps.last()) - } - - /// Returns true if the last recorded step is a STOP - #[inline] - fn is_last_step_stop_op(&self) -> bool { - self.last_step().map(|step| step.is_stop()).unwrap_or(false) - } - - /// Creates a VM trace by walking over `CallTraceNode`s - /// - /// does not have the code fields filled in - pub fn vm_trace(&self) -> VmTrace { - self.nodes.first().map(|node| self.make_vm_trace(node)).unwrap_or_default() - } - - /// Returns a VM trace without the code filled in - /// - /// Iteratively creates a VM trace by traversing the recorded nodes in the arena - fn make_vm_trace(&self, start: &CallTraceNode) -> VmTrace { - let mut child_idx_stack = Vec::with_capacity(self.nodes.len()); - let mut sub_stack = VecDeque::with_capacity(self.nodes.len()); - - let mut current = start; - let mut child_idx: usize = 0; - - // finds the deepest nested calls of each call frame and fills them up bottom to top - let instructions = 'outer: loop { - match current.children.get(child_idx) { - Some(child) => { - child_idx_stack.push(child_idx + 1); - - child_idx = 0; - current = self.nodes.get(*child).expect("there should be a child"); - } - None => { - let mut instructions = Vec::with_capacity(current.trace.steps.len()); - - for step in ¤t.trace.steps { - let maybe_sub_call = if step.is_calllike_op() { - sub_stack.pop_front().flatten() - } else { - None - }; - - if step.is_stop() && instructions.is_empty() && self.is_last_step_stop_op() - { - // This is a special case where there's a single STOP which is - // "optimised away", transfers for example - break 'outer instructions - } - - instructions.push(self.make_instruction(step, maybe_sub_call)); - } - - match current.parent { - Some(parent) => { - sub_stack.push_back(Some(VmTrace { - code: Default::default(), - ops: instructions, - })); - - child_idx = child_idx_stack.pop().expect("there should be a child idx"); - - current = self.nodes.get(parent).expect("there should be a parent"); - } - None => break instructions, - } - } - } - }; - - VmTrace { code: Default::default(), ops: instructions } - } - - /// Creates a VM instruction from a [CallTraceStep] and a [VmTrace] for the subcall if there is - /// one - fn make_instruction( - &self, - step: &CallTraceStep, - maybe_sub_call: Option, - ) -> VmInstruction { - let maybe_storage = step.storage_change.map(|storage_change| StorageDelta { - key: storage_change.key, - val: storage_change.value, - }); - - let maybe_memory = if step.memory.is_empty() { - None - } else { - Some(MemoryDelta { - off: step.memory_size, - data: step.memory.as_bytes().to_vec().into(), - }) - }; - - let maybe_execution = Some(VmExecutedOperation { - used: step.gas_remaining, - push: step.push_stack.clone().unwrap_or_default(), - mem: maybe_memory, - store: maybe_storage, - }); - - let cost = self - .spec_id - .and_then(|spec_id| { - spec_opcode_gas(spec_id).get(step.op.get() as usize).map(|op| op.get_gas()) - }) - .unwrap_or_default(); - - VmInstruction { - pc: step.pc, - cost: cost as u64, - ex: maybe_execution, - sub: maybe_sub_call, - op: Some(step.op.to_string()), - idx: None, - } - } -} - -/// An iterator for [TransactionTrace]s -struct TransactionTraceIter { - iter: Iter, - next_selfdestruct: Option, -} - -impl Iterator for TransactionTraceIter -where - Iter: Iterator, -{ - type Item = TransactionTrace; - - fn next(&mut self) -> Option { - if let Some(selfdestruct) = self.next_selfdestruct.take() { - return Some(selfdestruct) - } - let (mut trace, node) = self.iter.next()?; - if node.is_selfdestruct() { - // since selfdestructs are emitted as additional trace, increase the trace count - let mut addr = trace.trace_address.clone(); - addr.push(trace.subtraces); - // need to account for the additional selfdestruct trace - trace.subtraces += 1; - self.next_selfdestruct = node.parity_selfdestruct_trace(addr); - } - Some(trace) - } -} - -/// addresses are presorted via breadth first walk thru [CallTraceNode]s, this can be done by a -/// walker in [crate::tracing::builder::walker] -/// -/// iteratively fill the [VmTrace] code fields -pub(crate) fn populate_vm_trace_bytecodes( - db: DB, - trace: &mut VmTrace, - breadth_first_addresses: I, -) -> Result<(), DB::Error> -where - DB: DatabaseRef, - I: IntoIterator, -{ - let mut stack: VecDeque<&mut VmTrace> = VecDeque::new(); - stack.push_back(trace); - - let mut addrs = breadth_first_addresses.into_iter(); - - while let Some(curr_ref) = stack.pop_front() { - for op in curr_ref.ops.iter_mut() { - if let Some(sub) = op.sub.as_mut() { - stack.push_back(sub); - } - } - - let addr = addrs.next().expect("there should be an address"); - - let db_acc = db.basic_ref(addr)?.unwrap_or_default(); - - let code_hash = if db_acc.code_hash != KECCAK_EMPTY { db_acc.code_hash } else { continue }; - - curr_ref.code = db.code_by_hash_ref(code_hash)?.original_bytes(); - } - - Ok(()) -} - -/// Loops over all state accounts in the accounts diff that contains all accounts that are included -/// in the [ExecutionResult] state map and compares the balance and nonce against what's in the -/// `db`, which should point to the beginning of the transaction. -/// -/// It's expected that `DB` is a revm [Database](revm::db::Database) which at this point already -/// contains all the accounts that are in the state map and never has to fetch them from disk. -pub fn populate_state_diff<'a, DB, I>( - state_diff: &mut StateDiff, - db: DB, - account_diffs: I, -) -> Result<(), DB::Error> -where - I: IntoIterator, - DB: DatabaseRef, -{ - for (addr, changed_acc) in account_diffs.into_iter() { - // if the account was selfdestructed and created during the transaction, we can ignore it - if changed_acc.is_selfdestructed() && changed_acc.is_created() { - continue - } - - let addr = *addr; - let entry = state_diff.entry(addr).or_default(); - - // we check if this account was created during the transaction - if changed_acc.is_created() || changed_acc.is_loaded_as_not_existing() { - entry.balance = Delta::Added(changed_acc.info.balance); - entry.nonce = Delta::Added(U64::from(changed_acc.info.nonce)); - - // accounts without code are marked as added - let account_code = load_account_code(&db, &changed_acc.info).unwrap_or_default(); - entry.code = Delta::Added(account_code); - - // new storage values are marked as added, - // however we're filtering changed here to avoid adding entries for the zero value - for (key, slot) in changed_acc.storage.iter().filter(|(_, slot)| slot.is_changed()) { - entry.storage.insert((*key).into(), Delta::Added(slot.present_value.into())); - } - } else { - // account already exists, we need to fetch the account from the db - let db_acc = db.basic_ref(addr)?.unwrap_or_default(); - - // update _changed_ storage values - for (key, slot) in changed_acc.storage.iter().filter(|(_, slot)| slot.is_changed()) { - entry.storage.insert( - (*key).into(), - Delta::changed( - slot.previous_or_original_value.into(), - slot.present_value.into(), - ), - ); - } - - // check if the account was changed at all - if entry.storage.is_empty() && - db_acc == changed_acc.info && - !changed_acc.is_selfdestructed() - { - // clear the entry if the account was not changed - state_diff.remove(&addr); - continue - } - - entry.balance = if db_acc.balance == changed_acc.info.balance { - Delta::Unchanged - } else { - Delta::Changed(ChangedType { from: db_acc.balance, to: changed_acc.info.balance }) - }; - - // this is relevant for the caller and contracts - entry.nonce = if db_acc.nonce == changed_acc.info.nonce { - Delta::Unchanged - } else { - Delta::Changed(ChangedType { - from: U64::from(db_acc.nonce), - to: U64::from(changed_acc.info.nonce), - }) - }; - } - } - - Ok(()) -} - -/// Returns the number of items pushed on the stack by a given opcode. -/// This used to determine how many stack etries to put in the `push` element -/// in a parity vmTrace. -/// The value is obvious for most opcodes, but SWAP* and DUP* are a bit weird, -/// and we handle those as they are handled in parity vmtraces. -/// For reference: -pub(crate) fn stack_push_count(step_op: OpCode) -> usize { - let step_op = step_op.get(); - match step_op { - opcode::PUSH0..=opcode::PUSH32 => 1, - opcode::SWAP1..=opcode::SWAP16 => (step_op - opcode::SWAP1) as usize + 2, - opcode::DUP1..=opcode::DUP16 => (step_op - opcode::DUP1) as usize + 2, - opcode::CALLDATALOAD | - opcode::SLOAD | - opcode::MLOAD | - opcode::CALLDATASIZE | - opcode::LT | - opcode::GT | - opcode::DIV | - opcode::SDIV | - opcode::SAR | - opcode::AND | - opcode::EQ | - opcode::CALLVALUE | - opcode::ISZERO | - opcode::ADD | - opcode::EXP | - opcode::CALLER | - opcode::KECCAK256 | - opcode::SUB | - opcode::ADDRESS | - opcode::GAS | - opcode::MUL | - opcode::RETURNDATASIZE | - opcode::NOT | - opcode::SHR | - opcode::SHL | - opcode::EXTCODESIZE | - opcode::SLT | - opcode::OR | - opcode::NUMBER | - opcode::PC | - opcode::TIMESTAMP | - opcode::BALANCE | - opcode::SELFBALANCE | - opcode::MULMOD | - opcode::ADDMOD | - opcode::BASEFEE | - opcode::BLOCKHASH | - opcode::BYTE | - opcode::XOR | - opcode::ORIGIN | - opcode::CODESIZE | - opcode::MOD | - opcode::SIGNEXTEND | - opcode::GASLIMIT | - opcode::DIFFICULTY | - opcode::SGT | - opcode::GASPRICE | - opcode::MSIZE | - opcode::EXTCODEHASH | - opcode::SMOD | - opcode::CHAINID | - opcode::COINBASE => 1, - _ => 0, - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/builder/walker.rs b/crates/revm/revm-inspectors/src/tracing/builder/walker.rs deleted file mode 100644 index 4d88a2af4..000000000 --- a/crates/revm/revm-inspectors/src/tracing/builder/walker.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::tracing::types::CallTraceNode; -use std::collections::VecDeque; - -/// Traverses Reths internal tracing structure breadth-first -/// -/// This is a lazy iterator -pub(crate) struct CallTraceNodeWalkerBF<'trace> { - /// the entire arena - nodes: &'trace Vec, - - /// holds indexes of nodes to visit as we traverse - queue: VecDeque, -} - -impl<'trace> CallTraceNodeWalkerBF<'trace> { - pub(crate) fn new(nodes: &'trace Vec) -> Self { - let mut queue = VecDeque::with_capacity(nodes.len()); - queue.push_back(0); - - Self { nodes, queue } - } -} - -impl<'trace> Iterator for CallTraceNodeWalkerBF<'trace> { - type Item = &'trace CallTraceNode; - - fn next(&mut self) -> Option { - match self.queue.pop_front() { - Some(idx) => { - let curr = self.nodes.get(idx).expect("there should be a node"); - - self.queue.extend(curr.children.iter()); - - Some(curr) - } - None => None, - } - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/config.rs b/crates/revm/revm-inspectors/src/tracing/config.rs deleted file mode 100644 index e3940849f..000000000 --- a/crates/revm/revm-inspectors/src/tracing/config.rs +++ /dev/null @@ -1,225 +0,0 @@ -use alloy_rpc_trace_types::{geth::GethDefaultTracingOptions, parity::TraceType}; -use std::collections::HashSet; - -/// Gives guidance to the [TracingInspector](crate::tracing::TracingInspector). -/// -/// Use [TracingInspectorConfig::default_parity] or [TracingInspectorConfig::default_geth] to get -/// the default configs for specific styles of traces. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct TracingInspectorConfig { - /// Whether to record every individual opcode level step. - pub record_steps: bool, - /// Whether to record individual memory snapshots. - pub record_memory_snapshots: bool, - /// Whether to record individual stack snapshots. - pub record_stack_snapshots: StackSnapshotType, - /// Whether to record state diffs. - pub record_state_diff: bool, - /// Whether to ignore precompile calls. - pub exclude_precompile_calls: bool, - /// Whether to record individual return data - pub record_call_return_data: bool, - /// Whether to record logs - pub record_logs: bool, -} - -impl TracingInspectorConfig { - /// Returns a config with everything enabled. - pub const fn all() -> Self { - Self { - record_steps: true, - record_memory_snapshots: true, - record_stack_snapshots: StackSnapshotType::Full, - record_state_diff: false, - exclude_precompile_calls: false, - record_call_return_data: false, - record_logs: true, - } - } - - /// Returns a config for parity style traces. - /// - /// This config does _not_ record opcode level traces and is suited for `trace_transaction` - pub const fn default_parity() -> Self { - Self { - record_steps: false, - record_memory_snapshots: false, - record_stack_snapshots: StackSnapshotType::None, - record_state_diff: false, - exclude_precompile_calls: true, - record_call_return_data: false, - record_logs: false, - } - } - - /// Returns a config for geth style traces. - /// - /// This config does _not_ record opcode level traces and is suited for `debug_traceTransaction` - pub const fn default_geth() -> Self { - Self { - record_steps: true, - record_memory_snapshots: true, - record_stack_snapshots: StackSnapshotType::Full, - record_state_diff: true, - exclude_precompile_calls: false, - record_call_return_data: false, - record_logs: false, - } - } - - /// Returns the [TracingInspectorConfig] depending on the enabled [TraceType]s - /// - /// Note: the parity statediffs can be populated entirely via the execution result, so we don't - /// need statediff recording - #[inline] - pub fn from_parity_config(trace_types: &HashSet) -> Self { - let needs_vm_trace = trace_types.contains(&TraceType::VmTrace); - let snap_type = - if needs_vm_trace { StackSnapshotType::Pushes } else { StackSnapshotType::None }; - TracingInspectorConfig::default_parity() - .set_steps(needs_vm_trace) - .set_stack_snapshots(snap_type) - .set_memory_snapshots(needs_vm_trace) - } - - /// Returns a config for geth style traces based on the given [GethDefaultTracingOptions]. - #[inline] - pub fn from_geth_config(config: &GethDefaultTracingOptions) -> Self { - Self { - record_memory_snapshots: config.enable_memory.unwrap_or_default(), - record_stack_snapshots: if config.disable_stack.unwrap_or_default() { - StackSnapshotType::None - } else { - StackSnapshotType::Full - }, - record_state_diff: !config.disable_storage.unwrap_or_default(), - ..Self::default_geth() - } - } - - /// Configure whether calls to precompiles should be ignored. - /// - /// If set to `true`, calls to precompiles without value transfers will be ignored. - pub fn set_exclude_precompile_calls(mut self, exclude_precompile_calls: bool) -> Self { - self.exclude_precompile_calls = exclude_precompile_calls; - self - } - - /// Configure whether individual opcode level steps should be recorded - pub fn set_steps(mut self, record_steps: bool) -> Self { - self.record_steps = record_steps; - self - } - - /// Configure whether the tracer should record memory snapshots - pub fn set_memory_snapshots(mut self, record_memory_snapshots: bool) -> Self { - self.record_memory_snapshots = record_memory_snapshots; - self - } - - /// Configure how the tracer should record stack snapshots - pub fn set_stack_snapshots(mut self, record_stack_snapshots: StackSnapshotType) -> Self { - self.record_stack_snapshots = record_stack_snapshots; - self - } - - /// Sets state diff recording to true. - pub fn with_state_diffs(self) -> Self { - self.set_steps_and_state_diffs(true) - } - - /// Configure whether the tracer should record state diffs - pub fn set_state_diffs(mut self, record_state_diff: bool) -> Self { - self.record_state_diff = record_state_diff; - self - } - - /// Configure whether the tracer should record steps and state diffs. - /// - /// This is a convenience method for setting both [TracingInspectorConfig::set_steps] and - /// [TracingInspectorConfig::set_state_diffs] since tracking state diffs requires steps tracing. - pub fn set_steps_and_state_diffs(mut self, steps_and_diffs: bool) -> Self { - self.record_steps = steps_and_diffs; - self.record_state_diff = steps_and_diffs; - self - } - - /// Configure whether the tracer should record logs - pub fn set_record_logs(mut self, record_logs: bool) -> Self { - self.record_logs = record_logs; - self - } -} - -/// How much of the stack to record. Nothing, just the items pushed, or the full stack -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum StackSnapshotType { - /// Don't record stack snapshots - None, - /// Record only the items pushed to the stack - Pushes, - /// Record the full stack - Full, -} - -impl StackSnapshotType { - /// Returns true if this is the [StackSnapshotType::Full] variant - #[inline] - pub fn is_full(self) -> bool { - matches!(self, Self::Full) - } - - /// Returns true if this is the [StackSnapshotType::Pushes] variant - #[inline] - pub fn is_pushes(self) -> bool { - matches!(self, Self::Pushes) - } -} - -/// What kind of tracing style this is. -/// -/// This affects things like error messages. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub(crate) enum TraceStyle { - /// Parity style tracer - Parity, - /// Geth style tracer - #[allow(dead_code)] - Geth, -} - -impl TraceStyle { - /// Returns true if this is a parity style tracer. - pub(crate) const fn is_parity(self) -> bool { - matches!(self, Self::Parity) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parity_config() { - let mut s = HashSet::new(); - s.insert(TraceType::StateDiff); - let config = TracingInspectorConfig::from_parity_config(&s); - // not required - assert!(!config.record_steps); - assert!(!config.record_state_diff); - - let mut s = HashSet::new(); - s.insert(TraceType::VmTrace); - let config = TracingInspectorConfig::from_parity_config(&s); - assert!(config.record_steps); - assert!(!config.record_state_diff); - - let mut s = HashSet::new(); - s.insert(TraceType::VmTrace); - s.insert(TraceType::StateDiff); - let config = TracingInspectorConfig::from_parity_config(&s); - assert!(config.record_steps); - // not required for StateDiff - assert!(!config.record_state_diff); - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/fourbyte.rs b/crates/revm/revm-inspectors/src/tracing/fourbyte.rs deleted file mode 100644 index 5abf378d5..000000000 --- a/crates/revm/revm-inspectors/src/tracing/fourbyte.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Fourbyte tracing inspector -//! -//! Solidity contract functions are addressed using the first four byte of the Keccak-256 hash of -//! their signature. Therefore when calling the function of a contract, the caller must send this -//! function selector as well as the ABI-encoded arguments as call data. -//! -//! The 4byteTracer collects the function selectors of every function executed in the lifetime of a -//! transaction, along with the size of the supplied call data. The result is a map of -//! SELECTOR-CALLDATASIZE to number of occurrences entries, where the keys are SELECTOR-CALLDATASIZE -//! and the values are number of occurrences of this key. For example: -//! -//! ```json -//! { -//! "0x27dc297e-128": 1, -//! "0x38cc4831-0": 2, -//! "0x524f3889-96": 1, -//! "0xadf59f99-288": 1, -//! "0xc281d19e-0": 1 -//! } -//! ``` -//! -//! See also - -use alloy_primitives::{hex, Bytes, Selector}; -use alloy_rpc_trace_types::geth::FourByteFrame; -use revm::{ - interpreter::{CallInputs, Gas, InstructionResult}, - Database, EVMData, Inspector, -}; -use std::collections::HashMap; - -/// Fourbyte tracing inspector that records all function selectors and their calldata sizes. -#[derive(Debug, Clone, Default)] -pub struct FourByteInspector { - /// The map of SELECTOR to number of occurrences entries - inner: HashMap<(Selector, usize), u64>, -} - -impl FourByteInspector { - /// Returns the map of SELECTOR to number of occurrences entries - pub fn inner(&self) -> &HashMap<(Selector, usize), u64> { - &self.inner - } -} - -impl Inspector for FourByteInspector -where - DB: Database, -{ - fn call( - &mut self, - _data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - if call.input.len() >= 4 { - let selector = Selector::try_from(&call.input[..4]).expect("input is at least 4 bytes"); - let calldata_size = call.input[4..].len(); - *self.inner.entry((selector, calldata_size)).or_default() += 1; - } - - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } -} - -impl From for FourByteFrame { - fn from(value: FourByteInspector) -> Self { - FourByteFrame( - value - .inner - .into_iter() - .map(|((selector, calldata_size), count)| { - let key = format!("0x{}-{}", hex::encode(&selector[..]), calldata_size); - (key, count) - }) - .collect(), - ) - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/js/bigint.js b/crates/revm/revm-inspectors/src/tracing/js/bigint.js deleted file mode 100644 index d9a8411b2..000000000 --- a/crates/revm/revm-inspectors/src/tracing/js/bigint.js +++ /dev/null @@ -1 +0,0 @@ -var bigInt=function(undefined){"use strict";var BASE=1e7,LOG_BASE=7,MAX_INT=9007199254740992,MAX_INT_ARR=smallToArray(MAX_INT),LOG_MAX_INT=Math.log(MAX_INT);function Integer(v,radix){if(typeof v==="undefined")return Integer[0];if(typeof radix!=="undefined")return+radix===10?parseValue(v):parseBase(v,radix);return parseValue(v)}function BigInteger(value,sign){this.value=value;this.sign=sign;this.isSmall=false}BigInteger.prototype=Object.create(Integer.prototype);function SmallInteger(value){this.value=value;this.sign=value<0;this.isSmall=true}SmallInteger.prototype=Object.create(Integer.prototype);function isPrecise(n){return-MAX_INT0)return Math.floor(n);return Math.ceil(n)}function add(a,b){var l_a=a.length,l_b=b.length,r=new Array(l_a),carry=0,base=BASE,sum,i;for(i=0;i=base?1:0;r[i]=sum-carry*base}while(i0)r.push(carry);return r}function addAny(a,b){if(a.length>=b.length)return add(a,b);return add(b,a)}function addSmall(a,carry){var l=a.length,r=new Array(l),base=BASE,sum,i;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}BigInteger.prototype.add=function(v){var n=parseValue(v);if(this.sign!==n.sign){return this.subtract(n.negate())}var a=this.value,b=n.value;if(n.isSmall){return new BigInteger(addSmall(a,Math.abs(b)),this.sign)}return new BigInteger(addAny(a,b),this.sign)};BigInteger.prototype.plus=BigInteger.prototype.add;SmallInteger.prototype.add=function(v){var n=parseValue(v);var a=this.value;if(a<0!==n.sign){return this.subtract(n.negate())}var b=n.value;if(n.isSmall){if(isPrecise(a+b))return new SmallInteger(a+b);b=smallToArray(Math.abs(b))}return new BigInteger(addSmall(b,Math.abs(a)),a<0)};SmallInteger.prototype.plus=SmallInteger.prototype.add;function subtract(a,b){var a_l=a.length,b_l=b.length,r=new Array(a_l),borrow=0,base=BASE,i,difference;for(i=0;i=0){value=subtract(a,b)}else{value=subtract(b,a);sign=!sign}value=arrayToSmall(value);if(typeof value==="number"){if(sign)value=-value;return new SmallInteger(value)}return new BigInteger(value,sign)}function subtractSmall(a,b,sign){var l=a.length,r=new Array(l),carry=-b,base=BASE,i,difference;for(i=0;i=0)};SmallInteger.prototype.minus=SmallInteger.prototype.subtract;BigInteger.prototype.negate=function(){return new BigInteger(this.value,!this.sign)};SmallInteger.prototype.negate=function(){var sign=this.sign;var small=new SmallInteger(-this.value);small.sign=!sign;return small};BigInteger.prototype.abs=function(){return new BigInteger(this.value,false)};SmallInteger.prototype.abs=function(){return new SmallInteger(Math.abs(this.value))};function multiplyLong(a,b){var a_l=a.length,b_l=b.length,l=a_l+b_l,r=createArray(l),base=BASE,product,carry,i,a_i,b_j;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}function shiftLeft(x,n){var r=[];while(n-- >0)r.push(0);return r.concat(x)}function multiplyKaratsuba(x,y){var n=Math.max(x.length,y.length);if(n<=30)return multiplyLong(x,y);n=Math.ceil(n/2);var b=x.slice(n),a=x.slice(0,n),d=y.slice(n),c=y.slice(0,n);var ac=multiplyKaratsuba(a,c),bd=multiplyKaratsuba(b,d),abcd=multiplyKaratsuba(addAny(a,b),addAny(c,d));var product=addAny(addAny(ac,shiftLeft(subtract(subtract(abcd,ac),bd),n)),shiftLeft(bd,2*n));trim(product);return product}function useKaratsuba(l1,l2){return-.012*l1-.012*l2+15e-6*l1*l2>0}BigInteger.prototype.multiply=function(v){var n=parseValue(v),a=this.value,b=n.value,sign=this.sign!==n.sign,abs;if(n.isSmall){if(b===0)return Integer[0];if(b===1)return this;if(b===-1)return this.negate();abs=Math.abs(b);if(abs=0;shift--){quotientDigit=base-1;if(remainder[shift+b_l]!==divisorMostSignificantDigit){quotientDigit=Math.floor((remainder[shift+b_l]*base+remainder[shift+b_l-1])/divisorMostSignificantDigit)}carry=0;borrow=0;l=divisor.length;for(i=0;ib_l){highx=(highx+1)*base}guess=Math.ceil(highx/highy);do{check=multiplySmall(b,guess);if(compareAbs(check,part)<=0)break;guess--}while(guess);result.push(guess);part=subtract(part,check)}result.reverse();return[arrayToSmall(result),arrayToSmall(part)]}function divModSmall(value,lambda){var length=value.length,quotient=createArray(length),base=BASE,i,q,remainder,divisor;remainder=0;for(i=length-1;i>=0;--i){divisor=remainder*base+value[i];q=truncate(divisor/lambda);remainder=divisor-q*lambda;quotient[i]=q|0}return[quotient,remainder|0]}function divModAny(self,v){var value,n=parseValue(v);var a=self.value,b=n.value;var quotient;if(b===0)throw new Error("Cannot divide by zero");if(self.isSmall){if(n.isSmall){return[new SmallInteger(truncate(a/b)),new SmallInteger(a%b)]}return[Integer[0],self]}if(n.isSmall){if(b===1)return[self,Integer[0]];if(b==-1)return[self.negate(),Integer[0]];var abs=Math.abs(b);if(absb.length?1:-1}for(var i=a.length-1;i>=0;i--){if(a[i]!==b[i])return a[i]>b[i]?1:-1}return 0}BigInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall)return 1;return compareAbs(a,b)};SmallInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=Math.abs(this.value),b=n.value;if(n.isSmall){b=Math.abs(b);return a===b?0:a>b?1:-1}return-1};BigInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(this.sign!==n.sign){return n.sign?1:-1}if(n.isSmall){return this.sign?-1:1}return compareAbs(a,b)*(this.sign?-1:1)};BigInteger.prototype.compareTo=BigInteger.prototype.compare;SmallInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall){return a==b?0:a>b?1:-1}if(a<0!==n.sign){return a<0?-1:1}return a<0?1:-1};SmallInteger.prototype.compareTo=SmallInteger.prototype.compare;BigInteger.prototype.equals=function(v){return this.compare(v)===0};SmallInteger.prototype.eq=SmallInteger.prototype.equals=BigInteger.prototype.eq=BigInteger.prototype.equals;BigInteger.prototype.notEquals=function(v){return this.compare(v)!==0};SmallInteger.prototype.neq=SmallInteger.prototype.notEquals=BigInteger.prototype.neq=BigInteger.prototype.notEquals;BigInteger.prototype.greater=function(v){return this.compare(v)>0};SmallInteger.prototype.gt=SmallInteger.prototype.greater=BigInteger.prototype.gt=BigInteger.prototype.greater;BigInteger.prototype.lesser=function(v){return this.compare(v)<0};SmallInteger.prototype.lt=SmallInteger.prototype.lesser=BigInteger.prototype.lt=BigInteger.prototype.lesser;BigInteger.prototype.greaterOrEquals=function(v){return this.compare(v)>=0};SmallInteger.prototype.geq=SmallInteger.prototype.greaterOrEquals=BigInteger.prototype.geq=BigInteger.prototype.greaterOrEquals;BigInteger.prototype.lesserOrEquals=function(v){return this.compare(v)<=0};SmallInteger.prototype.leq=SmallInteger.prototype.lesserOrEquals=BigInteger.prototype.leq=BigInteger.prototype.lesserOrEquals;BigInteger.prototype.isEven=function(){return(this.value[0]&1)===0};SmallInteger.prototype.isEven=function(){return(this.value&1)===0};BigInteger.prototype.isOdd=function(){return(this.value[0]&1)===1};SmallInteger.prototype.isOdd=function(){return(this.value&1)===1};BigInteger.prototype.isPositive=function(){return!this.sign};SmallInteger.prototype.isPositive=function(){return this.value>0};BigInteger.prototype.isNegative=function(){return this.sign};SmallInteger.prototype.isNegative=function(){return this.value<0};BigInteger.prototype.isUnit=function(){return false};SmallInteger.prototype.isUnit=function(){return Math.abs(this.value)===1};BigInteger.prototype.isZero=function(){return false};SmallInteger.prototype.isZero=function(){return this.value===0};BigInteger.prototype.isDivisibleBy=function(v){var n=parseValue(v);var value=n.value;if(value===0)return false;if(value===1)return true;if(value===2)return this.isEven();return this.mod(n).equals(Integer[0])};SmallInteger.prototype.isDivisibleBy=BigInteger.prototype.isDivisibleBy;function isBasicPrime(v){var n=v.abs();if(n.isUnit())return false;if(n.equals(2)||n.equals(3)||n.equals(5))return true;if(n.isEven()||n.isDivisibleBy(3)||n.isDivisibleBy(5))return false;if(n.lesser(25))return true}BigInteger.prototype.isPrime=function(){var isPrime=isBasicPrime(this);if(isPrime!==undefined)return isPrime;var n=this.abs(),nPrev=n.prev();var a=[2,3,5,7,11,13,17,19],b=nPrev,d,t,i,x;while(b.isEven())b=b.divide(2);for(i=0;i-MAX_INT)return new SmallInteger(value-1);return new BigInteger(MAX_INT_ARR,true)};var powersOfTwo=[1];while(2*powersOfTwo[powersOfTwo.length-1]<=BASE)powersOfTwo.push(2*powersOfTwo[powersOfTwo.length-1]);var powers2Length=powersOfTwo.length,highestPower2=powersOfTwo[powers2Length-1];function shift_isSmall(n){return(typeof n==="number"||typeof n==="string")&&+Math.abs(n)<=BASE||n instanceof BigInteger&&n.value.length<=1}BigInteger.prototype.shiftLeft=function(n){if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftRight(-n);var result=this;while(n>=powers2Length){result=result.multiply(highestPower2);n-=powers2Length-1}return result.multiply(powersOfTwo[n])};SmallInteger.prototype.shiftLeft=BigInteger.prototype.shiftLeft;BigInteger.prototype.shiftRight=function(n){var remQuo;if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftLeft(-n);var result=this;while(n>=powers2Length){if(result.isZero())return result;remQuo=divModAny(result,highestPower2);result=remQuo[1].isNegative()?remQuo[0].prev():remQuo[0];n-=powers2Length-1}remQuo=divModAny(result,powersOfTwo[n]);return remQuo[1].isNegative()?remQuo[0].prev():remQuo[0]};SmallInteger.prototype.shiftRight=BigInteger.prototype.shiftRight;function bitwise(x,y,fn){y=parseValue(y);var xSign=x.isNegative(),ySign=y.isNegative();var xRem=xSign?x.not():x,yRem=ySign?y.not():y;var xDigit=0,yDigit=0;var xDivMod=null,yDivMod=null;var result=[];while(!xRem.isZero()||!yRem.isZero()){xDivMod=divModAny(xRem,highestPower2);xDigit=xDivMod[1].toJSNumber();if(xSign){xDigit=highestPower2-1-xDigit}yDivMod=divModAny(yRem,highestPower2);yDigit=yDivMod[1].toJSNumber();if(ySign){yDigit=highestPower2-1-yDigit}xRem=xDivMod[0];yRem=yDivMod[0];result.push(fn(xDigit,yDigit))}var sum=fn(xSign?1:0,ySign?1:0)!==0?bigInt(-1):bigInt(0);for(var i=result.length-1;i>=0;i-=1){sum=sum.multiply(highestPower2).add(bigInt(result[i]))}return sum}BigInteger.prototype.not=function(){return this.negate().prev()};SmallInteger.prototype.not=BigInteger.prototype.not;BigInteger.prototype.and=function(n){return bitwise(this,n,function(a,b){return a&b})};SmallInteger.prototype.and=BigInteger.prototype.and;BigInteger.prototype.or=function(n){return bitwise(this,n,function(a,b){return a|b})};SmallInteger.prototype.or=BigInteger.prototype.or;BigInteger.prototype.xor=function(n){return bitwise(this,n,function(a,b){return a^b})};SmallInteger.prototype.xor=BigInteger.prototype.xor;var LOBMASK_I=1<<30,LOBMASK_BI=(BASE&-BASE)*(BASE&-BASE)|LOBMASK_I;function roughLOB(n){var v=n.value,x=typeof v==="number"?v|LOBMASK_I:v[0]+v[1]*BASE|LOBMASK_BI;return x&-x}function max(a,b){a=parseValue(a);b=parseValue(b);return a.greater(b)?a:b}function min(a,b){a=parseValue(a);b=parseValue(b);return a.lesser(b)?a:b}function gcd(a,b){a=parseValue(a).abs();b=parseValue(b).abs();if(a.equals(b))return a;if(a.isZero())return b;if(b.isZero())return a;var c=Integer[1],d,t;while(a.isEven()&&b.isEven()){d=Math.min(roughLOB(a),roughLOB(b));a=a.divide(d);b=b.divide(d);c=c.multiply(d)}while(a.isEven()){a=a.divide(roughLOB(a))}do{while(b.isEven()){b=b.divide(roughLOB(b))}if(a.greater(b)){t=b;b=a;a=t}b=b.subtract(a)}while(!b.isZero());return c.isUnit()?a:a.multiply(c)}function lcm(a,b){a=parseValue(a).abs();b=parseValue(b).abs();return a.divide(gcd(a,b)).multiply(b)}function randBetween(a,b){a=parseValue(a);b=parseValue(b);var low=min(a,b),high=max(a,b);var range=high.subtract(low).add(1);if(range.isSmall)return low.add(Math.floor(Math.random()*range));var length=range.value.length-1;var result=[],restricted=true;for(var i=length;i>=0;i--){var top=restricted?range.value[i]:BASE;var digit=truncate(Math.random()*top);result.unshift(digit);if(digit=absBase){if(c==="1"&&absBase===1)continue;throw new Error(c+" is not a valid digit in base "+base+".")}else if(c.charCodeAt(0)-87>=absBase){throw new Error(c+" is not a valid digit in base "+base+".")}}}if(2<=base&&base<=36){if(length<=LOG_MAX_INT/Math.log(base)){var result=parseInt(text,base);if(isNaN(result)){throw new Error(c+" is not a valid digit in base "+base+".")}return new SmallInteger(parseInt(text,base))}}base=parseValue(base);var digits=[];var isNegative=text[0]==="-";for(i=isNegative?1:0;i");digits.push(parseValue(text.slice(start+1,i)))}else throw new Error(c+" is not a valid character")}return parseBaseFromArray(digits,base,isNegative)};function parseBaseFromArray(digits,base,isNegative){var val=Integer[0],pow=Integer[1],i;for(i=digits.length-1;i>=0;i--){val=val.add(digits[i].times(pow));pow=pow.times(base)}return isNegative?val.negate():val}function stringify(digit){var v=digit.value;if(typeof v==="number")v=[v];if(v.length===1&&v[0]<=35){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(v[0])}return"<"+v+">"}function toBase(n,base){base=bigInt(base);if(base.isZero()){if(n.isZero())return"0";throw new Error("Cannot convert nonzero numbers to base 0.")}if(base.equals(-1)){if(n.isZero())return"0";if(n.isNegative())return new Array(1-n).join("10");return"1"+new Array(+n).join("01")}var minusSign="";if(n.isNegative()&&base.isPositive()){minusSign="-";n=n.abs()}if(base.equals(1)){if(n.isZero())return"0";return minusSign+new Array(+n+1).join(1)}var out=[];var left=n,divmod;while(left.isNegative()||left.compareAbs(base)>=0){divmod=left.divmod(base);left=divmod.quotient;var digit=divmod.remainder;if(digit.isNegative()){digit=base.minus(digit).abs();left=left.next()}out.push(stringify(digit))}out.push(stringify(left));return minusSign+out.reverse().join("")}BigInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!==10)return toBase(this,radix);var v=this.value,l=v.length,str=String(v[--l]),zeros="0000000",digit;while(--l>=0){digit=String(v[l]);str+=zeros.slice(digit.length)+digit}var sign=this.sign?"-":"";return sign+str};SmallInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!=10)return toBase(this,radix);return String(this.value)};BigInteger.prototype.toJSON=SmallInteger.prototype.toJSON=function(){return this.toString()};BigInteger.prototype.valueOf=function(){return+this.toString()};BigInteger.prototype.toJSNumber=BigInteger.prototype.valueOf;SmallInteger.prototype.valueOf=function(){return this.value};SmallInteger.prototype.toJSNumber=SmallInteger.prototype.valueOf;function parseStringValue(v){if(isPrecise(+v)){var x=+v;if(x===truncate(x))return new SmallInteger(x);throw"Invalid integer: "+v}var sign=v[0]==="-";if(sign)v=v.slice(1);var split=v.split(/e/i);if(split.length>2)throw new Error("Invalid integer: "+split.join("e"));if(split.length===2){var exp=split[1];if(exp[0]==="+")exp=exp.slice(1);exp=+exp;if(exp!==truncate(exp)||!isPrecise(exp))throw new Error("Invalid integer: "+exp+" is not a valid exponent.");var text=split[0];var decimalPlace=text.indexOf(".");if(decimalPlace>=0){exp-=text.length-decimalPlace-1;text=text.slice(0,decimalPlace)+text.slice(decimalPlace+1)}if(exp<0)throw new Error("Cannot include negative exponent part for integers");text+=new Array(exp+1).join("0");v=text}var isValid=/^([0-9][0-9]*)$/.test(v);if(!isValid)throw new Error("Invalid integer: "+v);var r=[],max=v.length,l=LOG_BASE,min=max-l;while(max>0){r.push(+v.slice(min,max));min-=l;if(min<0)min=0;max-=l}trim(r);return new BigInteger(r,sign)}function parseNumberValue(v){if(isPrecise(v)){if(v!==truncate(v))throw new Error(v+" is not an integer.");return new SmallInteger(v)}return parseStringValue(v.toString())}function parseValue(v){if(typeof v==="number"){return parseNumberValue(v)}if(typeof v==="string"){return parseStringValue(v)}return v}for(var i=0;i<1e3;i++){Integer[i]=new SmallInteger(i);if(i>0)Integer[-i]=new SmallInteger(-i)}Integer.one=Integer[1];Integer.zero=Integer[0];Integer.minusOne=Integer[-1];Integer.max=max;Integer.min=min;Integer.gcd=gcd;Integer.lcm=lcm;Integer.isInstance=function(x){return x instanceof BigInteger||x instanceof SmallInteger};Integer.randBetween=randBetween;Integer.fromArray=function(digits,base,isNegative){return parseBaseFromArray(digits.map(parseValue),parseValue(base||10),isNegative)};return Integer}();if(typeof module!=="undefined"&&module.hasOwnProperty("exports")){module.exports=bigInt}if(typeof define==="function"&&define.amd){define("big-integer",[],function(){return bigInt})}; bigInt \ No newline at end of file diff --git a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs deleted file mode 100644 index 438571a5b..000000000 --- a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs +++ /dev/null @@ -1,936 +0,0 @@ -//! Type bindings for js tracing inspector - -use crate::tracing::{ - js::{ - builtins::{ - address_to_buf, bytes_to_address, bytes_to_hash, from_buf, to_bigint, to_buf, - to_buf_value, - }, - JsDbRequest, - }, - types::CallKind, -}; -use alloy_primitives::{Address, Bytes, B256, U256}; -use boa_engine::{ - native_function::NativeFunction, - object::{builtins::JsArrayBuffer, FunctionObjectBuilder}, - Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsValue, -}; -use boa_gc::{empty_trace, Finalize, Trace}; -use revm::{ - interpreter::{ - opcode::{PUSH0, PUSH32}, - OpCode, SharedMemory, Stack, - }, - primitives::{AccountInfo, State, KECCAK_EMPTY}, -}; -use std::{cell::RefCell, rc::Rc, sync::mpsc::channel}; -use tokio::sync::mpsc; - -/// A macro that creates a native function that returns via [JsValue::from] -macro_rules! js_value_getter { - ($value:ident, $ctx:ident) => { - FunctionObjectBuilder::new( - $ctx, - NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from($value))), - ) - .length(0) - .build() - }; -} - -/// A macro that creates a native function that returns a captured JsValue -macro_rules! js_value_capture_getter { - ($value:ident, $ctx:ident) => { - FunctionObjectBuilder::new( - $ctx, - NativeFunction::from_copy_closure_with_captures( - move |_this, _args, input, _ctx| Ok(JsValue::from(input.clone())), - $value, - ), - ) - .length(0) - .build() - }; -} - -/// A reference to a value that can be garbagae collected, but will not give access to the value if -/// it has been dropped. -/// -/// This is used to allow the JS tracer functions to access values at a certain point during -/// inspection by ref without having to clone them and capture them in the js object. -/// -/// JS tracer functions get access to evm internals via objects or function arguments, for example -/// `function step(log,evm)` where log has an object `stack` that has a function `peek(number)` that -/// returns a value from the stack. -/// -/// These functions could get garbage collected, however the data accessed by the function is -/// supposed to be ephemeral and only valid for the duration of the function call. -/// -/// This type supports garbage collection of (rust) references and prevents access to the value if -/// it has been dropped. -#[derive(Debug, Clone)] -pub(crate) struct GuardedNullableGcRef { - /// The lifetime is a lie to make it possible to use a reference in boa which requires 'static - inner: Rc>>, -} - -impl GuardedNullableGcRef { - /// Creates a garbage collectible reference to the given reference. - /// - /// SAFETY; the caller must ensure that the guard is dropped before the value is dropped. - pub(crate) fn new(val: &Val) -> (Self, RefGuard<'_, Val>) { - let inner = Rc::new(RefCell::new(Some(val))); - let guard = RefGuard { inner: Rc::clone(&inner) }; - - // SAFETY: guard enforces that the value is removed from the refcell before it is dropped - let this = Self { inner: unsafe { std::mem::transmute(inner) } }; - - (this, guard) - } - - /// Executes the given closure with a reference to the inner value if it is still present. - pub(crate) fn with_inner(&self, f: F) -> Option - where - F: FnOnce(&Val) -> R, - { - self.inner.borrow().map(f) - } -} - -impl Finalize for GuardedNullableGcRef {} - -unsafe impl Trace for GuardedNullableGcRef { - empty_trace!(); -} - -/// Guard the inner references, once this value is dropped the inner reference is also removed. -/// -/// This type guarantees that it never outlives the wrapped reference. -#[derive(Debug)] -pub(crate) struct RefGuard<'a, Val> { - inner: Rc>>, -} - -impl<'a, Val> Drop for RefGuard<'a, Val> { - fn drop(&mut self) { - self.inner.borrow_mut().take(); - } -} - -/// The Log object that is passed to the javascript inspector. -#[derive(Debug)] -pub(crate) struct StepLog { - /// Stack before step execution - pub(crate) stack: StackRef, - /// Opcode to be executed - pub(crate) op: OpObj, - /// All allocated memory in a step - pub(crate) memory: MemoryRef, - /// Program counter before step execution - pub(crate) pc: u64, - /// Remaining gas before step execution - pub(crate) gas_remaining: u64, - /// Gas cost of step execution - pub(crate) cost: u64, - /// Call depth - pub(crate) depth: u64, - /// Gas refund counter before step execution - pub(crate) refund: u64, - /// returns information about the error if one occurred, otherwise returns undefined - pub(crate) error: Option, - /// The contract object available to the js inspector - pub(crate) contract: Contract, -} - -impl StepLog { - /// Converts the contract object into a js object - /// - /// Caution: this expects a global property `bigint` to be present. - pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { - let Self { - stack, - op, - memory, - pc, - gas_remaining: gas, - cost, - depth, - refund, - error, - contract, - } = self; - let obj = JsObject::default(); - - // fields - let op = op.into_js_object(context)?; - let memory = memory.into_js_object(context)?; - let stack = stack.into_js_object(context)?; - let contract = contract.into_js_object(context)?; - - obj.set("op", op, false, context)?; - obj.set("memory", memory, false, context)?; - obj.set("stack", stack, false, context)?; - obj.set("contract", contract, false, context)?; - - // methods - let error = - if let Some(error) = error { JsValue::from(error) } else { JsValue::undefined() }; - let get_error = js_value_capture_getter!(error, context); - let get_pc = js_value_getter!(pc, context); - let get_gas = js_value_getter!(gas, context); - let get_cost = js_value_getter!(cost, context); - let get_refund = js_value_getter!(refund, context); - let get_depth = js_value_getter!(depth, context); - - obj.set("getPc", get_pc, false, context)?; - obj.set("getError", get_error, false, context)?; - obj.set("getGas", get_gas, false, context)?; - obj.set("getCost", get_cost, false, context)?; - obj.set("getDepth", get_depth, false, context)?; - obj.set("getRefund", get_refund, false, context)?; - - Ok(obj) - } -} - -/// Represents the memory object -#[derive(Debug, Clone)] -pub(crate) struct MemoryRef(pub(crate) GuardedNullableGcRef); - -impl MemoryRef { - /// Creates a new stack reference - pub(crate) fn new(mem: &SharedMemory) -> (Self, RefGuard<'_, SharedMemory>) { - let (inner, guard) = GuardedNullableGcRef::new(mem); - (MemoryRef(inner), guard) - } - - fn len(&self) -> usize { - self.0.with_inner(|mem| mem.len()).unwrap_or_default() - } - - pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { - let obj = JsObject::default(); - let len = self.len(); - - let length = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, _ctx| { - Ok(JsValue::from(len as u64)) - }), - ) - .length(0) - .build(); - - // slice returns the requested range of memory as a byte slice. - let slice = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, memory, ctx| { - let start = args.get_or_undefined(0).to_number(ctx)?; - let end = args.get_or_undefined(1).to_number(ctx)?; - if end < start || start < 0. || (end as usize) < memory.len() { - return Err(JsError::from_native(JsNativeError::typ().with_message( - format!( - "tracer accessed out of bound memory: offset {start}, end {end}" - ), - ))) - } - let start = start as usize; - let end = end as usize; - let size = end - start; - let slice = memory - .0 - .with_inner(|mem| mem.slice(start, size).to_vec()) - .unwrap_or_default(); - - to_buf_value(slice, ctx) - }, - self.clone(), - ), - ) - .length(2) - .build(); - - let get_uint = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, memory, ctx| { - let offset_f64 = args.get_or_undefined(0).to_number(ctx)?; - let len = memory.len(); - let offset = offset_f64 as usize; - if len < offset+32 || offset_f64 < 0. { - return Err(JsError::from_native( - JsNativeError::typ().with_message(format!("tracer accessed out of bound memory: available {len}, offset {offset}, size 32")) - )); - } - let slice = memory.0.with_inner(|mem| mem.slice(offset, 32).to_vec()).unwrap_or_default(); - to_buf_value(slice, ctx) - }, - self - ), - ) - .length(1) - .build(); - - obj.set("slice", slice, false, context)?; - obj.set("getUint", get_uint, false, context)?; - obj.set("length", length, false, context)?; - Ok(obj) - } -} - -impl Finalize for MemoryRef {} - -unsafe impl Trace for MemoryRef { - empty_trace!(); -} - -/// Represents the state object -#[derive(Debug, Clone)] -pub(crate) struct StateRef(pub(crate) GuardedNullableGcRef); - -impl StateRef { - /// Creates a new stack reference - pub(crate) fn new(state: &State) -> (Self, RefGuard<'_, State>) { - let (inner, guard) = GuardedNullableGcRef::new(state); - (StateRef(inner), guard) - } - - fn get_account(&self, address: &Address) -> Option { - self.0.with_inner(|state| state.get(address).map(|acc| acc.info.clone()))? - } -} - -impl Finalize for StateRef {} - -unsafe impl Trace for StateRef { - empty_trace!(); -} - -/// Represents the opcode object -#[derive(Debug)] -pub(crate) struct OpObj(pub(crate) u8); - -impl OpObj { - pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { - let obj = JsObject::default(); - let value = self.0; - let is_push = (PUSH0..=PUSH32).contains(&value); - - let to_number = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from(value))), - ) - .length(0) - .build(); - - let is_push = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from(is_push))), - ) - .length(0) - .build(); - - let to_string = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, _ctx| { - let op = OpCode::new(value) - .or_else(|| { - // if the opcode is invalid, we'll use the invalid opcode to represent it - // because this is invoked before the opcode is - // executed, the evm will eventually return a `Halt` - // with invalid/unknown opcode as result - let invalid_opcode = 0xfe; - OpCode::new(invalid_opcode) - }) - .expect("is valid opcode;"); - let s = op.to_string(); - Ok(JsValue::from(s)) - }), - ) - .length(0) - .build(); - - obj.set("toNumber", to_number, false, context)?; - obj.set("toString", to_string, false, context)?; - obj.set("isPush", is_push, false, context)?; - Ok(obj) - } -} - -impl From for OpObj { - fn from(op: u8) -> Self { - Self(op) - } -} - -/// Represents the stack object -#[derive(Debug)] -pub(crate) struct StackRef(pub(crate) GuardedNullableGcRef); - -impl StackRef { - /// Creates a new stack reference - pub(crate) fn new(stack: &Stack) -> (Self, RefGuard<'_, Stack>) { - let (inner, guard) = GuardedNullableGcRef::new(stack); - (StackRef(inner), guard) - } - - fn peek(&self, idx: usize, ctx: &mut Context<'_>) -> JsResult { - self.0 - .with_inner(|stack| { - let value = stack.peek(idx).map_err(|_| { - JsError::from_native(JsNativeError::typ().with_message(format!( - "tracer accessed out of bound stack: size {}, index {}", - stack.len(), - idx - ))) - })?; - to_bigint(value, ctx) - }) - .ok_or_else(|| { - JsError::from_native(JsNativeError::typ().with_message(format!( - "tracer accessed out of bound stack: size 0, index {}", - idx - ))) - })? - } - - pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { - let obj = JsObject::default(); - let len = self.0.with_inner(|stack| stack.len()).unwrap_or_default(); - let length = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from(len))), - ) - .length(0) - .build(); - - // peek returns the nth-from-the-top element of the stack. - let peek = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, stack, ctx| { - let idx_f64 = args.get_or_undefined(0).to_number(ctx)?; - let idx = idx_f64 as usize; - if len <= idx || idx_f64 < 0. { - return Err(JsError::from_native(JsNativeError::typ().with_message( - format!( - "tracer accessed out of bound stack: size {len}, index {idx_f64}" - ), - ))) - } - stack.peek(idx, ctx) - }, - self, - ), - ) - .length(1) - .build(); - - obj.set("length", length, false, context)?; - obj.set("peek", peek, false, context)?; - Ok(obj) - } -} - -impl Finalize for StackRef {} - -unsafe impl Trace for StackRef { - empty_trace!(); -} - -/// Represents the contract object -#[derive(Debug, Clone, Default)] -pub(crate) struct Contract { - pub(crate) caller: Address, - pub(crate) contract: Address, - pub(crate) value: U256, - pub(crate) input: Bytes, -} - -impl Contract { - /// Converts the contract object into a js object - /// - /// Caution: this expects a global property `bigint` to be present. - pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { - let Contract { caller, contract, value, input } = self; - let obj = JsObject::default(); - - let get_caller = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, ctx| { - to_buf_value(caller.as_slice().to_vec(), ctx) - }), - ) - .length(0) - .build(); - - let get_address = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, ctx| { - to_buf_value(contract.as_slice().to_vec(), ctx) - }), - ) - .length(0) - .build(); - - let get_value = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure(move |_this, _args, ctx| to_bigint(value, ctx)), - ) - .length(0) - .build(); - - let input = to_buf_value(input.to_vec(), context)?; - let get_input = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, _args, input, _ctx| Ok(input.clone()), - input, - ), - ) - .length(0) - .build(); - - obj.set("getCaller", get_caller, false, context)?; - obj.set("getAddress", get_address, false, context)?; - obj.set("getValue", get_value, false, context)?; - obj.set("getInput", get_input, false, context)?; - - Ok(obj) - } -} - -/// Represents the call frame object for exit functions -pub(crate) struct FrameResult { - pub(crate) gas_used: u64, - pub(crate) output: Bytes, - pub(crate) error: Option, -} - -impl FrameResult { - pub(crate) fn into_js_object(self, ctx: &mut Context<'_>) -> JsResult { - let Self { gas_used, output, error } = self; - let obj = JsObject::default(); - - let output = to_buf_value(output.to_vec(), ctx)?; - let get_output = FunctionObjectBuilder::new( - ctx, - NativeFunction::from_copy_closure_with_captures( - move |_this, _args, output, _ctx| Ok(output.clone()), - output, - ), - ) - .length(0) - .build(); - - let error = error.map(JsValue::from).unwrap_or_default(); - let get_error = js_value_capture_getter!(error, ctx); - let get_gas_used = js_value_getter!(gas_used, ctx); - - obj.set("getGasUsed", get_gas_used, false, ctx)?; - obj.set("getOutput", get_output, false, ctx)?; - obj.set("getError", get_error, false, ctx)?; - - Ok(obj) - } -} - -/// Represents the call frame object for enter functions -pub(crate) struct CallFrame { - pub(crate) contract: Contract, - pub(crate) kind: CallKind, - pub(crate) gas: u64, -} - -impl CallFrame { - pub(crate) fn into_js_object(self, ctx: &mut Context<'_>) -> JsResult { - let CallFrame { contract: Contract { caller, contract, value, input }, kind, gas } = self; - let obj = JsObject::default(); - - let get_from = FunctionObjectBuilder::new( - ctx, - NativeFunction::from_copy_closure(move |_this, _args, ctx| { - to_buf_value(caller.as_slice().to_vec(), ctx) - }), - ) - .length(0) - .build(); - - let get_to = FunctionObjectBuilder::new( - ctx, - NativeFunction::from_copy_closure(move |_this, _args, ctx| { - to_buf_value(contract.as_slice().to_vec(), ctx) - }), - ) - .length(0) - .build(); - - let get_value = FunctionObjectBuilder::new( - ctx, - NativeFunction::from_copy_closure(move |_this, _args, ctx| to_bigint(value, ctx)), - ) - .length(0) - .build(); - - let input = to_buf_value(input.to_vec(), ctx)?; - let get_input = FunctionObjectBuilder::new( - ctx, - NativeFunction::from_copy_closure_with_captures( - move |_this, _args, input, _ctx| Ok(input.clone()), - input, - ), - ) - .length(0) - .build(); - - let get_gas = js_value_getter!(gas, ctx); - let ty = kind.to_string(); - let get_type = js_value_capture_getter!(ty, ctx); - - obj.set("getFrom", get_from, false, ctx)?; - obj.set("getTo", get_to, false, ctx)?; - obj.set("getValue", get_value, false, ctx)?; - obj.set("getInput", get_input, false, ctx)?; - obj.set("getGas", get_gas, false, ctx)?; - obj.set("getType", get_type, false, ctx)?; - - Ok(obj) - } -} - -/// The `ctx` object that represents the context in which the transaction is executed. -pub(crate) struct EvmContext { - /// String, one of the two values CALL and CREATE - pub(crate) r#type: String, - /// Sender of the transaction - pub(crate) from: Address, - /// Target of the transaction - pub(crate) to: Option
, - pub(crate) input: Bytes, - /// Gas limit - pub(crate) gas: u64, - /// Number, amount of gas used in executing the transaction (excludes txdata costs) - pub(crate) gas_used: u64, - /// Number, gas price configured in the transaction being executed - pub(crate) gas_price: u64, - /// Number, intrinsic gas for the transaction being executed - pub(crate) intrinsic_gas: u64, - /// big.int Amount to be transferred in wei - pub(crate) value: U256, - /// Number, block number - pub(crate) block: u64, - pub(crate) output: Bytes, - /// Number, block number - pub(crate) time: String, - pub(crate) block_hash: Option, - pub(crate) tx_index: Option, - pub(crate) tx_hash: Option, -} - -impl EvmContext { - pub(crate) fn into_js_object(self, ctx: &mut Context<'_>) -> JsResult { - let Self { - r#type, - from, - to, - input, - gas, - gas_used, - gas_price, - intrinsic_gas, - value, - block, - output, - time, - block_hash, - tx_index, - tx_hash, - } = self; - let obj = JsObject::default(); - - // add properties - - obj.set("type", r#type, false, ctx)?; - obj.set("from", address_to_buf(from, ctx)?, false, ctx)?; - if let Some(to) = to { - obj.set("to", address_to_buf(to, ctx)?, false, ctx)?; - } else { - obj.set("to", JsValue::null(), false, ctx)?; - } - - obj.set("input", to_buf(input.to_vec(), ctx)?, false, ctx)?; - obj.set("gas", gas, false, ctx)?; - obj.set("gasUsed", gas_used, false, ctx)?; - obj.set("gasPrice", gas_price, false, ctx)?; - obj.set("intrinsicGas", intrinsic_gas, false, ctx)?; - obj.set("value", to_bigint(value, ctx)?, false, ctx)?; - obj.set("block", block, false, ctx)?; - obj.set("output", to_buf(output.to_vec(), ctx)?, false, ctx)?; - obj.set("time", time, false, ctx)?; - if let Some(block_hash) = block_hash { - obj.set("blockHash", to_buf(block_hash.as_slice().to_vec(), ctx)?, false, ctx)?; - } - if let Some(tx_index) = tx_index { - obj.set("txIndex", tx_index as u64, false, ctx)?; - } - if let Some(tx_hash) = tx_hash { - obj.set("txHash", to_buf(tx_hash.as_slice().to_vec(), ctx)?, false, ctx)?; - } - - Ok(obj) - } -} - -/// DB is the object that allows the js inspector to interact with the database. -#[derive(Debug, Clone)] -pub(crate) struct EvmDbRef { - state: StateRef, - to_db: mpsc::Sender, -} - -impl EvmDbRef { - /// Creates a new DB reference - pub(crate) fn new( - state: &State, - to_db: mpsc::Sender, - ) -> (Self, RefGuard<'_, State>) { - let (state, guard) = StateRef::new(state); - let this = Self { state, to_db }; - (this, guard) - } - - fn read_basic(&self, address: JsValue, ctx: &mut Context<'_>) -> JsResult> { - let buf = from_buf(address, ctx)?; - let address = bytes_to_address(buf); - if let acc @ Some(_) = self.state.get_account(&address) { - return Ok(acc) - } - let (tx, rx) = channel(); - if self.to_db.try_send(JsDbRequest::Basic { address, resp: tx }).is_err() { - return Err(JsError::from_native( - JsNativeError::error() - .with_message(format!("Failed to read address {address:?} from database",)), - )) - } - - match rx.recv() { - Ok(Ok(maybe_acc)) => Ok(maybe_acc), - _ => Err(JsError::from_native( - JsNativeError::error() - .with_message(format!("Failed to read address {address:?} from database",)), - )), - } - } - - fn read_code(&self, address: JsValue, ctx: &mut Context<'_>) -> JsResult { - let acc = self.read_basic(address, ctx)?; - let code_hash = acc.map(|acc| acc.code_hash).unwrap_or(KECCAK_EMPTY); - if code_hash == KECCAK_EMPTY { - return JsArrayBuffer::new(0, ctx) - } - - let (tx, rx) = channel(); - if self.to_db.try_send(JsDbRequest::Code { code_hash, resp: tx }).is_err() { - return Err(JsError::from_native( - JsNativeError::error() - .with_message(format!("Failed to read code hash {code_hash:?} from database",)), - )) - } - - let code = match rx.recv() { - Ok(Ok(code)) => code, - _ => { - return Err(JsError::from_native(JsNativeError::error().with_message(format!( - "Failed to read code hash {code_hash:?} from database", - )))) - } - }; - - to_buf(code.to_vec(), ctx) - } - - fn read_state( - &self, - address: JsValue, - slot: JsValue, - ctx: &mut Context<'_>, - ) -> JsResult { - let buf = from_buf(address, ctx)?; - let address = bytes_to_address(buf); - - let buf = from_buf(slot, ctx)?; - let slot = bytes_to_hash(buf); - - let (tx, rx) = channel(); - if self - .to_db - .try_send(JsDbRequest::StorageAt { address, index: slot.into(), resp: tx }) - .is_err() - { - return Err(JsError::from_native(JsNativeError::error().with_message(format!( - "Failed to read state for {address:?} at {slot:?} from database", - )))) - } - - let value = match rx.recv() { - Ok(Ok(value)) => value, - _ => { - return Err(JsError::from_native(JsNativeError::error().with_message(format!( - "Failed to read state for {address:?} at {slot:?} from database", - )))) - } - }; - let value: B256 = value.into(); - to_buf(value.as_slice().to_vec(), ctx) - } - - pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { - let obj = JsObject::default(); - let exists = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, db, ctx| { - let val = args.get_or_undefined(0).clone(); - let acc = db.read_basic(val, ctx)?; - let exists = acc.is_some(); - Ok(JsValue::from(exists)) - }, - self.clone(), - ), - ) - .length(1) - .build(); - - let get_balance = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, db, ctx| { - let val = args.get_or_undefined(0).clone(); - let acc = db.read_basic(val, ctx)?; - let balance = acc.map(|acc| acc.balance).unwrap_or_default(); - to_bigint(balance, ctx) - }, - self.clone(), - ), - ) - .length(1) - .build(); - - let get_nonce = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, db, ctx| { - let val = args.get_or_undefined(0).clone(); - let acc = db.read_basic(val, ctx)?; - let nonce = acc.map(|acc| acc.nonce).unwrap_or_default(); - Ok(JsValue::from(nonce)) - }, - self.clone(), - ), - ) - .length(1) - .build(); - - let get_code = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, db, ctx| { - let val = args.get_or_undefined(0).clone(); - Ok(db.read_code(val, ctx)?.into()) - }, - self.clone(), - ), - ) - .length(1) - .build(); - - let get_state = FunctionObjectBuilder::new( - context, - NativeFunction::from_copy_closure_with_captures( - move |_this, args, db, ctx| { - let addr = args.get_or_undefined(0).clone(); - let slot = args.get_or_undefined(1).clone(); - Ok(db.read_state(addr, slot, ctx)?.into()) - }, - self, - ), - ) - .length(2) - .build(); - - obj.set("getBalance", get_balance, false, context)?; - obj.set("getNonce", get_nonce, false, context)?; - obj.set("getCode", get_code, false, context)?; - obj.set("getState", get_state, false, context)?; - obj.set("exists", exists, false, context)?; - Ok(obj) - } -} - -impl Finalize for EvmDbRef {} - -unsafe impl Trace for EvmDbRef { - empty_trace!(); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tracing::js::builtins::BIG_INT_JS; - use boa_engine::{object::builtins::JsArrayBuffer, property::Attribute, Source}; - - #[test] - fn test_contract() { - let mut ctx = Context::default(); - let contract = Contract { - caller: Address::ZERO, - contract: Address::ZERO, - value: U256::from(1337u64), - input: vec![0x01, 0x02, 0x03].into(), - }; - let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS)).unwrap(); - ctx.register_global_property("bigint", big_int, Attribute::all()).unwrap(); - - let obj = contract.clone().into_js_object(&mut ctx).unwrap(); - let s = "({ - call: function(contract) { return contract.getCaller(); }, - value: function(contract) { return contract.getValue(); }, - input: function(contract) { return contract.getInput(); } - })"; - - let eval_obj = ctx.eval(Source::from_bytes(s)).unwrap(); - - let call = eval_obj.as_object().unwrap().get("call", &mut ctx).unwrap(); - let res = call - .as_callable() - .unwrap() - .call(&JsValue::undefined(), &[obj.clone().into()], &mut ctx) - .unwrap(); - assert!(res.is_object()); - assert!(res.as_object().unwrap().is_array_buffer()); - - let call = eval_obj.as_object().unwrap().get("value", &mut ctx).unwrap(); - let res = call - .as_callable() - .unwrap() - .call(&JsValue::undefined(), &[obj.clone().into()], &mut ctx) - .unwrap(); - assert_eq!( - res.to_string(&mut ctx).unwrap().to_std_string().unwrap(), - contract.value.to_string() - ); - - let call = eval_obj.as_object().unwrap().get("input", &mut ctx).unwrap(); - let res = call - .as_callable() - .unwrap() - .call(&JsValue::undefined(), &[obj.into()], &mut ctx) - .unwrap(); - - let buffer = JsArrayBuffer::from_object(res.as_object().unwrap().clone()).unwrap(); - let input = buffer.take().unwrap(); - assert_eq!(input, contract.input); - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/js/builtins.rs b/crates/revm/revm-inspectors/src/tracing/js/builtins.rs deleted file mode 100644 index 91a7a2672..000000000 --- a/crates/revm/revm-inspectors/src/tracing/js/builtins.rs +++ /dev/null @@ -1,258 +0,0 @@ -//! Builtin functions - -use alloy_primitives::{hex, Address, B256, U256}; -use boa_engine::{ - object::builtins::{JsArray, JsArrayBuffer}, - property::Attribute, - Context, JsArgs, JsError, JsNativeError, JsResult, JsString, JsValue, NativeFunction, Source, -}; -use boa_gc::{empty_trace, Finalize, Trace}; -use std::collections::HashSet; - -/// bigIntegerJS is the minified version of . -pub(crate) const BIG_INT_JS: &str = include_str!("bigint.js"); - -/// Registers all the builtin functions and global bigint property -/// -/// Note: this does not register the `isPrecompiled` builtin, as this requires the precompile -/// addresses, see [PrecompileList::register_callable]. -pub(crate) fn register_builtins(ctx: &mut Context<'_>) -> JsResult<()> { - let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS.as_bytes()))?; - ctx.register_global_property("bigint", big_int, Attribute::all())?; - ctx.register_global_builtin_callable("toHex", 1, NativeFunction::from_fn_ptr(to_hex))?; - ctx.register_global_callable("toWord", 1, NativeFunction::from_fn_ptr(to_word))?; - ctx.register_global_callable("toAddress", 1, NativeFunction::from_fn_ptr(to_address))?; - ctx.register_global_callable("toContract", 2, NativeFunction::from_fn_ptr(to_contract))?; - ctx.register_global_callable("toContract2", 3, NativeFunction::from_fn_ptr(to_contract2))?; - - Ok(()) -} - -/// Converts an array, hex string or Uint8Array to a []byte -pub(crate) fn from_buf(val: JsValue, context: &mut Context<'_>) -> JsResult> { - if let Some(obj) = val.as_object().cloned() { - if obj.is_array_buffer() { - let buf = JsArrayBuffer::from_object(obj)?; - return buf.take() - } else if obj.is_string() { - let js_string = obj.borrow().as_string().unwrap(); - return hex_decode_js_string(js_string) - } else if obj.is_array() { - let array = JsArray::from_object(obj)?; - let len = array.length(context)?; - let mut buf = Vec::with_capacity(len as usize); - for i in 0..len { - let val = array.get(i, context)?; - buf.push(val.to_number(context)? as u8); - } - return Ok(buf) - } - } - - Err(JsError::from_native(JsNativeError::typ().with_message("invalid buffer type"))) -} - -/// Create a new array buffer from the address' bytes. -pub(crate) fn address_to_buf(addr: Address, context: &mut Context<'_>) -> JsResult { - to_buf(addr.0.to_vec(), context) -} - -/// Create a new array buffer from byte block. -pub(crate) fn to_buf(bytes: Vec, context: &mut Context<'_>) -> JsResult { - JsArrayBuffer::from_byte_block(bytes, context) -} - -/// Create a new array buffer object from byte block. -pub(crate) fn to_buf_value(bytes: Vec, context: &mut Context<'_>) -> JsResult { - Ok(to_buf(bytes, context)?.into()) -} - -/// Converts a buffer type to an address. -/// -/// If the buffer is larger than the address size, it will be cropped from the left -pub(crate) fn bytes_to_address(buf: Vec) -> Address { - let mut address = Address::default(); - let mut buf = &buf[..]; - let address_len = address.0.len(); - if buf.len() > address_len { - // crop from left - buf = &buf[buf.len() - address.0.len()..]; - } - let address_slice = &mut address.0[address_len - buf.len()..]; - address_slice.copy_from_slice(buf); - address -} - -/// Converts a buffer type to a hash. -/// -/// If the buffer is larger than the hash size, it will be cropped from the left -pub(crate) fn bytes_to_hash(buf: Vec) -> B256 { - let mut hash = B256::default(); - let mut buf = &buf[..]; - let hash_len = hash.0.len(); - if buf.len() > hash_len { - // crop from left - buf = &buf[buf.len() - hash.0.len()..]; - } - let hash_slice = &mut hash.0[hash_len - buf.len()..]; - hash_slice.copy_from_slice(buf); - hash -} - -/// Converts a U256 to a bigint using the global bigint property -pub(crate) fn to_bigint(value: U256, ctx: &mut Context<'_>) -> JsResult { - let bigint = ctx.global_object().get("bigint", ctx)?; - if !bigint.is_callable() { - return Ok(JsValue::undefined()) - } - bigint.as_callable().unwrap().call( - &JsValue::undefined(), - &[JsValue::from(value.to_string())], - ctx, - ) -} -/// Takes three arguments: a JavaScript value that represents the sender's address, a string salt -/// value, and the initcode for the contract. Compute the address of a contract created by the -/// sender with the given salt and code hash, then converts the resulting address back into a byte -/// buffer for output. -pub(crate) fn to_contract2( - _: &JsValue, - args: &[JsValue], - ctx: &mut Context<'_>, -) -> JsResult { - // Extract the sender's address, salt and initcode from the arguments - let from = args.get_or_undefined(0).clone(); - let salt = match args.get_or_undefined(1).to_string(ctx) { - Ok(js_string) => { - let buf = hex_decode_js_string(js_string)?; - bytes_to_hash(buf) - } - Err(_) => { - return Err(JsError::from_native(JsNativeError::typ().with_message("invalid salt type"))) - } - }; - - let initcode = args.get_or_undefined(2).clone(); - - // Convert the sender's address to a byte buffer and then to an Address - let buf = from_buf(from, ctx)?; - let addr = bytes_to_address(buf); - - // Convert the initcode to a byte buffer - let code_buf = from_buf(initcode, ctx)?; - - // Compute the contract address - let contract_addr = addr.create2_from_code(salt, code_buf); - - // Convert the contract address to a byte buffer and return it as an ArrayBuffer - to_buf_value(contract_addr.0.to_vec(), ctx) -} - -/// Converts the sender's address to a byte buffer -pub(crate) fn to_contract( - _: &JsValue, - args: &[JsValue], - ctx: &mut Context<'_>, -) -> JsResult { - // Extract the sender's address and nonce from the arguments - let from = args.get_or_undefined(0).clone(); - let nonce = args.get_or_undefined(1).to_number(ctx)? as u64; - - // Convert the sender's address to a byte buffer and then to an Address - let buf = from_buf(from, ctx)?; - let addr = bytes_to_address(buf); - - // Compute the contract address - let contract_addr = addr.create(nonce); - - // Convert the contract address to a byte buffer and return it as an ArrayBuffer - to_buf_value(contract_addr.0.to_vec(), ctx) -} - -/// Converts a buffer type to an address -pub(crate) fn to_address( - _: &JsValue, - args: &[JsValue], - ctx: &mut Context<'_>, -) -> JsResult { - let val = args.get_or_undefined(0).clone(); - let buf = from_buf(val, ctx)?; - let address = bytes_to_address(buf); - to_buf_value(address.0.to_vec(), ctx) -} - -/// Converts a buffer type to a word -pub(crate) fn to_word(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult { - let val = args.get_or_undefined(0).clone(); - let buf = from_buf(val, ctx)?; - let hash = bytes_to_hash(buf); - to_buf_value(hash.0.to_vec(), ctx) -} - -/// Converts a buffer type to a hex string -pub(crate) fn to_hex(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult { - let val = args.get_or_undefined(0).clone(); - let buf = from_buf(val, ctx)?; - Ok(JsValue::from(hex::encode(buf))) -} - -/// Decodes a hex decoded js-string -fn hex_decode_js_string(js_string: JsString) -> JsResult> { - match js_string.to_std_string() { - Ok(s) => match hex::decode(s.as_str()) { - Ok(data) => Ok(data), - Err(err) => Err(JsError::from_native( - JsNativeError::error().with_message(format!("invalid hex string {s}: {err}",)), - )), - }, - Err(err) => Err(JsError::from_native( - JsNativeError::error() - .with_message(format!("invalid utf8 string {js_string:?}: {err}",)), - )), - } -} - -/// A container for all precompile addresses used for the `isPrecompiled` global callable. -#[derive(Debug, Clone)] -pub(crate) struct PrecompileList(pub(crate) HashSet
); - -impl PrecompileList { - /// Registers the global callable `isPrecompiled` - pub(crate) fn register_callable(self, ctx: &mut Context<'_>) -> JsResult<()> { - let is_precompiled = NativeFunction::from_copy_closure_with_captures( - move |_this, args, precompiles, ctx| { - let val = args.get_or_undefined(0).clone(); - let buf = from_buf(val, ctx)?; - let addr = bytes_to_address(buf); - Ok(precompiles.0.contains(&addr).into()) - }, - self, - ); - - ctx.register_global_callable("isPrecompiled", 1, is_precompiled)?; - - Ok(()) - } -} - -impl Finalize for PrecompileList {} - -unsafe impl Trace for PrecompileList { - empty_trace!(); -} - -#[cfg(test)] -mod tests { - use super::*; - use boa_engine::Source; - - #[test] - fn test_install_bigint() { - let mut ctx = Context::default(); - let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS.as_bytes())).unwrap(); - let value = JsValue::from(100); - let result = - big_int.as_callable().unwrap().call(&JsValue::undefined(), &[value], &mut ctx).unwrap(); - assert_eq!(result.to_string(&mut ctx).unwrap().to_std_string().unwrap(), "100"); - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/js/mod.rs b/crates/revm/revm-inspectors/src/tracing/js/mod.rs deleted file mode 100644 index f6982b6f6..000000000 --- a/crates/revm/revm-inspectors/src/tracing/js/mod.rs +++ /dev/null @@ -1,583 +0,0 @@ -//! Javascript inspector - -use crate::tracing::{ - js::{ - bindings::{ - CallFrame, Contract, EvmContext, EvmDbRef, FrameResult, MemoryRef, StackRef, StepLog, - }, - builtins::{register_builtins, PrecompileList}, - }, - types::CallKind, -}; -use alloy_primitives::{Address, Bytes, B256, U256}; -use boa_engine::{Context, JsError, JsObject, JsResult, JsValue, Source}; -use revm::{ - interpreter::{ - return_revert, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, - }, - precompile::Precompiles, - primitives::{AccountInfo, Env, ExecutionResult, Output, ResultAndState, TransactTo}, - Database, EVMData, Inspector, -}; -use tokio::sync::mpsc; - -pub(crate) mod bindings; -pub(crate) mod builtins; - -/// A javascript inspector that will delegate inspector functions to javascript functions -/// -/// See also -#[derive(Debug)] -pub struct JsInspector { - ctx: Context<'static>, - /// The javascript config provided to the inspector. - _config: JsValue, - /// The evaluated object that contains the inspector functions. - obj: JsObject, - - /// The javascript function that will be called when the result is requested. - result_fn: JsObject, - fault_fn: JsObject, - - // EVM inspector hook functions - /// Invoked when the EVM enters a new call that is _NOT_ the top level call. - /// - /// Corresponds to [Inspector::call] and [Inspector::create_end] but is also invoked on - /// [Inspector::selfdestruct]. - enter_fn: Option, - /// Invoked when the EVM exits a call that is _NOT_ the top level call. - /// - /// Corresponds to [Inspector::call_end] and [Inspector::create_end] but also invoked after - /// selfdestruct. - exit_fn: Option, - /// Executed before each instruction is executed. - step_fn: Option, - /// Keeps track of the current call stack. - call_stack: Vec, - /// sender half of a channel to communicate with the database service. - to_db_service: mpsc::Sender, - /// Marker to track whether the precompiles have been registered. - precompiles_registered: bool, -} - -impl JsInspector { - /// Creates a new inspector from a javascript code snipped that evaluates to an object with the - /// expected fields and a config object. - /// - /// The object must have the following fields: - /// - `result`: a function that will be called when the result is requested. - /// - `fault`: a function that will be called when the transaction fails. - /// - /// Optional functions are invoked during inspection: - /// - `setup`: a function that will be called before the inspection starts. - /// - `enter`: a function that will be called when the execution enters a new call. - /// - `exit`: a function that will be called when the execution exits a call. - /// - `step`: a function that will be called when the execution steps to the next instruction. - /// - /// This also accepts a sender half of a channel to communicate with the database service so the - /// DB can be queried from inside the inspector. - pub fn new( - code: String, - config: serde_json::Value, - to_db_service: mpsc::Sender, - ) -> Result { - // Instantiate the execution context - let mut ctx = Context::default(); - register_builtins(&mut ctx)?; - - // evaluate the code - let code = format!("({})", code); - let obj = - ctx.eval(Source::from_bytes(code.as_bytes())).map_err(JsInspectorError::EvalCode)?; - - let obj = obj.as_object().cloned().ok_or(JsInspectorError::ExpectedJsObject)?; - - // ensure all the fields are callables, if present - - let result_fn = obj - .get("result", &mut ctx)? - .as_object() - .cloned() - .ok_or(JsInspectorError::ResultFunctionMissing)?; - if !result_fn.is_callable() { - return Err(JsInspectorError::ResultFunctionMissing) - } - - let fault_fn = obj - .get("fault", &mut ctx)? - .as_object() - .cloned() - .ok_or(JsInspectorError::FaultFunctionMissing)?; - if !result_fn.is_callable() { - return Err(JsInspectorError::FaultFunctionMissing) - } - - let enter_fn = obj.get("enter", &mut ctx)?.as_object().cloned().filter(|o| o.is_callable()); - let exit_fn = obj.get("exit", &mut ctx)?.as_object().cloned().filter(|o| o.is_callable()); - let step_fn = obj.get("step", &mut ctx)?.as_object().cloned().filter(|o| o.is_callable()); - - let config = - JsValue::from_json(&config, &mut ctx).map_err(JsInspectorError::InvalidJsonConfig)?; - - if let Some(setup_fn) = obj.get("setup", &mut ctx)?.as_object() { - if !setup_fn.is_callable() { - return Err(JsInspectorError::SetupFunctionNotCallable) - } - - // call setup() - setup_fn - .call(&(obj.clone().into()), &[config.clone()], &mut ctx) - .map_err(JsInspectorError::SetupCallFailed)?; - } - - Ok(Self { - ctx, - _config: config, - obj, - result_fn, - fault_fn, - enter_fn, - exit_fn, - step_fn, - call_stack: Default::default(), - to_db_service, - precompiles_registered: false, - }) - } - - /// Calls the result function and returns the result as [serde_json::Value]. - /// - /// Note: This is supposed to be called after the inspection has finished. - pub fn json_result( - &mut self, - res: ResultAndState, - env: &Env, - ) -> Result { - Ok(self.result(res, env)?.to_json(&mut self.ctx)?) - } - - /// Calls the result function and returns the result. - pub fn result(&mut self, res: ResultAndState, env: &Env) -> Result { - let ResultAndState { result, state } = res; - let (db, _db_guard) = EvmDbRef::new(&state, self.to_db_service.clone()); - - let gas_used = result.gas_used(); - let mut to = None; - let mut output_bytes = None; - match result { - ExecutionResult::Success { output, .. } => match output { - Output::Call(out) => { - output_bytes = Some(out); - } - Output::Create(out, addr) => { - to = addr; - output_bytes = Some(out); - } - }, - ExecutionResult::Revert { output, .. } => { - output_bytes = Some(output); - } - ExecutionResult::Halt { .. } => {} - }; - - let ctx = EvmContext { - r#type: match env.tx.transact_to { - TransactTo::Call(target) => { - to = Some(target); - "CALL" - } - TransactTo::Create(_) => "CREATE", - } - .to_string(), - from: env.tx.caller, - to, - input: env.tx.data.clone(), - gas: env.tx.gas_limit, - gas_used, - gas_price: env.tx.gas_price.try_into().unwrap_or(u64::MAX), - value: env.tx.value, - block: env.block.number.try_into().unwrap_or(u64::MAX), - output: output_bytes.unwrap_or_default(), - time: env.block.timestamp.to_string(), - // TODO: fill in the following fields - intrinsic_gas: 0, - block_hash: None, - tx_index: None, - tx_hash: None, - }; - let ctx = ctx.into_js_object(&mut self.ctx)?; - let db = db.into_js_object(&mut self.ctx)?; - Ok(self.result_fn.call( - &(self.obj.clone().into()), - &[ctx.into(), db.into()], - &mut self.ctx, - )?) - } - - fn try_fault(&mut self, step: StepLog, db: EvmDbRef) -> JsResult<()> { - let step = step.into_js_object(&mut self.ctx)?; - let db = db.into_js_object(&mut self.ctx)?; - self.fault_fn.call(&(self.obj.clone().into()), &[step.into(), db.into()], &mut self.ctx)?; - Ok(()) - } - - fn try_step(&mut self, step: StepLog, db: EvmDbRef) -> JsResult<()> { - if let Some(step_fn) = &self.step_fn { - let step = step.into_js_object(&mut self.ctx)?; - let db = db.into_js_object(&mut self.ctx)?; - step_fn.call(&(self.obj.clone().into()), &[step.into(), db.into()], &mut self.ctx)?; - } - Ok(()) - } - - fn try_enter(&mut self, frame: CallFrame) -> JsResult<()> { - if let Some(enter_fn) = &self.enter_fn { - let frame = frame.into_js_object(&mut self.ctx)?; - enter_fn.call(&(self.obj.clone().into()), &[frame.into()], &mut self.ctx)?; - } - Ok(()) - } - - fn try_exit(&mut self, frame: FrameResult) -> JsResult<()> { - if let Some(exit_fn) = &self.exit_fn { - let frame = frame.into_js_object(&mut self.ctx)?; - exit_fn.call(&(self.obj.clone().into()), &[frame.into()], &mut self.ctx)?; - } - Ok(()) - } - - /// Returns the currently active call - /// - /// Panics: if there's no call yet - #[track_caller] - fn active_call(&self) -> &CallStackItem { - self.call_stack.last().expect("call stack is empty") - } - - #[inline] - fn pop_call(&mut self) { - self.call_stack.pop(); - } - - /// Returns true whether the active call is the root call. - #[inline] - fn is_root_call_active(&self) -> bool { - self.call_stack.len() == 1 - } - - /// Returns true if there's an enter function and the active call is not the root call. - #[inline] - fn can_call_enter(&self) -> bool { - self.enter_fn.is_some() && !self.is_root_call_active() - } - - /// Returns true if there's an exit function and the active call is not the root call. - #[inline] - fn can_call_exit(&mut self) -> bool { - self.enter_fn.is_some() && !self.is_root_call_active() - } - - /// Pushes a new call to the stack - fn push_call( - &mut self, - address: Address, - data: Bytes, - value: U256, - kind: CallKind, - caller: Address, - gas_limit: u64, - ) -> &CallStackItem { - let call = CallStackItem { - contract: Contract { caller, contract: address, value, input: data }, - kind, - gas_limit, - }; - self.call_stack.push(call); - self.active_call() - } - - /// Registers the precompiles in the JS context - fn register_precompiles(&mut self, precompiles: &Precompiles) { - if !self.precompiles_registered { - return - } - let precompiles = PrecompileList(precompiles.addresses().into_iter().copied().collect()); - - let _ = precompiles.register_callable(&mut self.ctx); - - self.precompiles_registered = true - } -} - -impl Inspector for JsInspector -where - DB: Database, -{ - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - if self.step_fn.is_none() { - return - } - - let (db, _db_guard) = - EvmDbRef::new(&data.journaled_state.state, self.to_db_service.clone()); - - let (stack, _stack_guard) = StackRef::new(&interp.stack); - let (memory, _memory_guard) = MemoryRef::new(interp.shared_memory); - let step = StepLog { - stack, - op: interp.current_opcode().into(), - memory, - pc: interp.program_counter() as u64, - gas_remaining: interp.gas.remaining(), - cost: interp.gas.spend(), - depth: data.journaled_state.depth(), - refund: interp.gas.refunded() as u64, - error: None, - contract: self.active_call().contract.clone(), - }; - - if self.try_step(step, db).is_err() { - interp.instruction_result = InstructionResult::Revert; - } - } - - fn log( - &mut self, - _evm_data: &mut EVMData<'_, DB>, - _address: &Address, - _topics: &[B256], - _data: &Bytes, - ) { - } - - fn step_end(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - if self.step_fn.is_none() { - return - } - - if matches!(interp.instruction_result, return_revert!()) { - let (db, _db_guard) = - EvmDbRef::new(&data.journaled_state.state, self.to_db_service.clone()); - - let (stack, _stack_guard) = StackRef::new(&interp.stack); - let (memory, _memory_guard) = MemoryRef::new(interp.shared_memory); - let step = StepLog { - stack, - op: interp.current_opcode().into(), - memory, - pc: interp.program_counter() as u64, - gas_remaining: interp.gas.remaining(), - cost: interp.gas.spend(), - depth: data.journaled_state.depth(), - refund: interp.gas.refunded() as u64, - error: Some(format!("{:?}", interp.instruction_result)), - contract: self.active_call().contract.clone(), - }; - - let _ = self.try_fault(step, db); - } - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - self.register_precompiles(&data.precompiles); - - // determine correct `from` and `to` based on the call scheme - let (from, to) = match inputs.context.scheme { - CallScheme::DelegateCall | CallScheme::CallCode => { - (inputs.context.address, inputs.context.code_address) - } - _ => (inputs.context.caller, inputs.context.address), - }; - - let value = inputs.transfer.value; - self.push_call( - to, - inputs.input.clone(), - value, - inputs.context.scheme.into(), - from, - inputs.gas_limit, - ); - - if self.can_call_enter() { - let call = self.active_call(); - let frame = CallFrame { - contract: call.contract.clone(), - kind: call.kind, - gas: inputs.gas_limit, - }; - if let Err(err) = self.try_enter(frame) { - return (InstructionResult::Revert, Gas::new(0), err.to_string().into()) - } - } - - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn call_end( - &mut self, - _data: &mut EVMData<'_, DB>, - _inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - if self.can_call_exit() { - let frame_result = - FrameResult { gas_used: remaining_gas.spend(), output: out.clone(), error: None }; - if let Err(err) = self.try_exit(frame_result) { - return (InstructionResult::Revert, Gas::new(0), err.to_string().into()) - } - } - - self.pop_call(); - - (ret, remaining_gas, out) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.register_precompiles(&data.precompiles); - - let _ = data.journaled_state.load_account(inputs.caller, data.db); - let nonce = data.journaled_state.account(inputs.caller).info.nonce; - let address = inputs.created_address(nonce); - self.push_call( - address, - inputs.init_code.clone(), - inputs.value, - inputs.scheme.into(), - inputs.caller, - inputs.gas_limit, - ); - - if self.can_call_enter() { - let call = self.active_call(); - let frame = - CallFrame { contract: call.contract.clone(), kind: call.kind, gas: call.gas_limit }; - if let Err(err) = self.try_enter(frame) { - return (InstructionResult::Revert, None, Gas::new(0), err.to_string().into()) - } - } - - (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::default()) - } - - fn create_end( - &mut self, - _data: &mut EVMData<'_, DB>, - _inputs: &CreateInputs, - ret: InstructionResult, - address: Option
, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - if self.can_call_exit() { - let frame_result = - FrameResult { gas_used: remaining_gas.spend(), output: out.clone(), error: None }; - if let Err(err) = self.try_exit(frame_result) { - return (InstructionResult::Revert, None, Gas::new(0), err.to_string().into()) - } - } - - self.pop_call(); - - (ret, address, remaining_gas, out) - } - - fn selfdestruct(&mut self, _contract: Address, _target: Address, _value: U256) { - // This is exempt from the root call constraint, because selfdestruct is treated as a - // new scope that is entered and immediately exited. - if self.enter_fn.is_some() { - let call = self.active_call(); - let frame = - CallFrame { contract: call.contract.clone(), kind: call.kind, gas: call.gas_limit }; - let _ = self.try_enter(frame); - } - - // exit with empty frame result ref - if self.exit_fn.is_some() { - let frame_result = FrameResult { gas_used: 0, output: Bytes::new(), error: None }; - let _ = self.try_exit(frame_result); - } - } -} - -/// Request variants to be sent from the inspector to the database -#[derive(Debug, Clone)] -pub enum JsDbRequest { - /// Bindings for [Database::basic] - Basic { - /// The address of the account to be loaded - address: Address, - /// The response channel - resp: std::sync::mpsc::Sender, String>>, - }, - /// Bindings for [Database::code_by_hash] - Code { - /// The code hash of the code to be loaded - code_hash: B256, - /// The response channel - resp: std::sync::mpsc::Sender>, - }, - /// Bindings for [Database::storage] - StorageAt { - /// The address of the account - address: Address, - /// Index of the storage slot - index: U256, - /// The response channel - resp: std::sync::mpsc::Sender>, - }, -} - -/// Represents an active call -#[derive(Debug)] -struct CallStackItem { - contract: Contract, - kind: CallKind, - gas_limit: u64, -} - -/// Error variants that can occur during JavaScript inspection. -#[derive(Debug, thiserror::Error)] -pub enum JsInspectorError { - /// Error originating from a JavaScript operation. - #[error(transparent)] - JsError(#[from] JsError), - - /// Failure during the evaluation of JavaScript code. - #[error("failed to evaluate JS code: {0}")] - EvalCode(JsError), - - /// The evaluated code is not a JavaScript object. - #[error("the evaluated code is not a JS object")] - ExpectedJsObject, - - /// The trace object must expose a function named `result()`. - #[error("trace object must expose a function result()")] - ResultFunctionMissing, - - /// The trace object must expose a function named `fault()`. - #[error("trace object must expose a function fault()")] - FaultFunctionMissing, - - /// The setup object must be a callable function. - #[error("setup object must be a function")] - SetupFunctionNotCallable, - - /// Failure during the invocation of the `setup()` function. - #[error("failed to call setup(): {0}")] - SetupCallFailed(JsError), - - /// Invalid JSON configuration encountered. - #[error("invalid JSON config: {0}")] - InvalidJsonConfig(JsError), -} diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs deleted file mode 100644 index 8b4f7eb88..000000000 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ /dev/null @@ -1,563 +0,0 @@ -use self::parity::stack_push_count; -use crate::tracing::{ - arena::PushTraceKind, - types::{ - CallKind, CallTraceNode, LogCallOrder, RecordedMemory, StorageChange, StorageChangeReason, - }, - utils::gas_used, -}; -use alloy_primitives::{Address, Bytes, Log, B256, U256}; -pub use arena::CallTraceArena; -use revm::{ - inspectors::GasInspector, - interpreter::{ - opcode, return_ok, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, - Interpreter, OpCode, - }, - primitives::SpecId, - Database, EVMData, Inspector, JournalEntry, -}; -use types::{CallTrace, CallTraceStep}; - -mod arena; -mod builder; -mod config; -mod fourbyte; -mod opcount; -pub mod types; -mod utils; -pub use builder::{ - geth::{self, GethTraceBuilder}, - parity::{self, ParityTraceBuilder}, -}; -pub use config::{StackSnapshotType, TracingInspectorConfig}; -pub use fourbyte::FourByteInspector; -pub use opcount::OpcodeCountInspector; - -#[cfg(feature = "js-tracer")] -pub mod js; - -/// An inspector that collects call traces. -/// -/// This [Inspector] can be hooked into the [EVM](revm::EVM) which then calls the inspector -/// functions, such as [Inspector::call] or [Inspector::call_end]. -/// -/// The [TracingInspector] keeps track of everything by: -/// 1. start tracking steps/calls on [Inspector::step] and [Inspector::call] -/// 2. complete steps/calls on [Inspector::step_end] and [Inspector::call_end] -#[derive(Debug, Clone)] -pub struct TracingInspector { - /// Configures what and how the inspector records traces. - config: TracingInspectorConfig, - /// Records all call traces - traces: CallTraceArena, - /// Tracks active calls - trace_stack: Vec, - /// Tracks active steps - step_stack: Vec, - /// Tracks the return value of the last call - last_call_return_data: Option, - /// The gas inspector used to track remaining gas. - gas_inspector: GasInspector, - /// The spec id of the EVM. - /// - /// This is filled during execution. - spec_id: Option, -} - -// === impl TracingInspector === - -impl TracingInspector { - /// Returns a new instance for the given config - pub fn new(config: TracingInspectorConfig) -> Self { - Self { - config, - traces: Default::default(), - trace_stack: vec![], - step_stack: vec![], - last_call_return_data: None, - gas_inspector: Default::default(), - spec_id: None, - } - } - - /// Returns the config of the inspector. - pub fn config(&self) -> &TracingInspectorConfig { - &self.config - } - - /// Gets a reference to the recorded call traces. - pub fn get_traces(&self) -> &CallTraceArena { - &self.traces - } - - /// Gets a mutable reference to the recorded call traces. - pub fn get_traces_mut(&mut self) -> &mut CallTraceArena { - &mut self.traces - } - - /// Manually the gas used of the root trace. - /// - /// This is useful if the root trace's gasUsed should mirror the actual gas used by the - /// transaction. - /// - /// This allows setting it manually by consuming the execution result's gas for example. - #[inline] - pub fn set_transaction_gas_used(&mut self, gas_used: u64) { - if let Some(node) = self.traces.arena.first_mut() { - node.trace.gas_used = gas_used; - } - } - - /// Convenience function for [ParityTraceBuilder::set_transaction_gas_used] that consumes the - /// type. - #[inline] - pub fn with_transaction_gas_used(mut self, gas_used: u64) -> Self { - self.set_transaction_gas_used(gas_used); - self - } - - /// Consumes the Inspector and returns a [ParityTraceBuilder]. - #[inline] - pub fn into_parity_builder(self) -> ParityTraceBuilder { - ParityTraceBuilder::new(self.traces.arena, self.spec_id, self.config) - } - - /// Consumes the Inspector and returns a [GethTraceBuilder]. - #[inline] - pub fn into_geth_builder(self) -> GethTraceBuilder { - GethTraceBuilder::new(self.traces.arena, self.config) - } - - /// Returns true if we're no longer in the context of the root call. - fn is_deep(&self) -> bool { - // the root call will always be the first entry in the trace stack - !self.trace_stack.is_empty() - } - - /// Returns true if this a call to a precompile contract. - /// - /// Returns true if the `to` address is a precompile contract and the value is zero. - #[inline] - fn is_precompile_call( - &self, - data: &EVMData<'_, DB>, - to: &Address, - value: U256, - ) -> bool { - if data.precompiles.contains(to) { - // only if this is _not_ the root call - return self.is_deep() && value.is_zero() - } - false - } - - /// Returns the currently active call trace. - /// - /// This will be the last call trace pushed to the stack: the call we entered most recently. - #[track_caller] - #[inline] - fn active_trace(&self) -> Option<&CallTraceNode> { - self.trace_stack.last().map(|idx| &self.traces.arena[*idx]) - } - - /// Returns the last trace [CallTrace] index from the stack. - /// - /// This will be the currently active call trace. - /// - /// # Panics - /// - /// If no [CallTrace] was pushed - #[track_caller] - #[inline] - fn last_trace_idx(&self) -> usize { - self.trace_stack.last().copied().expect("can't start step without starting a trace first") - } - - /// _Removes_ the last trace [CallTrace] index from the stack. - /// - /// # Panics - /// - /// If no [CallTrace] was pushed - #[track_caller] - #[inline] - fn pop_trace_idx(&mut self) -> usize { - self.trace_stack.pop().expect("more traces were filled than started") - } - - /// Starts tracking a new trace. - /// - /// Invoked on [Inspector::call]. - #[allow(clippy::too_many_arguments)] - fn start_trace_on_call( - &mut self, - data: &EVMData<'_, DB>, - address: Address, - input_data: Bytes, - value: U256, - kind: CallKind, - caller: Address, - mut gas_limit: u64, - maybe_precompile: Option, - ) { - // This will only be true if the inspector is configured to exclude precompiles and the call - // is to a precompile - let push_kind = if maybe_precompile.unwrap_or(false) { - // We don't want to track precompiles - PushTraceKind::PushOnly - } else { - PushTraceKind::PushAndAttachToParent - }; - - if self.trace_stack.is_empty() { - // this is the root call which should get the original gas limit of the transaction, - // because initialization costs are already subtracted from gas_limit - // For the root call this value should use the transaction's gas limit - // See and - gas_limit = data.env.tx.gas_limit; - - // we set the spec id here because we only need to do this once and this condition is - // hit exactly once - self.spec_id = Some(data.env.cfg.spec_id); - } - - self.trace_stack.push(self.traces.push_trace( - 0, - push_kind, - CallTrace { - depth: data.journaled_state.depth() as usize, - address, - kind, - data: input_data, - value, - status: InstructionResult::Continue, - caller, - maybe_precompile, - gas_limit, - ..Default::default() - }, - )); - } - - /// Fills the current trace with the outcome of a call. - /// - /// Invoked on [Inspector::call_end]. - /// - /// # Panics - /// - /// This expects an existing trace [Self::start_trace_on_call] - fn fill_trace_on_call_end( - &mut self, - data: &EVMData<'_, DB>, - status: InstructionResult, - gas: &Gas, - output: Bytes, - created_address: Option
, - ) { - let trace_idx = self.pop_trace_idx(); - let trace = &mut self.traces.arena[trace_idx].trace; - - if trace_idx == 0 { - // this is the root call which should get the gas used of the transaction - // refunds are applied after execution, which is when the root call ends - trace.gas_used = gas_used(data.env.cfg.spec_id, gas.spend(), gas.refunded() as u64); - } else { - trace.gas_used = gas.spend(); - } - - trace.status = status; - trace.success = matches!(status, return_ok!()); - trace.output = output.clone(); - - self.last_call_return_data = Some(output); - - if let Some(address) = created_address { - // A new contract was created via CREATE - trace.address = address; - } - } - - /// Starts tracking a step - /// - /// Invoked on [Inspector::step] - /// - /// # Panics - /// - /// This expects an existing [CallTrace], in other words, this panics if not within the context - /// of a call. - fn start_step(&mut self, interp: &Interpreter<'_>, data: &EVMData<'_, DB>) { - let trace_idx = self.last_trace_idx(); - let trace = &mut self.traces.arena[trace_idx]; - - self.step_stack.push(StackStep { trace_idx, step_idx: trace.trace.steps.len() }); - - let memory = self - .config - .record_memory_snapshots - .then(|| RecordedMemory::new(interp.shared_memory.context_memory().to_vec())) - .unwrap_or_default(); - let stack = if self.config.record_stack_snapshots.is_full() { - Some(interp.stack.data().clone()) - } else { - None - }; - - let op = OpCode::new(interp.current_opcode()) - .or_else(|| { - // if the opcode is invalid, we'll use the invalid opcode to represent it because - // this is invoked before the opcode is executed, the evm will eventually return a - // `Halt` with invalid/unknown opcode as result - let invalid_opcode = 0xfe; - OpCode::new(invalid_opcode) - }) - .expect("is valid opcode;"); - - trace.trace.steps.push(CallTraceStep { - depth: data.journaled_state.depth(), - pc: interp.program_counter(), - op, - contract: interp.contract.address, - stack, - push_stack: None, - memory_size: memory.len(), - memory, - gas_remaining: self.gas_inspector.gas_remaining(), - gas_refund_counter: interp.gas.refunded() as u64, - - // fields will be populated end of call - gas_cost: 0, - storage_change: None, - status: InstructionResult::Continue, - }); - } - - /// Fills the current trace with the output of a step. - /// - /// Invoked on [Inspector::step_end]. - fn fill_step_on_step_end( - &mut self, - interp: &Interpreter<'_>, - data: &EVMData<'_, DB>, - ) { - let StackStep { trace_idx, step_idx } = - self.step_stack.pop().expect("can't fill step without starting a step first"); - let step = &mut self.traces.arena[trace_idx].trace.steps[step_idx]; - - if self.config.record_stack_snapshots.is_pushes() { - let num_pushed = stack_push_count(step.op); - let start = interp.stack.len() - num_pushed; - step.push_stack = Some(interp.stack.data()[start..].to_vec()); - } - - if self.config.record_memory_snapshots { - // resize memory so opcodes that allocated memory is correctly displayed - if interp.shared_memory.len() > step.memory.len() { - step.memory.resize(interp.shared_memory.len()); - } - } - if self.config.record_state_diff { - let op = step.op.get(); - - let journal_entry = data - .journaled_state - .journal - .last() - // This should always work because revm initializes it as `vec![vec![]]` - // See [JournaledState::new](revm::JournaledState) - .expect("exists; initialized with vec") - .last(); - - step.storage_change = match (op, journal_entry) { - ( - opcode::SLOAD | opcode::SSTORE, - Some(JournalEntry::StorageChange { address, key, had_value }), - ) => { - // SAFETY: (Address,key) exists if part if StorageChange - let value = data.journaled_state.state[address].storage[key].present_value(); - let reason = match op { - opcode::SLOAD => StorageChangeReason::SLOAD, - opcode::SSTORE => StorageChangeReason::SSTORE, - _ => unreachable!(), - }; - let change = StorageChange { key: *key, value, had_value: *had_value, reason }; - Some(change) - } - _ => None, - }; - } - - // The gas cost is the difference between the recorded gas remaining at the start of the - // step the remaining gas here, at the end of the step. - step.gas_cost = step.gas_remaining - self.gas_inspector.gas_remaining(); - - // set the status - step.status = interp.instruction_result; - } -} - -impl Inspector for TracingInspector -where - DB: Database, -{ - fn initialize_interp(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - self.gas_inspector.initialize_interp(interp, data) - } - - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - if self.config.record_steps { - self.gas_inspector.step(interp, data); - self.start_step(interp, data); - } - } - - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { - self.gas_inspector.log(evm_data, address, topics, data); - - let trace_idx = self.last_trace_idx(); - let trace = &mut self.traces.arena[trace_idx]; - - if self.config.record_logs { - trace.ordering.push(LogCallOrder::Log(trace.logs.len())); - trace.logs.push(Log::new_unchecked(topics.to_vec(), data.clone())); - } - } - - fn step_end(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - if self.config.record_steps { - self.gas_inspector.step_end(interp, data); - self.fill_step_on_step_end(interp, data); - } - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - self.gas_inspector.call(data, inputs); - - // determine correct `from` and `to` based on the call scheme - let (from, to) = match inputs.context.scheme { - CallScheme::DelegateCall | CallScheme::CallCode => { - (inputs.context.address, inputs.context.code_address) - } - _ => (inputs.context.caller, inputs.context.address), - }; - - let value = if matches!(inputs.context.scheme, CallScheme::DelegateCall) { - // for delegate calls we need to use the value of the top trace - if let Some(parent) = self.active_trace() { - parent.trace.value - } else { - inputs.transfer.value - } - } else { - inputs.transfer.value - }; - - // if calls to precompiles should be excluded, check whether this is a call to a precompile - let maybe_precompile = - self.config.exclude_precompile_calls.then(|| self.is_precompile_call(data, &to, value)); - - self.start_trace_on_call( - data, - to, - inputs.input.clone(), - value, - inputs.context.scheme.into(), - from, - inputs.gas_limit, - maybe_precompile, - ); - - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CallInputs, - gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - self.gas_inspector.call_end(data, inputs, gas, ret, out.clone()); - - self.fill_trace_on_call_end(data, ret, &gas, out.clone(), None); - - (ret, gas, out) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.gas_inspector.create(data, inputs); - - let _ = data.journaled_state.load_account(inputs.caller, data.db); - let nonce = data.journaled_state.account(inputs.caller).info.nonce; - self.start_trace_on_call( - data, - inputs.created_address(nonce), - inputs.init_code.clone(), - inputs.value, - inputs.scheme.into(), - inputs.caller, - inputs.gas_limit, - Some(false), - ); - - (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::default()) - } - - /// Called when a contract has been created. - /// - /// InstructionResulting anything other than the values passed to this function (`(ret, - /// remaining_gas, address, out)`) will alter the result of the create. - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CreateInputs, - status: InstructionResult, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - self.gas_inspector.create_end(data, inputs, status, address, gas, retdata.clone()); - - // get the code of the created contract - let code = address - .and_then(|address| { - data.journaled_state - .account(address) - .info - .code - .as_ref() - .map(|code| code.bytes()[..code.len()].to_vec()) - }) - .unwrap_or_default(); - - self.fill_trace_on_call_end(data, status, &gas, code.into(), address); - - (status, address, gas, retdata) - } - - fn selfdestruct(&mut self, _contract: Address, target: Address, _value: U256) { - let trace_idx = self.last_trace_idx(); - let trace = &mut self.traces.arena[trace_idx].trace; - trace.selfdestruct_refund_target = Some(target) - } -} - -#[derive(Debug, Clone, Copy)] -struct StackStep { - trace_idx: usize, - step_idx: usize, -} diff --git a/crates/revm/revm-inspectors/src/tracing/opcount.rs b/crates/revm/revm-inspectors/src/tracing/opcount.rs deleted file mode 100644 index 2088f2165..000000000 --- a/crates/revm/revm-inspectors/src/tracing/opcount.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Opcount tracing inspector that simply counts all opcodes. -//! -//! See also - -use revm::{interpreter::Interpreter, Database, EVMData, Inspector}; - -/// An inspector that counts all opcodes. -#[derive(Debug, Clone, Copy, Default)] -pub struct OpcodeCountInspector { - /// opcode counter - count: usize, -} - -impl OpcodeCountInspector { - /// Returns the opcode counter - #[inline] - pub fn count(&self) -> usize { - self.count - } -} - -impl Inspector for OpcodeCountInspector -where - DB: Database, -{ - fn step(&mut self, _interp: &mut Interpreter<'_>, _data: &mut EVMData<'_, DB>) { - self.count += 1; - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/types.rs b/crates/revm/revm-inspectors/src/tracing/types.rs deleted file mode 100644 index 13274e660..000000000 --- a/crates/revm/revm-inspectors/src/tracing/types.rs +++ /dev/null @@ -1,684 +0,0 @@ -//! Types for representing call trace items. - -use crate::tracing::{config::TraceStyle, utils, utils::convert_memory}; -pub use alloy_primitives::Log; -use alloy_primitives::{Address, Bytes, U256, U64}; - -use alloy_rpc_trace_types::{ - geth::{CallFrame, CallLogFrame, GethDefaultTracingOptions, StructLog}, - parity::{ - Action, ActionType, CallAction, CallOutput, CallType, CreateAction, CreateOutput, - SelfdestructAction, TraceOutput, TransactionTrace, - }, -}; -use revm::interpreter::{opcode, CallContext, CallScheme, CreateScheme, InstructionResult, OpCode}; -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, VecDeque}; - -/// A trace of a call. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CallTrace { - /// The depth of the call - pub depth: usize, - /// Whether the call was successful - pub success: bool, - /// caller of this call - pub caller: Address, - /// The destination address of the call or the address from the created contract. - /// - /// In other words, this is the callee if the [CallKind::Call] or the address of the created - /// contract if [CallKind::Create]. - pub address: Address, - /// Whether this is a call to a precompile - /// - /// Note: This is an Option because not all tracers make use of this - pub maybe_precompile: Option, - /// Holds the target for the selfdestruct refund target if `status` is - /// [InstructionResult::SelfDestruct] - pub selfdestruct_refund_target: Option
, - /// The kind of call this is - pub kind: CallKind, - /// The value transferred in the call - pub value: U256, - /// The calldata for the call, or the init code for contract creations - pub data: Bytes, - /// The return data of the call if this was not a contract creation, otherwise it is the - /// runtime bytecode of the created contract - pub output: Bytes, - /// The gas cost of the call - pub gas_used: u64, - /// The gas limit of the call - pub gas_limit: u64, - /// The status of the trace's call - pub status: InstructionResult, - /// call context of the runtime - pub call_context: Option>, - /// Opcode-level execution steps - pub steps: Vec, -} - -impl CallTrace { - /// Returns true if the status code is an error or revert, See [InstructionResult::Revert] - #[inline] - pub fn is_error(&self) -> bool { - !self.status.is_ok() - } - - /// Returns true if the status code is a revert - #[inline] - pub fn is_revert(&self) -> bool { - self.status == InstructionResult::Revert - } - - /// Returns the error message if it is an erroneous result. - pub(crate) fn as_error_msg(&self, kind: TraceStyle) -> Option { - // See also - self.is_error().then(|| match self.status { - InstructionResult::Revert => { - if kind.is_parity() { "Reverted" } else { "execution reverted" }.to_string() - } - InstructionResult::OutOfGas | InstructionResult::MemoryOOG => { - if kind.is_parity() { "Out of gas" } else { "out of gas" }.to_string() - } - InstructionResult::OpcodeNotFound => { - if kind.is_parity() { "Bad instruction" } else { "invalid opcode" }.to_string() - } - InstructionResult::StackOverflow => "Out of stack".to_string(), - InstructionResult::InvalidJump => { - if kind.is_parity() { "Bad jump destination" } else { "invalid jump destination" } - .to_string() - } - InstructionResult::PrecompileError => { - if kind.is_parity() { "Built-in failed" } else { "precompiled failed" }.to_string() - } - status => format!("{:?}", status), - }) - } -} - -impl Default for CallTrace { - fn default() -> Self { - Self { - depth: Default::default(), - success: Default::default(), - caller: Default::default(), - address: Default::default(), - selfdestruct_refund_target: None, - kind: Default::default(), - value: Default::default(), - data: Default::default(), - maybe_precompile: None, - output: Default::default(), - gas_used: Default::default(), - gas_limit: Default::default(), - status: InstructionResult::Continue, - call_context: Default::default(), - steps: Default::default(), - } - } -} - -/// A node in the arena -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct CallTraceNode { - /// Parent node index in the arena - pub parent: Option, - /// Children node indexes in the arena - pub children: Vec, - /// This node's index in the arena - pub idx: usize, - /// The call trace - pub trace: CallTrace, - /// Recorded logs, if enabled - pub logs: Vec, - /// Ordering of child calls and logs - pub ordering: Vec, -} - -impl CallTraceNode { - /// Returns the call context's execution address - /// - /// See `Inspector::call` impl of [TracingInspector](crate::tracing::TracingInspector) - pub fn execution_address(&self) -> Address { - if self.trace.kind.is_delegate() { - self.trace.caller - } else { - self.trace.address - } - } - - /// Returns all storage slots touched by this trace and the value this storage. - /// - /// A touched slot is either a slot that was written to or read from. - /// - /// If the slot is accessed more than once, the result only includes the first time it was - /// accessed, in other words in only returns the original value of the slot. - pub fn touched_slots(&self) -> BTreeMap { - let mut touched_slots = BTreeMap::new(); - for change in self.trace.steps.iter().filter_map(|s| s.storage_change.as_ref()) { - match touched_slots.entry(change.key) { - std::collections::btree_map::Entry::Vacant(entry) => { - entry.insert(change.value); - } - std::collections::btree_map::Entry::Occupied(_) => { - // already touched - } - } - } - - touched_slots - } - - /// Pushes all steps onto the stack in reverse order - /// so that the first step is on top of the stack - pub(crate) fn push_steps_on_stack<'a>( - &'a self, - stack: &mut VecDeque>, - ) { - stack.extend(self.call_step_stack().into_iter().rev()); - } - - /// Returns a list of all steps in this trace in the order they were executed - /// - /// If the step is a call, the id of the child trace is set. - pub(crate) fn call_step_stack(&self) -> Vec> { - let mut stack = Vec::with_capacity(self.trace.steps.len()); - let mut child_id = 0; - for step in self.trace.steps.iter() { - let mut item = CallTraceStepStackItem { trace_node: self, step, call_child_id: None }; - - // If the opcode is a call, put the child trace on the stack - if step.is_calllike_op() { - // The opcode of this step is a call but it's possible that this step resulted - // in a revert or out of gas error in which case there's no actual child call executed and recorded: - if let Some(call_id) = self.children.get(child_id).copied() { - item.call_child_id = Some(call_id); - child_id += 1; - } - } - stack.push(item); - } - stack - } - - /// Returns true if this is a call to a precompile - #[inline] - pub(crate) fn is_precompile(&self) -> bool { - self.trace.maybe_precompile.unwrap_or(false) - } - - /// Returns the kind of call the trace belongs to - #[inline] - pub(crate) const fn kind(&self) -> CallKind { - self.trace.kind - } - - /// Returns the status of the call - #[inline] - pub(crate) const fn status(&self) -> InstructionResult { - self.trace.status - } - - /// Returns true if the call was a selfdestruct - #[inline] - pub(crate) fn is_selfdestruct(&self) -> bool { - self.status() == InstructionResult::SelfDestruct - } - - /// Converts this node into a parity `TransactionTrace` - pub(crate) fn parity_transaction_trace(&self, trace_address: Vec) -> TransactionTrace { - let action = self.parity_action(); - let result = if self.trace.is_error() && !self.trace.is_revert() { - // if the trace is a selfdestruct or an error that is not a revert, the result is None - None - } else { - Some(self.parity_trace_output()) - }; - let error = self.trace.as_error_msg(TraceStyle::Parity); - TransactionTrace { action, error, result, trace_address, subtraces: self.children.len() } - } - - /// Returns the `Output` for a parity trace - pub(crate) fn parity_trace_output(&self) -> TraceOutput { - match self.kind() { - CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { - TraceOutput::Call(CallOutput { - gas_used: U64::from(self.trace.gas_used), - output: self.trace.output.clone(), - }) - } - CallKind::Create | CallKind::Create2 => TraceOutput::Create(CreateOutput { - gas_used: U64::from(self.trace.gas_used), - code: self.trace.output.clone(), - address: self.trace.address, - }), - } - } - - /// If the trace is a selfdestruct, returns the `Action` for a parity trace. - pub(crate) fn parity_selfdestruct_action(&self) -> Option { - if self.is_selfdestruct() { - Some(Action::Selfdestruct(SelfdestructAction { - address: self.trace.address, - refund_address: self.trace.selfdestruct_refund_target.unwrap_or_default(), - balance: self.trace.value, - })) - } else { - None - } - } - - /// If the trace is a selfdestruct, returns the `CallFrame` for a geth call trace - pub(crate) fn geth_selfdestruct_call_trace(&self) -> Option { - if self.is_selfdestruct() { - Some(CallFrame { - typ: "SELFDESTRUCT".to_string(), - from: self.trace.caller, - to: self.trace.selfdestruct_refund_target, - value: Some(self.trace.value), - ..Default::default() - }) - } else { - None - } - } - - /// If the trace is a selfdestruct, returns the `TransactionTrace` for a parity trace. - pub(crate) fn parity_selfdestruct_trace( - &self, - trace_address: Vec, - ) -> Option { - let trace = self.parity_selfdestruct_action()?; - Some(TransactionTrace { - action: trace, - error: None, - result: None, - trace_address, - subtraces: 0, - }) - } - - /// Returns the `Action` for a parity trace. - /// - /// Caution: This does not include the selfdestruct action, if the trace is a selfdestruct, - /// since those are handled in addition to the call action. - pub(crate) fn parity_action(&self) -> Action { - match self.kind() { - CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { - Action::Call(CallAction { - from: self.trace.caller, - to: self.trace.address, - value: self.trace.value, - gas: U64::from(self.trace.gas_limit), - input: self.trace.data.clone(), - call_type: self.kind().into(), - }) - } - CallKind::Create | CallKind::Create2 => Action::Create(CreateAction { - from: self.trace.caller, - value: self.trace.value, - gas: U64::from(self.trace.gas_limit), - init: self.trace.data.clone(), - }), - } - } - - /// Converts this call trace into an _empty_ geth [CallFrame] - pub(crate) fn geth_empty_call_frame(&self, include_logs: bool) -> CallFrame { - let mut call_frame = CallFrame { - typ: self.trace.kind.to_string(), - from: self.trace.caller, - to: Some(self.trace.address), - value: Some(self.trace.value), - gas: U256::from(self.trace.gas_limit), - gas_used: U256::from(self.trace.gas_used), - input: self.trace.data.clone(), - output: (!self.trace.output.is_empty()).then(|| self.trace.output.clone()), - error: None, - revert_reason: None, - calls: Default::default(), - logs: Default::default(), - }; - - if self.trace.kind.is_static_call() { - // STATICCALL frames don't have a value - call_frame.value = None; - } - - // we need to populate error and revert reason - if !self.trace.success { - call_frame.revert_reason = utils::maybe_revert_reason(self.trace.output.as_ref()); - - // Note: the call tracer mimics parity's trace transaction and geth maps errors to parity style error messages, - call_frame.error = self.trace.as_error_msg(TraceStyle::Parity); - } - - if include_logs && !self.logs.is_empty() { - call_frame.logs = self - .logs - .iter() - .map(|log| CallLogFrame { - address: Some(self.execution_address()), - topics: Some(log.topics().to_vec()), - data: Some(log.data.clone()), - }) - .collect(); - } - - call_frame - } -} - -/// A unified representation of a call. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum CallKind { - /// Represents a regular call. - #[default] - Call, - /// Represents a static call. - StaticCall, - /// Represents a call code operation. - CallCode, - /// Represents a delegate call. - DelegateCall, - /// Represents a contract creation operation. - Create, - /// Represents a contract creation operation using the CREATE2 opcode. - Create2, -} - -impl CallKind { - /// Returns true if the call is a create - #[inline] - pub fn is_any_create(&self) -> bool { - matches!(self, CallKind::Create | CallKind::Create2) - } - - /// Returns true if the call is a delegate of some sorts - #[inline] - pub fn is_delegate(&self) -> bool { - matches!(self, CallKind::DelegateCall | CallKind::CallCode) - } - - /// Returns true if the call is [CallKind::StaticCall]. - #[inline] - pub fn is_static_call(&self) -> bool { - matches!(self, CallKind::StaticCall) - } -} - -impl std::fmt::Display for CallKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CallKind::Call => { - write!(f, "CALL") - } - CallKind::StaticCall => { - write!(f, "STATICCALL") - } - CallKind::CallCode => { - write!(f, "CALLCODE") - } - CallKind::DelegateCall => { - write!(f, "DELEGATECALL") - } - CallKind::Create => { - write!(f, "CREATE") - } - CallKind::Create2 => { - write!(f, "CREATE2") - } - } - } -} - -impl From for CallKind { - fn from(scheme: CallScheme) -> Self { - match scheme { - CallScheme::Call => CallKind::Call, - CallScheme::StaticCall => CallKind::StaticCall, - CallScheme::CallCode => CallKind::CallCode, - CallScheme::DelegateCall => CallKind::DelegateCall, - } - } -} - -impl From for CallKind { - fn from(create: CreateScheme) -> Self { - match create { - CreateScheme::Create => CallKind::Create, - CreateScheme::Create2 { .. } => CallKind::Create2, - } - } -} - -impl From for ActionType { - fn from(kind: CallKind) -> Self { - match kind { - CallKind::Call | CallKind::StaticCall | CallKind::DelegateCall | CallKind::CallCode => { - ActionType::Call - } - CallKind::Create => ActionType::Create, - CallKind::Create2 => ActionType::Create, - } - } -} - -impl From for CallType { - fn from(ty: CallKind) -> Self { - match ty { - CallKind::Call => CallType::Call, - CallKind::StaticCall => CallType::StaticCall, - CallKind::CallCode => CallType::CallCode, - CallKind::DelegateCall => CallType::DelegateCall, - CallKind::Create => CallType::None, - CallKind::Create2 => CallType::None, - } - } -} - -pub(crate) struct CallTraceStepStackItem<'a> { - /// The trace node that contains this step - pub(crate) trace_node: &'a CallTraceNode, - /// The step that this stack item represents - pub(crate) step: &'a CallTraceStep, - /// The index of the child call in the CallArena if this step's opcode is a call - pub(crate) call_child_id: Option, -} - -/// Ordering enum for calls and logs -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum LogCallOrder { - /// Contains the index of the corresponding log - Log(usize), - /// Contains the index of the corresponding trace node - Call(usize), -} - -/// Represents a tracked call step during execution -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CallTraceStep { - // Fields filled in `step` - /// Call depth - pub depth: u64, - /// Program counter before step execution - pub pc: usize, - /// Opcode to be executed - pub op: OpCode, - /// Current contract address - pub contract: Address, - /// Stack before step execution - pub stack: Option>, - /// The new stack items placed by this step if any - pub push_stack: Option>, - /// All allocated memory in a step - /// - /// This will be empty if memory capture is disabled - pub memory: RecordedMemory, - /// Size of memory at the beginning of the step - pub memory_size: usize, - /// Remaining gas before step execution - pub gas_remaining: u64, - /// Gas refund counter before step execution - pub gas_refund_counter: u64, - // Fields filled in `step_end` - /// Gas cost of step execution - pub gas_cost: u64, - /// Change of the contract state after step execution (effect of the SLOAD/SSTORE instructions) - pub storage_change: Option, - /// Final status of the step - /// - /// This is set after the step was executed. - pub status: InstructionResult, -} - -// === impl CallTraceStep === - -impl CallTraceStep { - /// Converts this step into a geth [StructLog] - /// - /// This sets memory and stack capture based on the `opts` parameter. - pub(crate) fn convert_to_geth_struct_log(&self, opts: &GethDefaultTracingOptions) -> StructLog { - let mut log = StructLog { - depth: self.depth, - error: self.as_error(), - gas: self.gas_remaining, - gas_cost: self.gas_cost, - op: self.op.to_string(), - pc: self.pc as u64, - refund_counter: (self.gas_refund_counter > 0).then_some(self.gas_refund_counter), - // Filled, if not disabled manually - stack: None, - // Filled in `CallTraceArena::geth_trace` as a result of compounding all slot changes - return_data: None, - // Filled via trace object - storage: None, - // Only enabled if `opts.enable_memory` is true - memory: None, - // This is None in the rpc response - memory_size: None, - }; - - if opts.is_stack_enabled() { - log.stack = self.stack.clone(); - } - - if opts.is_memory_enabled() { - log.memory = Some(self.memory.memory_chunks()); - } - - log - } - - /// Returns true if the step is a STOP opcode - #[inline] - pub(crate) fn is_stop(&self) -> bool { - matches!(self.op.get(), opcode::STOP) - } - - /// Returns true if the step is a call operation, any of - /// CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE, CREATE2 - #[inline] - pub(crate) fn is_calllike_op(&self) -> bool { - matches!( - self.op.get(), - opcode::CALL | - opcode::DELEGATECALL | - opcode::STATICCALL | - opcode::CREATE | - opcode::CALLCODE | - opcode::CREATE2 - ) - } - - // Returns true if the status code is an error or revert, See [InstructionResult::Revert] - #[inline] - pub(crate) fn is_error(&self) -> bool { - self.status as u8 >= InstructionResult::Revert as u8 - } - - /// Returns the error message if it is an erroneous result. - #[inline] - pub(crate) fn as_error(&self) -> Option { - self.is_error().then(|| format!("{:?}", self.status)) - } -} - -/// Represents the source of a storage change - e.g., whether it came -/// from an SSTORE or SLOAD instruction. -#[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum StorageChangeReason { - /// SLOAD opcode - SLOAD, - /// SSTORE opcode - SSTORE, -} - -/// Represents a storage change during execution. -/// -/// This maps to evm internals: -/// [JournalEntry::StorageChange](revm::JournalEntry::StorageChange) -/// -/// It is used to track both storage change and warm load of a storage slot. For warm load in regard -/// to EIP-2929 AccessList had_value will be None. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct StorageChange { - /// key of the storage slot - pub key: U256, - /// Current value of the storage slot - pub value: U256, - /// The previous value of the storage slot, if any - pub had_value: Option, - /// How this storage was accessed - pub reason: StorageChangeReason, -} - -/// Represents the memory captured during execution -/// -/// This is a wrapper around the [SharedMemory](revm::interpreter::SharedMemory) context memory. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct RecordedMemory(pub(crate) Vec); - -impl RecordedMemory { - #[inline] - pub(crate) fn new(mem: Vec) -> Self { - Self(mem) - } - - /// Returns the memory as a byte slice - #[inline] - pub fn as_bytes(&self) -> &[u8] { - &self.0 - } - - #[inline] - pub(crate) fn resize(&mut self, size: usize) { - self.0.resize(size, 0); - } - - /// Returns the size of the memory - #[inline] - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns whether the memory is empty - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Converts the memory into 32byte hex chunks - #[inline] - pub fn memory_chunks(&self) -> Vec { - convert_memory(self.as_bytes()) - } -} - -impl AsRef<[u8]> for RecordedMemory { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} diff --git a/crates/revm/revm-inspectors/src/tracing/utils.rs b/crates/revm/revm-inspectors/src/tracing/utils.rs deleted file mode 100644 index d13c8aa1c..000000000 --- a/crates/revm/revm-inspectors/src/tracing/utils.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Util functions for revm related ops - -use alloy_primitives::{hex, Bytes}; -use alloy_sol_types::{ContractError, GenericRevertReason}; -use revm::{ - primitives::{SpecId, KECCAK_EMPTY}, - DatabaseRef, -}; - -/// creates the memory data in 32byte chunks -/// see -#[inline] -pub(crate) fn convert_memory(data: &[u8]) -> Vec { - let mut memory = Vec::with_capacity((data.len() + 31) / 32); - for idx in (0..data.len()).step_by(32) { - let len = std::cmp::min(idx + 32, data.len()); - memory.push(hex::encode(&data[idx..len])); - } - memory -} - -/// Get the gas used, accounting for refunds -#[inline] -pub(crate) fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { - let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 }; - spent - (refunded).min(spent / refund_quotient) -} - -/// Loads the code for the given account from the account itself or the database -/// -/// Returns None if the code hash is the KECCAK_EMPTY hash -#[inline] -pub(crate) fn load_account_code( - db: DB, - db_acc: &revm::primitives::AccountInfo, -) -> Option { - db_acc - .code - .as_ref() - .map(|code| code.original_bytes()) - .or_else(|| { - if db_acc.code_hash == KECCAK_EMPTY { - None - } else { - db.code_by_hash_ref(db_acc.code_hash).ok().map(|code| code.original_bytes()) - } - }) - .map(Into::into) -} - -/// Returns a non empty revert reason if the output is a revert/error. -#[inline] -pub(crate) fn maybe_revert_reason(output: &[u8]) -> Option { - let reason = match GenericRevertReason::decode(output)? { - GenericRevertReason::ContractError(err) => { - match err { - ContractError::Revert(revert) => { - // return the raw revert reason and don't use the revert's display message - revert.reason - } - err => err.to_string(), - } - } - GenericRevertReason::RawString(err) => err, - }; - if reason.is_empty() { - None - } else { - Some(reason) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_sol_types::{GenericContractError, SolInterface}; - - #[test] - fn decode_empty_revert() { - let reason = GenericRevertReason::decode("".as_bytes()).map(|x| x.to_string()); - assert_eq!(reason, Some("".to_string())); - } - - #[test] - fn decode_revert_reason() { - let err = GenericContractError::Revert("my revert".into()); - let encoded = err.abi_encode(); - let reason = maybe_revert_reason(&encoded).unwrap(); - assert_eq!(reason, "my revert"); - } -} diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 8cc805646..6aace89ef 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -23,7 +23,7 @@ pub mod state_change; pub use factory::EvmProcessorFactory; /// reexport for convenience -pub use reth_revm_inspectors::*; +pub use revm_inspectors::*; /// Re-export everything pub use revm::{self, *}; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 89aaa1e0f..10b9fa50c 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -21,7 +21,7 @@ reth-provider = { workspace = true, features = ["test-utils"] } reth-transaction-pool = { workspace = true, features = ["test-utils"] } reth-network-api.workspace = true reth-rpc-engine-api.workspace = true -reth-revm.workspace = true +reth-revm = { workspace = true, features = ["js-tracer"] } reth-tasks.workspace = true reth-consensus-common.workspace = true reth-rpc-types-compat.workspace = true diff --git a/deny.toml b/deny.toml index ac039d148..60abd71df 100644 --- a/deny.toml +++ b/deny.toml @@ -18,9 +18,7 @@ multiple-versions = "warn" wildcards = "allow" highlight = "all" # List of crates to deny -deny = [ - { name = "openssl" }, -] +deny = [{ name = "openssl" }] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -97,7 +95,7 @@ allow-git = [ # TODO: remove, see ./Cargo.toml "https://github.com/bluealloy/revm", "https://github.com/alloy-rs/alloy", - "https://github.com/ethereum/c-kzg-4844", + "https://github.com/paradigmxyz/evm-inspectors", "https://github.com/sigp/discv5", "https://github.com/stevefan1999-personal/rust-igd", ]