Skip to content

Commit

Permalink
Merge pull request #98 from OffchainLabs/adding-trace-call
Browse files Browse the repository at this point in the history
simulate api for debug trace call
  • Loading branch information
GreatSoshiant authored Sep 25, 2024
2 parents ca60c9c + c7f62e1 commit a8f8aad
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 2 deletions.
55 changes: 54 additions & 1 deletion main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
use alloy_primitives::TxHash;
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand};
use constants::DEFAULT_ENDPOINT;
use ethers::types::H160;
use ethers::abi::Bytes;
use ethers::types::{H160, U256};
use eyre::{bail, eyre, Context, Result};
use std::path::PathBuf;
use std::{fmt, path::Path};
Expand Down Expand Up @@ -97,6 +98,9 @@ enum Apis {
/// Trace a transaction.
#[command(visible_alias = "t")]
Trace(TraceArgs),
/// Simulate a transaction.
#[command(visible_alias = "s")]
Simulate(SimulateArgs),
}

#[derive(Args, Clone, Debug)]
Expand Down Expand Up @@ -266,6 +270,45 @@ struct TraceArgs {
use_native_tracer: bool,
}

#[derive(Args, Clone, Debug)]
pub struct SimulateArgs {
/// RPC endpoint.
#[arg(short, long, default_value = "http://localhost:8547")]
endpoint: String,

/// From address.
#[arg(short, long)]
from: Option<H160>,

/// To address.
#[arg(short, long)]
to: Option<H160>,

/// Gas limit.
#[arg(long)]
gas: Option<u64>,

/// Gas price.
#[arg(long)]
gas_price: Option<U256>,

/// Value to send with the transaction.
#[arg(short, long)]
value: Option<U256>,

/// Data to send with the transaction, as a hex string (with or without '0x' prefix).
#[arg(short, long)]
data: Option<Bytes>,

/// Project path.
#[arg(short, long, default_value = ".")]
project: PathBuf,

/// If set, use the native tracer instead of the JavaScript one.
#[arg(short, long, default_value_t = false)]
use_native_tracer: bool,
}

#[derive(Clone, Debug, Args)]
#[clap(group(ArgGroup::new("key").required(true).args(&["private_key_path", "private_key", "keystore_path"])))]
struct AuthOpts {
Expand Down Expand Up @@ -473,6 +516,9 @@ async fn main_impl(args: Opts) -> Result<()> {
"stylus activate failed"
);
}
Apis::Simulate(args) => {
run!(simulate(args).await, "failed to simulate transaction");
}
Apis::Cgen { input, out_dir } => {
run!(gen::c_gen(&input, &out_dir), "failed to generate c code");
}
Expand Down Expand Up @@ -557,6 +603,13 @@ async fn trace(args: TraceArgs) -> Result<()> {
Ok(())
}

async fn simulate(args: SimulateArgs) -> Result<()> {
let provider = sys::new_provider(&args.endpoint)?;
let trace = Trace::simulate(provider, &args).await?;
println!("{}", trace.json);
Ok(())
}

async fn replay(args: ReplayArgs) -> Result<()> {
if !args.child {
let rust_gdb = sys::command_exists("rust-gdb");
Expand Down
91 changes: 90 additions & 1 deletion main/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
#![allow(clippy::redundant_closure_call)]

use crate::util::color::{Color, DebugColor};
use crate::SimulateArgs;
use alloy_primitives::{Address, TxHash, B256, U256};
use ethers::{
providers::{JsonRpcClient, Middleware, Provider},
types::{GethDebugTracerType, GethDebugTracingOptions, GethTrace, Transaction},
types::{
BlockId, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions,
GethTrace, Transaction, TransactionRequest,
},
utils::__serde_json::{from_value, Value},
};
use eyre::{bail, OptionExt, Result, WrapErr};
Expand Down Expand Up @@ -77,6 +81,91 @@ impl Trace {
frame: self.top_frame,
}
}
pub async fn simulate<T: JsonRpcClient>(
provider: Provider<T>,
args: &SimulateArgs,
) -> Result<Self> {
// Build the transaction request
let mut tx_request = TransactionRequest::new();

if let Some(from) = args.from {
tx_request = tx_request.from(from);
}
if let Some(to) = args.to {
tx_request = tx_request.to(to);
}
if let Some(gas) = args.gas {
tx_request = tx_request.gas(gas);
}
if let Some(gas_price) = args.gas_price {
tx_request = tx_request.gas_price(gas_price);
}
if let Some(value) = args.value {
tx_request = tx_request.value(value);
}
if let Some(data) = &args.data {
tx_request = tx_request.data(data.clone());
}

// Use the same tracer as in Trace::new
let query = if args.use_native_tracer {
"stylusTracer"
} else {
include_str!("query.js")
};

// Corrected construction of tracer_options
let tracer_options = GethDebugTracingCallOptions {
tracing_options: GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::JsTracer(query.to_owned())),
..Default::default()
},
..Default::default()
};

// Use the latest block; alternatively, this can be made configurable
let block_id = None::<BlockId>;

let GethTrace::Unknown(json) = provider
.debug_trace_call(tx_request, block_id, tracer_options)
.await?
else {
bail!("Malformed tracing result");
};

if let Value::Array(arr) = json.clone() {
if arr.is_empty() {
bail!("No trace frames found.");
}
}
// Since this is a simulated transaction, we create a dummy Transaction object
let tx = Transaction {
from: args.from.unwrap_or_default(),
to: args.to,
gas: args
.gas
.map(|gas| {
let bytes = [0u8; 32]; // U256 in both libraries is 32 bytes
gas.to_be_bytes().copy_from_slice(&bytes[..8]); // Convert alloy_primitives::U256 to bytes
ethers::types::U256::from_big_endian(&bytes) // Convert bytes to ethers::types::U256
})
.unwrap_or_else(ethers::types::U256::zero), // Default to 0 if no gas is provided
gas_price: args.gas_price,
value: args.value.unwrap_or_else(ethers::types::U256::zero),
input: args.data.clone().unwrap_or_default().into(),
// Default values for other fields
..Default::default()
};

// Parse the trace frames
let top_frame = TraceFrame::parse_frame(None, json.clone())?;

Ok(Self {
top_frame,
tx,
json,
})
}
}

#[derive(Serialize, Deserialize)]
Expand Down

0 comments on commit a8f8aad

Please sign in to comment.