Skip to main content

cast/
args.rs

1use crate::{
2    base::{Base, NumberWithBase},
3    cmd::{erc20::IERC20, rpc_provider},
4    opts::{Cast as CastArgs, CastSubcommand, ToBaseArgs},
5    traces::identifier::SignaturesIdentifier,
6    tx::CastTxSender,
7};
8use alloy_consensus::{
9    Typed2718,
10    transaction::{Recovered, SignerRecoverable},
11};
12use alloy_dyn_abi::{DynSolType, DynSolValue, ErrorExt, EventExt, Specifier};
13use alloy_eips::{Encodable2718, eip7702::SignedAuthorization};
14use alloy_ens::{NameOrAddress, ProviderEnsExt, namehash};
15use alloy_network::{BlockResponse, Ethereum, Network, eip2718::Decodable2718};
16use alloy_primitives::{
17    Address, B256, Bytes, I256, Keccak256, LogData, TxHash, U64, U256, b256, eip191_hash_message,
18    hex, keccak256,
19    utils::{ParseUnits, Unit},
20};
21use alloy_provider::Provider;
22use alloy_rlp::Decodable;
23use alloy_rpc_types::BlockId;
24use clap::{CommandFactory, Parser};
25use clap_complete::generate;
26use eyre::{ContextCompat, OptionExt, Result, WrapErr};
27use foundry_block_explorers::Client;
28use foundry_cli::{
29    json::{print_json_object, print_json_value_or_scalar, print_list, print_scalar, print_tokens},
30    opts::RpcOpts,
31    utils::{self, LoadConfig},
32};
33use foundry_common::{
34    abi::{
35        abi_decode_calldata, encode_function_args, encode_function_args_packed, get_error,
36        get_event, get_func,
37    },
38    fmt::{UIfmt, UIfmtSignatureExt, format_uint_exp, get_pretty_block_attr, get_pretty_tx_attr},
39    fs,
40    provider::{ProviderBuilder, RetryProvider},
41    selectors::{
42        ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic,
43        decode_function_selector, decode_selectors, import_selectors, parse_signatures,
44        pretty_calldata,
45    },
46    shell, stdin,
47    tempo::classify_payment_lane,
48};
49use foundry_config::Chain;
50use foundry_evm_networks::NetworkVariant;
51use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope};
52use rayon::prelude::*;
53use serde::Serialize;
54use std::{
55    str::FromStr,
56    sync::atomic::{AtomicBool, Ordering},
57    time::Instant,
58};
59use tempo_alloy::TempoNetwork;
60use tempo_contracts::precompiles::{ITIP20ChannelReserve, TIP20_CHANNEL_RESERVE_ADDRESS};
61
62#[cfg(feature = "base")]
63use base_common_network::Base as BaseNetwork;
64
65#[cfg(feature = "optimism")]
66use op_alloy_network::Optimism;
67
68/// Runs `$body` with `$provider` bound to a provider for the selected `--network`.
69/// Optionally binds `$network_type` to the selected network type.
70///
71/// The fallback arm is used for Ethereum and when no network is selected: either `$default` is a
72/// provider expression that `$body` runs against, or a full `_ => $default` arm.
73macro_rules! with_network_provider {
74    ($network:expr, $config:expr, $default:expr, |$provider:ident $(, $network_type:ident)?| $body:expr) => {
75        with_network_provider!($network, $config, |$provider $(, $network_type)?| $body, _ => {
76            $(type $network_type = Ethereum;)?
77            let $provider = $default;
78            $body
79        })
80    };
81    ($network:expr, $config:expr, |$provider:ident $(, $network_type:ident)?| $body:expr, _ => $default:expr) => {
82        match $network {
83            #[cfg(feature = "base")]
84            Some(NetworkVariant::Base) => {
85                $(type $network_type = BaseNetwork;)?
86                let $provider = ProviderBuilder::<BaseNetwork>::from_config($config)?.build()?;
87                $body
88            }
89            #[cfg(feature = "optimism")]
90            Some(NetworkVariant::Optimism) => {
91                $(type $network_type = Optimism;)?
92                let $provider = ProviderBuilder::<Optimism>::from_config($config)?.build()?;
93                $body
94            }
95            Some(NetworkVariant::Tempo) => {
96                $(type $network_type = TempoNetwork;)?
97                let $provider = ProviderBuilder::<TempoNetwork>::from_config($config)?.build()?;
98                $body
99            }
100            _ => $default,
101        }
102    };
103}
104
105/// Run the `cast` command-line interface.
106pub fn run() -> Result<()> {
107    foundry_cli::opts::GlobalArgs::check_markdown_help::<CastArgs>();
108
109    setup()?;
110
111    let args = CastArgs::parse();
112    args.global.init()?;
113    args.global.tokio_runtime().block_on(run_command(args))
114}
115
116/// Setup the global logger and other utilities.
117pub fn setup() -> Result<()> {
118    utils::common_setup();
119    utils::subscriber();
120
121    Ok(())
122}
123
124/// Run the subcommand.
125#[allow(clippy::large_stack_frames)]
126pub async fn run_command(args: CastArgs) -> Result<()> {
127    match args.cmd {
128        // Constants
129        CastSubcommand::MaxInt { r#type } | CastSubcommand::MaxUint { r#type } => {
130            print_scalar(int_bound(&r#type, true)?)?;
131        }
132        CastSubcommand::MinInt { r#type } => print_scalar(int_bound(&r#type, false)?)?,
133        CastSubcommand::AddressZero => print_scalar(format!("{:?}", Address::ZERO))?,
134        CastSubcommand::HashZero => print_scalar(format!("{:?}", B256::ZERO))?,
135
136        // Conversions & transformations
137        CastSubcommand::FromUtf8 { text } => {
138            print_scalar(hex::encode_prefixed(stdin::unwrap(text, false)?))?;
139        }
140        CastSubcommand::ToAscii { hexdata } => {
141            let bytes = hex::decode(stdin::unwrap(hexdata, false)?.trim())?;
142            eyre::ensure!(bytes.iter().all(u8::is_ascii), "Invalid ASCII bytes");
143            print_scalar(String::from_utf8(bytes).unwrap())?;
144        }
145        CastSubcommand::ToUtf8 { hexdata } => {
146            let bytes = hex::decode(stdin::unwrap(hexdata, false)?)?;
147            print_scalar(String::from_utf8_lossy(&bytes).into_owned())?;
148        }
149        CastSubcommand::FromFixedPoint { value, decimals } => {
150            let (value, decimals) = stdin::unwrap2(value, decimals)?;
151            print_scalar(ParseUnits::parse_units(&value, Unit::from_str(&decimals)?)?.to_string())?;
152        }
153        CastSubcommand::ToFixedPoint { value, decimals } => {
154            let (value, decimals) = stdin::unwrap2(value, decimals)?;
155
156            let number = NumberWithBase::parse_int(&value, None)?;
157            let sign = if number.is_nonnegative() { "" } else { "-" };
158            let mut value = number.to_string().trim_start_matches('-').to_string();
159            let value_len = value.len();
160            let decimals_num = NumberWithBase::parse_uint(&decimals, None)?.number();
161            let decimals: usize = decimals_num
162                .try_into()
163                .ok()
164                .filter(|&d: &usize| d <= u16::MAX as usize)
165                .ok_or_else(|| eyre::eyre!("decimals out of range: {decimals_num}"))?;
166
167            if decimals >= value_len {
168                value = format!("0.{value:0>decimals$}");
169            } else {
170                value.insert(value_len - decimals, '.');
171            }
172            print_scalar(format!("{sign}{value}"))?;
173        }
174        CastSubcommand::ConcatHex { data } => {
175            let input;
176            let values = if data.is_empty() {
177                input = stdin::read(true)?;
178                itertools::Either::Left(input.split_whitespace())
179            } else {
180                itertools::Either::Right(data.iter().map(String::as_str))
181            };
182            let out = values.map(strip_0x).collect::<String>();
183            print_scalar(format!("0x{out}"))?;
184        }
185        CastSubcommand::FromBin => {
186            print_scalar(hex::encode_prefixed(stdin::read_bytes(false)?))?;
187        }
188        CastSubcommand::ToHexdata { input } => {
189            let value = stdin::unwrap_line(input)?;
190            let output = match value {
191                s if s.starts_with('@') => hex::encode(std::env::var(&s[1..])?),
192                s if s.starts_with('/') => hex::encode(fs::read(s)?),
193                s => s.split(':').map(|s| s.trim_start_matches("0x").to_lowercase()).collect(),
194            };
195            print_scalar(format!("0x{output}"))?;
196        }
197        CastSubcommand::ToCheckSumAddress { address, chain_id } => {
198            print_scalar(stdin::unwrap_line(address)?.to_checksum(chain_id))?;
199        }
200        CastSubcommand::ToUint256 { value } => {
201            let n = NumberWithBase::parse_uint(&stdin::unwrap_line(value)?, None)?;
202            print_scalar(format!("{n:#066x}"))?;
203        }
204        CastSubcommand::ToInt256 { value } => {
205            let n = NumberWithBase::parse_int(&stdin::unwrap_line(value)?, None)?;
206            print_scalar(format!("{n:#066x}"))?;
207        }
208        CastSubcommand::ToUnit { value, unit } => {
209            let value = stdin::unwrap_line(value)?;
210            let value = DynSolType::coerce_str(&DynSolType::Uint(256), &value)?
211                .as_uint()
212                .wrap_err("Could not convert to uint")?
213                .0;
214            let unit = unit.parse().wrap_err("could not parse units")?;
215            print_scalar(format_unit_as_string(ParseUnits::U256(value), unit))?;
216        }
217        CastSubcommand::ParseUnits { value, unit } => {
218            let value = stdin::unwrap_line(value)?;
219            let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?;
220
221            print_scalar(ParseUnits::parse_units(&value, unit)?.to_string())?;
222        }
223        CastSubcommand::FormatUnits { value, unit } => {
224            print_scalar(format_units(&stdin::unwrap_line(value)?, unit)?)?;
225        }
226        CastSubcommand::FromWei { value, unit } => {
227            print_scalar(
228                signed_parse_units(&NumberWithBase::parse_int(&stdin::unwrap_line(value)?, None)?)?
229                    .format_units(unit.parse()?),
230            )?;
231        }
232        CastSubcommand::ToWei { value, unit } => {
233            let value = stdin::unwrap_line(value)?;
234            let unit = unit.parse().wrap_err("could not parse units")?;
235            print_scalar(ParseUnits::parse_units(&value, unit)?.to_string())?;
236        }
237        CastSubcommand::FromRlp { value, as_int } => {
238            let bytes = hex::decode(stdin::unwrap_line(value)?).wrap_err("Could not decode hex")?;
239            let value = if as_int {
240                U256::decode(&mut &bytes[..])?.to_string()
241            } else {
242                crate::rlp_converter::Item::decode(&mut &bytes[..])
243                    .wrap_err("Could not decode rlp")?
244                    .to_string()
245            };
246            print_scalar(value)?;
247        }
248        CastSubcommand::ToRlp { value } => {
249            let value = stdin::unwrap_line(value)?;
250            let val =
251                serde_json::from_str(&value).unwrap_or_else(|_| serde_json::Value::String(value));
252            let item = crate::rlp_converter::Item::value_to_item(&val)?;
253            print_scalar(format!("0x{}", hex::encode(alloy_rlp::encode(item))))?;
254        }
255        CastSubcommand::ToHex(ToBaseArgs { value, base_in }) => {
256            let value = stdin::unwrap_line(value)?;
257            print_scalar(to_base(&value, base_in.as_deref(), "hex")?)?;
258        }
259        CastSubcommand::ToDec(ToBaseArgs { value, base_in }) => {
260            let value = stdin::unwrap_line(value)?;
261            print_scalar(to_base(&value, base_in.as_deref(), "dec")?)?;
262        }
263        CastSubcommand::ToBase { base: ToBaseArgs { value, base_in }, base_out } => {
264            let (value, base_out) = stdin::unwrap2(value, base_out)?;
265            print_scalar(to_base(&value, base_in.as_deref(), &base_out)?)?;
266        }
267        CastSubcommand::ToBytes32 { bytes } => {
268            let s = stdin::unwrap_line(bytes)?;
269            let s = strip_0x(&s);
270            if s.len() > 64 {
271                eyre::bail!("string >32 bytes");
272            }
273
274            let padded = format!("{s:0<64}");
275            print_scalar(padded.parse::<B256>()?.to_string())?;
276        }
277        CastSubcommand::ToBytesMemory { data } => {
278            let data = stdin::unwrap_line(data)?;
279            const WORD: usize = 32;
280
281            let data = hex::decode(data).wrap_err("Could not decode hex")?;
282            let padded_len = data.len().next_multiple_of(WORD);
283            let mut out = Vec::with_capacity(WORD + padded_len);
284            out.extend_from_slice(&U256::from(data.len()).to_be_bytes::<WORD>());
285            out.extend_from_slice(&data);
286            out.resize(WORD + padded_len, 0);
287            print_scalar(hex::encode_prefixed(out))?;
288        }
289        CastSubcommand::Pad { data, right, left: _, len } => {
290            let s = stdin::unwrap_line(data)?;
291            let s = strip_0x(&s);
292            let hex_len = len
293                .checked_mul(2)
294                .filter(|&h| h <= u16::MAX as usize)
295                .ok_or_else(|| eyre::eyre!("len out of range: {len}"))?;
296
297            // Validate input
298            if s.len() > hex_len {
299                eyre::bail!("input length exceeds target length");
300            }
301            if !s.chars().all(|c| c.is_ascii_hexdigit()) {
302                eyre::bail!("input is not a valid hex");
303            }
304
305            print_scalar(if right {
306                format!("0x{s:0<hex_len$}")
307            } else {
308                format!("0x{s:0>hex_len$}")
309            })?;
310        }
311        CastSubcommand::FormatBytes32String { string } => {
312            let s = stdin::unwrap_line(string)?;
313            let str_bytes: &[u8] = s.as_bytes();
314            eyre::ensure!(
315                str_bytes.len() <= 32,
316                "bytes32 strings must not exceed 32 bytes in length"
317            );
318
319            let mut bytes32: [u8; 32] = [0u8; 32];
320            bytes32[..str_bytes.len()].copy_from_slice(str_bytes);
321            print_scalar(hex::encode_prefixed(bytes32))?;
322        }
323        CastSubcommand::ParseBytes32String { bytes } => {
324            let s = stdin::unwrap_line(bytes)?;
325            let bytes = hex::decode(s)?;
326            eyre::ensure!(bytes.len() == 32, "expected 32 byte hex-string");
327            let len = bytes.iter().take_while(|x| **x != 0).count();
328            print_scalar(std::str::from_utf8(&bytes[..len])?)?;
329        }
330        CastSubcommand::ParseBytes32Address { bytes } => {
331            let s = stdin::unwrap_line(bytes)?;
332            let s = strip_0x(&s);
333            if s.len() != 64 {
334                eyre::bail!("expected 64 byte hex-string, got {s}");
335            }
336            let Some(s) = s.strip_prefix("000000000000000000000000") else {
337                eyre::bail!("Not convertible to address, there are non-zero bytes");
338            };
339            print_scalar(Address::from_str(s)?.to_checksum(None))?;
340        }
341
342        // ABI encoding & decoding
343        CastSubcommand::DecodeAbi { sig, calldata, input } => {
344            print_tokens(&abi_decode_calldata(&sig, &calldata, input, false)?)?;
345        }
346        CastSubcommand::AbiEncode { sig, packed, args } => {
347            let out = if packed {
348                // If the signature is a tuple, we need to prefix it to make it a function
349                let sig = if sig.trim_start().starts_with('(') { format!("foo{sig}") } else { sig };
350
351                let func = get_func(&sig)?;
352                let encoded = encode_function_args_packed(&func, &args).map_err(|e| {
353                    eyre::eyre!("Could not ABI encode the function and arguments: {e}")
354                })?;
355                hex::encode_prefixed(encoded)
356            } else {
357                let func = get_func(&sig)?;
358                let encoded = encode_function_args(&func, &args).map_err(|e| {
359                    eyre::eyre!("Could not ABI encode the function and arguments: {e}")
360                })?;
361                hex::encode_prefixed(&encoded[4..])
362            };
363            print_scalar(out)?;
364        }
365        // TODO(json): multi-line output (one line per topic + data field), needs structured object
366        // envelope
367        CastSubcommand::AbiEncodeEvent { sig, args } => {
368            let event = get_event(&sig)?;
369            if event.inputs.len() != args.len() {
370                eyre::bail!(
371                    "encode length mismatch: expected {} types, got {}",
372                    event.inputs.len(),
373                    args.len(),
374                );
375            }
376
377            let types = event
378                .inputs
379                .iter()
380                .map(Specifier::<DynSolType>::resolve)
381                .collect::<Result<Vec<_>, _>>()?;
382            let tokens = std::iter::zip(&types, &args)
383                .map(|(ty, arg)| Ok(DynSolType::coerce_str(ty, arg.as_ref())?))
384                .collect::<Result<Vec<_>>>()?;
385
386            let mut topics = if event.anonymous { vec![] } else { vec![event.selector()] };
387            // Non-indexed parameters are encoded together as the event body.
388            let mut data_tokens = Vec::new();
389            for (input, token) in event.inputs.iter().zip(tokens) {
390                if input.indexed {
391                    topics.push(encode_event_topic(&token));
392                } else {
393                    data_tokens.push(token);
394                }
395            }
396
397            let data = DynSolValue::Tuple(data_tokens).abi_encode_params();
398            let log_data = LogData::new_unchecked(topics, data.into());
399            if shell::is_json() {
400                #[derive(serde::Serialize)]
401                struct EncodedEvent {
402                    topics: Vec<String>,
403                    data: String,
404                }
405                print_json_object(EncodedEvent {
406                    topics: log_data.topics().iter().map(|t| t.to_string()).collect(),
407                    data: hex::encode_prefixed(&log_data.data),
408                })?;
409            } else {
410                for (i, topic) in log_data.topics().iter().enumerate() {
411                    sh_println!("[topic{i}]: {topic}")?;
412                }
413                if !log_data.data.is_empty() {
414                    sh_println!("[data]: {}", hex::encode_prefixed(log_data.data))?;
415                }
416            }
417        }
418        CastSubcommand::DecodeCalldata { sig, calldata, file } => {
419            let raw_hex = match file {
420                Some(file_path) => fs::read_to_string(&file_path)?.trim().to_string(),
421                None => calldata.unwrap(),
422            };
423            print_tokens(&abi_decode_calldata(&sig, &raw_hex, true, true)?)?;
424        }
425        CastSubcommand::CalldataEncode { sig, args, file } => {
426            let args = match file {
427                Some(file_path) => fs::read_to_string(file_path)?
428                    .lines()
429                    .map(str::trim)
430                    .filter(|line| !line.is_empty())
431                    .map(String::from)
432                    .collect(),
433                None => args,
434            };
435            print_scalar(hex::encode_prefixed(encode_function_args(&get_func(&sig)?, &args)?))?;
436        }
437        CastSubcommand::DecodeString { data } => {
438            print_tokens(&abi_decode_calldata("Any(string)", &data, true, true)?)?;
439        }
440        CastSubcommand::DecodeEvent { sig, data } => {
441            let decoded_event = if let Some(event_sig) = sig {
442                let event = get_event(&event_sig)?;
443                event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)?
444            } else {
445                let data = strip_0x(&data);
446                let selector: B256 = data.get(..64).unwrap_or_default().parse()?;
447                let Some(event) = SignaturesIdentifier::new(false)?.identify_event(selector).await
448                else {
449                    eyre::bail!("No matching event signature found for selector `{selector}`");
450                };
451                let _ = sh_println!("{}", event.signature());
452                let data = data.get(64..).unwrap_or_default();
453                get_event(&event.signature())?
454                    .decode_log_parts(core::iter::once(selector), &hex::decode(data)?)?
455            };
456            print_tokens(&decoded_event.body)?;
457        }
458        CastSubcommand::DecodeError { sig, data } => {
459            let error = if let Some(err_sig) = sig {
460                get_error(&err_sig)?
461            } else {
462                let data = strip_0x(&data);
463                let selector = data.get(..8).unwrap_or_default();
464                let Some(error) =
465                    SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await
466                else {
467                    eyre::bail!("No matching error signature found for selector `{selector}`");
468                };
469                let _ = sh_println!("{}", error.signature());
470                error
471            };
472            print_tokens(&error.decode_error(&hex::decode(data)?)?.body)?;
473        }
474        CastSubcommand::Interface(cmd) => cmd.run().await?,
475        CastSubcommand::CreationCode(cmd) => cmd.run().await?,
476        CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
477        CastSubcommand::Artifact(cmd) => cmd.run().await?,
478        CastSubcommand::Bind(cmd) => cmd.run().await?,
479        CastSubcommand::B2EPayload(cmd) => cmd.run().await?,
480        CastSubcommand::PrettyCalldata { calldata, offline } => {
481            let calldata = stdin::unwrap_line(calldata)?;
482            print_scalar(pretty_calldata(&calldata, offline).await?.to_string())?;
483        }
484        // JSON: --optimize conflicts with --json at the clap level; optimize=None uses print_scalar
485        CastSubcommand::Sig { sig, optimize } => {
486            let sig = stdin::unwrap_line(sig)?;
487            match optimize {
488                Some(opt) => {
489                    sh_status!("Starting to optimize signature...")?;
490                    let start_time = Instant::now();
491                    let (selector, signature) = get_selector(&sig, opt)?;
492                    sh_status!("Successfully generated in {:?}", start_time.elapsed())?;
493                    sh_println!("Selector: {selector}")?;
494                    sh_println!("Optimized signature: {signature}")?;
495                }
496                None => print_scalar(get_selector(&sig, 0)?.0)?,
497            }
498        }
499
500        // Blockchain & RPC queries
501        CastSubcommand::AccessList(cmd) => cmd.run().await?,
502        CastSubcommand::Age { block, rpc } => {
503            let timestamp = rpc_provider(&rpc)?
504                .get_block(block.unwrap_or_default())
505                .await?
506                .ok_or_eyre("block not found")?
507                .header
508                .timestamp;
509            let age = i64::try_from(timestamp)
510                .ok()
511                .and_then(|timestamp| chrono::DateTime::from_timestamp(timestamp, 0))
512                .ok_or_eyre("invalid timestamp")?
513                .format("%a %b %e %H:%M:%S %Y");
514            print_scalar(format!("{age} UTC"))?;
515        }
516        CastSubcommand::Balance { block, who, ether, rpc, erc20, overrides } => {
517            if erc20.is_none() && !overrides.is_empty() {
518                eyre::bail!("call overrides require `--erc20` when using `cast balance`");
519            }
520            let (provider, account_addr) = rpc_provider_and_address(&rpc, who).await?;
521
522            match erc20 {
523                Some(token) => {
524                    let token = IERC20::new(token, &provider);
525                    let balance_call =
526                        token.balanceOf(account_addr).block(block.unwrap_or_default());
527                    let balance = overrides.apply(balance_call.call())?.await?;
528
529                    sh_warn!("--erc20 flag is deprecated, use `cast erc20 balance` instead")?;
530                    print_scalar(format_uint_exp(balance))?;
531                }
532                None => {
533                    let value = provider
534                        .get_balance(account_addr)
535                        .block_id(block.unwrap_or_default())
536                        .await?;
537                    let out = if ether {
538                        ParseUnits::U256(value).format_units(Unit::ETHER)
539                    } else {
540                        value.to_string()
541                    };
542                    print_scalar(out)?;
543                }
544            }
545        }
546        CastSubcommand::BaseFee { block, rpc } => {
547            let fee = rpc_provider(&rpc)?
548                .get_block(block.unwrap_or_default())
549                .await?
550                .ok_or_eyre("block not found")?
551                .header
552                .base_fee_per_gas
553                .ok_or_eyre("base fee not found")?;
554            print_scalar(fee.to_string())?;
555        }
556        CastSubcommand::Block { block, full, fields, raw, rpc, network } => {
557            let config = rpc.load_config()?;
558            #[cfg(feature = "base")]
559            let network = if network.is_none() && (raw || fields.iter().any(|f| f == "raw")) {
560                crate::cmd::resolve_transaction_network(&config, false)
561                    .await?
562                    .is_base()
563                    .then_some(NetworkVariant::Base)
564            } else {
565                network
566            };
567            let block = block.unwrap_or_default();
568            // Can use either --raw or specify raw as a field
569            let output = if raw || fields.contains(&"raw".into()) {
570                with_network_provider!(
571                    network,
572                    &config,
573                    ProviderBuilder::<Ethereum>::from_config(&config)?.build()?,
574                    |provider, N| {
575                        let block_id = block;
576                        let block = provider
577                            .get_block(block_id)
578                            .kind(full.into())
579                            .await?
580                            .ok_or_else(|| eyre::eyre!("block {:?} not found", block_id))?;
581                        hex::encode_prefixed(alloy_rlp::encode(
582                            AsRef::<<N as Network>::Header>::as_ref(block.header()),
583                        ))
584                    }
585                )
586            } else {
587                let provider = utils::get_provider(&config)?;
588                if fields.contains(&"transactions".into()) && !full {
589                    eyre::bail!("use --full to view transactions");
590                }
591
592                let block = provider
593                    .get_block(block)
594                    .kind(full.into())
595                    .await?
596                    .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?;
597
598                if !fields.is_empty() {
599                    let mut result = String::new();
600                    for field in fields {
601                        result.push_str(
602                            &get_pretty_block_attr::<alloy_network::AnyNetwork>(&block, &field)
603                                .unwrap_or_else(|| format!("{field} is not a valid block field")),
604                        );
605
606                        result.push('\n');
607                    }
608                    result.trim_end().to_string()
609                } else if shell::is_json() {
610                    serde_json::to_value(&block).unwrap().to_string()
611                } else {
612                    block.pretty()
613                }
614            };
615            print_json_value_or_scalar(output)?;
616        }
617        CastSubcommand::BlockNumber { rpc, block } => {
618            let provider = rpc_provider(&rpc)?;
619            let number = match block {
620                Some(id) => {
621                    provider
622                        .get_block(id)
623                        .await?
624                        .ok_or_else(|| eyre::eyre!("block {id:?} not found"))?
625                        .header
626                        .number
627                }
628                None => provider.get_block_number().await?,
629            };
630            print_scalar(number)?;
631        }
632        CastSubcommand::Bal(cmd) => cmd.run().await?,
633        CastSubcommand::Chain { rpc } => {
634            let provider = rpc_provider(&rpc)?;
635            const GENESIS_CHAINS: &[(&str, &str)] = &[
636                ("0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9", "kovan"),
637                ("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "ropsten"),
638                (
639                    "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b",
640                    "optimism-mainnet",
641                ),
642                (
643                    "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1",
644                    "optimism-goerli",
645                ),
646                (
647                    "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5",
648                    "optimism-kovan",
649                ),
650                ("0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa", "fraxtal"),
651                (
652                    "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489",
653                    "fraxtal-testnet",
654                ),
655                (
656                    "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442",
657                    "arbitrum-mainnet",
658                ),
659                ("0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303", "morden"),
660                ("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", "rinkeby"),
661                ("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a", "goerli"),
662                ("0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4", "kotti"),
663                (
664                    "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b",
665                    "polygon-pos",
666                ),
667                (
668                    "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697",
669                    "polygon-pos-amoy-testnet",
670                ),
671                (
672                    "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741",
673                    "polygon-zkevm",
674                ),
675                (
676                    "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9",
677                    "polygon-zkevm-cardona-testnet",
678                ),
679                ("0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756", "gnosis"),
680                ("0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a", "chiado"),
681                ("0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34", "bsctest"),
682                ("0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b", "bsc"),
683                ("0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee", "ink"),
684                (
685                    "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834",
686                    "ink-sepolia",
687                ),
688            ];
689
690            let genesis_hash = provider
691                .get_block_by_number(0.into())
692                .await?
693                .ok_or_eyre("block not found")?
694                .header
695                .hash
696                .to_string();
697            let chain = match genesis_hash.as_str() {
698                // Ethereum and Ethereum Classic share the genesis block and split at the DAO fork.
699                "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => {
700                    match provider
701                        .get_block_by_number(1920000.into())
702                        .await?
703                        .ok_or_eyre("block not found")?
704                        .header
705                        .hash
706                        .to_string()
707                        .as_str()
708                    {
709                        "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => {
710                            "etclive"
711                        }
712                        _ => "ethlive",
713                    }
714                }
715                // Avalanche and Fuji share the genesis block.
716                "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => {
717                    match provider
718                        .get_block_by_number(1.into())
719                        .await?
720                        .ok_or_eyre("block not found")?
721                        .header
722                        .hash
723                        .to_string()
724                        .as_str()
725                    {
726                        "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => {
727                            "avalanche-fuji"
728                        }
729                        _ => "avalanche",
730                    }
731                }
732                hash => GENESIS_CHAINS
733                    .iter()
734                    .find(|(genesis, _)| *genesis == hash)
735                    .map_or("unknown", |(_, chain)| chain),
736            };
737            print_scalar(chain)?;
738        }
739        CastSubcommand::ChainId { rpc } => {
740            print_scalar(rpc_provider(&rpc)?.get_chain_id().await?.to_string())?;
741        }
742        CastSubcommand::Client { rpc } => {
743            print_scalar(rpc_provider(&rpc)?.get_client_version().await?)?;
744        }
745        CastSubcommand::Code { block, who, disassemble, rpc } => {
746            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
747            let code = provider.get_code_at(who).block_id(block.unwrap_or_default()).await?;
748            print_scalar(if disassemble {
749                crate::cmd::disassemble(&code)?
750            } else {
751                code.to_string()
752            })?;
753        }
754        CastSubcommand::Codesize { block, who, rpc } => {
755            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
756            print_scalar(
757                provider
758                    .get_code_at(who)
759                    .block_id(block.unwrap_or_default())
760                    .await?
761                    .len()
762                    .to_string(),
763            )?;
764        }
765        CastSubcommand::ComputeAddress { address, nonce, salt, init_code, init_code_hash, rpc } => {
766            let address = stdin::unwrap_line(address)?;
767            let salt = salt.unwrap_or(B256::ZERO);
768            let computed = if let Some(init_code_hash) = init_code_hash {
769                address.create2(salt, init_code_hash)
770            } else if let Some(init_code) = init_code {
771                address.create2(salt, keccak256(hex::decode(init_code)?))
772            } else {
773                // CREATE addresses depend on the deployer nonce, which is fetched over RPC.
774                let nonce = match nonce {
775                    Some(nonce) => nonce,
776                    None => rpc_provider(&rpc)?.get_transaction_count(address).await?,
777                };
778                address.create(nonce)
779            };
780            print_scalar(computed.to_checksum(None))?;
781        }
782        CastSubcommand::Disassemble { bytecode } => {
783            let bytecode = stdin::unwrap_line(bytecode)?;
784            print_scalar(crate::cmd::disassemble(&hex::decode(bytecode)?)?)?;
785        }
786        CastSubcommand::Selectors { bytecode, resolve } => {
787            let bytecode = stdin::unwrap_line(bytecode)?;
788            let code = hex::decode(&bytecode)?;
789            let info = evmole::contract_info(
790                evmole::ContractInfoArgs::new(&code)
791                    .with_selectors()
792                    .with_arguments()
793                    .with_state_mutability(),
794            );
795            let functions = info
796                .functions
797                .expect("functions extraction was requested")
798                .into_iter()
799                .filter(|f| f.dispatch == evmole::SelectorDispatch::Abi)
800                .map(|f| {
801                    let arguments = f
802                        .arguments
803                        .expect("arguments extraction was requested")
804                        .iter()
805                        .map(|t| t.sol_type_name())
806                        .collect::<Vec<_>>()
807                        .join(",");
808                    let mutability =
809                        f.state_mutability.expect("state_mutability extraction was requested");
810                    (
811                        alloy_primitives::Selector::from(f.selector),
812                        arguments,
813                        mutability.as_json_str(),
814                    )
815                })
816                .collect::<Vec<_>>();
817
818            let resolve_results: Vec<String> = if resolve {
819                let selectors = functions
820                    .iter()
821                    .map(|&(selector, ..)| SelectorKind::Function(selector))
822                    .collect::<Vec<_>>();
823                let ds = decode_selectors(&selectors).await?;
824                ds.into_iter().map(|v| v.join("|")).collect()
825            } else {
826                vec![]
827            };
828
829            if shell::is_json() {
830                #[derive(serde::Serialize)]
831                struct SelectorInfo {
832                    selector: String,
833                    arguments: String,
834                    state_mutability: String,
835                    #[serde(skip_serializing_if = "Option::is_none")]
836                    resolved: Option<String>,
837                }
838                let infos = functions
839                    .into_iter()
840                    .enumerate()
841                    .map(|(pos, (selector, arguments, state_mutability))| SelectorInfo {
842                        selector: selector.to_string(),
843                        arguments,
844                        state_mutability: state_mutability.to_string(),
845                        resolved: resolve_results.get(pos).cloned(),
846                    })
847                    .collect::<Vec<_>>();
848                print_json_object(infos)?;
849            } else {
850                let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0);
851                let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0);
852                for (pos, (selector, arguments, state_mutability)) in
853                    functions.into_iter().enumerate()
854                {
855                    if resolve {
856                        let resolved = &resolve_results[pos];
857                        sh_println!(
858                            "{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}"
859                        )?
860                    } else {
861                        sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}")?
862                    }
863                }
864            }
865        }
866        CastSubcommand::FindBlock(cmd) => cmd.run().await?,
867        CastSubcommand::GasPrice { rpc } => {
868            print_scalar(rpc_provider(&rpc)?.get_gas_price().await?.to_string())?;
869        }
870        CastSubcommand::Index { key_type, key, slot_number } => {
871            let mut hasher = Keccak256::new();
872
873            let k_ty = DynSolType::parse(&key_type).wrap_err("Could not parse type")?;
874            let k = k_ty.coerce_str(&key).wrap_err("Could not parse value")?;
875            match k_ty {
876                // For value types, `h` pads the value to 32 bytes in the same way as when storing
877                // the value in memory.
878                DynSolType::Bool
879                | DynSolType::Int(_)
880                | DynSolType::Uint(_)
881                | DynSolType::FixedBytes(_)
882                | DynSolType::Address
883                | DynSolType::Function => hasher.update(k.as_word().unwrap()),
884
885                // For strings and byte arrays, `h(k)` is just the unpadded data.
886                DynSolType::String | DynSolType::Bytes => hasher.update(k.as_packed_seq().unwrap()),
887
888                DynSolType::Array(..)
889                | DynSolType::FixedArray(..)
890                | DynSolType::Tuple(..)
891                | DynSolType::CustomStruct { .. } => {
892                    eyre::bail!("Type `{k_ty}` is not supported as a mapping key");
893                }
894            }
895
896            let p = DynSolType::Uint(256)
897                .coerce_str(&slot_number)
898                .wrap_err("Could not parse slot number")?;
899            let p = p.as_word().unwrap();
900            hasher.update(p);
901
902            let location = hasher.finalize();
903            print_scalar(location.to_string())?;
904        }
905        CastSubcommand::IndexErc7201 { id, formula_id } => {
906            eyre::ensure!(formula_id == "erc7201", "unsupported formula ID: {formula_id}");
907            let id = stdin::unwrap_line(id)?;
908            print_scalar(foundry_common::erc7201(&id).to_string())?;
909        }
910        CastSubcommand::Implementation { block, beacon, who, rpc } => {
911            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
912            // bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)
913            const BEACON_SLOT: B256 =
914                b256!("0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50");
915            // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
916            const IMPLEMENTATION_SLOT: B256 =
917                b256!("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
918
919            let slot = if beacon { BEACON_SLOT } else { IMPLEMENTATION_SLOT };
920            print_scalar(address_at_slot(&provider, who, slot, block).await?)?;
921        }
922        CastSubcommand::Admin { block, who, rpc } => {
923            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
924            // bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
925            const ADMIN_SLOT: B256 =
926                b256!("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103");
927            print_scalar(address_at_slot(&provider, who, ADMIN_SLOT, block).await?)?;
928        }
929        CastSubcommand::Nonce { block, who, rpc } => {
930            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
931            print_scalar(
932                provider.get_transaction_count(who).block_id(block.unwrap_or_default()).await?,
933            )?;
934        }
935        CastSubcommand::Codehash { block, who, slots, rpc } => {
936            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
937            print_scalar(
938                provider
939                    .get_proof(who, slots)
940                    .block_id(block.unwrap_or_default())
941                    .await?
942                    .code_hash
943                    .to_string(),
944            )?;
945        }
946        CastSubcommand::StorageRoot { block, who, slots, rpc } => {
947            let (provider, who) = rpc_provider_and_address(&rpc, who).await?;
948            print_scalar(
949                provider
950                    .get_proof(who, slots)
951                    .block_id(block.unwrap_or_default())
952                    .await?
953                    .storage_hash
954                    .to_string(),
955            )?;
956        }
957        CastSubcommand::ChannelId {
958            payer,
959            payee,
960            token,
961            salt,
962            operator,
963            authorized_signer,
964            expiring_nonce_hash,
965            reserve,
966            block,
967            rpc,
968        } => {
969            let provider = rpc_provider(&rpc)?;
970            let payer = payer.resolve(&provider).await?;
971            let payee = payee.resolve(&provider).await?;
972            let token = token.resolve(&provider).await?;
973            let operator = resolve_or(operator, Address::ZERO, &provider).await?;
974            let authorized_signer = resolve_or(authorized_signer, Address::ZERO, &provider).await?;
975            let reserve = resolve_or(reserve, TIP20_CHANNEL_RESERVE_ADDRESS, &provider).await?;
976
977            let channel_id = ITIP20ChannelReserve::new(reserve, &provider)
978                .computeChannelId(
979                    payer,
980                    payee,
981                    operator,
982                    token,
983                    salt,
984                    authorized_signer,
985                    expiring_nonce_hash,
986                )
987                .block(block.unwrap_or_default())
988                .call()
989                .await?;
990            print_scalar(format!("{channel_id:#x}"))?;
991        }
992        CastSubcommand::Proof { address, slots, rpc, block } => {
993            let (provider, address) = rpc_provider_and_address(&rpc, address).await?;
994            let value =
995                provider.get_proof(address, slots).block_id(block.unwrap_or_default()).await?;
996            print_json_object(value)?;
997        }
998        CastSubcommand::Rpc(cmd) => cmd.run().await?,
999        CastSubcommand::Storage(cmd) => cmd.run().await?,
1000
1001        // Calls & transactions
1002        CastSubcommand::Call(cmd) => cmd.run().await?,
1003        CastSubcommand::Estimate(cmd) => cmd.run().await?,
1004        CastSubcommand::MakeTx(cmd) => cmd.run().await?,
1005        CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => {
1006            let provider = rpc_provider(&rpc)?;
1007            let raw_tx = hex::decode(strip_0x(&raw_tx))?;
1008            let pending_tx = provider.send_raw_transaction(&raw_tx).await?;
1009            if cast_async {
1010                print_scalar(format!("{:#x}", pending_tx.inner().tx_hash()))?;
1011            } else {
1012                print_json_object(pending_tx.get_receipt().await?)?;
1013            }
1014        }
1015        CastSubcommand::Receipt { tx_hash, field, cast_async, confirmations, rpc } => {
1016            // JSON: The receipt helper already formats the output.
1017            sh_println!(
1018                "{}",
1019                CastTxSender::new(rpc_provider(&rpc)?)
1020                    .receipt(tx_hash, field, confirmations, None, cast_async)
1021                    .await?
1022            )?
1023        }
1024        CastSubcommand::Run(cmd) => cmd.run().await?,
1025        CastSubcommand::SendTx(cmd) => cmd.run().await?,
1026        CastSubcommand::BatchMakeTx(cmd) => cmd.run().await?,
1027        CastSubcommand::BatchSend(cmd) => cmd.run().await?,
1028        CastSubcommand::Classify { raw_tx } => {
1029            let raw_tx = hex::decode(stdin::unwrap_line(raw_tx)?)?;
1030            let out = format_lane_classification(&raw_tx, "failed to decode raw transaction")?;
1031            print_json_value_or_scalar(out)?
1032        }
1033        CastSubcommand::Tx { tx_hash, from, nonce, field, raw, lane, rpc, to_request, network } => {
1034            let config = rpc.load_config()?;
1035            #[cfg(feature = "base")]
1036            let network = match network {
1037                Some(network) => Some(network),
1038                None => crate::cmd::resolve_transaction_network(&config, false)
1039                    .await?
1040                    .is_base()
1041                    .then_some(NetworkVariant::Base),
1042            };
1043            // Can use either --raw or specify raw as a field
1044            let is_raw = raw || field.as_deref() == Some("raw");
1045            let output = if is_raw || lane {
1046                let encoded: Bytes = with_network_provider!(
1047                    network,
1048                    &config,
1049                    |provider| {
1050                        let tx =
1051                            transaction_response(&provider, tx_hash, from, nonce).await?;
1052                        tx.as_ref().encoded_2718().into()
1053                    },
1054                    _ => {
1055                        let provider = utils::get_provider(&config)?;
1056                        let tx =
1057                            transaction_response(&provider, tx_hash, from, nonce).await?;
1058                        FoundryTxEnvelope::encode_rpc_2718(&tx).wrap_err_with(|| {
1059                            format!("Cannot EIP-2718 encode transaction type 0x{:x}", tx.ty())
1060                        })?
1061                    }
1062                );
1063                if lane {
1064                    format_lane_classification(
1065                        &encoded,
1066                        "failed to decode transaction for lane classification",
1067                    )?
1068                } else {
1069                    hex::encode_prefixed(encoded)
1070                }
1071            } else {
1072                with_network_provider!(
1073                    network,
1074                    &config,
1075                    utils::get_provider(&config)?,
1076                    |provider| {
1077                        let tx = transaction_response(&provider, tx_hash, from, nonce).await?;
1078                        format_transaction(&provider, tx, field, to_request)?
1079                    }
1080                )
1081            };
1082            print_json_value_or_scalar(output)?;
1083        }
1084
1085        // 4Byte
1086        CastSubcommand::FourByte { selector } => {
1087            let selector = stdin::unwrap_line(selector)?;
1088            let sigs = decode_function_selector(selector).await?;
1089            if sigs.is_empty() {
1090                eyre::bail!("No matching function signatures found for selector `{selector}`");
1091            }
1092            print_list(&sigs)?;
1093        }
1094
1095        // JSON envelope intentionally unsupported: output combines an interactive selector
1096        // disambiguation step with decoded token output; no single stable shape exists.
1097        CastSubcommand::FourByteCalldata { calldata } => {
1098            let calldata = stdin::unwrap_line(calldata)?;
1099
1100            if calldata.len() == 10 {
1101                let sigs = decode_function_selector(calldata.parse()?).await?;
1102                if sigs.is_empty() {
1103                    eyre::bail!("No matching function signatures found for calldata `{calldata}`");
1104                }
1105                for sig in sigs {
1106                    sh_println!("{sig}")?
1107                }
1108                return Ok(());
1109            }
1110
1111            let sigs = decode_calldata(&calldata).await?;
1112            for (i, sig) in sigs.iter().enumerate() {
1113                let _ = sh_println!("{}) \"{sig}\"", i + 1);
1114            }
1115
1116            let sig = match sigs.len() {
1117                0 => eyre::bail!("No signatures found"),
1118                1 => &sigs[0],
1119                _ => {
1120                    let i: usize = prompt!("Select a function signature by number: ")?;
1121                    sigs.get(i - 1).ok_or_else(|| eyre::eyre!("Invalid signature index"))?
1122                }
1123            };
1124
1125            print_tokens(&abi_decode_calldata(sig, &calldata, true, true)?)?;
1126        }
1127
1128        CastSubcommand::FourByteEvent { topic } => {
1129            let topic = stdin::unwrap_line(topic)?;
1130            let sigs = decode_event_topic(topic).await?;
1131            if sigs.is_empty() {
1132                eyre::bail!("No matching event signatures found for topic `{topic}`");
1133            }
1134            print_list(&sigs)?;
1135        }
1136        // JSON envelope intentionally unsupported: output is a human-readable summary from an
1137        // external selector registry API with no stable machine-readable schema.
1138        CastSubcommand::UploadSignature { signatures } => {
1139            let signatures = stdin::unwrap_vec(signatures)?;
1140            let ParsedSignatures { signatures, abis } = parse_signatures(signatures);
1141            if !abis.is_empty() {
1142                import_selectors(SelectorImportData::Abi(abis)).await?.describe();
1143            }
1144            if !signatures.is_empty() {
1145                import_selectors(SelectorImportData::Raw(signatures)).await?.describe();
1146            }
1147        }
1148
1149        // ENS
1150        CastSubcommand::Namehash { name } => {
1151            print_scalar(namehash(&stdin::unwrap_line(name)?).to_string())?;
1152        }
1153        CastSubcommand::LookupAddress { who, rpc, verify } => {
1154            let provider = rpc_provider(&rpc)?;
1155            let who = stdin::unwrap_line(who)?;
1156            let name = provider.lookup_address(&who).await?;
1157            if verify {
1158                let address = provider.resolve_name(&name).await?;
1159                eyre::ensure!(
1160                    address == who,
1161                    "Reverse lookup verification failed: got `{address}`, expected `{who}`"
1162                );
1163            }
1164            print_scalar(name)?;
1165        }
1166        CastSubcommand::ResolveName { who, rpc, verify } => {
1167            let provider = rpc_provider(&rpc)?;
1168            let who = stdin::unwrap_line(who)?;
1169            let address = provider
1170                .resolve_name(&who)
1171                .await
1172                .wrap_err(format!("Failed to resolve ENS name: {who}"))?;
1173            if verify {
1174                let name = provider.lookup_address(&address).await?;
1175                eyre::ensure!(
1176                    name == who,
1177                    "Forward lookup verification failed: got `{name}`, expected `{who}`"
1178                );
1179            }
1180            print_scalar(address.to_string())?;
1181        }
1182
1183        // Misc
1184        CastSubcommand::Keccak { data } => {
1185            let bytes = match data {
1186                Some(data) => data.into_bytes(),
1187                None => stdin::read_bytes(false)?,
1188            };
1189            let out = match String::from_utf8(bytes) {
1190                Ok(s) => {
1191                    // Hex-decode if data starts with 0x.
1192                    if s.starts_with("0x") {
1193                        keccak256(hex::decode(s.trim_end())?)
1194                    } else {
1195                        keccak256(s)
1196                    }
1197                    .to_string()
1198                }
1199                Err(e) => hex::encode_prefixed(keccak256(e.as_bytes())),
1200            };
1201            print_scalar(out)?;
1202        }
1203        CastSubcommand::HashMessage { message } => {
1204            print_scalar(eip191_hash_message(stdin::unwrap(message, false)?).to_string())?;
1205        }
1206        CastSubcommand::SigEvent { event_string } => {
1207            let event = get_event(&stdin::unwrap_line(event_string)?)?;
1208            print_scalar(format!("{:?}", event.selector()))?;
1209        }
1210        CastSubcommand::LeftShift { value, bits, base_in, base_out } => {
1211            print_scalar(shift(&value, &bits, base_in.as_deref(), &base_out, |value, bits| {
1212                value << bits
1213            })?)?;
1214        }
1215        CastSubcommand::RightShift { value, bits, base_in, base_out } => {
1216            print_scalar(shift(&value, &bits, base_in.as_deref(), &base_out, |value, bits| {
1217                value.wrapping_shr(bits.saturating_to())
1218            })?)?;
1219        }
1220        // TODO(json): multi-line source code or directory expansion, needs structured envelope
1221        CastSubcommand::Source {
1222            address,
1223            directory,
1224            explorer_api_url,
1225            explorer_url,
1226            etherscan,
1227            flatten,
1228        } => {
1229            let config = etherscan.load_config()?;
1230            let chain = config.chain.unwrap_or_default();
1231            let api_key = config.get_etherscan_api_key(Some(chain));
1232            let client = explorer_client(chain, api_key, explorer_api_url, explorer_url)?;
1233            let metadata = client.contract_source_code(address.parse()?).await?;
1234            match (directory, flatten) {
1235                (Some(dir), false) => {
1236                    metadata.source_tree().write_to(&dir)?;
1237                }
1238                (None, false) => sh_println!("{}", metadata.source_code())?,
1239                (dir, true) => {
1240                    let Some(metadata) = metadata.items.first() else {
1241                        eyre::bail!("Empty contract source code");
1242                    };
1243
1244                    let tmp = tempfile::tempdir()?;
1245                    let project = foundry_common::compile::etherscan_project(metadata, tmp.path())?;
1246                    let target_path = project.find_contract_path(&metadata.contract_name)?;
1247
1248                    let flattened = foundry_common::flatten(project, &target_path)?;
1249
1250                    if let Some(path) = dir {
1251                        fs::create_dir_all(path.parent().unwrap())?;
1252                        fs::write(&path, flattened)?;
1253                        sh_status!("Flattened file written at {}", path.display())?
1254                    } else {
1255                        sh_println!("{flattened}")?
1256                    }
1257                }
1258            }
1259        }
1260        CastSubcommand::Create2(cmd) => cmd.execute()?,
1261        CastSubcommand::Wallet { command } => command.run().await?,
1262        CastSubcommand::Safe { command } => command.run().await?,
1263        CastSubcommand::Completions { shell } => {
1264            generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout())
1265        }
1266        CastSubcommand::Logs(cmd) => cmd.run().await?,
1267        CastSubcommand::Events(cmd) => cmd.run().await?,
1268        CastSubcommand::DecodeTransaction { tx, network } => {
1269            let tx = stdin::unwrap_line(tx)?;
1270            let decoded_tx = match network {
1271                #[cfg(feature = "optimism")]
1272                Some(NetworkVariant::Optimism) => decode_raw_transaction::<Optimism>(&tx)?,
1273                Some(NetworkVariant::Tempo) => decode_raw_transaction::<TempoNetwork>(&tx)?,
1274                #[cfg(feature = "base")]
1275                Some(NetworkVariant::Base) => decode_raw_transaction::<BaseNetwork>(&tx)?,
1276                Some(NetworkVariant::Ethereum) => decode_raw_transaction::<Ethereum>(&tx)?,
1277                #[cfg(feature = "monad")]
1278                Some(NetworkVariant::Monad) => decode_raw_transaction::<Ethereum>(&tx)?,
1279                // Without an explicit `--network` override, decode with the Foundry envelope,
1280                // which dispatches on the EIP-2718 type byte for the transaction types compiled
1281                // into `FoundryNetwork`, including Tempo txs (`0x76`).
1282                None => decode_raw_transaction::<FoundryNetwork>(&tx)?,
1283            };
1284            print_json_object(decoded_tx)?;
1285        }
1286        CastSubcommand::RecoverAuthority { auth } => {
1287            let auth: SignedAuthorization = serde_json::from_str(&auth)?;
1288            print_scalar(auth.recover_authority()?.to_string())?;
1289        }
1290        CastSubcommand::TxPool { command } => command.run().await?,
1291        CastSubcommand::Erc20Token { command } => command.run().await?,
1292        CastSubcommand::Erc4626 { command } => command.run().await?,
1293        CastSubcommand::Tip20Token { command } => command.run().await?,
1294        CastSubcommand::ReceivePolicy { command } => command.run().await?,
1295        CastSubcommand::Tip403 { command } => command.run().await?,
1296        CastSubcommand::StorageCredits { command } => command.run().await?,
1297        CastSubcommand::Keychain { command } => command.run().await?,
1298        CastSubcommand::KeyAuthorization { command } => command.run().await?,
1299        CastSubcommand::Tempo(args) => args.run().await?,
1300        CastSubcommand::VirtualAddress { command } => command.run().await?,
1301        #[cfg(any(feature = "base", feature = "optimism"))]
1302        CastSubcommand::DAEstimate(cmd) => cmd.run().await?,
1303        CastSubcommand::Trace(cmd) => cmd.run().await?,
1304    };
1305
1306    Ok(())
1307}
1308
1309/// Builds the default provider for `rpc` and resolves `who` against it.
1310async fn rpc_provider_and_address(
1311    rpc: &RpcOpts,
1312    who: NameOrAddress,
1313) -> Result<(RetryProvider, Address)> {
1314    let provider = rpc_provider(rpc)?;
1315    let who = who.resolve(&provider).await?;
1316    Ok((provider, who))
1317}
1318
1319/// Resolves `who` when given, otherwise returns `default`.
1320async fn resolve_or(
1321    who: Option<NameOrAddress>,
1322    default: Address,
1323    provider: &RetryProvider,
1324) -> Result<Address> {
1325    Ok(match who {
1326        Some(who) => who.resolve(provider).await?,
1327        None => default,
1328    })
1329}
1330
1331/// Validates that `encoded` is a supported EIP-2718 transaction and renders its Tempo T5 payment
1332/// lane classification.
1333fn format_lane_classification(encoded: &[u8], decode_context: &'static str) -> Result<String> {
1334    FoundryTxEnvelope::decode_2718(&mut &encoded[..]).wrap_err(decode_context)?;
1335    let classification = classify_payment_lane(encoded);
1336    if shell::is_json() {
1337        Ok(serde_json::to_string_pretty(&classification)?)
1338    } else {
1339        Ok(serde_json::to_string(&classification)?)
1340    }
1341}
1342
1343async fn address_at_slot<N: alloy_network::Network>(
1344    provider: &impl Provider<N>,
1345    who: Address,
1346    slot: B256,
1347    block: Option<BlockId>,
1348) -> Result<String> {
1349    let value =
1350        provider.get_storage_at(who, slot.into()).block_id(block.unwrap_or_default()).await?;
1351    Ok(format!("{:?}", Address::from_word(value.into())))
1352}
1353
1354async fn transaction_response<N: Network>(
1355    provider: &impl Provider<N>,
1356    tx_hash: Option<String>,
1357    from: Option<NameOrAddress>,
1358    nonce: Option<u64>,
1359) -> Result<N::TransactionResponse> {
1360    if let Some(tx_hash) = tx_hash {
1361        let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?;
1362        provider
1363            .get_transaction_by_hash(tx_hash)
1364            .await?
1365            .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))
1366    } else if let Some(from) = from {
1367        let nonce = U64::from(nonce.unwrap_or_default());
1368        let from = from.resolve(provider.root()).await?;
1369        provider
1370            .raw_request::<_, Option<N::TransactionResponse>>(
1371                "eth_getTransactionBySenderAndNonce".into(),
1372                (from, nonce),
1373            )
1374            .await?
1375            .ok_or_else(|| {
1376                eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::<u64>())
1377            })
1378    } else {
1379        eyre::bail!("tx hash or from address is required")
1380    }
1381}
1382
1383fn format_transaction<N: Network>(
1384    _provider: &impl Provider<N>,
1385    tx: N::TransactionResponse,
1386    field: Option<String>,
1387    to_request: bool,
1388) -> Result<String>
1389where
1390    N::TransactionResponse: UIfmt,
1391    N::TxEnvelope: UIfmtSignatureExt,
1392{
1393    Ok(if let Some(field) = field {
1394        if let Some(value) = get_pretty_tx_attr::<N>(&tx, &field) {
1395            value
1396        } else {
1397            let tx_json = serde_json::to_value(&tx)?;
1398            let value =
1399                tx_json.get(&field).ok_or_else(|| eyre::eyre!("invalid tx field: {field}"))?;
1400            match value {
1401                serde_json::Value::String(value) => value.clone(),
1402                value => value.to_string(),
1403            }
1404        }
1405    } else if shell::is_json() {
1406        // to_value first to sort json object keys
1407        serde_json::to_value(&tx)?.to_string()
1408    } else if to_request {
1409        serde_json::to_string_pretty(&Into::<N::TransactionRequest>::into(tx))?
1410    } else {
1411        tx.pretty()
1412    })
1413}
1414
1415fn int_bound(s: &str, max: bool) -> Result<String> {
1416    let ty = DynSolType::parse(s).wrap_err("Invalid type, expected `(u)int<bit size>`")?;
1417    match ty {
1418        DynSolType::Int(n) => {
1419            let max_value = (U256::MAX & U256::from(1).wrapping_shl(n - 1)) - U256::from(1);
1420            if max {
1421                Ok(max_value.to_string())
1422            } else {
1423                Ok((I256::from_raw(max_value).wrapping_neg() + I256::MINUS_ONE).to_string())
1424            }
1425        }
1426        DynSolType::Uint(n) if max => {
1427            let mut max_value = U256::MAX;
1428            if n < 256 {
1429                max_value &= U256::from(1).wrapping_shl(n).wrapping_sub(U256::from(1));
1430            }
1431            Ok(max_value.to_string())
1432        }
1433        DynSolType::Uint(_) => Ok("0".to_string()),
1434        _ => Err(eyre::eyre!("Type is not int/uint: {s}")),
1435    }
1436}
1437
1438/// Converts a parsed, possibly-negative [`NumberWithBase`] into a [`ParseUnits`], preserving
1439/// its sign.
1440///
1441/// `NumberWithBase::number()` returns the two's-complement bits of a negative value modulo
1442/// 2^256, which is a wider range than [`I256`] can represent (magnitudes up to 2^255 only).
1443/// A magnitude beyond that range would silently reinterpret as a small *positive* [`I256`]
1444/// if constructed unconditionally via [`I256::from_raw`] -- reject it instead.
1445fn signed_parse_units(value: &NumberWithBase) -> Result<ParseUnits> {
1446    if value.is_nonnegative() {
1447        return Ok(ParseUnits::U256(value.number()));
1448    }
1449    let signed = I256::from_raw(value.number());
1450    if !signed.is_negative() {
1451        eyre::bail!("value out of range for a signed 256-bit integer");
1452    }
1453    Ok(ParseUnits::I256(signed))
1454}
1455
1456fn format_unit_as_string(value: ParseUnits, unit: Unit) -> String {
1457    let mut formatted = value.format_units(unit);
1458    // Trim empty fractional part.
1459    if let Some(dot) = formatted.find('.') {
1460        let fractional = &formatted[dot + 1..];
1461        if fractional.chars().all(|c: char| c == '0') {
1462            formatted = formatted[..dot].to_string();
1463        }
1464    }
1465    formatted
1466}
1467
1468pub(super) fn format_units(value: &str, unit: u8) -> Result<String> {
1469    let value = NumberWithBase::parse_int(value, None)?;
1470    let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?;
1471    let parsed = signed_parse_units(&value)?;
1472    Ok(format_unit_as_string(parsed, unit))
1473}
1474
1475fn to_base(value: &str, base_in: Option<&str>, base_out: &str) -> Result<String> {
1476    let base_in = Base::unwrap_or_detect(base_in, value)?;
1477    let base_out = base_out.parse()?;
1478    if base_in == base_out {
1479        return Ok(value.to_string());
1480    }
1481    let n = NumberWithBase::parse_int_in(value, base_in)?.with_base(base_out);
1482    Ok(format!("{n:#?}"))
1483}
1484
1485/// Parses `value` and `bits`, applies `shift` and formats the result with the `base_out`
1486/// prefix.
1487fn shift(
1488    value: &str,
1489    bits: &str,
1490    base_in: Option<&str>,
1491    base_out: &str,
1492    shift: impl FnOnce(U256, U256) -> U256,
1493) -> Result<String> {
1494    let base_out = base_out.parse()?;
1495    let value = NumberWithBase::parse_uint(value, base_in)?.number();
1496    let bits = NumberWithBase::parse_uint(bits, None)?.number();
1497    Ok(format!("{:#?}", NumberWithBase::from(shift(value, bits)).with_base(base_out)))
1498}
1499
1500fn explorer_client(
1501    chain: Chain,
1502    api_key: Option<String>,
1503    api_url: Option<String>,
1504    explorer_url: Option<String>,
1505) -> Result<Client> {
1506    let mut builder = Client::builder();
1507
1508    let deduced = chain.etherscan_urls();
1509
1510    let explorer_url = explorer_url
1511        .or(deduced.map(|d| d.1.to_string()))
1512        .ok_or_eyre("Please provide the explorer browser URL using `--explorer-url`")?;
1513    builder = builder.with_url(explorer_url)?;
1514
1515    let api_url = api_url
1516        .or(deduced.map(|d| d.0.to_string()))
1517        .ok_or_eyre("Please provide the explorer API URL using `--explorer-api-url`")?;
1518    builder = builder.with_api_url(api_url)?;
1519
1520    if let Some(api_key) = api_key {
1521        builder = builder.with_api_key(api_key);
1522    }
1523
1524    builder.build().map_err(Into::into)
1525}
1526
1527fn decode_raw_transaction<N: Network<TxEnvelope: SignerRecoverable + Serialize>>(
1528    tx: &str,
1529) -> Result<String> {
1530    let tx_hex = hex::decode(tx)?;
1531    let tx: N::TxEnvelope = Decodable2718::decode_2718(&mut tx_hex.as_slice())?;
1532    if let Ok(signer) = tx.recover_signer() {
1533        Ok(serde_json::to_string_pretty(&Recovered::new_unchecked(tx, signer))?)
1534    } else {
1535        Ok(serde_json::to_string_pretty(&tx)?)
1536    }
1537}
1538
1539fn get_selector(signature: &str, optimize: usize) -> Result<(String, String)> {
1540    if optimize > 4 {
1541        eyre::bail!("number of leading zeroes must not be greater than 4");
1542    }
1543    if optimize == 0 {
1544        let selector = get_func(signature)?.selector();
1545        return Ok((selector.to_string(), String::from(signature)));
1546    }
1547    let Some((name, params)) = signature.split_once('(') else {
1548        eyre::bail!("invalid function signature");
1549    };
1550
1551    let num_threads = rayon::current_num_threads();
1552    let found = AtomicBool::new(false);
1553
1554    // Each thread walks its own residue class of nonces until one of them finds a match.
1555    (0..num_threads as u32)
1556        .into_par_iter()
1557        .find_map_any(|mut nonce| {
1558            while nonce < u32::MAX && !found.load(Ordering::Relaxed) {
1559                let input = format!("{name}{nonce}({params}");
1560                let selector = &keccak256(input.as_bytes())[..4];
1561                if selector.iter().take_while(|&&byte| byte == 0).count() == optimize {
1562                    found.store(true, Ordering::Relaxed);
1563                    return Some((hex::encode_prefixed(selector), input));
1564                }
1565                nonce += num_threads as u32;
1566            }
1567            None
1568        })
1569        .ok_or_eyre("No selector found")
1570}
1571
1572fn strip_0x(s: &str) -> &str {
1573    s.strip_prefix("0x").unwrap_or(s)
1574}
1575
1576/// Encodes the topic of an indexed event parameter.
1577///
1578/// Value types are encoded as their 32-byte word. Reference types are hashed over the special
1579/// in-place encoding defined for indexed event parameters, which differs from regular ABI
1580/// encoding: `string` and `bytes` contribute their raw contents, and array or struct members are
1581/// concatenated recursively without any offsets or length prefixes.
1582///
1583/// See <https://docs.soliditylang.org/en/latest/abi-spec.html#encoding-of-indexed-event-parameters>
1584pub(super) fn encode_event_topic(value: &DynSolValue) -> B256 {
1585    if let Some(word) = value.as_word() {
1586        return word;
1587    }
1588    // Top-level `string` and `bytes` hash their raw contents without padding.
1589    if let Some(bytes) = value.as_packed_seq() {
1590        return keccak256(bytes);
1591    }
1592    let mut preimage = Vec::new();
1593    encode_event_topic_preimage(value, &mut preimage);
1594    keccak256(preimage)
1595}
1596
1597/// Encodes a value into the in-place preimage of an indexed event parameter: words as-is,
1598/// `string`/`bytes` right-padded to a multiple of 32 bytes, and sequences as the concatenation of
1599/// their encoded members.
1600fn encode_event_topic_preimage(value: &DynSolValue, out: &mut Vec<u8>) {
1601    if let Some(word) = value.as_word() {
1602        out.extend_from_slice(word.as_slice());
1603    } else if let Some(bytes) = value.as_packed_seq() {
1604        let pad = bytes.len().next_multiple_of(32) - bytes.len();
1605        out.extend_from_slice(bytes);
1606        out.resize(out.len() + pad, 0);
1607    } else if let Some(values) = value.as_fixed_seq().or_else(|| value.as_array()) {
1608        for value in values {
1609            encode_event_topic_preimage(value, out);
1610        }
1611    }
1612}
1613
1614#[cfg(test)]
1615mod tests {
1616    use super::*;
1617    use alloy_sol_types::{EventTopic, sol_data};
1618
1619    /// Compares [`super::encode_event_topic`] against alloy's static [`EventTopic`]
1620    /// implementation, which `sol!`-generated events use to compute indexed topics.
1621    #[test]
1622    fn encode_event_topic_matches_static_encoding() {
1623        let uint = |n: u64| DynSolValue::Uint(U256::from(n), 256);
1624        let string = |s: &str| DynSolValue::String(s.into());
1625        let topic = |v: &DynSolValue| super::encode_event_topic(v);
1626
1627        let long = "abcdefghijklmnopqrstuvwxyz0123456789abcd";
1628        for s in ["", "hello", long] {
1629            assert_eq!(
1630                topic(&string(s)),
1631                <sol_data::String as EventTopic>::encode_topic(&s.to_string()).0,
1632                "string {s:?}"
1633            );
1634        }
1635
1636        let bytes = hex::decode("deadbeef").unwrap();
1637        assert_eq!(
1638            topic(&DynSolValue::Bytes(bytes.clone())),
1639            <sol_data::Bytes as EventTopic>::encode_topic(&Bytes::from(bytes)).0,
1640        );
1641
1642        let addr = Address::repeat_byte(0x42);
1643        assert_eq!(
1644            topic(&DynSolValue::Address(addr)),
1645            <sol_data::Address as EventTopic>::encode_topic(&addr).0,
1646        );
1647
1648        assert_eq!(
1649            topic(&DynSolValue::Array(vec![uint(1), uint(2)])),
1650            <sol_data::Array<sol_data::Uint<256>> as EventTopic>::encode_topic(&vec![
1651                U256::from(1),
1652                U256::from(2)
1653            ])
1654            .0,
1655        );
1656
1657        assert_eq!(
1658            topic(&DynSolValue::FixedArray(vec![uint(7), uint(9)])),
1659            <sol_data::FixedArray<sol_data::Uint<256>, 2> as EventTopic>::encode_topic(&[
1660                U256::from(7),
1661                U256::from(9)
1662            ])
1663            .0,
1664        );
1665
1666        assert_eq!(
1667            topic(&DynSolValue::Array(vec![string("alpha"), string(long)])),
1668            <sol_data::Array<sol_data::String> as EventTopic>::encode_topic(&vec![
1669                "alpha".to_string(),
1670                long.to_string()
1671            ])
1672            .0,
1673        );
1674
1675        assert_eq!(
1676            topic(&DynSolValue::Tuple(vec![uint(7), string("hello")])),
1677            <(sol_data::Uint<256>, sol_data::String) as EventTopic>::encode_topic(&(
1678                U256::from(7),
1679                "hello".to_string()
1680            ))
1681            .0,
1682        );
1683
1684        assert_eq!(
1685            topic(&DynSolValue::Array(vec![
1686                DynSolValue::Array(vec![uint(1)]),
1687                DynSolValue::Array(vec![uint(2), uint(3)]),
1688            ])),
1689            <sol_data::Array<sol_data::Array<sol_data::Uint<256>>> as EventTopic>::encode_topic(
1690                &vec![vec![U256::from(1)], vec![U256::from(2), U256::from(3)]]
1691            )
1692            .0,
1693        );
1694    }
1695}