diff --git a/Cargo.toml b/Cargo.toml index ab31cc86fcad2..bb56c61c248df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -248,7 +248,7 @@ alloy-network = { version = "1.1.2", default-features = false } alloy-provider = { version = "1.1.2", default-features = false } alloy-pubsub = { version = "1.1.2", default-features = false } alloy-rpc-client = { version = "1.1.2", default-features = false } -alloy-rpc-types = { version = "1.1.2", default-features = true } +alloy-rpc-types = { version = "1.1.2", default-features = true, features = ["trace"] } alloy-rpc-types-beacon = { version = "1.1.2", default-features = true } alloy-rpc-types-eth = { version = "1.1.2", default-features = false } alloy-serde = { version = "1.1.2", default-features = false } diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index b10e5f3a8df8d..72522ea89ce52 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -45,6 +45,7 @@ alloy-provider = { workspace = true, features = [ "ipc", "trace-api", "txpool-api", + "debug-api" ] } alloy-rlp.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "trace"] } diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 38fc00b8f4e91..ab8337a2b6354 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -5,8 +5,11 @@ use alloy_primitives::{ Address, Bytes, U256, map::{AddressSet, HashMap}, }; -use alloy_provider::Provider; -use alloy_rpc_types::BlockTransactions; +use alloy_provider::{Provider, ext::DebugApi}; +use alloy_rpc_types::{ + BlockTransactions, + trace::geth::{GethDebugTracingOptions, PreStateConfig}, +}; use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ @@ -65,6 +68,10 @@ pub struct RunArgs { #[arg(long, default_value_t = false)] disable_labels: bool, + /// Disable the debug trace API. + #[arg(long, default_value_t = false)] + disable_debug_trace_api: bool, + /// Label addresses in the trace. /// /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth @@ -224,9 +231,34 @@ impl RunArgs { env.tx.clone(), executor.spec_id(), ); - + let mut debug_trace_api_succeeded = false; + if !self.disable_debug_trace_api { + trace!(?tx_hash, "attempting to fetch prestate trace"); + + match provider + .debug_trace_transaction( + tx_hash, + GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()), + ) + .await + { + Ok(trace_state) => match trace_state.try_into_pre_state_frame() { + Ok(pre_state_frame) => { + executor.apply_prestate_trace(pre_state_frame.into_pre_state())?; + debug_trace_api_succeeded = true; + trace!("optimistic prestate trace applied successfully"); + } + Err(err) => { + trace!(%err, "failed to parse prestate trace response"); + } + }, + Err(err) => { + trace!(?err, "debug_traceTransaction failed, falling back to block replay"); + } + } + } // Set the state to the moment right before the transaction - if !self.quick { + if !self.quick && !debug_trace_api_succeeded { if !shell::is_json() { sh_println!("Executing previous transactions from the block.")?; } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index c59986a47c015..c0b8ac1c8e8ab 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2923,7 +2923,6 @@ contract LocalProjectScript is Script { .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" -Executing previous transactions from the block. Compiling project to generate artifacts Nothing to compile @@ -2937,7 +2936,6 @@ Nothing to compile .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" -Executing previous transactions from the block. Traces: [..] → new @0x5FbDB2315678afecb367f032d93F642f64180aa3 ├─ emit topic 0: 0xa7263295d3a687d750d1fd377b5df47de69d7db8decc745aaa4bbee44dc1688d @@ -2955,7 +2953,6 @@ Transaction successfully executed. .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" -Executing previous transactions from the block. Compiling project to generate artifacts No files changed, compilation skipped Traces: @@ -3020,7 +3017,6 @@ forgetest_async!(show_state_changes_in_traces, |prj, cmd| { ]) .assert_success() .stdout_eq(str![[r#" -Executing previous transactions from the block. Traces: [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) ├─ storage changes: @@ -4482,7 +4478,6 @@ forgetest_async!(cast_send_with_data, |prj, cmd| { ]) .assert_success() .stdout_eq(str![[r#" -Executing previous transactions from the block. Traces: [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) ├─ storage changes: diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 563aeb3310e6c..5e59a52d536cc 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -18,6 +18,7 @@ use alloy_primitives::{ Address, Bytes, Log, TxKind, U256, keccak256, map::{AddressHashMap, HashMap}, }; +use alloy_rpc_types::trace::geth::AccountState; use alloy_sol_types::{SolCall, sol}; use foundry_evm_core::{ EvmEnv, @@ -44,6 +45,7 @@ use revm::{ }; use std::{ borrow::Cow, + collections::BTreeMap, sync::{ Arc, atomic::{AtomicBool, Ordering}, @@ -530,6 +532,29 @@ impl Executor { self.commit(&mut result); Ok(result) } + pub fn apply_prestate_trace( + &mut self, + trace: BTreeMap, + ) -> eyre::Result<()> { + let backend = self.backend_mut(); + for (address, account_state) in trace { + let code = account_state.code.map(Bytecode::new_raw).unwrap_or_default(); + let info = revm::state::AccountInfo { + nonce: account_state.nonce.unwrap_or_default(), + balance: account_state.balance.unwrap_or_default(), + ..Default::default() + } + .with_code(code); + backend.insert_account_info(address, info); + + for (key, value) in account_state.storage { + let slot = U256::from_be_bytes(key.0); + let val = U256::from_be_bytes(value.0); + backend.insert_account_storage(address, slot, val)?; + } + } + Ok(()) + } /// Commit the changeset to the database and adjust `self.inspector_config` values according to /// the executed call result.