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