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