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