Skip to main content

cast/cmd/
call.rs

1use super::run::fetch_contracts_bytecode_from_trace;
2use crate::{
3    Cast,
4    debug::handle_traces,
5    traces::TraceKind,
6    tx::{CastTxBuilder, SenderKind},
7};
8use alloy_ens::NameOrAddress;
9use alloy_network::{Network, NetworkTransactionBuilder, TransactionBuilder};
10use alloy_primitives::{Address, B256, Bytes, TxKind, U256, hex, map::HashMap};
11use alloy_provider::Provider;
12use alloy_rpc_types::{
13    BlockId, BlockNumberOrTag, BlockOverrides,
14    state::{StateOverride, StateOverridesBuilder},
15};
16use clap::Parser;
17use eyre::Result;
18use foundry_cli::{
19    ExitCode, diagnostic,
20    json::{JsonEnvelope, JsonMessage, print_json},
21    opts::{ChainValueParser, RpcOpts, TransactionOpts},
22    utils::{LoadConfig, TraceResult, parse_ether_value},
23};
24use foundry_common::{
25    FoundryTransactionBuilder,
26    abi::{encode_function_args, get_func},
27    provider::{ProviderBuilder, curl_transport::generate_curl_command},
28    sh_println, shell,
29};
30use foundry_compilers::artifacts::EvmVersion;
31use foundry_config::{
32    Chain, Config,
33    figment::{
34        self, Metadata, Profile,
35        value::{Dict, Map},
36    },
37};
38#[cfg(feature = "optimism")]
39use foundry_evm::core::evm::OpEvmNetwork;
40use foundry_evm::{
41    core::{
42        FoundryBlock, FoundryTransaction,
43        evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork},
44    },
45    executors::TracingExecutor,
46    opts::EvmOpts,
47    traces::{InternalTraceMode, TraceMode},
48};
49use foundry_wallets::WalletOpts;
50use regex::Regex;
51use serde::Serialize;
52use std::{str::FromStr, sync::LazyLock};
53
54/// Stable payload emitted in the `cast call` envelope under `--machine`.
55#[derive(Clone, Debug, Serialize)]
56pub struct CallData {
57    /// Raw `0x`-prefixed hex return data from `eth_call`.
58    pub raw: String,
59}
60
61/// Emit a typed `network.rpc.error` envelope and exit `Network (6)`.
62fn rpc_failure(err: &eyre::Report) -> ! {
63    let cause_chain: Vec<String> = err.chain().map(ToString::to_string).collect();
64    let message = cause_chain.first().cloned().unwrap_or_else(|| err.to_string());
65    let envelope = JsonEnvelope::error(
66        JsonMessage::error(diagnostic::network::RPC_ERROR, message)
67            .with_details(serde_json::json!({ "cause_chain": cause_chain })),
68    );
69    let _ = print_json(&envelope);
70    std::process::exit(ExitCode::Network.to_i32());
71}
72
73// matches override pattern <address>:<slot>:<value>
74// e.g. 0x123:0x1:0x1234
75static OVERRIDE_PATTERN: LazyLock<Regex> =
76    LazyLock::new(|| Regex::new(r"^([^:]+):([^:]+):([^:]+)$").unwrap());
77
78/// CLI arguments for `cast call`.
79///
80/// ## State Override Flags
81///
82/// The following flags can be used to override the state for the call:
83///
84/// * `--override-balance <address>:<balance>` - Override the balance of an account
85/// * `--override-nonce <address>:<nonce>` - Override the nonce of an account
86/// * `--override-code <address>:<code>` - Override the code of an account
87/// * `--override-state <address>:<slot>:<value>` - Override a storage slot of an account
88///
89/// Multiple overrides can be specified for the same account. For example:
90///
91/// ```bash
92/// cast call 0x... "transfer(address,uint256)" 0x... 100 \
93///   --override-balance 0x123:0x1234 \
94///   --override-nonce 0x123:1 \
95///   --override-code 0x123:0x1234 \
96///   --override-state 0x123:0x1:0x1234
97///   --override-state-diff 0x123:0x1:0x1234
98/// ```
99#[derive(Debug, Parser)]
100pub struct CallArgs {
101    /// The destination of the transaction.
102    #[arg(value_parser = NameOrAddress::from_str)]
103    to: Option<NameOrAddress>,
104
105    /// The signature of the function to call.
106    sig: Option<String>,
107
108    /// The arguments of the function to call.
109    #[arg(allow_negative_numbers = true)]
110    args: Vec<String>,
111
112    /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\].
113    #[arg(
114        long,
115        conflicts_with_all = &["sig", "args"]
116    )]
117    data: Option<String>,
118
119    /// Forks the remote rpc, executes the transaction locally and prints a trace
120    #[arg(long, default_value_t = false)]
121    trace: bool,
122
123    /// Disables the labels in the traces.
124    /// Can only be set with `--trace`.
125    #[arg(long, default_value_t = false, requires = "trace")]
126    disable_labels: bool,
127
128    /// Opens an interactive debugger.
129    /// Can only be used with `--trace`.
130    #[arg(long, requires = "trace")]
131    debug: bool,
132
133    /// Identify internal functions in traces.
134    ///
135    /// This will trace internal functions and decode stack parameters.
136    ///
137    /// Parameters stored in memory (such as bytes or arrays) are currently decoded only when a
138    /// single function is matched, similarly to `--debug`, for performance reasons.
139    #[arg(long, requires = "trace")]
140    decode_internal: bool,
141
142    /// Labels to apply to the traces; format: `address:label`.
143    /// Can only be used with `--trace`.
144    #[arg(long, requires = "trace")]
145    labels: Vec<String>,
146
147    /// The EVM Version to use.
148    /// Can only be used with `--trace`.
149    #[arg(long, requires = "trace")]
150    evm_version: Option<EvmVersion>,
151
152    /// The block height to query at.
153    ///
154    /// Can also be the tags earliest, finalized, safe, latest, or pending.
155    #[arg(long, short)]
156    block: Option<BlockId>,
157
158    #[command(subcommand)]
159    command: Option<CallSubcommands>,
160
161    #[command(flatten)]
162    tx: TransactionOpts,
163
164    #[command(flatten)]
165    rpc: RpcOpts,
166
167    #[command(flatten)]
168    wallet: WalletOpts,
169
170    #[arg(
171        short,
172        long,
173        alias = "chain-id",
174        env = "CHAIN",
175        value_parser = ChainValueParser::default(),
176    )]
177    pub chain: Option<Chain>,
178
179    /// Use current project artifacts for trace decoding.
180    #[arg(long, visible_alias = "la")]
181    pub with_local_artifacts: bool,
182
183    /// Override the accounts balance.
184    /// Format: "address:balance,address:balance"
185    #[arg(long = "override-balance", value_name = "ADDRESS:BALANCE", value_delimiter = ',')]
186    pub balance_overrides: Option<Vec<String>>,
187
188    /// Override the accounts nonce.
189    /// Format: "address:nonce,address:nonce"
190    #[arg(long = "override-nonce", value_name = "ADDRESS:NONCE", value_delimiter = ',')]
191    pub nonce_overrides: Option<Vec<String>>,
192
193    /// Override the accounts code.
194    /// Format: "address:code,address:code"
195    #[arg(long = "override-code", value_name = "ADDRESS:CODE", value_delimiter = ',')]
196    pub code_overrides: Option<Vec<String>>,
197
198    /// Override the accounts state and replace the current state entirely with the new one.
199    /// Format: "address:slot:value,address:slot:value"
200    #[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE", value_delimiter = ',')]
201    pub state_overrides: Option<Vec<String>>,
202
203    /// Override the accounts state specific slots and preserve the rest of the state.
204    /// Format: "address:slot:value,address:slot:value"
205    #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE", value_delimiter = ',')]
206    pub state_diff_overrides: Option<Vec<String>>,
207
208    /// Override the block timestamp.
209    #[arg(long = "block.time", value_name = "TIME")]
210    pub block_time: Option<u64>,
211
212    /// Override the block number.
213    #[arg(long = "block.number", value_name = "NUMBER")]
214    pub block_number: Option<u64>,
215}
216
217#[derive(Debug, Parser)]
218pub enum CallSubcommands {
219    /// ignores the address field and simulates creating a contract
220    #[command(name = "--create")]
221    Create {
222        /// Bytecode of contract.
223        code: String,
224
225        /// The signature of the constructor.
226        sig: Option<String>,
227
228        /// The arguments of the constructor.
229        #[arg(allow_negative_numbers = true)]
230        args: Vec<String>,
231
232        /// Ether to send in the transaction.
233        ///
234        /// Either specified in wei, or as a string with a unit type.
235        ///
236        /// Examples: 1ether, 10gwei, 0.01ether
237        #[arg(long, value_parser = parse_ether_value)]
238        value: Option<U256>,
239    },
240}
241
242impl CallArgs {
243    pub async fn run(self) -> Result<()> {
244        // Reject flags whose stdout shape or interactivity conflicts with the envelope contract.
245        if foundry_cli::is_machine() {
246            // `--interactive` and the hardware / cloud signer flags can trigger TTY prompts,
247            // hardware confirmations, or SDK auth output (e.g. AWS SSO device-auth URLs printed
248            // directly to stdout), all of which would corrupt the envelope.
249            let unsupported = [
250                ("--trace", self.trace),
251                ("--debug", self.debug),
252                ("--decode-internal", self.decode_internal),
253                ("--curl", self.rpc.curl),
254                ("--interactive", self.wallet.raw.interactive),
255                ("--ledger", self.wallet.ledger),
256                ("--trezor", self.wallet.trezor),
257                ("--aws", self.wallet.aws),
258                ("--gcp", self.wallet.gcp),
259            ]
260            .into_iter()
261            .filter_map(|(name, on)| on.then_some(name))
262            .collect::<Vec<_>>();
263            if !unsupported.is_empty() {
264                foundry_cli::machine::bail_machine_usage_with_details(
265                    format!(
266                        "`cast call` under `--machine` does not yet support {}; \
267                         run without `--machine` or omit those flags.",
268                        unsupported.join(", ")
269                    ),
270                    serde_json::json!({ "unsupported_flags": unsupported }),
271                );
272            }
273            // Unlocked keystore: prompts for the password on TTY at signer construction.
274            let has_keystore =
275                self.wallet.keystore_path.is_some() || self.wallet.keystore_account_name.is_some();
276            let has_noninteractive_password = self.wallet.keystore_password.is_some()
277                || self.wallet.keystore_password_file.is_some();
278            if has_keystore && !has_noninteractive_password {
279                foundry_cli::machine::bail_machine_usage(
280                    "`cast call --machine` requires `--password`, `--password-file`, or the \
281                     `ETH_PASSWORD` env var when `--keystore` / `--account` is set; an unlocked \
282                     keystore would prompt on stdin and corrupt the envelope.",
283                );
284            }
285        }
286
287        // Handle --curl mode early, before any provider interaction
288        if self.rpc.curl {
289            return self.run_curl().await;
290        }
291
292        if self.tx.tempo.is_tempo() {
293            return self.run_with_network::<TempoEvmNetwork>().await;
294        }
295
296        let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
297        let mut evm_opts = figment.extract::<EvmOpts>()?;
298        if let Some(chain) = self.chain {
299            evm_opts.networks = evm_opts.networks.with_chain_id(chain.id());
300        }
301        evm_opts.infer_network_from_fork().await;
302
303        if evm_opts.networks.is_tempo() {
304            return self.run_with_network::<TempoEvmNetwork>().await;
305        }
306
307        #[cfg(feature = "optimism")]
308        if evm_opts.networks.is_optimism() {
309            return self.run_with_network::<OpEvmNetwork>().await;
310        }
311
312        self.run_with_network::<EthEvmNetwork>().await
313    }
314
315    pub async fn run_with_network<FEN: FoundryEvmNetwork>(self) -> Result<()>
316    where
317        <FEN::Network as Network>::TransactionRequest: FoundryTransactionBuilder<FEN::Network>,
318    {
319        let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
320        let evm_opts = figment.extract::<EvmOpts>()?;
321        let mut config = Config::from_provider(figment)?.sanitized();
322        let state_overrides = self.get_state_overrides()?;
323        let block_overrides = self.get_block_overrides()?;
324
325        let Self {
326            to,
327            mut sig,
328            mut args,
329            mut tx,
330            command,
331            block,
332            trace,
333            evm_version,
334            debug,
335            decode_internal,
336            labels,
337            data,
338            with_local_artifacts,
339            disable_labels,
340            wallet,
341            ..
342        } = self;
343
344        if let Some(data) = data {
345            sig = Some(data);
346        }
347
348        // Provider construction (transport setup) is a network operation, so connection /
349        // DNS / TCP failures here are typed `network.rpc.error` under `--machine`.
350        let machine_mode = foundry_cli::is_machine();
351        let provider =
352            match ProviderBuilder::<FEN::Network>::from_config(&config).and_then(|b| b.build()) {
353                Ok(p) => p,
354                Err(err) if machine_mode => rpc_failure(&err),
355                Err(err) => return Err(err),
356            };
357        let sender = SenderKind::from_wallet_opts(wallet).await?;
358        let from = sender.address();
359
360        let code = if let Some(CallSubcommands::Create {
361            code,
362            sig: create_sig,
363            args: create_args,
364            value,
365        }) = command
366        {
367            sig = create_sig;
368            args = create_args;
369            if let Some(value) = value {
370                tx.value = Some(value);
371            }
372            Some(code)
373        } else {
374            None
375        };
376
377        // `CastTxBuilder` performs the first RPC pings (chain_id, gas estimation, ENS
378        // resolution); transport/connectivity failures here are typed `network.rpc.error`.
379        let builder_result: Result<_> = async {
380            CastTxBuilder::new(&provider, tx, &config)
381                .await?
382                .with_to(to)
383                .await?
384                .with_code_sig_and_args(code, sig, args)
385                .await?
386                .raw()
387                .build(sender)
388                .await
389        }
390        .await;
391        let (tx, func) = match builder_result {
392            Ok(v) => v,
393            Err(err) if machine_mode => rpc_failure(&err),
394            Err(err) => return Err(err),
395        };
396
397        if trace {
398            if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = self.block {
399                // Override Config `fork_block_number` (if set) with CLI value.
400                config.fork_block_number = Some(block_number);
401            }
402
403            let create2_deployer = evm_opts.create2_deployer;
404            let (mut evm_env, tx_env, fork, chain, networks) =
405                TracingExecutor::<FEN>::get_fork_material(&mut config, evm_opts).await?;
406
407            // modify settings that usually set in eth_call
408            evm_env.cfg_env.disable_block_gas_limit = true;
409            evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
410            evm_env.block_env.set_gas_limit(u64::MAX);
411
412            // Apply the block overrides.
413            if let Some(block_overrides) = block_overrides {
414                if let Some(number) = block_overrides.number {
415                    evm_env.block_env.set_number(number.to());
416                }
417                if let Some(time) = block_overrides.time {
418                    evm_env.block_env.set_timestamp(U256::from(time));
419                }
420            }
421
422            let trace_mode = TraceMode::Call
423                .with_debug(debug)
424                .with_decode_internal(if decode_internal {
425                    InternalTraceMode::Full
426                } else {
427                    InternalTraceMode::None
428                })
429                .with_state_changes(shell::verbosity() > 4);
430            let mut executor = TracingExecutor::<FEN>::new(
431                (evm_env, tx_env),
432                fork,
433                evm_version,
434                trace_mode,
435                networks,
436                create2_deployer,
437                state_overrides,
438            )?;
439
440            let value = tx.value().unwrap_or_default();
441            let input = tx.input().cloned().unwrap_or_default();
442            let tx_kind = tx.kind().expect("set by builder");
443            let env_tx = executor.tx_env_mut();
444
445            // Set transaction options with --trace
446            if let Some(gas_limit) = tx.gas_limit() {
447                env_tx.set_gas_limit(gas_limit);
448            }
449
450            if let Some(gas_price) = tx.gas_price() {
451                env_tx.set_gas_price(gas_price);
452            }
453
454            if let Some(max_fee_per_gas) = tx.max_fee_per_gas() {
455                env_tx.set_gas_price(max_fee_per_gas);
456            }
457
458            if let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas() {
459                env_tx.set_gas_priority_fee(Some(max_priority_fee_per_gas));
460            }
461
462            if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() {
463                env_tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
464            }
465
466            if let Some(nonce) = tx.nonce() {
467                env_tx.set_nonce(nonce);
468            }
469
470            env_tx.set_tx_type(tx.output_tx_type().into());
471
472            if let Some(access_list) = tx.access_list().cloned() {
473                env_tx.set_access_list(access_list);
474            }
475
476            if let Some(auth) = tx.authorization_list().cloned() {
477                env_tx.set_signed_authorization(auth);
478            }
479
480            let trace = match tx_kind {
481                TxKind::Create => {
482                    let deploy_result = executor.deploy(from, input, value, None);
483                    TraceResult::try_from(deploy_result)?
484                }
485                TxKind::Call(to) => TraceResult::from_raw(
486                    executor.transact_raw(from, to, input, value)?,
487                    TraceKind::Execution,
488                ),
489            };
490
491            let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &trace)?;
492            handle_traces(
493                trace,
494                &config,
495                chain,
496                &contracts_bytecode,
497                labels,
498                with_local_artifacts,
499                debug,
500                decode_internal,
501                disable_labels,
502                None,
503                None,
504            )
505            .await?;
506
507            return Ok(());
508        }
509
510        // Bypass the ABI decoder under `--machine`; the envelope carries raw hex only.
511        // Only the eth_call itself maps to `network.rpc.error`; setup/local errors fall through.
512        let decode_func = (!machine_mode).then_some(func.as_ref()).flatten();
513        let response = match Cast::new(&provider)
514            .call(&tx, decode_func, block, state_overrides, block_overrides)
515            .await
516        {
517            Ok(r) => r,
518            Err(err) if machine_mode => rpc_failure(&err),
519            Err(err) => return Err(err),
520        };
521
522        // Skip the empty-code lookup under `--machine`: it exists only for the human warning.
523        if !machine_mode
524            && response == "0x"
525            && let Some(contract_address) = tx.to()
526        {
527            let code = provider.get_code_at(contract_address).await?;
528            if code.is_empty() {
529                sh_warn!("Contract code is empty")?;
530            }
531        }
532
533        if machine_mode {
534            print_json(&JsonEnvelope::success(CallData { raw: response }))?;
535        } else {
536            sh_println!("{}", response)?;
537        }
538
539        Ok(())
540    }
541
542    /// Handle --curl mode by generating curl command without any RPC interaction.
543    async fn run_curl(self) -> Result<()> {
544        let config = self.rpc.load_config()?;
545        let url = config.get_rpc_url_or_localhost_http()?;
546        let jwt = config.get_rpc_jwt_secret()?;
547
548        // Get call data - either from --data or from sig + args
549        let data = if let Some(data) = &self.data {
550            hex::decode(data)?
551        } else if let Some(sig) = &self.sig {
552            // If sig is already hex data, use it directly
553            if let Ok(data) = hex::decode(sig) {
554                data
555            } else {
556                // Parse function signature and encode args
557                let func = get_func(sig)?;
558                encode_function_args(&func, &self.args)?
559            }
560        } else {
561            Vec::new()
562        };
563
564        // Resolve the destination address (must be a raw address for curl mode)
565        let to = self.to.as_ref().map(|n| match n {
566            NameOrAddress::Address(addr) => Ok(*addr),
567            NameOrAddress::Name(name) => {
568                eyre::bail!("ENS names are not supported with --curl. Please use a raw address instead of '{}'", name)
569            }
570        }).transpose()?;
571
572        // Build eth_call params
573        let call_object = serde_json::json!({
574            "to": to,
575            "data": format!("0x{}", hex::encode(&data)),
576        });
577
578        let block_param = self
579            .block
580            .map(|b| serde_json::to_value(b).unwrap_or(serde_json::json!("latest")))
581            .unwrap_or(serde_json::json!("latest"));
582
583        let params = serde_json::json!([call_object, block_param]);
584
585        let curl_cmd = generate_curl_command(
586            url.as_ref(),
587            "eth_call",
588            params,
589            config.eth_rpc_headers.as_deref(),
590            jwt.as_deref(),
591        );
592
593        sh_println!("{}", curl_cmd)?;
594        Ok(())
595    }
596
597    /// Parse state overrides from command line arguments.
598    pub fn get_state_overrides(&self) -> eyre::Result<Option<StateOverride>> {
599        // Early return if no override set - <https://github.com/foundry-rs/foundry/issues/10705>
600        if [
601            self.balance_overrides.as_ref(),
602            self.nonce_overrides.as_ref(),
603            self.code_overrides.as_ref(),
604            self.state_overrides.as_ref(),
605            self.state_diff_overrides.as_ref(),
606        ]
607        .iter()
608        .all(Option::is_none)
609        {
610            return Ok(None);
611        }
612
613        let mut state_overrides_builder = StateOverridesBuilder::default();
614
615        // Parse balance overrides
616        for override_str in self.balance_overrides.iter().flatten() {
617            let (addr, balance) = address_value_override(override_str)?;
618            state_overrides_builder =
619                state_overrides_builder.with_balance(addr.parse()?, balance.parse()?);
620        }
621
622        // Parse nonce overrides
623        for override_str in self.nonce_overrides.iter().flatten() {
624            let (addr, nonce) = address_value_override(override_str)?;
625            state_overrides_builder =
626                state_overrides_builder.with_nonce(addr.parse()?, nonce.parse()?);
627        }
628
629        // Parse code overrides
630        for override_str in self.code_overrides.iter().flatten() {
631            let (addr, code_str) = address_value_override(override_str)?;
632            state_overrides_builder =
633                state_overrides_builder.with_code(addr.parse()?, Bytes::from_str(code_str)?);
634        }
635
636        type StateOverrides = HashMap<Address, HashMap<B256, B256>>;
637        let parse_state_overrides =
638            |overrides: &Option<Vec<String>>| -> Result<StateOverrides, eyre::Report> {
639                let mut state_overrides: StateOverrides = StateOverrides::default();
640
641                overrides.iter().flatten().try_for_each(|s| -> Result<(), eyre::Report> {
642                    let (addr, slot, value) = address_slot_value_override(s)?;
643                    state_overrides.entry(addr).or_default().insert(slot.into(), value.into());
644                    Ok(())
645                })?;
646
647                Ok(state_overrides)
648            };
649
650        // Parse and apply state overrides
651        for (addr, entries) in parse_state_overrides(&self.state_overrides)? {
652            state_overrides_builder = state_overrides_builder.with_state(addr, entries);
653        }
654
655        // Parse and apply state diff overrides
656        for (addr, entries) in parse_state_overrides(&self.state_diff_overrides)? {
657            state_overrides_builder = state_overrides_builder.with_state_diff(addr, entries)
658        }
659
660        Ok(Some(state_overrides_builder.build()))
661    }
662
663    /// Parse block overrides from command line arguments.
664    pub fn get_block_overrides(&self) -> eyre::Result<Option<BlockOverrides>> {
665        let mut overrides = BlockOverrides::default();
666        if let Some(number) = self.block_number {
667            overrides = overrides.with_number(U256::from(number));
668        }
669        if let Some(time) = self.block_time {
670            overrides = overrides.with_time(time);
671        }
672        if overrides.is_empty() { Ok(None) } else { Ok(Some(overrides)) }
673    }
674}
675
676impl figment::Provider for CallArgs {
677    fn metadata(&self) -> Metadata {
678        Metadata::named("CallArgs")
679    }
680
681    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
682        let mut map = Map::new();
683
684        if let Some(evm_version) = self.evm_version {
685            map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
686        }
687
688        Ok(Map::from([(Config::selected_profile(), map)]))
689    }
690}
691
692/// Parse an override string in the format address:value.
693fn address_value_override(address_override: &str) -> Result<(&str, &str)> {
694    address_override.split_once(':').ok_or_else(|| {
695        eyre::eyre!("Invalid override {address_override}. Expected <address>:<value>")
696    })
697}
698
699/// Parse an override string in the format address:slot:value.
700fn address_slot_value_override(address_override: &str) -> Result<(Address, U256, U256)> {
701    let captures = OVERRIDE_PATTERN.captures(address_override).ok_or_else(|| {
702        eyre::eyre!("Invalid override {address_override}. Expected <address>:<slot>:<value>")
703    })?;
704
705    Ok((
706        captures[1].parse()?, // Address
707        captures[2].parse()?, // Slot (U256)
708        captures[3].parse()?, // Value (U256)
709    ))
710}
711
712#[cfg(test)]
713mod tests {
714    use super::*;
715    use alloy_primitives::{U64, address, b256, fixed_bytes};
716
717    #[test]
718    fn test_get_state_overrides() {
719        let call_args = CallArgs::parse_from([
720            "foundry-cli",
721            "--override-balance",
722            "0x0000000000000000000000000000000000000001:2",
723            "--override-nonce",
724            "0x0000000000000000000000000000000000000001:3",
725            "--override-code",
726            "0x0000000000000000000000000000000000000001:0x04",
727            "--override-state",
728            "0x0000000000000000000000000000000000000001:5:6",
729            "--override-state-diff",
730            "0x0000000000000000000000000000000000000001:7:8",
731        ]);
732        let overrides = call_args.get_state_overrides().unwrap().unwrap();
733        let address = address!("0x0000000000000000000000000000000000000001");
734        if let Some(account_override) = overrides.get(&address) {
735            if let Some(balance) = account_override.balance {
736                assert_eq!(balance, U256::from(2));
737            }
738            if let Some(nonce) = account_override.nonce {
739                assert_eq!(nonce, 3);
740            }
741            if let Some(code) = &account_override.code {
742                assert_eq!(*code, Bytes::from([0x04]));
743            }
744            if let Some(state) = &account_override.state
745                && let Some(value) = state.get(&b256!(
746                    "0x0000000000000000000000000000000000000000000000000000000000000005"
747                ))
748            {
749                assert_eq!(
750                    *value,
751                    b256!("0x0000000000000000000000000000000000000000000000000000000000000006")
752                );
753            }
754            if let Some(state_diff) = &account_override.state_diff
755                && let Some(value) = state_diff.get(&b256!(
756                    "0x0000000000000000000000000000000000000000000000000000000000000007"
757                ))
758            {
759                assert_eq!(
760                    *value,
761                    b256!("0x0000000000000000000000000000000000000000000000000000000000000008")
762                );
763            }
764        }
765    }
766
767    #[test]
768    fn test_get_state_overrides_empty() {
769        let call_args = CallArgs::parse_from([""]);
770        let overrides = call_args.get_state_overrides().unwrap();
771        assert_eq!(overrides, None);
772    }
773
774    #[test]
775    fn test_get_block_overrides() {
776        let mut call_args = CallArgs::parse_from([""]);
777        call_args.block_number = Some(1);
778        call_args.block_time = Some(2);
779        let overrides = call_args.get_block_overrides().unwrap().unwrap();
780        assert_eq!(overrides.number, Some(U256::from(1)));
781        assert_eq!(overrides.time, Some(2));
782    }
783
784    #[test]
785    fn test_get_block_overrides_empty() {
786        let call_args = CallArgs::parse_from([""]);
787        let overrides = call_args.get_block_overrides().unwrap();
788        assert_eq!(overrides, None);
789    }
790
791    #[test]
792    fn test_address_value_override_success() {
793        let text = "0x0000000000000000000000000000000000000001:2";
794        let (address, value) = address_value_override(text).unwrap();
795        assert_eq!(address, "0x0000000000000000000000000000000000000001");
796        assert_eq!(value, "2");
797    }
798
799    #[test]
800    fn test_address_value_override_error() {
801        let text = "invalid_value";
802        let error = address_value_override(text).unwrap_err();
803        assert_eq!(error.to_string(), "Invalid override invalid_value. Expected <address>:<value>");
804    }
805
806    #[test]
807    fn test_address_slot_value_override_success() {
808        let text = "0x0000000000000000000000000000000000000001:2:3";
809        let (address, slot, value) = address_slot_value_override(text).unwrap();
810        assert_eq!(*address, fixed_bytes!("0x0000000000000000000000000000000000000001"));
811        assert_eq!(slot, U256::from(2));
812        assert_eq!(value, U256::from(3));
813    }
814
815    #[test]
816    fn test_address_slot_value_override_error() {
817        let text = "invalid_value";
818        let error = address_slot_value_override(text).unwrap_err();
819        assert_eq!(
820            error.to_string(),
821            "Invalid override invalid_value. Expected <address>:<slot>:<value>"
822        );
823    }
824
825    #[test]
826    fn can_parse_call_data() {
827        let data = hex::encode("hello");
828        let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
829        assert_eq!(args.data, Some(data));
830
831        let data = hex::encode_prefixed("hello");
832        let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
833        assert_eq!(args.data, Some(data));
834    }
835
836    #[test]
837    fn can_parse_state_overrides() {
838        let args = CallArgs::parse_from([
839            "foundry-cli",
840            "--override-balance",
841            "0x123:0x1234",
842            "--override-nonce",
843            "0x123:1",
844            "--override-code",
845            "0x123:0x1234",
846            "--override-state",
847            "0x123:0x1:0x1234",
848        ]);
849
850        assert_eq!(args.balance_overrides, Some(vec!["0x123:0x1234".to_string()]));
851        assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string()]));
852        assert_eq!(args.code_overrides, Some(vec!["0x123:0x1234".to_string()]));
853        assert_eq!(args.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string()]));
854    }
855
856    #[test]
857    fn can_parse_multiple_state_overrides() {
858        let args = CallArgs::parse_from([
859            "foundry-cli",
860            "--override-balance",
861            "0x123:0x1234",
862            "--override-balance",
863            "0x456:0x5678",
864            "--override-nonce",
865            "0x123:1",
866            "--override-nonce",
867            "0x456:2",
868            "--override-code",
869            "0x123:0x1234",
870            "--override-code",
871            "0x456:0x5678",
872            "--override-state",
873            "0x123:0x1:0x1234",
874            "--override-state",
875            "0x456:0x2:0x5678",
876        ]);
877
878        assert_eq!(
879            args.balance_overrides,
880            Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
881        );
882        assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string(), "0x456:2".to_string()]));
883        assert_eq!(
884            args.code_overrides,
885            Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
886        );
887        assert_eq!(
888            args.state_overrides,
889            Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()])
890        );
891    }
892
893    #[test]
894    fn test_negative_args_with_flags() {
895        // Test that negative args work with flags
896        let args = CallArgs::parse_from([
897            "foundry-cli",
898            "--trace",
899            "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
900            "process(int256)",
901            "-999999",
902            "--debug",
903        ]);
904
905        assert!(args.trace);
906        assert!(args.debug);
907        assert_eq!(args.args, vec!["-999999"]);
908    }
909
910    #[test]
911    fn test_transaction_opts_with_trace() {
912        // Test that transaction options are correctly parsed when using --trace
913        let args = CallArgs::parse_from([
914            "foundry-cli",
915            "--trace",
916            "--gas-limit",
917            "1000000",
918            "--gas-price",
919            "20000000000",
920            "--priority-gas-price",
921            "2000000000",
922            "--nonce",
923            "42",
924            "--value",
925            "1000000000000000000", // 1 ETH
926            "--blob-gas-price",
927            "10000000000",
928            "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE",
929            "balanceOf(address)",
930            "0x123456789abcdef123456789abcdef123456789a",
931        ]);
932
933        assert!(args.trace);
934        assert_eq!(args.tx.gas_limit, Some(U256::from(1000000u32)));
935        assert_eq!(args.tx.gas_price, Some(U256::from(20000000000u64)));
936        assert_eq!(args.tx.priority_gas_price, Some(U256::from(2000000000u64)));
937        assert_eq!(args.tx.nonce, Some(U64::from(42)));
938        assert_eq!(args.tx.value, Some(U256::from(1000000000000000000u64)));
939        assert_eq!(args.tx.blob_gas_price, Some(U256::from(10000000000u64)));
940    }
941}