1use crate::{
2 Cast, SimpleCast,
3 cmd::erc20::IERC20,
4 introspect::REGISTRY,
5 opts::{Cast as CastArgs, CastSubcommand, ToBaseArgs},
6 traces::identifier::SignaturesIdentifier,
7 tx::CastTxSender,
8};
9use alloy_dyn_abi::{ErrorExt, EventExt};
10use alloy_eips::eip7702::SignedAuthorization;
11use alloy_ens::{ProviderEnsExt, namehash};
12use alloy_network::{Ethereum, eip2718::Decodable2718};
13use alloy_primitives::{Address, B256, eip191_hash_message, hex, keccak256};
14use alloy_provider::Provider;
15use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest};
16use clap::CommandFactory;
17use clap_complete::generate;
18use eyre::{Result, WrapErr};
19use foundry_cli::{
20 json::{print_json_object, print_json_value_or_scalar, print_list, print_scalar, print_tokens},
21 utils::{self, LoadConfig},
22};
23use foundry_common::{
24 abi::{get_error, get_event},
25 fmt::format_uint_exp,
26 fs,
27 provider::ProviderBuilder,
28 selectors::{
29 ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic,
30 decode_function_selector, decode_selectors, import_selectors, parse_signatures,
31 pretty_calldata,
32 },
33 shell, stdin,
34};
35use foundry_evm_networks::NetworkVariant;
36use foundry_primitives::{FoundryTxEnvelope, PaymentLaneClassification};
37#[cfg(feature = "optimism")]
38use op_alloy_network::Optimism;
39use std::time::Instant;
40use tempo_alloy::TempoNetwork;
41use tempo_contracts::precompiles::{ITIP20ChannelReserve, TIP20_CHANNEL_RESERVE_ADDRESS};
42
43pub fn run() -> Result<()> {
45 foundry_cli::machine::check_machine();
48 foundry_cli::opts::GlobalArgs::check_introspect_with(CastArgs::command, ®ISTRY);
49 foundry_cli::opts::GlobalArgs::check_markdown_help::<CastArgs>();
50
51 setup()?;
52
53 let args = foundry_cli::parse_or_exit::<CastArgs>();
54 args.global.init()?;
55 args.global.tokio_runtime().block_on(run_command(args))
56}
57
58pub fn setup() -> Result<()> {
60 utils::common_setup();
61 utils::subscriber();
62
63 Ok(())
64}
65
66#[allow(clippy::large_stack_frames)]
68pub async fn run_command(args: CastArgs) -> Result<()> {
69 match args.cmd {
70 CastSubcommand::MaxInt { r#type } => {
72 let out = SimpleCast::max_int(&r#type)?;
73 print_scalar(out)?;
74 }
75 CastSubcommand::MinInt { r#type } => {
76 let out = SimpleCast::min_int(&r#type)?;
77 print_scalar(out)?;
78 }
79 CastSubcommand::MaxUint { r#type } => {
80 let out = SimpleCast::max_int(&r#type)?;
81 print_scalar(out)?;
82 }
83 CastSubcommand::AddressZero => {
84 print_scalar(format!("{:?}", Address::ZERO))?;
85 }
86 CastSubcommand::HashZero => {
87 print_scalar(format!("{:?}", B256::ZERO))?;
88 }
89
90 CastSubcommand::FromUtf8 { text } => {
92 let value = stdin::unwrap(text, false)?;
93 let out = SimpleCast::from_utf8(&value);
94 print_scalar(out)?;
95 }
96 CastSubcommand::ToAscii { hexdata } => {
97 let value = stdin::unwrap(hexdata, false)?;
98 let out = SimpleCast::to_ascii(value.trim())?;
99 print_scalar(out)?;
100 }
101 CastSubcommand::ToUtf8 { hexdata } => {
102 let value = stdin::unwrap(hexdata, false)?;
103 let out = SimpleCast::to_utf8(&value)?;
104 print_scalar(out)?;
105 }
106 CastSubcommand::FromFixedPoint { value, decimals } => {
107 let (value, decimals) = stdin::unwrap2(value, decimals)?;
108 let out = SimpleCast::from_fixed_point(&value, &decimals)?;
109 print_scalar(out)?;
110 }
111 CastSubcommand::ToFixedPoint { value, decimals } => {
112 let (value, decimals) = stdin::unwrap2(value, decimals)?;
113 let out = SimpleCast::to_fixed_point(&value, &decimals)?;
114 print_scalar(out)?;
115 }
116 CastSubcommand::ConcatHex { data } => {
117 let out = if data.is_empty() {
118 let s = stdin::read(true)?;
119 SimpleCast::concat_hex(s.split_whitespace())
120 } else {
121 SimpleCast::concat_hex(data)
122 };
123 print_scalar(out)?;
124 }
125 CastSubcommand::FromBin => {
126 let hex = stdin::read_bytes(false)?;
127 let out = hex::encode_prefixed(hex);
128 print_scalar(out)?;
129 }
130 CastSubcommand::ToHexdata { input } => {
131 let value = stdin::unwrap_line(input)?;
132 let output = match value {
133 s if s.starts_with('@') => hex::encode(std::env::var(&s[1..])?),
134 s if s.starts_with('/') => hex::encode(fs::read(s)?),
135 s => s.split(':').map(|s| s.trim_start_matches("0x").to_lowercase()).collect(),
136 };
137 print_scalar(format!("0x{output}"))?;
138 }
139 CastSubcommand::ToCheckSumAddress { address, chain_id } => {
140 let value = stdin::unwrap_line(address)?;
141 let out = value.to_checksum(chain_id);
142 print_scalar(out)?;
143 }
144 CastSubcommand::ToUint256 { value } => {
145 let value = stdin::unwrap_line(value)?;
146 let out = SimpleCast::to_uint256(&value)?;
147 print_scalar(out)?;
148 }
149 CastSubcommand::ToInt256 { value } => {
150 let value = stdin::unwrap_line(value)?;
151 let out = SimpleCast::to_int256(&value)?;
152 print_scalar(out)?;
153 }
154 CastSubcommand::ToUnit { value, unit } => {
155 let value = stdin::unwrap_line(value)?;
156 let out = SimpleCast::to_unit(&value, &unit)?;
157 print_scalar(out)?;
158 }
159 CastSubcommand::ParseUnits { value, unit } => {
160 let value = stdin::unwrap_line(value)?;
161 let out = SimpleCast::parse_units(&value, unit)?;
162 print_scalar(out)?;
163 }
164 CastSubcommand::FormatUnits { value, unit } => {
165 let value = stdin::unwrap_line(value)?;
166 let out = SimpleCast::format_units(&value, unit)?;
167 print_scalar(out)?;
168 }
169 CastSubcommand::FromWei { value, unit } => {
170 let value = stdin::unwrap_line(value)?;
171 let out = SimpleCast::from_wei(&value, &unit)?;
172 print_scalar(out)?;
173 }
174 CastSubcommand::ToWei { value, unit } => {
175 let value = stdin::unwrap_line(value)?;
176 let out = SimpleCast::to_wei(&value, &unit)?;
177 print_scalar(out)?;
178 }
179 CastSubcommand::FromRlp { value, as_int } => {
180 let value = stdin::unwrap_line(value)?;
181 let out = SimpleCast::from_rlp(value, as_int)?;
182 print_scalar(out)?;
183 }
184 CastSubcommand::ToRlp { value } => {
185 let value = stdin::unwrap_line(value)?;
186 let out = SimpleCast::to_rlp(&value)?;
187 print_scalar(out)?;
188 }
189 CastSubcommand::ToHex(ToBaseArgs { value, base_in }) => {
190 let value = stdin::unwrap_line(value)?;
191 let out = SimpleCast::to_base(&value, base_in.as_deref(), "hex")?;
192 print_scalar(out)?;
193 }
194 CastSubcommand::ToDec(ToBaseArgs { value, base_in }) => {
195 let value = stdin::unwrap_line(value)?;
196 let out = SimpleCast::to_base(&value, base_in.as_deref(), "dec")?;
197 print_scalar(out)?;
198 }
199 CastSubcommand::ToBase { base: ToBaseArgs { value, base_in }, base_out } => {
200 let (value, base_out) = stdin::unwrap2(value, base_out)?;
201 let out = SimpleCast::to_base(&value, base_in.as_deref(), &base_out)?;
202 print_scalar(out)?;
203 }
204 CastSubcommand::ToBytes32 { bytes } => {
205 let value = stdin::unwrap_line(bytes)?;
206 let out = SimpleCast::to_bytes32(&value)?;
207 print_scalar(out)?;
208 }
209 CastSubcommand::Pad { data, right, left: _, len } => {
210 let value = stdin::unwrap_line(data)?;
211 let out = SimpleCast::pad(&value, right, len)?;
212 print_scalar(out)?;
213 }
214 CastSubcommand::FormatBytes32String { string } => {
215 let value = stdin::unwrap_line(string)?;
216 let out = SimpleCast::format_bytes32_string(&value)?;
217 print_scalar(out)?;
218 }
219 CastSubcommand::ParseBytes32String { bytes } => {
220 let value = stdin::unwrap_line(bytes)?;
221 let out = SimpleCast::parse_bytes32_string(&value)?;
222 print_scalar(out)?;
223 }
224 CastSubcommand::ParseBytes32Address { bytes } => {
225 let value = stdin::unwrap_line(bytes)?;
226 let out = SimpleCast::parse_bytes32_address(&value)?;
227 print_scalar(out)?;
228 }
229
230 CastSubcommand::DecodeAbi { sig, calldata, input } => {
232 let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?;
233 print_tokens(&tokens)?;
234 }
235 CastSubcommand::AbiEncode { sig, packed, args } => {
236 let out = if packed {
237 SimpleCast::abi_encode_packed(&sig, &args)?
238 } else {
239 SimpleCast::abi_encode(&sig, &args)?
240 };
241 print_scalar(out)?;
242 }
243 CastSubcommand::AbiEncodeEvent { sig, args } => {
246 let log_data = SimpleCast::abi_encode_event(&sig, &args)?;
247 if shell::is_json() {
248 #[derive(serde::Serialize)]
249 struct EncodedEvent {
250 topics: Vec<String>,
251 data: String,
252 }
253 let encoded = EncodedEvent {
254 topics: log_data.topics().iter().map(|t| t.to_string()).collect(),
255 data: hex::encode_prefixed(&log_data.data),
256 };
257 print_json_object(encoded)?;
258 } else {
259 for (i, topic) in log_data.topics().iter().enumerate() {
260 sh_println!("[topic{}]: {}", i, topic)?;
261 }
262 if !log_data.data.is_empty() {
263 sh_println!("[data]: {}", hex::encode_prefixed(log_data.data))?;
264 }
265 }
266 }
267 CastSubcommand::DecodeCalldata { sig, calldata, file } => {
268 let raw_hex = if let Some(file_path) = file {
269 let contents = fs::read_to_string(&file_path)?;
270 contents.trim().to_string()
271 } else {
272 calldata.unwrap()
273 };
274
275 let tokens = SimpleCast::calldata_decode(&sig, &raw_hex, true)?;
276 print_tokens(&tokens)?;
277 }
278 CastSubcommand::CalldataEncode { sig, args, file } => {
279 let final_args = if let Some(file_path) = file {
280 let contents = fs::read_to_string(file_path)?;
281 contents
282 .lines()
283 .map(str::trim)
284 .filter(|line| !line.is_empty())
285 .map(String::from)
286 .collect()
287 } else {
288 args
289 };
290 let out = SimpleCast::calldata_encode(sig, &final_args)?;
291 print_scalar(out)?;
292 }
293 CastSubcommand::DecodeString { data } => {
294 let tokens = SimpleCast::calldata_decode("Any(string)", &data, true)?;
295 print_tokens(&tokens)?;
296 }
297 CastSubcommand::DecodeEvent { sig, data } => {
298 let decoded_event = if let Some(event_sig) = sig {
299 let event = get_event(event_sig.as_str())?;
300 event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)?
301 } else {
302 let data = crate::strip_0x(&data);
303 let selector = data.get(..64).unwrap_or_default();
304 let selector = selector.parse()?;
305 let identified_event =
306 SignaturesIdentifier::new(false)?.identify_event(selector).await;
307 if let Some(event) = identified_event {
308 let _ = sh_println!("{}", event.signature());
309 let data = data.get(64..).unwrap_or_default();
310 get_event(event.signature().as_str())?
311 .decode_log_parts(core::iter::once(selector), &hex::decode(data)?)?
312 } else {
313 eyre::bail!("No matching event signature found for selector `{selector}`")
314 }
315 };
316 print_tokens(&decoded_event.body)?;
317 }
318 CastSubcommand::DecodeError { sig, data } => {
319 let error = if let Some(err_sig) = sig {
320 get_error(err_sig.as_str())?
321 } else {
322 let data = crate::strip_0x(&data);
323 let selector = data.get(..8).unwrap_or_default();
324 let identified_error =
325 SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await;
326 if let Some(error) = identified_error {
327 let _ = sh_println!("{}", error.signature());
328 error
329 } else {
330 eyre::bail!("No matching error signature found for selector `{selector}`")
331 }
332 };
333 let decoded_error = error.decode_error(&hex::decode(data)?)?;
334 print_tokens(&decoded_error.body)?;
335 }
336 CastSubcommand::Interface(cmd) => cmd.run().await?,
337 CastSubcommand::CreationCode(cmd) => cmd.run().await?,
338 CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
339 CastSubcommand::Artifact(cmd) => cmd.run().await?,
340 CastSubcommand::Bind(cmd) => cmd.run().await?,
341 CastSubcommand::B2EPayload(cmd) => cmd.run().await?,
342 CastSubcommand::PrettyCalldata { calldata, offline } => {
343 let calldata = stdin::unwrap_line(calldata)?;
344 let out = pretty_calldata(&calldata, offline).await?.to_string();
345 print_scalar(out)?;
346 }
347 CastSubcommand::Sig { sig, optimize } => {
349 let sig = stdin::unwrap_line(sig)?;
350 match optimize {
351 Some(opt) => {
352 sh_status!("Starting to optimize signature...")?;
353 let start_time = Instant::now();
354 let (selector, signature) = SimpleCast::get_selector(&sig, opt)?;
355 sh_status!("Successfully generated in {:?}", start_time.elapsed())?;
356 sh_println!("Selector: {selector}")?;
357 sh_println!("Optimized signature: {signature}")?;
358 }
359 None => {
360 let out = SimpleCast::get_selector(&sig, 0)?.0;
361 print_scalar(out)?;
362 }
363 }
364 }
365
366 CastSubcommand::AccessList(cmd) => cmd.run().await?,
368 CastSubcommand::Age { block, rpc } => {
369 let config = rpc.load_config()?;
370 let provider = utils::get_provider(&config)?;
371 let out = format!(
372 "{} UTC",
373 Cast::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await?
374 );
375 print_scalar(out)?;
376 }
377 CastSubcommand::Balance { block, who, ether, rpc, erc20 } => {
378 let config = rpc.load_config()?;
379 let provider = utils::get_provider(&config)?;
380 let account_addr = who.resolve(&provider).await?;
381
382 match erc20 {
383 Some(token) => {
384 let balance = IERC20::new(token, &provider)
385 .balanceOf(account_addr)
386 .block(block.unwrap_or_default())
387 .call()
388 .await?;
389
390 sh_warn!("--erc20 flag is deprecated, use `cast erc20 balance` instead")?;
391 print_scalar(format_uint_exp(balance))?;
392 }
393 None => {
394 let value = Cast::new(&provider).balance(account_addr, block).await?;
395 let out = if ether {
396 SimpleCast::from_wei(&value.to_string(), "eth")?
397 } else {
398 value.to_string()
399 };
400 print_scalar(out)?;
401 }
402 }
403 }
404 CastSubcommand::BaseFee { block, rpc } => {
405 let config = rpc.load_config()?;
406 let provider = utils::get_provider(&config)?;
407 let out = Cast::new(provider)
408 .base_fee(block.unwrap_or(BlockId::Number(Latest)))
409 .await?
410 .to_string();
411 print_scalar(out)?;
412 }
413 CastSubcommand::Block { block, full, fields, raw, rpc, network } => {
414 let config = rpc.load_config()?;
415 let is_raw_block = raw || fields.contains(&"raw".into());
417 let output = if is_raw_block {
418 match network {
419 #[cfg(feature = "optimism")]
420 Some(NetworkVariant::Optimism) => {
421 let provider =
422 ProviderBuilder::<Optimism>::from_config(&config)?.build()?;
423
424 Cast::new(&provider)
425 .block_raw(block.unwrap_or(BlockId::Number(Latest)), full)
426 .await?
427 }
428 Some(NetworkVariant::Tempo) => {
429 let provider =
430 ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
431 Cast::new(&provider)
432 .block_raw(block.unwrap_or(BlockId::Number(Latest)), full)
433 .await?
434 }
435 _ => {
437 let provider =
438 ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
439 Cast::new(&provider)
440 .block_raw(block.unwrap_or(BlockId::Number(Latest)), full)
441 .await?
442 }
443 }
444 } else {
445 let provider = utils::get_provider(&config)?;
446 Cast::new(provider)
447 .block(block.unwrap_or(BlockId::Number(Latest)), full, fields)
448 .await?
449 };
450 print_json_value_or_scalar(output)?;
451 }
452 CastSubcommand::BlockNumber { rpc, block } => {
453 let config = rpc.load_config()?;
454 let provider = utils::get_provider(&config)?;
455 let number = match block {
456 Some(id) => {
457 provider
458 .get_block(id)
459 .await?
460 .ok_or_else(|| eyre::eyre!("block {id:?} not found"))?
461 .header
462 .number
463 }
464 None => Cast::new(provider).block_number().await?,
465 };
466 print_scalar(number)?;
467 }
468 CastSubcommand::Chain { rpc } => {
469 let config = rpc.load_config()?;
470 let provider = utils::get_provider(&config)?;
471 let out = Cast::new(provider).chain().await?.to_string();
472 print_scalar(out)?;
473 }
474 CastSubcommand::ChainId { rpc } => {
475 let config = rpc.load_config()?;
476 let provider = utils::get_provider(&config)?;
477 let out = Cast::new(provider).chain_id().await?.to_string();
478 print_scalar(out)?;
479 }
480 CastSubcommand::Client { rpc } => {
481 let config = rpc.load_config()?;
482 let provider = utils::get_provider(&config)?;
483 let out = provider.get_client_version().await?;
484 print_scalar(out)?;
485 }
486 CastSubcommand::Code { block, who, disassemble, rpc } => {
487 let config = rpc.load_config()?;
488 let provider = utils::get_provider(&config)?;
489 let who = who.resolve(&provider).await?;
490 let out = Cast::new(provider).code(who, block, disassemble).await?;
491 print_scalar(out)?;
492 }
493 CastSubcommand::Codesize { block, who, rpc } => {
494 let config = rpc.load_config()?;
495 let provider = utils::get_provider(&config)?;
496 let who = who.resolve(&provider).await?;
497 let out = Cast::new(provider).codesize(who, block).await?;
498 print_scalar(out)?;
499 }
500 CastSubcommand::ComputeAddress { address, nonce, salt, init_code, init_code_hash, rpc } => {
501 let address = stdin::unwrap_line(address)?;
502 let computed = {
503 if let Some(init_code_hash) = init_code_hash {
505 address.create2(salt.unwrap_or(B256::ZERO), init_code_hash)
506 } else if let Some(init_code) = init_code {
507 address.create2(salt.unwrap_or(B256::ZERO), keccak256(hex::decode(init_code)?))
508 } else {
509 let config = rpc.load_config()?;
511 let provider = utils::get_provider(&config)?;
512 Cast::new(provider).compute_address(address, nonce).await?
513 }
514 };
515 let addr = computed.to_checksum(None);
516 print_scalar(addr)?;
517 }
518 CastSubcommand::Disassemble { bytecode } => {
519 let bytecode = stdin::unwrap_line(bytecode)?;
520 let out = SimpleCast::disassemble(&hex::decode(bytecode)?)?;
521 print_scalar(out)?;
522 }
523 CastSubcommand::Selectors { bytecode, resolve } => {
524 let bytecode = stdin::unwrap_line(bytecode)?;
525 let functions = SimpleCast::extract_functions(&bytecode)?;
526
527 let resolve_results: Vec<String> = if resolve {
528 let selectors = functions
529 .iter()
530 .map(|&(selector, ..)| SelectorKind::Function(selector))
531 .collect::<Vec<_>>();
532 let ds = decode_selectors(&selectors).await?;
533 ds.into_iter().map(|v| v.join("|")).collect()
534 } else {
535 vec![]
536 };
537
538 if shell::is_json() {
539 #[derive(serde::Serialize)]
540 struct SelectorInfo {
541 selector: String,
542 arguments: String,
543 state_mutability: String,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 resolved: Option<String>,
546 }
547 let infos: Vec<SelectorInfo> = functions
548 .into_iter()
549 .enumerate()
550 .map(|(pos, (selector, arguments, state_mutability))| SelectorInfo {
551 selector: selector.to_string(),
552 arguments,
553 state_mutability: state_mutability.to_string(),
554 resolved: resolve_results.get(pos).cloned(),
555 })
556 .collect();
557 print_json_object(infos)?;
558 } else {
559 let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0);
560 let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0);
561 for (pos, (selector, arguments, state_mutability)) in
562 functions.into_iter().enumerate()
563 {
564 if resolve {
565 let resolved = &resolve_results[pos];
566 sh_println!(
567 "{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}"
568 )?
569 } else {
570 sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}")?
571 }
572 }
573 }
574 }
575 CastSubcommand::FindBlock(cmd) => cmd.run().await?,
576 CastSubcommand::GasPrice { rpc } => {
577 let config = rpc.load_config()?;
578 let provider = utils::get_provider(&config)?;
579 let out = Cast::new(provider).gas_price().await?.to_string();
580 print_scalar(out)?;
581 }
582 CastSubcommand::Index { key_type, key, slot_number } => {
583 let out = SimpleCast::index(&key_type, &key, &slot_number)?;
584 print_scalar(out)?;
585 }
586 CastSubcommand::IndexErc7201 { id, formula_id } => {
587 eyre::ensure!(formula_id == "erc7201", "unsupported formula ID: {formula_id}");
588 let id = stdin::unwrap_line(id)?;
589 let out = foundry_common::erc7201(&id).to_string();
590 print_scalar(out)?;
591 }
592 CastSubcommand::Implementation { block, beacon, who, rpc } => {
593 let config = rpc.load_config()?;
594 let provider = utils::get_provider(&config)?;
595 let who = who.resolve(&provider).await?;
596 let out = Cast::new(provider).implementation(who, beacon, block).await?;
597 print_scalar(out)?;
598 }
599 CastSubcommand::Admin { block, who, rpc } => {
600 let config = rpc.load_config()?;
601 let provider = utils::get_provider(&config)?;
602 let who = who.resolve(&provider).await?;
603 let out = Cast::new(provider).admin(who, block).await?;
604 print_scalar(out)?;
605 }
606 CastSubcommand::Nonce { block, who, rpc } => {
607 let config = rpc.load_config()?;
608 let provider = utils::get_provider(&config)?;
609 let who = who.resolve(&provider).await?;
610 let out = Cast::new(provider).nonce(who, block).await?;
611 print_scalar(out)?;
612 }
613 CastSubcommand::Codehash { block, who, slots, rpc } => {
614 let config = rpc.load_config()?;
615 let provider = utils::get_provider(&config)?;
616 let who = who.resolve(&provider).await?;
617 let out = Cast::new(provider).codehash(who, slots, block).await?;
618 print_scalar(out)?;
619 }
620 CastSubcommand::StorageRoot { block, who, slots, rpc } => {
621 let config = rpc.load_config()?;
622 let provider = utils::get_provider(&config)?;
623 let who = who.resolve(&provider).await?;
624 let out = Cast::new(provider).storage_root(who, slots, block).await?;
625 print_scalar(out)?;
626 }
627 CastSubcommand::ChannelId {
628 payer,
629 payee,
630 token,
631 salt,
632 operator,
633 authorized_signer,
634 expiring_nonce_hash,
635 reserve,
636 block,
637 rpc,
638 } => {
639 let config = rpc.load_config()?;
640 let provider = utils::get_provider(&config)?;
641 let payer = payer.resolve(&provider).await?;
642 let payee = payee.resolve(&provider).await?;
643 let token = token.resolve(&provider).await?;
644 let operator = match operator {
645 Some(operator) => operator.resolve(&provider).await?,
646 None => Address::ZERO,
647 };
648 let authorized_signer = match authorized_signer {
649 Some(authorized_signer) => authorized_signer.resolve(&provider).await?,
650 None => Address::ZERO,
651 };
652 let reserve = match reserve {
653 Some(reserve) => reserve.resolve(&provider).await?,
654 None => TIP20_CHANNEL_RESERVE_ADDRESS,
655 };
656
657 let channel_id = ITIP20ChannelReserve::new(reserve, &provider)
658 .computeChannelId(
659 payer,
660 payee,
661 operator,
662 token,
663 salt,
664 authorized_signer,
665 expiring_nonce_hash,
666 )
667 .block(block.unwrap_or_default())
668 .call()
669 .await?;
670 print_scalar(format!("{channel_id:#x}"))?;
671 }
672 CastSubcommand::Proof { address, slots, rpc, block } => {
673 let config = rpc.load_config()?;
674 let provider = utils::get_provider(&config)?;
675 let address = address.resolve(&provider).await?;
676 let value = provider
677 .get_proof(address, slots.into_iter().collect())
678 .block_id(block.unwrap_or_default())
679 .await?;
680 print_json_object(value)?;
681 }
682 CastSubcommand::Rpc(cmd) => cmd.run().await?,
683 CastSubcommand::Storage(cmd) => cmd.run().await?,
684
685 CastSubcommand::Call(cmd) => cmd.run().await?,
687 CastSubcommand::Estimate(cmd) => cmd.run().await?,
688 CastSubcommand::MakeTx(cmd) => cmd.run().await?,
689 CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => {
690 let config = rpc.load_config()?;
691 let provider = utils::get_provider(&config)?;
692 let cast = Cast::new(&provider);
693 let pending_tx = cast.publish(raw_tx).await?;
694 let tx_hash = pending_tx.inner().tx_hash();
695
696 if cast_async {
697 print_scalar(format!("{tx_hash:#x}"))?;
698 } else {
699 let receipt = pending_tx.get_receipt().await?;
700 print_json_object(receipt)?;
701 }
702 }
703 CastSubcommand::Receipt { tx_hash, field, cast_async, confirmations, rpc } => {
704 let config = rpc.load_config()?;
705 let provider = utils::get_provider(&config)?;
706 sh_println!(
708 "{}",
709 CastTxSender::new(provider)
710 .receipt(tx_hash, field, confirmations, None, cast_async)
711 .await?
712 )?
713 }
714 CastSubcommand::Run(cmd) => cmd.run().await?,
715 CastSubcommand::SendTx(cmd) => cmd.run().await?,
716 CastSubcommand::BatchMakeTx(cmd) => cmd.run().await?,
717 CastSubcommand::BatchSend(cmd) => cmd.run().await?,
718 CastSubcommand::Classify { raw_tx } => {
719 let raw_tx = stdin::unwrap_line(raw_tx)?;
720 print_json_value_or_scalar(classify_raw_transaction_output(&raw_tx)?)?
721 }
722 CastSubcommand::Tx { tx_hash, from, nonce, field, raw, lane, rpc, to_request, network } => {
723 let config = rpc.load_config()?;
724 let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw");
726 let output = match network {
727 #[cfg(feature = "optimism")]
728 Some(NetworkVariant::Optimism) => {
729 let provider = ProviderBuilder::<Optimism>::from_config(&config)?.build()?;
730
731 Cast::new(&provider)
732 .transaction(tx_hash, from, nonce, field, is_raw, to_request, lane)
733 .await?
734 }
735 Some(NetworkVariant::Tempo) => {
736 let provider =
737 ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
738 Cast::new(&provider)
739 .transaction(tx_hash, from, nonce, field, is_raw, to_request, lane)
740 .await?
741 }
742 _ => {
744 let provider = utils::get_provider(&config)?;
745 Cast::new(&provider)
746 .transaction(tx_hash, from, nonce, field, is_raw, to_request, lane)
747 .await?
748 }
749 };
750 print_json_value_or_scalar(output)?;
751 }
752
753 CastSubcommand::FourByte { selector } => {
755 let selector = stdin::unwrap_line(selector)?;
756 let sigs = decode_function_selector(selector).await?;
757 if sigs.is_empty() {
758 eyre::bail!("No matching function signatures found for selector `{selector}`");
759 }
760 print_list(&sigs)?;
761 }
762
763 CastSubcommand::FourByteCalldata { calldata } => {
766 let calldata = stdin::unwrap_line(calldata)?;
767
768 if calldata.len() == 10 {
769 let sigs = decode_function_selector(calldata.parse()?).await?;
770 if sigs.is_empty() {
771 eyre::bail!("No matching function signatures found for calldata `{calldata}`");
772 }
773 for sig in sigs {
774 sh_println!("{sig}")?
775 }
776 return Ok(());
777 }
778
779 let sigs = decode_calldata(&calldata).await?;
780 sigs.iter().enumerate().for_each(|(i, sig)| {
781 let _ = sh_println!("{}) \"{sig}\"", i + 1);
782 });
783
784 let sig = match sigs.len() {
785 0 => eyre::bail!("No signatures found"),
786 1 => sigs.first().unwrap(),
787 _ => {
788 let i: usize = prompt!("Select a function signature by number: ")?;
789 sigs.get(i - 1).ok_or_else(|| eyre::eyre!("Invalid signature index"))?
790 }
791 };
792
793 let tokens = SimpleCast::calldata_decode(sig, &calldata, true)?;
794 print_tokens(&tokens)?;
795 }
796
797 CastSubcommand::FourByteEvent { topic } => {
798 let topic = stdin::unwrap_line(topic)?;
799 let sigs = decode_event_topic(topic).await?;
800 if sigs.is_empty() {
801 eyre::bail!("No matching event signatures found for topic `{topic}`");
802 }
803 print_list(&sigs)?;
804 }
805 CastSubcommand::UploadSignature { signatures } => {
808 let signatures = stdin::unwrap_vec(signatures)?;
809 let ParsedSignatures { signatures, abis } = parse_signatures(signatures);
810 if !abis.is_empty() {
811 import_selectors(SelectorImportData::Abi(abis)).await?.describe();
812 }
813 if !signatures.is_empty() {
814 import_selectors(SelectorImportData::Raw(signatures)).await?.describe();
815 }
816 }
817
818 CastSubcommand::Namehash { name } => {
820 let name = stdin::unwrap_line(name)?;
821 let out = namehash(&name).to_string();
822 print_scalar(out)?;
823 }
824 CastSubcommand::LookupAddress { who, rpc, verify } => {
825 let config = rpc.load_config()?;
826 let provider = utils::get_provider(&config)?;
827
828 let who = stdin::unwrap_line(who)?;
829 let name = provider.lookup_address(&who).await?;
830 if verify {
831 let address = provider.resolve_name(&name).await?;
832 eyre::ensure!(
833 address == who,
834 "Reverse lookup verification failed: got `{address}`, expected `{who}`"
835 );
836 }
837 print_scalar(name)?;
838 }
839 CastSubcommand::ResolveName { who, rpc, verify } => {
840 let config = rpc.load_config()?;
841 let provider = utils::get_provider(&config)?;
842
843 let who = stdin::unwrap_line(who)?;
844 let address = provider
845 .resolve_name(&who)
846 .await
847 .wrap_err(format!("Failed to resolve ENS name: {who}"))?;
848 if verify {
849 let name = provider.lookup_address(&address).await?;
850 eyre::ensure!(
851 name == who,
852 "Forward lookup verification failed: got `{name}`, expected `{who}`"
853 );
854 }
855 print_scalar(address.to_string())?;
856 }
857
858 CastSubcommand::Keccak { data } => {
860 let bytes = match data {
861 Some(data) => data.into_bytes(),
862 None => stdin::read_bytes(false)?,
863 };
864 let out = match String::from_utf8(bytes) {
865 Ok(s) => SimpleCast::keccak(&s)?,
866 Err(e) => format!("0x{}", hex::encode(keccak256(e.as_bytes()))),
867 };
868 print_scalar(out)?;
869 }
870 CastSubcommand::HashMessage { message } => {
871 let message = stdin::unwrap(message, false)?;
872 let out = eip191_hash_message(message).to_string();
873 print_scalar(out)?;
874 }
875 CastSubcommand::SigEvent { event_string } => {
876 let event_string = stdin::unwrap_line(event_string)?;
877 let parsed_event = get_event(&event_string)?;
878 print_scalar(format!("{:?}", parsed_event.selector()))?;
879 }
880 CastSubcommand::LeftShift { value, bits, base_in, base_out } => {
881 let out = SimpleCast::left_shift(&value, &bits, base_in.as_deref(), &base_out)?;
882 print_scalar(out)?;
883 }
884 CastSubcommand::RightShift { value, bits, base_in, base_out } => {
885 let out = SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)?;
886 print_scalar(out)?;
887 }
888 CastSubcommand::Source {
890 address,
891 directory,
892 explorer_api_url,
893 explorer_url,
894 etherscan,
895 flatten,
896 } => {
897 let config = etherscan.load_config()?;
898 let chain = config.chain.unwrap_or_default();
899 let api_key = config.get_etherscan_api_key(Some(chain));
900 match (directory, flatten) {
901 (Some(dir), false) => {
902 SimpleCast::expand_etherscan_source_to_directory(
903 chain,
904 address,
905 api_key,
906 dir,
907 explorer_api_url,
908 explorer_url,
909 )
910 .await?
911 }
912 (None, false) => sh_println!(
913 "{}",
914 SimpleCast::etherscan_source(
915 chain,
916 address,
917 api_key,
918 explorer_api_url,
919 explorer_url
920 )
921 .await?
922 )?,
923 (dir, true) => {
924 SimpleCast::etherscan_source_flatten(
925 chain,
926 address,
927 api_key,
928 dir,
929 explorer_api_url,
930 explorer_url,
931 )
932 .await?;
933 }
934 }
935 }
936 CastSubcommand::Create2(cmd) => {
937 cmd.run()?;
938 }
939 CastSubcommand::Wallet { command } => command.run().await?,
940 CastSubcommand::Completions { shell } => {
941 generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout())
942 }
943 CastSubcommand::Logs(cmd) => cmd.run().await?,
944 CastSubcommand::DecodeTransaction { tx, network } => {
945 let tx = stdin::unwrap_line(tx)?;
946 let decoded_tx = match network {
947 #[cfg(feature = "optimism")]
948 Some(NetworkVariant::Optimism) => {
949 SimpleCast::decode_raw_transaction::<Optimism>(&tx)?
950 }
951 Some(NetworkVariant::Tempo) => {
952 SimpleCast::decode_raw_transaction::<TempoNetwork>(&tx)?
953 }
954 _ => SimpleCast::decode_raw_transaction::<Ethereum>(&tx)?,
955 };
956 print_json_object(decoded_tx)?;
957 }
958 CastSubcommand::RecoverAuthority { auth } => {
959 let auth: SignedAuthorization = serde_json::from_str(&auth)?;
960 let out = auth.recover_authority()?.to_string();
961 print_scalar(out)?;
962 }
963 CastSubcommand::TxPool { command } => command.run().await?,
964 CastSubcommand::Erc20Token { command } => command.run().await?,
965 CastSubcommand::Tip20Token { command } => command.run().await?,
966 CastSubcommand::Keychain { command } => command.run().await?,
967 CastSubcommand::KeyAuth { command } => command.run().await?,
968 CastSubcommand::Tempo { command } => command.run().await?,
969 CastSubcommand::VirtualAddress { command } => command.run().await?,
970 #[cfg(feature = "optimism")]
971 CastSubcommand::DAEstimate(cmd) => {
972 cmd.run().await?;
973 }
974 CastSubcommand::Trace(cmd) => cmd.run().await?,
975 };
976
977 Ok(())
978}
979
980pub(crate) fn classify_raw_transaction_output(raw_tx: &str) -> Result<String> {
981 let raw_tx = hex::decode(raw_tx)?;
982 let mut data = raw_tx.as_slice();
983 let tx =
984 FoundryTxEnvelope::decode_2718(&mut data).wrap_err("failed to decode raw transaction")?;
985 format_lane_classification(&tx.classify_t5_payment_lane())
986}
987
988pub(crate) fn format_lane_classification(
989 classification: &PaymentLaneClassification,
990) -> Result<String> {
991 if shell::is_json() {
992 Ok(serde_json::to_string_pretty(classification)?)
993 } else {
994 Ok(serde_json::to_string(classification)?)
995 }
996}
997
998#[cfg(test)]
999mod tests {
1000 use super::*;
1001 use foundry_cli::introspect::{
1002 CommandRegistry, INTROSPECT_SCHEMA_ID, IntrospectDocument, OutputMode, build_document,
1003 capability_violations, duplicate_command_ids, render_introspect_document,
1004 };
1005
1006 #[test]
1015 fn introspect_command_ids_are_unique() {
1016 let dups = std::thread::Builder::new()
1017 .stack_size(16 * 1024 * 1024)
1018 .spawn(|| {
1019 let cmd = CastArgs::command();
1020 let doc = build_document(&cmd, ®ISTRY);
1021 duplicate_command_ids(&doc)
1022 })
1023 .expect("spawn worker thread")
1024 .join()
1025 .expect("worker thread join");
1026 assert!(dups.is_empty(), "duplicate cast command_ids: {dups:?}");
1027 }
1028
1029 #[test]
1033 fn introspect_document_is_valid_json() {
1034 let json = std::thread::Builder::new()
1035 .stack_size(16 * 1024 * 1024)
1036 .spawn(|| {
1037 let cmd = CastArgs::command();
1038 render_introspect_document(&cmd, &CommandRegistry::EMPTY)
1039 })
1040 .expect("spawn worker thread")
1041 .join()
1042 .expect("worker thread join");
1043 let doc: IntrospectDocument = serde_json::from_str(&json).expect("valid JSON");
1044 assert_eq!(doc.schema_id, INTROSPECT_SCHEMA_ID);
1045 assert_eq!(doc.binary.name, "cast");
1046 }
1047
1048 #[test]
1052 fn introspect_capabilities_are_consistent() {
1053 let v = std::thread::Builder::new()
1054 .stack_size(16 * 1024 * 1024)
1055 .spawn(|| {
1056 let cmd = CastArgs::command();
1057 let doc = build_document(&cmd, ®ISTRY);
1058 capability_violations(&doc)
1059 })
1060 .expect("spawn worker thread")
1061 .join()
1062 .expect("worker thread join");
1063 assert!(v.is_empty(), "cast capability violations: {v:?}");
1064 }
1065
1066 #[test]
1069 fn registered_commands_pin_stable_ids() {
1070 let ids = std::thread::Builder::new()
1071 .stack_size(16 * 1024 * 1024)
1072 .spawn(|| {
1073 let cmd = <CastArgs as clap::CommandFactory>::command();
1074 let doc = build_document(&cmd, ®ISTRY);
1075 fn walk(c: &foundry_cli::introspect::CommandInfo) -> Vec<String> {
1076 let mut out = Vec::new();
1077 if matches!(c.capabilities.output_mode, OutputMode::Envelope) {
1078 out.push(c.command_id.clone());
1079 }
1080 for sub in &c.subcommands {
1081 out.extend(walk(sub));
1082 }
1083 out
1084 }
1085 doc.commands.iter().flat_map(walk).collect::<Vec<_>>()
1086 })
1087 .expect("spawn worker thread")
1088 .join()
1089 .expect("worker thread join");
1090 assert!(
1091 ids.iter().any(|s| s == "cast.call"),
1092 "cast.call missing from envelope ids: {ids:?}"
1093 );
1094 }
1095}