1use super::{
2 call_overrides::CallOverrideOpts,
3 run::{fetch_contracts_bytecode_from_trace, fetch_contracts_bytecode_via_rpc},
4};
5use crate::{
6 Cast,
7 debug::handle_traces,
8 rpc_trace::{call_frame_to_arena, is_method_not_found_error, is_missing_state_error},
9 traces::TraceKind,
10 tx::{CastTxBuilder, SenderKind},
11};
12use alloy_ens::NameOrAddress;
13use alloy_network::{Network, NetworkTransactionBuilder, TransactionBuilder};
14use alloy_primitives::{Bytes, TxKind, U256, hex, map::AddressHashMap};
15use alloy_provider::{Provider, ext::DebugApi};
16use alloy_rpc_types::{
17 BlockId, BlockNumberOrTag, BlockOverrides,
18 state::StateOverride,
19 trace::geth::{
20 CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions,
21 GethDebugTracingOptions, GethTrace,
22 },
23};
24use clap::Parser;
25use eyre::Result;
26use foundry_cli::{
27 opts::{ChainValueParser, RpcOpts, TracingArgs, TransactionOpts},
28 utils::{LoadConfig, TraceResult, load_config_from_provider, parse_ether_value},
29};
30use foundry_common::{
31 FoundryTransactionBuilder,
32 abi::{encode_function_args, get_func},
33 provider::{ProviderBuilder, curl_transport::generate_curl_command},
34 sh_println, shell,
35};
36use foundry_compilers::artifacts::EvmVersion;
37use foundry_config::{
38 Chain, Config, TracingConfig,
39 figment::{
40 self, Metadata, Profile,
41 value::{Dict, Map},
42 },
43};
44#[cfg(feature = "optimism")]
45use foundry_evm::core::evm::OpEvmNetwork;
46use foundry_evm::{
47 core::{
48 FoundryBlock, FoundryTransaction,
49 evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork},
50 },
51 executors::TracingExecutor,
52 opts::EvmOpts,
53 traces::{InternalTraceMode, SparsedTraceArena, TraceRequirements},
54};
55use foundry_wallets::WalletOpts;
56use std::str::FromStr;
57
58#[derive(Debug, Parser)]
80pub struct CallArgs {
81 #[arg(value_parser = NameOrAddress::from_str)]
83 to: Option<NameOrAddress>,
84
85 sig: Option<String>,
87
88 #[arg(allow_negative_numbers = true)]
90 args: Vec<String>,
91
92 #[arg(
94 long,
95 conflicts_with_all = &["sig", "args"]
96 )]
97 data: Option<String>,
98
99 #[arg(long, default_value_t = false)]
101 trace: bool,
102
103 #[arg(
112 long = "debug-trace-call",
113 default_value_t = false,
114 conflicts_with_all = ["trace", "debug", "decode_internal", "evm_version"]
115 )]
116 debug_trace_call: bool,
117
118 #[arg(long, requires = "trace")]
121 debug: bool,
122
123 #[command(flatten)]
124 tracing: TracingArgs,
125
126 #[arg(long, requires = "trace")]
129 evm_version: Option<EvmVersion>,
130
131 #[arg(long, short)]
135 block: Option<BlockId>,
136
137 #[command(subcommand)]
138 command: Option<CallSubcommands>,
139
140 #[command(flatten)]
141 tx: TransactionOpts,
142
143 #[command(flatten)]
144 rpc: RpcOpts,
145
146 #[command(flatten)]
147 wallet: WalletOpts,
148
149 #[arg(
150 short,
151 long,
152 alias = "chain-id",
153 env = "CHAIN",
154 value_parser = ChainValueParser::default(),
155 )]
156 pub chain: Option<Chain>,
157
158 #[arg(long, visible_alias = "la")]
160 pub with_local_artifacts: bool,
161
162 #[command(flatten)]
163 pub overrides: CallOverrideOpts,
164}
165
166#[derive(Debug, Parser)]
167pub enum CallSubcommands {
168 #[command(name = "--create")]
170 Create {
171 code: String,
173
174 sig: Option<String>,
176
177 #[arg(allow_negative_numbers = true)]
179 args: Vec<String>,
180
181 #[arg(long, value_parser = parse_ether_value)]
187 value: Option<U256>,
188 },
189}
190
191impl CallArgs {
192 fn resolve_tracing(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
193 if self.debug_trace_call {
194 self.tracing.resolve_call_tracer(config, verbosity)
195 } else {
196 self.tracing.resolve(config, verbosity)
197 }
198 }
199
200 pub async fn run(mut self) -> Result<()> {
201 self.validate_trace_args()?;
202
203 if self.rpc.curl {
205 return self.run_curl().await;
206 }
207
208 if self.tx.tempo.is_tempo() {
209 return self.run_with_network::<TempoEvmNetwork>().await;
210 }
211
212 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
213 let mut evm_opts = figment.extract::<EvmOpts>()?;
214 if let Some(chain) = self.chain {
215 evm_opts.networks = evm_opts.networks.with_chain_id(chain.id());
216 }
217 evm_opts.infer_network_from_fork().await;
218 if self.chain.is_none() {
219 self.chain = evm_opts.env.chain_id.map(Chain::from_id);
220 }
221
222 if evm_opts.networks.is_tempo() {
223 return self.run_with_network::<TempoEvmNetwork>().await;
224 }
225
226 #[cfg(feature = "optimism")]
227 if evm_opts.networks.is_optimism() {
228 return self.run_with_network::<OpEvmNetwork>().await;
229 }
230
231 self.run_with_network::<EthEvmNetwork>().await
232 }
233
234 fn validate_trace_args(&self) -> Result<()> {
235 if !self.trace
236 && !self.debug_trace_call
237 && (self.tracing.disable_labels
238 || self.tracing.compact_labels
239 || !self.tracing.labels.is_empty()
240 || self.tracing.trace_depth.is_some())
241 {
242 eyre::bail!("trace rendering options require `--trace` or `--debug-trace-call`");
243 }
244
245 if self.tracing.decode_internal && !self.trace {
246 eyre::bail!("`--decode-internal` requires `--trace`");
247 }
248
249 Ok(())
250 }
251
252 pub async fn run_with_network<FEN: FoundryEvmNetwork>(self) -> Result<()>
253 where
254 <FEN::Network as Network>::TransactionRequest: FoundryTransactionBuilder<FEN::Network>,
255 {
256 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
257 let evm_opts = figment.extract::<EvmOpts>()?;
258 let mut config = load_config_from_provider(figment)?;
259 let state_overrides = self.get_state_overrides()?;
260 let block_overrides = self.get_block_overrides()?;
261 let tracing = self.resolve_tracing(&config.tracing, shell::verbosity());
262
263 let Self {
264 to,
265 mut sig,
266 mut args,
267 mut tx,
268 command,
269 block,
270 trace,
271 evm_version,
272 debug,
273 data,
274 with_local_artifacts,
275 wallet,
276 ..
277 } = self;
278
279 if let Some(data) = data {
280 sig = Some(data);
281 }
282
283 let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?.build()?;
284 let sender = SenderKind::from_wallet_opts(wallet).await?;
285 let from = sender.address();
286
287 let code = if let Some(CallSubcommands::Create {
288 code,
289 sig: create_sig,
290 args: create_args,
291 value,
292 }) = command
293 {
294 sig = create_sig;
295 args = create_args;
296 if let Some(value) = value {
297 tx.value = Some(value);
298 }
299 Some(code)
300 } else {
301 None
302 };
303
304 let (tx, func) = CastTxBuilder::new(&provider, tx, &config)
305 .await?
306 .with_to(to)
307 .await?
308 .with_code_sig_and_args(code, sig, args)
309 .await?
310 .raw()
311 .build(sender)
312 .await?;
313
314 if self.debug_trace_call {
315 let block = self.block.unwrap_or(BlockId::latest());
316 let mut call_options = GethDebugTracingCallOptions::default().with_tracing_options(
317 GethDebugTracingOptions::default()
318 .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
319 .with_call_config(CallConfig::default().with_log()),
320 );
321 let mut override_bytecode = AddressHashMap::<Bytes>::default();
325 if with_local_artifacts && let Some(overrides) = &state_overrides {
326 for (address, account) in overrides {
327 if let Some(code) = &account.code {
328 override_bytecode.insert(*address, code.clone());
329 }
330 }
331 }
332
333 if let Some(state_overrides) = state_overrides {
335 call_options = call_options.with_state_overrides(state_overrides);
336 }
337 if let Some(block_overrides) = block_overrides {
338 call_options = call_options.with_block_overrides(block_overrides);
339 }
340
341 let geth_trace = provider
342 .debug_trace_call(tx, block, call_options)
343 .await
344 .map_err(|err| -> eyre::Report {
345 if is_method_not_found_error(&err) {
350 eyre::eyre!(
351 "the RPC endpoint does not support `debug_traceCall` (method not found); use a node with the `debug` namespace enabled (e.g. a local anvil/reth or an archive endpoint), or drop `--debug-trace-call` to run the call locally with `--trace`"
352 )
353 } else if is_missing_state_error(&err) {
354 eyre::eyre!(
355 "the RPC endpoint does not have the historical state for the requested block; use an archive endpoint, or target a more recent block with `--block`"
356 )
357 } else {
358 err.into()
359 }
360 })?;
361 let GethTrace::CallTracer(frame) = geth_trace else {
362 eyre::bail!(
363 "`debug_traceCall` did not return a callTracer frame; the RPC endpoint may not \
364 support the `callTracer`"
365 );
366 };
367
368 let success = frame.error.is_none() && frame.revert_reason.is_none();
369 let gas_used = frame.gas_used.saturating_to();
370 let arena = SparsedTraceArena {
371 arena: call_frame_to_arena(&frame),
372 ignored: Default::default(),
373 };
374 let result = TraceResult {
375 success,
376 traces: Some(vec![(TraceKind::Execution, arena)]),
377 gas_used,
378 };
379
380 let contracts_bytecode = if with_local_artifacts {
385 let mut contracts_bytecode =
386 fetch_contracts_bytecode_via_rpc(&provider, &result, block).await?;
387 contracts_bytecode.extend(override_bytecode);
390 contracts_bytecode
391 } else {
392 Default::default()
393 };
394
395 let chain = alloy_chains::Chain::from_id(provider.get_chain_id().await?);
396 handle_traces(
397 result,
398 &config,
399 chain,
400 &contracts_bytecode,
401 &tracing,
402 with_local_artifacts,
403 false,
404 None,
405 )
406 .await?;
407
408 return Ok(());
409 }
410
411 if trace {
412 if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = self.block {
413 config.fork_block_number = Some(block_number);
415 }
416
417 let create2_deployer = evm_opts.create2_deployer;
418 let verbosity = tracing.verbosity;
419 let (mut evm_env, tx_env, fork, chain, networks) =
420 TracingExecutor::<FEN>::get_fork_material(&mut config, evm_opts).await?;
421
422 evm_env.cfg_env.disable_block_gas_limit = true;
424 evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
425 evm_env.block_env.set_gas_limit(u64::MAX);
426
427 if let Some(block_overrides) = block_overrides {
429 if let Some(number) = block_overrides.number {
430 evm_env.block_env.set_number(number.to());
431 }
432 if let Some(time) = block_overrides.time {
433 evm_env.block_env.set_timestamp(U256::from(time));
434 }
435 }
436
437 let trace_requirements = TraceRequirements::none()
438 .with_calls(true)
439 .with_debug(debug)
440 .with_decode_internal(if tracing.decode_internal {
441 InternalTraceMode::Full
442 } else {
443 InternalTraceMode::None
444 })
445 .with_state_changes(verbosity > 4);
446 let mut executor = TracingExecutor::<FEN>::new(
447 (evm_env, tx_env),
448 fork,
449 evm_version,
450 trace_requirements,
451 networks,
452 create2_deployer,
453 state_overrides,
454 )?;
455
456 let value = tx.value().unwrap_or_default();
457 let input = tx.input().cloned().unwrap_or_default();
458 let tx_kind = tx.kind().expect("set by builder");
459
460 if let Some(gas_limit) = tx.gas_limit() {
465 executor.set_gas_limit(gas_limit);
466 }
467
468 let env_tx = executor.tx_env_mut();
469
470 if let Some(gas_price) = tx.gas_price() {
472 env_tx.set_gas_price(gas_price);
473 }
474
475 if let Some(max_fee_per_gas) = tx.max_fee_per_gas() {
476 env_tx.set_gas_price(max_fee_per_gas);
477 }
478
479 if let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas() {
480 env_tx.set_gas_priority_fee(Some(max_priority_fee_per_gas));
481 }
482
483 if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() {
484 env_tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
485 }
486
487 if let Some(nonce) = tx.nonce() {
488 env_tx.set_nonce(nonce);
489 }
490
491 env_tx.set_tx_type(tx.output_tx_type().into());
492
493 if let Some(access_list) = tx.access_list().cloned() {
494 env_tx.set_access_list(access_list);
495 }
496
497 if let Some(auth) = tx.authorization_list().cloned() {
498 env_tx.set_signed_authorization(auth);
499 }
500
501 let trace = match tx_kind {
502 TxKind::Create => {
503 let deploy_result = executor.deploy(from, input, value, None);
504 TraceResult::try_from(deploy_result)?
505 }
506 TxKind::Call(to) => TraceResult::from_raw(
507 executor.transact_raw(from, to, input, value)?,
508 TraceKind::Execution,
509 ),
510 };
511
512 let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &trace)?;
513 handle_traces(
514 trace,
515 &config,
516 chain,
517 &contracts_bytecode,
518 &tracing,
519 with_local_artifacts,
520 debug,
521 None,
522 )
523 .await?;
524
525 return Ok(());
526 }
527
528 let response = Cast::new(&provider)
529 .call(&tx, func.as_ref(), block, state_overrides, block_overrides)
530 .await?;
531
532 if response == "0x"
533 && let Some(contract_address) = tx.to()
534 {
535 let code = provider.get_code_at(contract_address).await?;
536 if code.is_empty() {
537 sh_warn!("Contract code is empty")?;
538 }
539 }
540
541 sh_println!("{}", response)?;
542
543 Ok(())
544 }
545
546 async fn run_curl(self) -> Result<()> {
548 let config = self.rpc.load_config()?;
549 let url = config.get_rpc_url_or_localhost_http()?;
550 let jwt = config.get_rpc_jwt_secret()?;
551
552 let data = if let Some(data) = &self.data {
554 hex::decode(data)?
555 } else if let Some(sig) = &self.sig {
556 if let Ok(data) = hex::decode(sig) {
558 data
559 } else {
560 let func = get_func(sig)?;
562 encode_function_args(&func, &self.args)?
563 }
564 } else {
565 Vec::new()
566 };
567
568 let to = self.to.as_ref().map(|n| match n {
570 NameOrAddress::Address(addr) => Ok(*addr),
571 NameOrAddress::Name(name) => {
572 eyre::bail!("ENS names are not supported with --curl. Please use a raw address instead of '{}'", name);
573 }
574 }).transpose()?;
575
576 let mut call_object = serde_json::json!({
582 "to": to,
583 "data": format!("0x{}", hex::encode(&data)),
584 });
585 if let Some(from) = self.wallet.from {
586 call_object["from"] = serde_json::json!(from);
587 }
588 if let Some(value) = self.tx.value {
589 call_object["value"] = serde_json::json!(value);
590 }
591 if let Some(gas_limit) = self.tx.gas_limit {
592 call_object["gas"] = serde_json::json!(gas_limit);
593 }
594 if let Some(nonce) = self.tx.nonce {
595 call_object["nonce"] = serde_json::json!(nonce);
596 }
597
598 let block_param = self
599 .block
600 .map(|b| serde_json::to_value(b).unwrap_or(serde_json::json!("latest")))
601 .unwrap_or(serde_json::json!("latest"));
602
603 let (method, params) = if self.debug_trace_call {
608 let mut call_options = GethDebugTracingCallOptions::default().with_tracing_options(
609 GethDebugTracingOptions::default()
610 .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
611 .with_call_config(CallConfig::default().with_log()),
612 );
613 if let Some(state_overrides) = self.get_state_overrides()? {
614 call_options = call_options.with_state_overrides(state_overrides);
615 }
616 if let Some(block_overrides) = self.get_block_overrides()? {
617 call_options = call_options.with_block_overrides(block_overrides);
618 }
619 ("debug_traceCall", serde_json::json!([call_object, block_param, call_options]))
620 } else {
621 ("eth_call", serde_json::json!([call_object, block_param]))
622 };
623
624 let curl_cmd = generate_curl_command(
625 url.as_ref(),
626 method,
627 params,
628 config.eth_rpc_headers.as_deref(),
629 jwt.as_deref(),
630 )?;
631
632 sh_println!("{}", curl_cmd)?;
633 Ok(())
634 }
635
636 pub fn get_state_overrides(&self) -> Result<Option<StateOverride>> {
638 self.overrides.get_state_overrides()
639 }
640
641 pub fn get_block_overrides(&self) -> Result<Option<BlockOverrides>> {
643 self.overrides.get_block_overrides()
644 }
645}
646
647impl figment::Provider for CallArgs {
648 fn metadata(&self) -> Metadata {
649 Metadata::named("CallArgs")
650 }
651
652 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
653 let mut map = Map::new();
654
655 if let Some(evm_version) = self.evm_version {
656 map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
657 }
658 if let Some(chain) = self.chain {
659 map.insert("chain_id".into(), chain.id().into());
660 }
661
662 Ok(Map::from([(Config::selected_profile(), map)]))
663 }
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669 use alloy_primitives::U64;
670
671 #[test]
672 fn can_parse_call_data() {
673 let data = hex::encode("hello");
674 let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
675 assert_eq!(args.data, Some(data));
676
677 let data = hex::encode_prefixed("hello");
678 let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
679 assert_eq!(args.data, Some(data));
680 }
681
682 #[test]
683 fn chain_is_merged_into_config() {
684 let args = CallArgs::parse_from(["foundry-cli", "--chain", "1"]);
685 let config = Config::from_provider(Config::figment().merge(&args)).unwrap();
686
687 assert_eq!(config.chain, Some(Chain::mainnet()));
688 }
689
690 #[test]
691 fn can_parse_state_overrides() {
692 let args = CallArgs::parse_from([
693 "foundry-cli",
694 "--override-balance",
695 "0x123:0x1234",
696 "--override-nonce",
697 "0x123:1",
698 "--override-code",
699 "0x123:0x1234",
700 "--override-state",
701 "0x123:0x1:0x1234",
702 ]);
703
704 assert_eq!(args.overrides.balance_overrides, Some(vec!["0x123:0x1234".to_string()]));
705 assert_eq!(args.overrides.nonce_overrides, Some(vec!["0x123:1".to_string()]));
706 assert_eq!(args.overrides.code_overrides, Some(vec!["0x123:0x1234".to_string()]));
707 assert_eq!(args.overrides.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string()]));
708 }
709
710 #[test]
711 fn can_parse_multiple_state_overrides() {
712 let args = CallArgs::parse_from([
713 "foundry-cli",
714 "--override-balance",
715 "0x123:0x1234",
716 "--override-balance",
717 "0x456:0x5678",
718 "--override-nonce",
719 "0x123:1",
720 "--override-nonce",
721 "0x456:2",
722 "--override-code",
723 "0x123:0x1234",
724 "--override-code",
725 "0x456:0x5678",
726 "--override-state",
727 "0x123:0x1:0x1234",
728 "--override-state",
729 "0x456:0x2:0x5678",
730 ]);
731
732 assert_eq!(
733 args.overrides.balance_overrides,
734 Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
735 );
736 assert_eq!(
737 args.overrides.nonce_overrides,
738 Some(vec!["0x123:1".to_string(), "0x456:2".to_string()])
739 );
740 assert_eq!(
741 args.overrides.code_overrides,
742 Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
743 );
744 assert_eq!(
745 args.overrides.state_overrides,
746 Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()])
747 );
748 }
749
750 #[test]
751 fn test_negative_args_with_flags() {
752 let args = CallArgs::parse_from([
754 "foundry-cli",
755 "--trace",
756 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
757 "process(int256)",
758 "-999999",
759 "--debug",
760 ]);
761
762 assert!(args.trace);
763 assert!(args.debug);
764 assert_eq!(args.args, vec!["-999999"]);
765 }
766
767 #[test]
768 fn test_transaction_opts_with_trace() {
769 let args = CallArgs::parse_from([
771 "foundry-cli",
772 "--trace",
773 "--gas-limit",
774 "1000000",
775 "--gas-price",
776 "20000000000",
777 "--priority-gas-price",
778 "2000000000",
779 "--nonce",
780 "42",
781 "--value",
782 "1000000000000000000", "--blob-gas-price",
784 "10000000000",
785 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
786 "balanceOf(address)",
787 "0x123456789abcdef123456789abcdef123456789a",
788 ]);
789
790 assert!(args.trace);
791 assert_eq!(args.tx.gas_limit, Some(U256::from(1000000u32)));
792 assert_eq!(args.tx.gas_price, Some(U256::from(20000000000u64)));
793 assert_eq!(args.tx.priority_gas_price, Some(U256::from(2000000000u64)));
794 assert_eq!(args.tx.nonce, Some(U64::from(42)));
795 assert_eq!(args.tx.value, Some(U256::from(1000000000000000000u64)));
796 assert_eq!(args.tx.blob_gas_price, Some(U256::from(10000000000u64)));
797 }
798
799 #[test]
800 fn debug_trace_call_conflicts_with_trace() {
801 let result = CallArgs::try_parse_from(["foundry-cli", "--trace", "--debug-trace-call"]);
802 assert!(result.is_err(), "--trace and --debug-trace-call must be mutually exclusive");
803 }
804
805 #[test]
806 fn debug_trace_call_rejects_local_trace_flags() {
807 for flag in ["--debug", "--decode-internal"] {
808 let result = CallArgs::try_parse_from([
809 "foundry-cli",
810 "--debug-trace-call",
811 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
812 flag,
813 ]);
814 assert!(result.is_err(), "--debug-trace-call must reject {flag}");
815 }
816 let result = CallArgs::try_parse_from([
818 "foundry-cli",
819 "--debug-trace-call",
820 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
821 "--evm-version",
822 "shanghai",
823 ]);
824 assert!(result.is_err(), "--debug-trace-call must reject --evm-version");
825 }
826
827 #[test]
828 fn debug_trace_call_ignores_configured_internal_decoding() {
829 let args = CallArgs::parse_from(["foundry-cli", "--debug-trace-call"]);
830 let config = TracingConfig { decode_internal: true, ..Default::default() };
831
832 assert!(!args.resolve_tracing(&config, 0).decode_internal);
833 }
834}