Skip to content

Commit ad1d090

Browse files
nhtyyNEvalir
authored
[feat] cast call --trace (& code reuse in cast run) (foundry-rs#5477)
* initial start * traces working * Update opts.rs * clean up * comments/debugger * comment * change build -> fill * start of move to evm crate * rename * put fork setup on tracing executor * comment * return err on no traces * moving trace handlers to cast::cmd::utils * rm formatting * fmt * use a ref * fix unneeded borrow * unused import * panic instead of bail * initial * Update cli/src/cmd/cast/call.rs Co-authored-by: evalir <[email protected]> * Update cli/src/cmd/utils.rs Co-authored-by: evalir <[email protected]> * Update cli/src/cmd/utils.rs Co-authored-by: evalir <[email protected]> * Update evm/src/trace/mod.rs Co-authored-by: evalir <[email protected]> * comment * unresolve convo * TraceResult::from * clippy --fix * use from/try_from * clippy --fix * clean up, no extra url in panic * formatting * fix imports * fix clap rxequirements * Update cli/src/cmd/cast/call.rs * cargo fmt * move tracing executor to its own file --------- Co-authored-by: N <[email protected]> Co-authored-by: evalir <[email protected]>
1 parent 10cba9f commit ad1d090

File tree

5 files changed

+424
-202
lines changed

5 files changed

+424
-202
lines changed

cli/src/cmd/cast/call.rs

Lines changed: 163 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
// cast estimate subcommands
22
use crate::{
3+
cmd::utils::{handle_traces, TraceResult},
34
opts::{EthereumOpts, TransactionOpts},
45
utils::{self, parse_ether_value},
56
};
67
use cast::{Cast, TxBuilder};
78
use clap::Parser;
8-
use ethers::types::{BlockId, NameOrAddress, U256};
9+
use ethers::{
10+
solc::EvmVersion,
11+
types::{BlockId, NameOrAddress, U256},
12+
};
913
use eyre::WrapErr;
10-
use foundry_config::Config;
14+
use forge::executor::opts::EvmOpts;
15+
use foundry_config::{find_project_root_path, Config};
16+
use foundry_evm::trace::TracingExecutor;
1117
use std::str::FromStr;
1218

19+
type Provider =
20+
ethers::providers::Provider<ethers::providers::RetryClient<ethers::providers::Http>>;
21+
1322
/// CLI arguments for `cast call`.
1423
#[derive(Debug, Parser)]
1524
pub struct CallArgs {
@@ -31,13 +40,41 @@ pub struct CallArgs {
3140
)]
3241
data: Option<String>,
3342

43+
/// Forks the remote rpc, executes the transaction locally and prints a trace
44+
#[clap(long, default_value_t = false)]
45+
trace: bool,
46+
47+
/// Can only be used with "--trace"
48+
///
49+
/// opens an interactive debugger
50+
#[clap(long, requires = "trace")]
51+
debug: bool,
52+
53+
/// Can only be used with "--trace"
54+
///
55+
/// prints a more verbose trace
56+
#[clap(long, requires = "trace")]
57+
verbose: bool,
58+
59+
/// Can only be used with "--trace"
60+
/// Labels to apply to the traces.
61+
///
62+
/// Format: `address:label`
63+
#[clap(long, requires = "trace")]
64+
labels: Vec<String>,
65+
66+
/// Can only be used with "--trace"
67+
///
68+
/// The EVM Version to use.
69+
#[clap(long, requires = "trace")]
70+
evm_version: Option<EvmVersion>,
71+
3472
/// The block height to query at.
3573
///
3674
/// Can also be the tags earliest, finalized, safe, latest, or pending.
3775
#[clap(long, short)]
3876
block: Option<BlockId>,
3977

40-
/// Simulate a contract deployment.
4178
#[clap(subcommand)]
4279
command: Option<CallSubcommands>,
4380

@@ -50,6 +87,7 @@ pub struct CallArgs {
5087

5188
#[derive(Debug, Parser)]
5289
pub enum CallSubcommands {
90+
/// ignores the address field and simulates creating a contract
5391
#[clap(name = "--create")]
5492
Create {
5593
/// Bytecode of contract.
@@ -73,54 +111,158 @@ pub enum CallSubcommands {
73111

74112
impl CallArgs {
75113
pub async fn run(self) -> eyre::Result<()> {
76-
let CallArgs { to, sig, args, data, tx, eth, command, block } = self;
114+
let CallArgs {
115+
to,
116+
sig,
117+
args,
118+
data,
119+
tx,
120+
eth,
121+
command,
122+
block,
123+
trace,
124+
evm_version,
125+
debug,
126+
verbose,
127+
labels,
128+
} = self;
77129

78130
let config = Config::from(&eth);
79131
let provider = utils::get_provider(&config)?;
80132
let chain = utils::get_chain(config.chain_id, &provider).await?;
81133
let sender = eth.wallet.sender().await;
82134

83-
let mut builder = TxBuilder::new(&provider, sender, to, chain, tx.legacy).await?;
135+
let mut builder: TxBuilder<'_, Provider> =
136+
TxBuilder::new(&provider, sender, to, chain, tx.legacy).await?;
137+
84138
builder
85139
.gas(tx.gas_limit)
86140
.etherscan_api_key(config.get_etherscan_api_key(Some(chain)))
87141
.gas_price(tx.gas_price)
88142
.priority_gas_price(tx.priority_gas_price)
89143
.nonce(tx.nonce);
144+
90145
match command {
91146
Some(CallSubcommands::Create { code, sig, args, value }) => {
92-
builder.value(value);
147+
if trace {
148+
let figment = Config::figment_with_root(find_project_root_path(None).unwrap())
149+
.merge(eth.rpc);
150+
151+
let evm_opts = figment.extract::<EvmOpts>()?;
152+
153+
let (env, fork, chain) =
154+
TracingExecutor::get_fork_material(&config, evm_opts).await?;
93155

94-
let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?;
156+
let mut executor =
157+
foundry_evm::trace::TracingExecutor::new(env, fork, evm_version, debug)
158+
.await;
95159

96-
if let Some(s) = sig {
97-
let (mut sigdata, _func) = builder.create_args(&s, args).await?;
98-
data.append(&mut sigdata);
160+
let trace = match executor.deploy(
161+
sender,
162+
code.into(),
163+
value.unwrap_or(U256::zero()),
164+
None,
165+
) {
166+
Ok(deploy_result) => TraceResult::from(deploy_result),
167+
Err(evm_err) => TraceResult::try_from(evm_err)?,
168+
};
169+
170+
handle_traces(trace, &config, chain, labels, verbose, debug).await?;
171+
172+
return Ok(())
99173
}
100174

101-
builder.set_data(data);
175+
// fill the builder after the conditional so we dont move values
176+
fill_create(&mut builder, value, code, sig, args).await?;
102177
}
103178
_ => {
104-
builder.value(tx.value);
179+
// fill first here because we need to use the builder in the conditional
180+
fill_tx(&mut builder, tx.value, sig, args, data).await?;
105181

106-
if let Some(sig) = sig {
107-
builder.set_args(sig.as_str(), args).await?;
108-
}
109-
if let Some(data) = data {
110-
// Note: `sig+args` and `data` are mutually exclusive
111-
builder.set_data(
112-
hex::decode(data).wrap_err("Expected hex encoded function data")?,
113-
);
182+
if trace {
183+
let figment = Config::figment_with_root(find_project_root_path(None).unwrap())
184+
.merge(eth.rpc);
185+
186+
let evm_opts = figment.extract::<EvmOpts>()?;
187+
188+
let (env, fork, chain) =
189+
TracingExecutor::get_fork_material(&config, evm_opts).await?;
190+
191+
let mut executor =
192+
foundry_evm::trace::TracingExecutor::new(env, fork, evm_version, debug)
193+
.await;
194+
195+
let (tx, _) = builder.build();
196+
197+
let trace = TraceResult::from(executor.call_raw_committing(
198+
sender,
199+
tx.to_addr().copied().expect("an address to be here"),
200+
tx.data().cloned().unwrap_or_default().to_vec().into(),
201+
tx.value().copied().unwrap_or_default(),
202+
)?);
203+
204+
handle_traces(trace, &config, chain, labels, verbose, debug).await?;
205+
206+
return Ok(())
114207
}
115208
}
116209
};
117210

118-
let builder_output = builder.build();
211+
let builder_output: (
212+
ethers::types::transaction::eip2718::TypedTransaction,
213+
Option<ethers::abi::Function>,
214+
) = builder.build();
215+
119216
println!("{}", Cast::new(provider).call(builder_output, block).await?);
217+
120218
Ok(())
121219
}
122220
}
123221

222+
/// fills the builder from create arg
223+
async fn fill_create(
224+
builder: &mut TxBuilder<'_, Provider>,
225+
value: Option<U256>,
226+
code: String,
227+
sig: Option<String>,
228+
args: Vec<String>,
229+
) -> eyre::Result<()> {
230+
builder.value(value);
231+
232+
let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?;
233+
234+
if let Some(s) = sig {
235+
let (mut sigdata, _func) = builder.create_args(&s, args).await?;
236+
data.append(&mut sigdata);
237+
}
238+
239+
builder.set_data(data);
240+
241+
Ok(())
242+
}
243+
244+
/// fills the builder from args
245+
async fn fill_tx(
246+
builder: &mut TxBuilder<'_, Provider>,
247+
value: Option<U256>,
248+
sig: Option<String>,
249+
args: Vec<String>,
250+
data: Option<String>,
251+
) -> eyre::Result<()> {
252+
builder.value(value);
253+
254+
if let Some(sig) = sig {
255+
builder.set_args(sig.as_str(), args).await?;
256+
}
257+
258+
if let Some(data) = data {
259+
// Note: `sig+args` and `data` are mutually exclusive
260+
builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?);
261+
}
262+
263+
Ok(())
264+
}
265+
124266
#[cfg(test)]
125267
mod tests {
126268
use super::*;

0 commit comments

Comments
 (0)