cast/cmd/
call.rs

1use crate::{
2    traces::TraceKind,
3    tx::{CastTxBuilder, SenderKind},
4    Cast,
5};
6use alloy_primitives::{TxKind, U256};
7use alloy_rpc_types::{BlockId, BlockNumberOrTag};
8use clap::Parser;
9use eyre::Result;
10use foundry_cli::{
11    opts::{EthereumOpts, TransactionOpts},
12    utils::{self, handle_traces, parse_ether_value, TraceResult},
13};
14use foundry_common::{ens::NameOrAddress, shell};
15use foundry_compilers::artifacts::EvmVersion;
16use foundry_config::{
17    figment::{
18        self,
19        value::{Dict, Map},
20        Figment, Metadata, Profile,
21    },
22    Config,
23};
24use foundry_evm::{
25    executors::TracingExecutor,
26    opts::EvmOpts,
27    traces::{InternalTraceMode, TraceMode},
28};
29use std::str::FromStr;
30
31/// CLI arguments for `cast call`.
32#[derive(Debug, Parser)]
33pub struct CallArgs {
34    /// The destination of the transaction.
35    #[arg(value_parser = NameOrAddress::from_str)]
36    to: Option<NameOrAddress>,
37
38    /// The signature of the function to call.
39    sig: Option<String>,
40
41    /// The arguments of the function to call.
42    args: Vec<String>,
43
44    /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\].
45    #[arg(
46        long,
47        conflicts_with_all = &["sig", "args"]
48    )]
49    data: Option<String>,
50
51    /// Forks the remote rpc, executes the transaction locally and prints a trace
52    #[arg(long, default_value_t = false)]
53    trace: bool,
54
55    /// Opens an interactive debugger.
56    /// Can only be used with `--trace`.
57    #[arg(long, requires = "trace")]
58    debug: bool,
59
60    #[arg(long, requires = "trace")]
61    decode_internal: bool,
62
63    /// Labels to apply to the traces; format: `address:label`.
64    /// Can only be used with `--trace`.
65    #[arg(long, requires = "trace")]
66    labels: Vec<String>,
67
68    /// The EVM Version to use.
69    /// Can only be used with `--trace`.
70    #[arg(long, requires = "trace")]
71    evm_version: Option<EvmVersion>,
72
73    /// The block height to query at.
74    ///
75    /// Can also be the tags earliest, finalized, safe, latest, or pending.
76    #[arg(long, short)]
77    block: Option<BlockId>,
78
79    /// Enable Odyssey features.
80    #[arg(long, alias = "alphanet")]
81    pub odyssey: bool,
82
83    #[command(subcommand)]
84    command: Option<CallSubcommands>,
85
86    #[command(flatten)]
87    tx: TransactionOpts,
88
89    #[command(flatten)]
90    eth: EthereumOpts,
91
92    /// Use current project artifacts for trace decoding.
93    #[arg(long, visible_alias = "la")]
94    pub with_local_artifacts: bool,
95}
96
97#[derive(Debug, Parser)]
98pub enum CallSubcommands {
99    /// ignores the address field and simulates creating a contract
100    #[command(name = "--create")]
101    Create {
102        /// Bytecode of contract.
103        code: String,
104
105        /// The signature of the constructor.
106        sig: Option<String>,
107
108        /// The arguments of the constructor.
109        args: Vec<String>,
110
111        /// Ether to send in the transaction.
112        ///
113        /// Either specified in wei, or as a string with a unit type.
114        ///
115        /// Examples: 1ether, 10gwei, 0.01ether
116        #[arg(long, value_parser = parse_ether_value)]
117        value: Option<U256>,
118    },
119}
120
121impl CallArgs {
122    pub async fn run(self) -> Result<()> {
123        let figment = Into::<Figment>::into(&self.eth).merge(&self);
124        let evm_opts = figment.extract::<EvmOpts>()?;
125        let mut config = Config::from_provider(figment)?.sanitized();
126
127        let Self {
128            to,
129            mut sig,
130            mut args,
131            mut tx,
132            eth,
133            command,
134            block,
135            trace,
136            evm_version,
137            debug,
138            decode_internal,
139            labels,
140            data,
141            with_local_artifacts,
142            ..
143        } = self;
144
145        if let Some(data) = data {
146            sig = Some(data);
147        }
148
149        let provider = utils::get_provider(&config)?;
150        let sender = SenderKind::from_wallet_opts(eth.wallet).await?;
151        let from = sender.address();
152
153        let code = if let Some(CallSubcommands::Create {
154            code,
155            sig: create_sig,
156            args: create_args,
157            value,
158        }) = command
159        {
160            sig = create_sig;
161            args = create_args;
162            if let Some(value) = value {
163                tx.value = Some(value);
164            }
165            Some(code)
166        } else {
167            None
168        };
169
170        let (tx, func) = CastTxBuilder::new(&provider, tx, &config)
171            .await?
172            .with_to(to)
173            .await?
174            .with_code_sig_and_args(code, sig, args)
175            .await?
176            .build_raw(sender)
177            .await?;
178
179        if trace {
180            if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = self.block {
181                // Override Config `fork_block_number` (if set) with CLI value.
182                config.fork_block_number = Some(block_number);
183            }
184
185            let create2_deployer = evm_opts.create2_deployer;
186            let (mut env, fork, chain, odyssey) =
187                TracingExecutor::get_fork_material(&config, evm_opts).await?;
188
189            // modify settings that usually set in eth_call
190            env.cfg.disable_block_gas_limit = true;
191            env.block.gas_limit = U256::MAX;
192
193            let trace_mode = TraceMode::Call
194                .with_debug(debug)
195                .with_decode_internal(if decode_internal {
196                    InternalTraceMode::Full
197                } else {
198                    InternalTraceMode::None
199                })
200                .with_state_changes(shell::verbosity() > 4);
201            let mut executor = TracingExecutor::new(
202                env,
203                fork,
204                evm_version,
205                trace_mode,
206                odyssey,
207                create2_deployer,
208            )?;
209
210            let value = tx.value.unwrap_or_default();
211            let input = tx.inner.input.into_input().unwrap_or_default();
212            let tx_kind = tx.inner.to.expect("set by builder");
213
214            let trace = match tx_kind {
215                TxKind::Create => {
216                    let deploy_result = executor.deploy(from, input, value, None);
217                    TraceResult::try_from(deploy_result)?
218                }
219                TxKind::Call(to) => TraceResult::from_raw(
220                    executor.transact_raw(from, to, input, value)?,
221                    TraceKind::Execution,
222                ),
223            };
224
225            handle_traces(
226                trace,
227                &config,
228                chain,
229                labels,
230                with_local_artifacts,
231                debug,
232                decode_internal,
233            )
234            .await?;
235
236            return Ok(());
237        }
238
239        sh_println!("{}", Cast::new(provider).call(&tx, func.as_ref(), block).await?)?;
240
241        Ok(())
242    }
243}
244
245impl figment::Provider for CallArgs {
246    fn metadata(&self) -> Metadata {
247        Metadata::named("CallArgs")
248    }
249
250    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
251        let mut map = Map::new();
252
253        if self.odyssey {
254            map.insert("odyssey".into(), self.odyssey.into());
255        }
256
257        if let Some(evm_version) = self.evm_version {
258            map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
259        }
260
261        Ok(Map::from([(Config::selected_profile(), map)]))
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268    use alloy_primitives::{hex, Address};
269
270    #[test]
271    fn can_parse_call_data() {
272        let data = hex::encode("hello");
273        let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
274        assert_eq!(args.data, Some(data));
275
276        let data = hex::encode_prefixed("hello");
277        let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]);
278        assert_eq!(args.data, Some(data));
279    }
280
281    #[test]
282    fn call_sig_and_data_exclusive() {
283        let data = hex::encode("hello");
284        let to = Address::ZERO;
285        let args = CallArgs::try_parse_from([
286            "foundry-cli",
287            to.to_string().as_str(),
288            "signature",
289            "--data",
290            format!("0x{data}").as_str(),
291        ]);
292
293        assert!(args.is_err());
294    }
295}