Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simulate api for debug trace call #98

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
/// 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 @@
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 @@ -444,10 +487,10 @@
// supported. These extensions are now incorporated as part of the `cargo-stylus` command itself and
// will be the preferred method of running them.
fn is_deprecated_extension(subcommand: &str) -> bool {
match subcommand {
"cargo-stylus-check" | "cargo-stylus-cgen" | "cargo-stylus-replay" => true,
_ => false,
}

Check warning on line 493 in main/src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

match expression looks like `matches!` macro

warning: match expression looks like `matches!` macro --> main/src/main.rs:490:5 | 490 | / match subcommand { 491 | | "cargo-stylus-check" | "cargo-stylus-cgen" | "cargo-stylus-replay" => true, 492 | | _ => false, 493 | | } | |_____^ help: try: `matches!(subcommand, "cargo-stylus-check" | "cargo-stylus-cgen" | "cargo-stylus-replay")` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro = note: `#[warn(clippy::match_like_matches_macro)]` on by default
}

async fn main_impl(args: Opts) -> Result<()> {
Expand All @@ -473,6 +516,9 @@
"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 @@
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
Loading