1use super::{
2 auth::{confirm_auth_rpc_disclosure, confirm_auth_rpc_disclosure_before_network_resolution},
3 call_overrides::CallOverrideOpts,
4 run::{fetch_contracts_bytecode_from_trace, fetch_contracts_bytecode_via_rpc},
5};
6use crate::{
7 Cast,
8 debug::{ensure_remote_trace_context_unchanged, handle_traces, select_remote_trace_hardfork},
9 rpc_trace::{call_frame_to_arena, is_method_not_found_error, is_missing_state_error},
10 traces::TraceKind,
11 tx::{CastTxBuilder, SenderKind},
12};
13use alloy_consensus::BlockHeader;
14use alloy_eips::BlockNumHash;
15use alloy_ens::NameOrAddress;
16use alloy_network::{
17 BlockResponse, Network, NetworkTransactionBuilder, TransactionBuilder,
18 primitives::HeaderResponse,
19};
20use alloy_primitives::{B256, Bytes, TxKind, U256, hex, map::AddressHashMap};
21use alloy_provider::{Provider, ext::DebugApi};
22use alloy_rpc_types::{
23 BlockId, BlockNumberOrTag, BlockOverrides,
24 state::StateOverride,
25 trace::geth::{
26 CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions,
27 GethDebugTracingOptions, GethTrace,
28 },
29};
30use clap::Parser;
31use eyre::Result;
32use foundry_cli::{
33 opts::{ChainValueParser, RpcOpts, TracingArgs, TransactionOpts},
34 utils::{LoadConfig, TraceResult, parse_ether_value},
35};
36use foundry_common::{
37 FoundryTransactionBuilder,
38 abi::{encode_function_args, get_func},
39 provider::{ProviderBuilder, curl_transport::generate_curl_command},
40 sh_println, shell,
41};
42use foundry_compilers::artifacts::EvmVersion;
43use foundry_config::{
44 Chain, Config, FoundryHardfork, TracingConfig,
45 figment::{
46 self, Metadata, Profile,
47 value::{Dict, Map},
48 },
49};
50#[cfg(feature = "optimism")]
51use foundry_evm::core::evm::OpEvmNetwork;
52use foundry_evm::{
53 core::{
54 FoundryBlock, FoundryTransaction,
55 evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, context_for_child_transaction},
56 },
57 executors::TracingExecutor,
58 opts::EvmOpts,
59 traces::{InternalTraceMode, SparsedTraceArena, TraceRequirements},
60};
61use foundry_evm_networks::NetworkConfigs;
62use foundry_wallets::{BrowserWalletOpts, WalletOpts};
63use revm::context::Block;
64use std::str::FromStr;
65
66#[derive(Debug, Parser)]
91pub struct CallArgs {
92 #[arg(value_parser = NameOrAddress::from_str)]
94 to: Option<NameOrAddress>,
95
96 sig: Option<String>,
98
99 #[arg(allow_negative_numbers = true)]
101 args: Vec<String>,
102
103 #[arg(
105 long,
106 conflicts_with_all = &["sig", "args"]
107 )]
108 data: Option<String>,
109
110 #[arg(long, default_value_t = false)]
112 trace: bool,
113
114 #[arg(long, requires = "from", conflicts_with = "browser")]
124 delegate: bool,
125
126 #[arg(
135 long = "debug-trace-call",
136 default_value_t = false,
137 conflicts_with_all = ["trace", "debug", "decode_internal", "evm_version"]
138 )]
139 debug_trace_call: bool,
140
141 #[arg(long, requires = "trace")]
144 debug: bool,
145
146 #[command(flatten)]
147 tracing: TracingArgs,
148
149 #[arg(long, requires = "trace")]
152 evm_version: Option<EvmVersion>,
153
154 #[arg(long, short)]
158 block: Option<BlockId>,
159
160 #[command(subcommand)]
161 command: Option<CallSubcommands>,
162
163 #[command(flatten)]
164 tx: TransactionOpts,
165
166 #[arg(long)]
168 force: bool,
169
170 #[command(flatten)]
171 rpc: RpcOpts,
172
173 #[command(flatten)]
174 wallet: WalletOpts,
175
176 #[command(flatten)]
177 browser: BrowserWalletOpts,
178
179 #[arg(
180 short,
181 long,
182 alias = "chain-id",
183 env = "CHAIN",
184 value_parser = ChainValueParser::default(),
185 )]
186 pub chain: Option<Chain>,
187
188 #[arg(long, visible_alias = "la")]
190 pub with_local_artifacts: bool,
191
192 #[command(flatten)]
193 pub overrides: CallOverrideOpts,
194}
195
196#[derive(Debug, Parser)]
197pub enum CallSubcommands {
198 #[command(name = "--create")]
200 Create {
201 code: String,
203
204 sig: Option<String>,
206
207 #[arg(allow_negative_numbers = true)]
209 args: Vec<String>,
210
211 #[arg(long, value_parser = parse_ether_value)]
217 value: Option<U256>,
218 },
219}
220
221struct AuthDisclosurePreflight {
222 confirmed: bool,
223 sender: Option<SenderKind<'static>>,
224}
225
226fn infer_network_from_chain_id(networks: NetworkConfigs, chain_id: u64) -> Result<NetworkConfigs> {
227 networks.try_with_chain_id(chain_id).map_err(eyre::Report::msg)
228}
229
230impl CallArgs {
231 fn resolve_tracing(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
232 if self.debug_trace_call {
233 self.tracing.resolve_call_tracer(config, verbosity)
234 } else {
235 self.tracing.resolve(config, verbosity)
236 }
237 }
238
239 pub async fn run(mut self) -> Result<()> {
240 self.validate_trace_args()?;
241
242 if self.rpc.curl {
244 if self.browser.browser {
245 eyre::bail!("--browser cannot be combined with --curl; use --from <ADDRESS>");
246 }
247 if self.delegate {
248 eyre::bail!("--delegate cannot be combined with --curl");
251 }
252 return self.run_curl().await;
253 }
254
255 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
256 let (mut config, mut evm_opts) = super::load_cast_config_and_evm_opts(figment)?;
257 evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned());
258 if self.tx.tempo.is_tempo() {
259 evm_opts.networks = NetworkConfigs::with_tempo();
260 } else if let Some(chain) = self.chain {
261 evm_opts.networks = infer_network_from_chain_id(evm_opts.networks, chain.id())?;
262 }
263 let Some(auth_preflight) = self.preflight_auth_disclosure().await? else {
264 return Ok(());
265 };
266 evm_opts.infer_network_from_fork().await?;
267 if self.chain.is_none()
268 && let Some(chain_id) = evm_opts.env.chain_id
269 {
270 let chain = Chain::from_id(chain_id);
271 self.chain = Some(chain);
272 config.chain = Some(chain);
273 }
274
275 if evm_opts.networks.is_tempo() {
276 return self
277 .run_with_network_and_opts::<TempoEvmNetwork>(config, evm_opts, auth_preflight)
278 .await;
279 }
280
281 #[cfg(feature = "monad")]
282 if evm_opts.networks.is_monad() {
283 return self
284 .run_with_network_and_opts::<foundry_evm::core::evm::MonadEvmNetwork>(
285 config,
286 evm_opts,
287 auth_preflight,
288 )
289 .await;
290 }
291
292 #[cfg(feature = "optimism")]
293 if evm_opts.networks.is_optimism() {
294 return self
295 .run_with_network_and_opts::<OpEvmNetwork>(config, evm_opts, auth_preflight)
296 .await;
297 }
298
299 self.run_with_network_and_opts::<EthEvmNetwork>(config, evm_opts, auth_preflight).await
300 }
301
302 const fn will_disclose_auth(&self) -> bool {
305 !self.tx.auth.is_empty() && (!self.trace || matches!(self.tx.access_list, Some(None)))
306 }
307
308 async fn preflight_auth_disclosure(&self) -> Result<Option<AuthDisclosurePreflight>> {
309 if !self.will_disclose_auth() {
310 return Ok(Some(AuthDisclosurePreflight { confirmed: false, sender: None }));
311 }
312
313 let sender = if self.browser.browser {
314 None
315 } else {
316 Some(SenderKind::from_wallet_opts(self.wallet.clone()).await?)
317 };
318 let browser_sender = SenderKind::from(self.wallet.from.unwrap_or_default());
319 let validation_sender = sender.as_ref().unwrap_or(&browser_sender);
320 if !confirm_auth_rpc_disclosure_before_network_resolution(
321 &self.tx.auth,
322 validation_sender,
323 self.force,
324 )? {
325 return Ok(None);
326 }
327
328 Ok(Some(AuthDisclosurePreflight { confirmed: true, sender }))
329 }
330
331 fn validate_trace_args(&self) -> Result<()> {
332 if !self.trace
333 && !self.debug_trace_call
334 && (self.tracing.disable_labels
335 || self.tracing.compact_labels
336 || !self.tracing.labels.is_empty()
337 || self.tracing.trace_depth.is_some())
338 {
339 eyre::bail!("trace rendering options require `--trace` or `--debug-trace-call`");
340 }
341
342 if self.tracing.decode_internal && !self.trace {
343 eyre::bail!("`--decode-internal` requires `--trace`");
344 }
345
346 Ok(())
347 }
348
349 async fn run_with_network_and_opts<FEN: FoundryEvmNetwork>(
350 self,
351 mut config: Box<Config>,
352 evm_opts: EvmOpts,
353 auth_preflight: AuthDisclosurePreflight,
354 ) -> Result<()>
355 where
356 <FEN::Network as Network>::TransactionRequest: FoundryTransactionBuilder<FEN::Network>,
357 {
358 config.networks = evm_opts.networks;
359 let mut state_overrides = self.get_state_overrides()?;
360 let block_overrides = self.get_block_overrides()?;
361 config.tracing = self.resolve_tracing(&config.tracing, shell::verbosity());
362 let tracing = config.tracing.clone();
363
364 let Self {
365 mut to,
366 mut sig,
367 mut args,
368 mut tx,
369 command,
370 block,
371 trace,
372 debug_trace_call,
373 evm_version,
374 debug,
375 data,
376 with_local_artifacts,
377 wallet,
378 browser,
379 force,
380 delegate,
381 ..
382 } = self;
383
384 if let Some(data) = data {
385 sig = Some(data);
386 }
387
388 let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?.build()?;
389 let endpoint_identity =
390 if debug_trace_call { Some(evm_opts.discover_fork_endpoint().await?) } else { None };
391 let sender = if let Some(sender) = auth_preflight.sender {
392 sender
393 } else if let Some(browser) = browser.run::<FEN::Network>().await? {
394 browser.address().into()
395 } else {
396 SenderKind::from_wallet_opts(wallet).await?
397 };
398 let from = sender.address();
399
400 if delegate {
406 if command.is_some() {
407 eyre::bail!("`--delegate` cannot be combined with `--create`");
408 }
409 let Some(target) = to else {
410 eyre::bail!("`--delegate` requires a destination address");
411 };
412 let target = target.resolve(&provider).await?;
413 let overrides = state_overrides.get_or_insert_with(Default::default);
414 if overrides.get(&from).is_some_and(|account| account.code.is_some()) {
415 eyre::bail!("`--delegate` conflicts with `--override-code` for the sender {from}");
416 }
417 let code = match overrides.get(&target).and_then(|account| account.code.clone()) {
420 Some(code) => code,
421 None => provider.get_code_at(target).block_id(block.unwrap_or_default()).await?,
422 };
423 if code.is_empty() {
424 eyre::bail!("`--delegate` destination {target} has no code to delegate to");
425 }
426 overrides.entry(from).or_default().code = Some(code);
427 to = Some(NameOrAddress::Address(target));
428 }
429
430 let code = if let Some(CallSubcommands::Create {
431 code,
432 sig: create_sig,
433 args: create_args,
434 value,
435 }) = command
436 {
437 sig = create_sig;
438 args = create_args;
439 if let Some(value) = value {
440 tx.value = Some(value);
441 }
442 Some(code)
443 } else {
444 None
445 };
446
447 let builder = CastTxBuilder::new(&provider, tx, &config)
448 .await?
449 .with_to(to)
450 .await?
451 .with_code_sig_and_args(code, sig, args)
452 .await?
453 .raw();
454 let will_disclose =
455 (!trace && builder.has_auth()) || builder.will_disclose_auth_during_build();
456 if will_disclose
457 && !auth_preflight.confirmed
458 && !confirm_auth_rpc_disclosure(&builder, &sender, force)?
459 {
460 return Ok(());
461 }
462 let (mut tx, func) = builder.build(sender).await?;
463
464 if delegate {
467 tx.set_to(from);
468 }
469
470 if debug_trace_call {
471 let endpoint_identity = endpoint_identity
472 .as_ref()
473 .ok_or_else(|| eyre::eyre!("remote trace endpoint identity was not captured"))?;
474 let requested_block = block.unwrap_or(BlockId::latest());
475 let fetched_block = provider.get_block(requested_block).await?;
476 let resolved_canonical_block =
477 if matches!(requested_block, BlockId::Number(_)) && !requested_block.is_pending() {
478 fetched_block.as_ref().map(|block| {
479 BlockNumHash::new(block.header().number(), block.header().hash())
480 })
481 } else {
482 None
483 };
484 let block = pin_remote_trace_block(
485 requested_block,
486 fetched_block.as_ref().map(|block| block.header().hash()),
487 )?;
488 let block_time_override = block_overrides.as_ref().and_then(|overrides| overrides.time);
489 let mut call_options = GethDebugTracingCallOptions::default().with_tracing_options(
490 GethDebugTracingOptions::default()
491 .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
492 .with_call_config(CallConfig::default().with_log()),
493 );
494 let mut override_bytecode = AddressHashMap::<Bytes>::default();
498 if with_local_artifacts && let Some(overrides) = &state_overrides {
499 for (address, account) in overrides {
500 if let Some(code) = &account.code {
501 override_bytecode.insert(*address, code.clone());
502 }
503 }
504 }
505
506 if let Some(state_overrides) = state_overrides {
508 call_options = call_options.with_state_overrides(state_overrides);
509 }
510 if let Some(block_overrides) = block_overrides {
511 call_options = call_options.with_block_overrides(block_overrides);
512 }
513
514 let geth_trace = provider
515 .debug_trace_call(tx, block, call_options)
516 .await
517 .map_err(|err| -> eyre::Report {
518 if is_method_not_found_error(&err) {
523 eyre::eyre!(
524 "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`"
525 )
526 } else if is_missing_state_error(&err) {
527 eyre::eyre!(
528 "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`"
529 )
530 } else {
531 err.into()
532 }
533 })?;
534 let GethTrace::CallTracer(frame) = geth_trace else {
535 eyre::bail!(
536 "`debug_traceCall` did not return a callTracer frame; the RPC endpoint may not \
537 support the `callTracer`"
538 );
539 };
540
541 let success = frame.error.is_none() && frame.revert_reason.is_none();
542 let gas_used = frame.gas_used.saturating_to();
543 let arena = SparsedTraceArena {
544 arena: call_frame_to_arena(&frame),
545 ignored: Default::default(),
546 diagnostics: Default::default(),
547 };
548 let result = TraceResult {
549 success,
550 traces: Some(vec![(TraceKind::Execution, arena)]),
551 gas_used,
552 };
553
554 let contracts_bytecode = if with_local_artifacts {
559 let mut contracts_bytecode =
560 fetch_contracts_bytecode_via_rpc(&provider, &result, block).await?;
561 contracts_bytecode.extend(override_bytecode);
564 contracts_bytecode
565 } else {
566 Default::default()
567 };
568 let final_endpoint_identity = evm_opts.discover_fork_endpoint().await?;
569 ensure_remote_trace_context_unchanged(endpoint_identity, &final_endpoint_identity)?;
570
571 let execution_network = endpoint_identity.network;
574 let chain = alloy_chains::Chain::from_id(endpoint_identity.source_chain_id);
575 let block_timestamp = if let Some(timestamp) = block_time_override {
576 Some(timestamp)
577 } else {
578 fetched_block.as_ref().map(|block| block.header().timestamp())
579 };
580 let resolved_hardfork = select_remote_trace_hardfork(
583 config.hardfork,
584 endpoint_identity.hardfork,
585 execution_network,
586 )
587 .or_else(|| {
588 block_timestamp.and_then(|timestamp| {
589 FoundryHardfork::from_chain_and_timestamp(chain.id(), timestamp)
590 })
591 });
592 if let Some(resolved_block) = resolved_canonical_block {
593 let canonical_block =
594 provider.get_block_by_number(resolved_block.number.into()).await?;
595 ensure_remote_trace_block_is_canonical(
596 resolved_block,
597 canonical_block.as_ref().map(|block| {
598 BlockNumHash::new(block.header().number(), block.header().hash())
599 }),
600 )?;
601 }
602 handle_traces(
603 result,
604 &config,
605 chain,
606 &contracts_bytecode,
607 &tracing,
608 with_local_artifacts,
609 false,
610 resolved_hardfork,
611 endpoint_identity.network_profile,
612 )
613 .await?;
614
615 return Ok(());
616 }
617
618 if trace {
619 if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = block {
620 config.fork_block_number = Some(block_number);
622 }
623
624 let create2_deployer = evm_opts.create2_deployer;
625 let verbosity = tracing.verbosity;
626 let (mut evm_env, tx_env, fork, chain, networks, endpoint_hardfork) =
627 TracingExecutor::<FEN>::get_fork_material(&mut config, evm_opts).await?;
628 let context_block_number = evm_env.block_env.number().saturating_to();
629 evm_env.cfg_env.disable_block_gas_limit = true;
631 evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
632
633 if let Some(block_overrides) = block_overrides {
635 if let Some(number) = block_overrides.number {
636 evm_env.block_env.set_number(number.to());
637 }
638 if let Some(time) = block_overrides.time {
639 evm_env.block_env.set_timestamp(U256::from(time));
640 }
641 }
642 let resolved_hardfork = TracingExecutor::<FEN>::resolve_spec_for_chain(
643 &config,
644 networks,
645 chain.id(),
646 endpoint_hardfork,
647 &mut evm_env,
648 evm_version,
649 );
650 TracingExecutor::<FEN>::extend_precompile_labels(
651 &mut config,
652 networks,
653 resolved_hardfork,
654 );
655
656 let trace_requirements = TraceRequirements::none()
657 .with_calls(true)
658 .with_debug(debug)
659 .with_decode_internal(if tracing.decode_internal {
660 InternalTraceMode::Full
661 } else {
662 InternalTraceMode::None
663 })
664 .with_state_changes(verbosity > 4);
665 let mut executor = TracingExecutor::<FEN>::new(
666 (evm_env, tx_env),
667 fork,
668 None,
669 trace_requirements,
670 networks,
671 create2_deployer,
672 state_overrides,
673 )?;
674
675 let value = tx.value().unwrap_or_default();
676 let input = tx.input().cloned().unwrap_or_default();
677 let tx_kind = tx.kind().expect("set by builder");
678
679 if let Some(gas_limit) = tx.gas_limit() {
683 executor.set_gas_limit(gas_limit);
684 }
685
686 let env_tx = executor.tx_env_mut();
687
688 if let Some(gas_price) = tx.gas_price() {
690 env_tx.set_gas_price(gas_price);
691 }
692
693 if let Some(max_fee_per_gas) = tx.max_fee_per_gas() {
694 env_tx.set_gas_price(max_fee_per_gas);
695 }
696
697 if let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas() {
698 env_tx.set_gas_priority_fee(Some(max_priority_fee_per_gas));
699 }
700
701 if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() {
702 env_tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
703 }
704
705 if let Some(nonce) = tx.nonce() {
706 env_tx.set_nonce(nonce);
707 }
708
709 env_tx.set_tx_type(tx.output_tx_type().into());
710
711 if let Some(access_list) = tx.access_list().cloned() {
712 env_tx.set_access_list(access_list);
713 }
714
715 if let Some(auth) = tx.authorization_list().cloned() {
716 env_tx.set_signed_authorization(auth);
717 }
718
719 let mut context_tx = executor.tx_env().clone();
720 context_tx.set_caller(from);
721 context_tx.set_kind(tx_kind);
722 context_tx.set_data(input.clone());
723 context_tx.set_value(value);
724 let chain_context = context_for_child_transaction::<FEN, _>(
725 &provider,
726 context_block_number,
727 &context_tx,
728 networks,
729 )
730 .await?;
731
732 let trace = match tx_kind {
733 TxKind::Create => {
734 let deploy_result =
735 executor.deploy_with_context(from, input, value, chain_context, None);
736 TraceResult::try_from(deploy_result)?
737 }
738 TxKind::Call(to) => TraceResult::from_raw(
739 executor.transact_raw_with_context(from, to, input, value, chain_context)?,
740 TraceKind::Execution,
741 ),
742 };
743
744 let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &trace)?;
745 handle_traces(
746 trace,
747 &config,
748 chain,
749 &contracts_bytecode,
750 &tracing,
751 with_local_artifacts,
752 debug,
753 resolved_hardfork,
754 networks,
755 )
756 .await?;
757
758 return Ok(());
759 }
760
761 let response = Cast::new(&provider)
762 .call(&tx, func.as_ref(), block, state_overrides, block_overrides)
763 .await?;
764
765 if response == "0x"
768 && !delegate
769 && let Some(contract_address) = tx.to()
770 {
771 let code = provider.get_code_at(contract_address).await?;
772 if code.is_empty() {
773 sh_warn!("Contract code is empty")?;
774 }
775 }
776
777 let mut shell = shell::Shell::get();
779 let out = shell.out();
780 writeln!(out, "{response}")?;
781 out.flush()?;
782
783 Ok(())
784 }
785
786 async fn run_curl(self) -> Result<()> {
788 let config = self.rpc.load_config()?;
789 let url = config.get_rpc_url_or_localhost_http()?;
790 let jwt = config.get_rpc_jwt_secret()?;
791
792 let data = if let Some(data) = &self.data {
794 hex::decode(data)?
795 } else if let Some(sig) = &self.sig {
796 if let Ok(data) = hex::decode(sig) {
798 data
799 } else {
800 let func = get_func(sig)?;
802 encode_function_args(&func, &self.args)?
803 }
804 } else {
805 Vec::new()
806 };
807
808 let to = self.to.as_ref().map(|n| match n {
810 NameOrAddress::Address(addr) => Ok(*addr),
811 NameOrAddress::Name(name) => {
812 eyre::bail!("ENS names are not supported with --curl. Please use a raw address instead of '{}'", name);
813 }
814 }).transpose()?;
815
816 let mut call_object = serde_json::json!({
822 "to": to,
823 "data": format!("0x{}", hex::encode(&data)),
824 });
825 if let Some(from) = self.wallet.from {
826 call_object["from"] = serde_json::json!(from);
827 }
828 if let Some(value) = self.tx.value {
829 call_object["value"] = serde_json::json!(value);
830 }
831 if let Some(gas_limit) = self.tx.gas_limit {
832 call_object["gas"] = serde_json::json!(gas_limit);
833 }
834 if let Some(nonce) = self.tx.nonce {
835 call_object["nonce"] = serde_json::json!(nonce);
836 }
837
838 let block_param = self
839 .block
840 .map(|b| serde_json::to_value(b).unwrap_or(serde_json::json!("latest")))
841 .unwrap_or(serde_json::json!("latest"));
842
843 let (method, params) = if self.debug_trace_call {
848 let mut call_options = GethDebugTracingCallOptions::default().with_tracing_options(
849 GethDebugTracingOptions::default()
850 .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
851 .with_call_config(CallConfig::default().with_log()),
852 );
853 if let Some(state_overrides) = self.get_state_overrides()? {
854 call_options = call_options.with_state_overrides(state_overrides);
855 }
856 if let Some(block_overrides) = self.get_block_overrides()? {
857 call_options = call_options.with_block_overrides(block_overrides);
858 }
859 ("debug_traceCall", serde_json::json!([call_object, block_param, call_options]))
860 } else {
861 ("eth_call", serde_json::json!([call_object, block_param]))
862 };
863
864 let curl_cmd = generate_curl_command(
865 url.as_ref(),
866 method,
867 params,
868 config.eth_rpc_headers.as_deref(),
869 jwt.as_deref(),
870 )?;
871
872 sh_println!("{}", curl_cmd)?;
873 Ok(())
874 }
875
876 pub fn get_state_overrides(&self) -> Result<Option<StateOverride>> {
878 self.overrides.get_state_overrides()
879 }
880
881 pub fn get_block_overrides(&self) -> Result<Option<BlockOverrides>> {
883 self.overrides.get_block_overrides()
884 }
885}
886
887fn pin_remote_trace_block(requested: BlockId, fetched_hash: Option<B256>) -> Result<BlockId> {
888 if requested.is_pending() {
889 return Ok(requested);
890 }
891
892 let fetched_hash = fetched_hash.ok_or_else(|| {
893 eyre::eyre!("block {requested:?} was not found while preparing the remote trace")
894 })?;
895 if let BlockId::Hash(requested_hash) = requested {
896 if requested_hash.block_hash != fetched_hash {
897 eyre::bail!(
898 "the RPC endpoint returned block {fetched_hash} for requested block {}; retry the command",
899 requested_hash.block_hash
900 );
901 }
902 return Ok(requested);
904 }
905
906 Ok(BlockId::hash(fetched_hash))
907}
908
909fn ensure_remote_trace_block_is_canonical(
910 expected: BlockNumHash,
911 actual: Option<BlockNumHash>,
912) -> Result<()> {
913 let Some(actual) = actual else {
914 eyre::bail!(
915 "block {} at {} changed canonicality while collecting its remote trace: the canonical block lookup no longer reports that height; retry the command",
916 expected.hash,
917 expected.number,
918 );
919 };
920 if actual != expected {
921 eyre::bail!(
922 "block {} at {} changed canonicality while collecting its remote trace: the canonical block lookup reported block {} at {}; retry the command",
923 expected.hash,
924 expected.number,
925 actual.hash,
926 actual.number,
927 );
928 }
929
930 Ok(())
931}
932
933impl figment::Provider for CallArgs {
934 fn metadata(&self) -> Metadata {
935 Metadata::named("CallArgs")
936 }
937
938 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
939 let mut map = Map::new();
940
941 if let Some(evm_version) = self.evm_version {
942 map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
943 }
944 if let Some(chain) = self.chain {
945 map.insert("chain_id".into(), chain.id().into());
946 }
947
948 Ok(Map::from([(Config::selected_profile(), map)]))
949 }
950}
951
952#[cfg(test)]
953mod tests {
954 use super::*;
955 use alloy_eips::RpcBlockHash;
956 use alloy_primitives::U64;
957
958 #[test]
959 fn pending_remote_trace_block_remains_unpinned() {
960 assert_eq!(pin_remote_trace_block(BlockId::pending(), None).unwrap(), BlockId::pending());
961 }
962
963 #[test]
964 fn non_pending_remote_trace_blocks_are_pinned() {
965 let hash = B256::repeat_byte(0x11);
966 for requested in [
967 BlockId::number(42),
968 BlockId::earliest(),
969 BlockId::latest(),
970 BlockId::safe(),
971 BlockId::finalized(),
972 ] {
973 assert_eq!(pin_remote_trace_block(requested, Some(hash)).unwrap(), BlockId::hash(hash));
974 }
975 }
976
977 #[test]
978 fn non_pending_remote_trace_block_must_exist() {
979 let err = pin_remote_trace_block(BlockId::number(42), None).unwrap_err();
980 assert!(err.to_string().contains("was not found while preparing the remote trace"));
981 }
982
983 #[test]
984 fn hash_remote_trace_block_preserves_canonical_requirement() {
985 let hash = B256::repeat_byte(0x22);
986 for require_canonical in [None, Some(false), Some(true)] {
987 let requested = BlockId::Hash(RpcBlockHash { block_hash: hash, require_canonical });
988 assert_eq!(pin_remote_trace_block(requested, Some(hash)).unwrap(), requested);
989 }
990 }
991
992 #[test]
993 fn hash_remote_trace_block_must_match_response() {
994 let err = pin_remote_trace_block(
995 BlockId::hash_canonical(B256::repeat_byte(0x33)),
996 Some(B256::repeat_byte(0x44)),
997 )
998 .unwrap_err();
999 assert!(err.to_string().contains("returned block"));
1000 }
1001
1002 #[test]
1003 fn remote_trace_block_must_remain_canonical() {
1004 let expected = BlockNumHash::new(42, B256::repeat_byte(0x55));
1005
1006 ensure_remote_trace_block_is_canonical(expected, Some(expected)).unwrap();
1007
1008 let err = ensure_remote_trace_block_is_canonical(expected, None).unwrap_err();
1009 assert!(err.to_string().contains("no longer reports that height"));
1010
1011 let err = ensure_remote_trace_block_is_canonical(
1012 expected,
1013 Some(BlockNumHash::new(expected.number, B256::repeat_byte(0x66))),
1014 )
1015 .unwrap_err();
1016 assert!(err.to_string().contains("changed canonicality"));
1017 }
1018
1019 #[test]
1020 fn can_parse_call_data() {
1021 let data = hex::encode("hello");
1022 let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
1023 assert_eq!(args.data, Some(data));
1024
1025 let data = hex::encode_prefixed("hello");
1026 let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
1027 assert_eq!(args.data, Some(data));
1028 }
1029
1030 #[test]
1031 fn chain_is_merged_into_config() {
1032 let args = CallArgs::parse_from(["foundry-cli", "--chain", "1"]);
1033 let config = Config::from_provider(Config::figment().merge(&args)).unwrap();
1034
1035 assert_eq!(config.chain, Some(Chain::mainnet()));
1036 }
1037
1038 #[test]
1039 #[cfg(not(feature = "monad"))]
1040 fn chain_id_rejects_disabled_monad_network() {
1041 let error = infer_network_from_chain_id(NetworkConfigs::default(), 143).unwrap_err();
1042
1043 assert_eq!(
1044 error.to_string(),
1045 "cannot infer execution network from chain ID 143: network family `monad` is not \
1046 enabled in this build"
1047 );
1048 }
1049
1050 #[test]
1051 fn explicit_ethereum_overrides_chain_id_inference() {
1052 let ethereum = NetworkConfigs::with_ethereum();
1053 let inferred = infer_network_from_chain_id(ethereum, 143).unwrap();
1054
1055 assert_eq!(inferred, ethereum);
1056 }
1057
1058 #[test]
1059 fn can_parse_state_overrides() {
1060 let args = CallArgs::parse_from([
1061 "foundry-cli",
1062 "--override-balance",
1063 "0x123:0x1234",
1064 "--override-nonce",
1065 "0x123:1",
1066 "--override-code",
1067 "0x123:0x1234",
1068 "--override-state",
1069 "0x123:0x1:0x1234",
1070 ]);
1071
1072 assert_eq!(args.overrides.balance_overrides, Some(vec!["0x123:0x1234".to_string()]));
1073 assert_eq!(args.overrides.nonce_overrides, Some(vec!["0x123:1".to_string()]));
1074 assert_eq!(args.overrides.code_overrides, Some(vec!["0x123:0x1234".to_string()]));
1075 assert_eq!(args.overrides.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string()]));
1076 }
1077
1078 #[test]
1079 fn can_parse_multiple_state_overrides() {
1080 let args = CallArgs::parse_from([
1081 "foundry-cli",
1082 "--override-balance",
1083 "0x123:0x1234",
1084 "--override-balance",
1085 "0x456:0x5678",
1086 "--override-nonce",
1087 "0x123:1",
1088 "--override-nonce",
1089 "0x456:2",
1090 "--override-code",
1091 "0x123:0x1234",
1092 "--override-code",
1093 "0x456:0x5678",
1094 "--override-state",
1095 "0x123:0x1:0x1234",
1096 "--override-state",
1097 "0x456:0x2:0x5678",
1098 ]);
1099
1100 assert_eq!(
1101 args.overrides.balance_overrides,
1102 Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
1103 );
1104 assert_eq!(
1105 args.overrides.nonce_overrides,
1106 Some(vec!["0x123:1".to_string(), "0x456:2".to_string()])
1107 );
1108 assert_eq!(
1109 args.overrides.code_overrides,
1110 Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
1111 );
1112 assert_eq!(
1113 args.overrides.state_overrides,
1114 Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()])
1115 );
1116 }
1117
1118 #[test]
1119 fn test_negative_args_with_flags() {
1120 let args = CallArgs::parse_from([
1122 "foundry-cli",
1123 "--trace",
1124 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
1125 "process(int256)",
1126 "-999999",
1127 "--debug",
1128 ]);
1129
1130 assert!(args.trace);
1131 assert!(args.debug);
1132 assert_eq!(args.args, vec!["-999999"]);
1133 }
1134
1135 #[test]
1136 fn test_transaction_opts_with_trace() {
1137 let args = CallArgs::parse_from([
1139 "foundry-cli",
1140 "--trace",
1141 "--gas-limit",
1142 "1000000",
1143 "--gas-price",
1144 "20000000000",
1145 "--priority-gas-price",
1146 "2000000000",
1147 "--nonce",
1148 "42",
1149 "--value",
1150 "1000000000000000000", "--blob-gas-price",
1152 "10000000000",
1153 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
1154 "balanceOf(address)",
1155 "0x123456789abcdef123456789abcdef123456789a",
1156 ]);
1157
1158 assert!(args.trace);
1159 assert_eq!(args.tx.gas_limit, Some(U256::from(1000000u32)));
1160 assert_eq!(args.tx.gas_price, Some(U256::from(20000000000u64)));
1161 assert_eq!(args.tx.priority_gas_price, Some(U256::from(2000000000u64)));
1162 assert_eq!(args.tx.nonce, Some(U64::from(42)));
1163 assert_eq!(args.tx.value, Some(U256::from(1000000000000000000u64)));
1164 assert_eq!(args.tx.blob_gas_price, Some(U256::from(10000000000u64)));
1165 }
1166
1167 #[test]
1168 fn debug_trace_call_conflicts_with_trace() {
1169 let result = CallArgs::try_parse_from(["foundry-cli", "--trace", "--debug-trace-call"]);
1170 assert!(result.is_err(), "--trace and --debug-trace-call must be mutually exclusive");
1171 }
1172
1173 #[test]
1174 fn debug_trace_call_rejects_local_trace_flags() {
1175 for flag in ["--debug", "--decode-internal"] {
1176 let result = CallArgs::try_parse_from([
1177 "foundry-cli",
1178 "--debug-trace-call",
1179 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
1180 flag,
1181 ]);
1182 assert!(result.is_err(), "--debug-trace-call must reject {flag}");
1183 }
1184 let result = CallArgs::try_parse_from([
1186 "foundry-cli",
1187 "--debug-trace-call",
1188 "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
1189 "--evm-version",
1190 "shanghai",
1191 ]);
1192 assert!(result.is_err(), "--debug-trace-call must reject --evm-version");
1193 }
1194
1195 #[test]
1196 fn debug_trace_call_ignores_configured_internal_decoding() {
1197 let args = CallArgs::parse_from(["foundry-cli", "--debug-trace-call"]);
1198 let config = TracingConfig { decode_internal: true, ..Default::default() };
1199
1200 assert!(!args.resolve_tracing(&config, 0).decode_internal);
1201 }
1202}