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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
68 changes: 65 additions & 3 deletions main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use alloy_primitives::TxHash;
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand};
use constants::DEFAULT_ENDPOINT;
use ethers::types::H160;
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 +97,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 +269,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<String>,
GreatSoshiant marked this conversation as resolved.
Show resolved Hide resolved

/// 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 @@ -415,9 +457,9 @@
_ => {}
};

// see if custom extension exists
// see if custom extension exists and is not a deprecated extension
let custom = format!("cargo-stylus-{arg}");
if sys::command_exists(&custom) {
if sys::command_exists(&custom) && !is_deprecated_extension(&arg) {
let mut command = sys::new_command(&custom);
command.arg(arg).args(args);

Expand All @@ -440,6 +482,16 @@
runtime.block_on(main_impl(opts))
}

// Checks if a cargo stylus extension is an old, deprecated extension which is no longer
// 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 492 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:489:5 | 489 | / match subcommand { 490 | | "cargo-stylus-check" | "cargo-stylus-cgen" | "cargo-stylus-replay" => true, 491 | | _ => false, 492 | | } | |_____^ 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<()> {
macro_rules! run {
($expr:expr, $($msg:expr),+) => {
Expand All @@ -463,6 +515,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 @@ -547,6 +602,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
100 changes: 99 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,100 @@
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 {
let data_bytes = hex::decode(data.trim_start_matches("0x"))?;
tx_request = tx_request.data(data_bytes);
}

// 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

Check warning on line 153 in main/src/trace.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

warning: redundant closure --> main/src/trace.rs:153:33 | 153 | .unwrap_or_else(|| ethers::types::U256::zero()), // Default to 0 if no gas is provided | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `ethers::types::U256::zero` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `#[warn(clippy::redundant_closure)]` on by default
gas_price: args.gas_price,
value: args.value.unwrap_or_else(|| ethers::types::U256::zero()),

Check warning on line 155 in main/src/trace.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

warning: redundant closure --> main/src/trace.rs:155:46 | 155 | value: args.value.unwrap_or_else(|| ethers::types::U256::zero()), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `ethers::types::U256::zero` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
input: args
.data
.as_ref()
.map(|d| {
hex::decode(d.strip_prefix("0x").unwrap_or(&d))

Check warning on line 160 in main/src/trace.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> main/src/trace.rs:160:64 | 160 | hex::decode(d.strip_prefix("0x").unwrap_or(&d)) | ^^ help: change this to: `d` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
.unwrap_or_default()
.into()
})
.unwrap_or_default(),
// 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