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#[derive(Debug, Parser)]
33pub struct CallArgs {
34 #[arg(value_parser = NameOrAddress::from_str)]
36 to: Option<NameOrAddress>,
37
38 sig: Option<String>,
40
41 args: Vec<String>,
43
44 #[arg(
46 long,
47 conflicts_with_all = &["sig", "args"]
48 )]
49 data: Option<String>,
50
51 #[arg(long, default_value_t = false)]
53 trace: bool,
54
55 #[arg(long, requires = "trace")]
58 debug: bool,
59
60 #[arg(long, requires = "trace")]
61 decode_internal: bool,
62
63 #[arg(long, requires = "trace")]
66 labels: Vec<String>,
67
68 #[arg(long, requires = "trace")]
71 evm_version: Option<EvmVersion>,
72
73 #[arg(long, short)]
77 block: Option<BlockId>,
78
79 #[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 #[arg(long, visible_alias = "la")]
94 pub with_local_artifacts: bool,
95}
96
97#[derive(Debug, Parser)]
98pub enum CallSubcommands {
99 #[command(name = "--create")]
101 Create {
102 code: String,
104
105 sig: Option<String>,
107
108 args: Vec<String>,
110
111 #[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 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 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}