cast/cmd/
constructor_args.rs

1use super::{creation_code::fetch_creation_code_from_etherscan, interface::load_abi_from_file};
2use alloy_dyn_abi::DynSolType;
3use alloy_primitives::{Address, Bytes};
4use alloy_provider::Provider;
5use clap::{Parser, command};
6use eyre::{OptionExt, Result, eyre};
7use foundry_cli::{
8    opts::{EtherscanOpts, RpcOpts},
9    utils::{self, LoadConfig, fetch_abi_from_etherscan},
10};
11use foundry_config::Config;
12
13foundry_config::impl_figment_convert!(ConstructorArgsArgs, etherscan, rpc);
14
15/// CLI arguments for `cast creation-args`.
16#[derive(Parser)]
17pub struct ConstructorArgsArgs {
18    /// An Ethereum address, for which the bytecode will be fetched.
19    contract: Address,
20
21    /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is
22    /// not verified on Etherscan
23    #[arg(long)]
24    abi_path: Option<String>,
25
26    #[command(flatten)]
27    etherscan: EtherscanOpts,
28
29    #[command(flatten)]
30    rpc: RpcOpts,
31}
32
33impl ConstructorArgsArgs {
34    pub async fn run(self) -> Result<()> {
35        let mut config = self.load_config()?;
36
37        let Self { contract, abi_path, etherscan: _, rpc: _ } = self;
38
39        let provider = utils::get_provider(&config)?;
40        config.chain = Some(provider.get_chain_id().await?.into());
41
42        let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?;
43
44        let args_arr = parse_constructor_args(bytecode, contract, &config, abi_path).await?;
45        for arg in args_arr {
46            let _ = sh_println!("{arg}");
47        }
48
49        Ok(())
50    }
51}
52
53/// Fetches the constructor arguments values and types from the creation bytecode and ABI.
54async fn parse_constructor_args(
55    bytecode: Bytes,
56    contract: Address,
57    config: &Config,
58    abi_path: Option<String>,
59) -> Result<Vec<String>> {
60    let abi = if let Some(abi_path) = abi_path {
61        load_abi_from_file(&abi_path, None)?
62    } else {
63        fetch_abi_from_etherscan(contract, config).await?
64    };
65
66    let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?;
67    let (abi, _) = abi;
68
69    let constructor = abi.constructor.ok_or_else(|| eyre!("No constructor found."))?;
70
71    if constructor.inputs.is_empty() {
72        return Err(eyre!("No constructor arguments found."));
73    }
74
75    let args_size = constructor.inputs.len() * 32;
76    if bytecode.len() < args_size {
77        return Err(eyre!(
78            "Invalid creation bytecode length: have {} bytes, need at least {} for {} constructor inputs",
79            bytecode.len(),
80            args_size,
81            constructor.inputs.len()
82        ));
83    }
84    let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec());
85
86    let display_args: Vec<String> = args_bytes
87        .chunks(32)
88        .enumerate()
89        .map(|(i, arg)| format_arg(&constructor.inputs[i].ty, arg))
90        .collect::<Result<Vec<_>>>()?;
91
92    Ok(display_args)
93}
94
95fn format_arg(ty: &str, arg: &[u8]) -> Result<String> {
96    let arg_type: DynSolType = ty.parse()?;
97    let decoded = arg_type.abi_decode(arg)?;
98    let bytes = Bytes::from(arg.to_vec());
99
100    Ok(format!("{bytes} → {decoded:?}"))
101}