cast/cmd/
call.rs

1use crate::{
2    traces::TraceKind,
3    tx::{CastTxBuilder, SenderKind},
4    Cast,
5};
6use alloy_ens::NameOrAddress;
7use alloy_primitives::{Address, Bytes, TxKind, U256};
8use alloy_rpc_types::{
9    state::{StateOverride, StateOverridesBuilder},
10    BlockId, BlockNumberOrTag,
11};
12use clap::Parser;
13use eyre::Result;
14use foundry_cli::{
15    opts::{EthereumOpts, TransactionOpts},
16    utils::{self, handle_traces, parse_ether_value, TraceResult},
17};
18use foundry_common::shell;
19use foundry_compilers::artifacts::EvmVersion;
20use foundry_config::{
21    figment::{
22        self,
23        value::{Dict, Map},
24        Figment, Metadata, Profile,
25    },
26    Config,
27};
28use foundry_evm::{
29    executors::TracingExecutor,
30    opts::EvmOpts,
31    traces::{InternalTraceMode, TraceMode},
32};
33use regex::Regex;
34use revm::context::TransactionType;
35use std::{str::FromStr, sync::LazyLock};
36
37// matches override pattern <address>:<slot>:<value>
38// e.g. 0x123:0x1:0x1234
39static OVERRIDE_PATTERN: LazyLock<Regex> =
40    LazyLock::new(|| Regex::new(r"^([^:]+):([^:]+):([^:]+)$").unwrap());
41
42/// CLI arguments for `cast call`.
43///
44/// ## State Override Flags
45///
46/// The following flags can be used to override the state for the call:
47///
48/// * `--override-balance <address>:<balance>` - Override the balance of an account
49/// * `--override-nonce <address>:<nonce>` - Override the nonce of an account
50/// * `--override-code <address>:<code>` - Override the code of an account
51/// * `--override-state <address>:<slot>:<value>` - Override a storage slot of an account
52///
53/// Multiple overrides can be specified for the same account. For example:
54///
55/// ```bash
56/// cast call 0x... "transfer(address,uint256)" 0x... 100 \
57///   --override-balance 0x123:0x1234 \
58///   --override-nonce 0x123:1 \
59///   --override-code 0x123:0x1234 \
60///   --override-state 0x123:0x1:0x1234
61///   --override-state-diff 0x123:0x1:0x1234
62/// ```
63#[derive(Debug, Parser)]
64pub struct CallArgs {
65    /// The destination of the transaction.
66    #[arg(value_parser = NameOrAddress::from_str)]
67    to: Option<NameOrAddress>,
68
69    /// The signature of the function to call.
70    sig: Option<String>,
71
72    /// The arguments of the function to call.
73    args: Vec<String>,
74
75    /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\].
76    #[arg(
77        long,
78        conflicts_with_all = &["sig", "args"]
79    )]
80    data: Option<String>,
81
82    /// Forks the remote rpc, executes the transaction locally and prints a trace
83    #[arg(long, default_value_t = false)]
84    trace: bool,
85
86    /// Opens an interactive debugger.
87    /// Can only be used with `--trace`.
88    #[arg(long, requires = "trace")]
89    debug: bool,
90
91    #[arg(long, requires = "trace")]
92    decode_internal: bool,
93
94    /// Labels to apply to the traces; format: `address:label`.
95    /// Can only be used with `--trace`.
96    #[arg(long, requires = "trace")]
97    labels: Vec<String>,
98
99    /// The EVM Version to use.
100    /// Can only be used with `--trace`.
101    #[arg(long, requires = "trace")]
102    evm_version: Option<EvmVersion>,
103
104    /// The block height to query at.
105    ///
106    /// Can also be the tags earliest, finalized, safe, latest, or pending.
107    #[arg(long, short)]
108    block: Option<BlockId>,
109
110    /// Enable Odyssey features.
111    #[arg(long, alias = "alphanet")]
112    pub odyssey: bool,
113
114    #[command(subcommand)]
115    command: Option<CallSubcommands>,
116
117    #[command(flatten)]
118    tx: TransactionOpts,
119
120    #[command(flatten)]
121    eth: EthereumOpts,
122
123    /// Use current project artifacts for trace decoding.
124    #[arg(long, visible_alias = "la")]
125    pub with_local_artifacts: bool,
126
127    /// Override the balance of an account.
128    /// Format: address:balance
129    #[arg(long = "override-balance", value_name = "ADDRESS:BALANCE")]
130    pub balance_overrides: Option<Vec<String>>,
131
132    /// Override the nonce of an account.
133    /// Format: address:nonce
134    #[arg(long = "override-nonce", value_name = "ADDRESS:NONCE")]
135    pub nonce_overrides: Option<Vec<String>>,
136
137    /// Override the code of an account.
138    /// Format: address:code
139    #[arg(long = "override-code", value_name = "ADDRESS:CODE")]
140    pub code_overrides: Option<Vec<String>>,
141
142    /// Override the state of an account.
143    /// Format: address:slot:value
144    #[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE")]
145    pub state_overrides: Option<Vec<String>>,
146
147    /// Override the state diff of an account.
148    /// Format: address:slot:value
149    #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE")]
150    pub state_diff_overrides: Option<Vec<String>>,
151}
152
153#[derive(Debug, Parser)]
154pub enum CallSubcommands {
155    /// ignores the address field and simulates creating a contract
156    #[command(name = "--create")]
157    Create {
158        /// Bytecode of contract.
159        code: String,
160
161        /// The signature of the constructor.
162        sig: Option<String>,
163
164        /// The arguments of the constructor.
165        args: Vec<String>,
166
167        /// Ether to send in the transaction.
168        ///
169        /// Either specified in wei, or as a string with a unit type.
170        ///
171        /// Examples: 1ether, 10gwei, 0.01ether
172        #[arg(long, value_parser = parse_ether_value)]
173        value: Option<U256>,
174    },
175}
176
177impl CallArgs {
178    pub async fn run(self) -> Result<()> {
179        let figment = Into::<Figment>::into(&self.eth).merge(&self);
180        let evm_opts = figment.extract::<EvmOpts>()?;
181        let mut config = Config::from_provider(figment)?.sanitized();
182        let state_overrides = self.get_state_overrides()?;
183
184        let Self {
185            to,
186            mut sig,
187            mut args,
188            mut tx,
189            eth,
190            command,
191            block,
192            trace,
193            evm_version,
194            debug,
195            decode_internal,
196            labels,
197            data,
198            with_local_artifacts,
199            ..
200        } = self;
201
202        if let Some(data) = data {
203            sig = Some(data);
204        }
205
206        let provider = utils::get_provider(&config)?;
207        let sender = SenderKind::from_wallet_opts(eth.wallet).await?;
208        let from = sender.address();
209
210        let code = if let Some(CallSubcommands::Create {
211            code,
212            sig: create_sig,
213            args: create_args,
214            value,
215        }) = command
216        {
217            sig = create_sig;
218            args = create_args;
219            if let Some(value) = value {
220                tx.value = Some(value);
221            }
222            Some(code)
223        } else {
224            None
225        };
226
227        let (tx, func) = CastTxBuilder::new(&provider, tx, &config)
228            .await?
229            .with_to(to)
230            .await?
231            .with_code_sig_and_args(code, sig, args)
232            .await?
233            .build_raw(sender)
234            .await?;
235
236        if trace {
237            if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = self.block {
238                // Override Config `fork_block_number` (if set) with CLI value.
239                config.fork_block_number = Some(block_number);
240            }
241
242            let create2_deployer = evm_opts.create2_deployer;
243            let (mut env, fork, chain, odyssey) =
244                TracingExecutor::get_fork_material(&config, evm_opts).await?;
245
246            // modify settings that usually set in eth_call
247            env.evm_env.cfg_env.disable_block_gas_limit = true;
248            env.evm_env.block_env.gas_limit = u64::MAX;
249
250            let trace_mode = TraceMode::Call
251                .with_debug(debug)
252                .with_decode_internal(if decode_internal {
253                    InternalTraceMode::Full
254                } else {
255                    InternalTraceMode::None
256                })
257                .with_state_changes(shell::verbosity() > 4);
258            let mut executor = TracingExecutor::new(
259                env,
260                fork,
261                evm_version,
262                trace_mode,
263                odyssey,
264                create2_deployer,
265            )?;
266
267            let value = tx.value.unwrap_or_default();
268            let input = tx.inner.input.into_input().unwrap_or_default();
269            let tx_kind = tx.inner.to.expect("set by builder");
270            let env_tx = &mut executor.env_mut().tx;
271
272            if let Some(tx_type) = tx.inner.transaction_type {
273                env_tx.tx_type = tx_type;
274            }
275
276            if let Some(access_list) = tx.inner.access_list {
277                env_tx.access_list = access_list;
278
279                if env_tx.tx_type == TransactionType::Legacy as u8 {
280                    env_tx.tx_type = TransactionType::Eip2930 as u8;
281                }
282            }
283
284            let trace = match tx_kind {
285                TxKind::Create => {
286                    let deploy_result = executor.deploy(from, input, value, None);
287                    TraceResult::try_from(deploy_result)?
288                }
289                TxKind::Call(to) => TraceResult::from_raw(
290                    executor.transact_raw(from, to, input, value)?,
291                    TraceKind::Execution,
292                ),
293            };
294
295            handle_traces(
296                trace,
297                &config,
298                chain,
299                labels,
300                with_local_artifacts,
301                debug,
302                decode_internal,
303            )
304            .await?;
305
306            return Ok(());
307        }
308
309        sh_println!(
310            "{}",
311            Cast::new(provider).call(&tx, func.as_ref(), block, state_overrides).await?
312        )?;
313
314        Ok(())
315    }
316    /// Parse state overrides from command line arguments
317    pub fn get_state_overrides(&self) -> eyre::Result<StateOverride> {
318        let mut state_overrides_builder = StateOverridesBuilder::default();
319
320        // Parse balance overrides
321        for override_str in self.balance_overrides.iter().flatten() {
322            let (addr, balance) = address_value_override(override_str)?;
323            state_overrides_builder =
324                state_overrides_builder.with_balance(addr.parse()?, balance.parse()?);
325        }
326
327        // Parse nonce overrides
328        for override_str in self.nonce_overrides.iter().flatten() {
329            let (addr, nonce) = address_value_override(override_str)?;
330            state_overrides_builder =
331                state_overrides_builder.with_nonce(addr.parse()?, nonce.parse()?);
332        }
333
334        // Parse code overrides
335        for override_str in self.code_overrides.iter().flatten() {
336            let (addr, code_str) = address_value_override(override_str)?;
337            state_overrides_builder =
338                state_overrides_builder.with_code(addr.parse()?, Bytes::from_str(code_str)?);
339        }
340
341        // Parse state overrides
342        for override_str in self.state_overrides.iter().flatten() {
343            let (addr, slot, value) = address_slot_value_override(override_str)?;
344            state_overrides_builder =
345                state_overrides_builder.with_state(addr, [(slot.into(), value.into())]);
346        }
347
348        // Parse state diff overrides
349        for override_str in self.state_diff_overrides.iter().flatten() {
350            let (addr, slot, value) = address_slot_value_override(override_str)?;
351            state_overrides_builder =
352                state_overrides_builder.with_state_diff(addr, [(slot.into(), value.into())]);
353        }
354
355        Ok(state_overrides_builder.build())
356    }
357}
358
359impl figment::Provider for CallArgs {
360    fn metadata(&self) -> Metadata {
361        Metadata::named("CallArgs")
362    }
363
364    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
365        let mut map = Map::new();
366
367        if self.odyssey {
368            map.insert("odyssey".into(), self.odyssey.into());
369        }
370
371        if let Some(evm_version) = self.evm_version {
372            map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
373        }
374
375        Ok(Map::from([(Config::selected_profile(), map)]))
376    }
377}
378
379/// Parse an override string in the format address:value.
380fn address_value_override(address_override: &str) -> Result<(&str, &str)> {
381    address_override.split_once(':').ok_or_else(|| {
382        eyre::eyre!("Invalid override {address_override}. Expected <address>:<value>")
383    })
384}
385
386/// Parse an override string in the format address:slot:value.
387fn address_slot_value_override(address_override: &str) -> Result<(Address, U256, U256)> {
388    let captures = OVERRIDE_PATTERN.captures(address_override).ok_or_else(|| {
389        eyre::eyre!("Invalid override {address_override}. Expected <address>:<slot>:<value>")
390    })?;
391
392    Ok((
393        captures[1].parse()?, // Address
394        captures[2].parse()?, // Slot (U256)
395        captures[3].parse()?, // Value (U256)
396    ))
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402    use alloy_primitives::hex;
403
404    #[test]
405    fn can_parse_call_data() {
406        let data = hex::encode("hello");
407        let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
408        assert_eq!(args.data, Some(data));
409
410        let data = hex::encode_prefixed("hello");
411        let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
412        assert_eq!(args.data, Some(data));
413    }
414
415    #[test]
416    fn can_parse_state_overrides() {
417        let args = CallArgs::parse_from([
418            "foundry-cli",
419            "--override-balance",
420            "0x123:0x1234",
421            "--override-nonce",
422            "0x123:1",
423            "--override-code",
424            "0x123:0x1234",
425            "--override-state",
426            "0x123:0x1:0x1234",
427        ]);
428
429        assert_eq!(args.balance_overrides, Some(vec!["0x123:0x1234".to_string()]));
430        assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string()]));
431        assert_eq!(args.code_overrides, Some(vec!["0x123:0x1234".to_string()]));
432        assert_eq!(args.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string()]));
433    }
434
435    #[test]
436    fn can_parse_multiple_state_overrides() {
437        let args = CallArgs::parse_from([
438            "foundry-cli",
439            "--override-balance",
440            "0x123:0x1234",
441            "--override-balance",
442            "0x456:0x5678",
443            "--override-nonce",
444            "0x123:1",
445            "--override-nonce",
446            "0x456:2",
447            "--override-code",
448            "0x123:0x1234",
449            "--override-code",
450            "0x456:0x5678",
451            "--override-state",
452            "0x123:0x1:0x1234",
453            "--override-state",
454            "0x456:0x2:0x5678",
455        ]);
456
457        assert_eq!(
458            args.balance_overrides,
459            Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
460        );
461        assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string(), "0x456:2".to_string()]));
462        assert_eq!(
463            args.code_overrides,
464            Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
465        );
466        assert_eq!(
467            args.state_overrides,
468            Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()])
469        );
470    }
471}