Skip to main content

cast/
args.rs

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