cast/cmd/
artifact.rs

1use super::{
2    creation_code::{fetch_creation_code_from_etherscan, parse_code_output},
3    interface::load_abi_from_file,
4};
5use alloy_primitives::Address;
6use alloy_provider::Provider;
7use clap::{Parser, command};
8use eyre::Result;
9use foundry_cli::{
10    opts::{EtherscanOpts, RpcOpts},
11    utils::{self, LoadConfig},
12};
13use foundry_common::{abi::fetch_abi_from_etherscan, fs};
14use serde_json::json;
15use std::path::PathBuf;
16
17foundry_config::impl_figment_convert!(ArtifactArgs, etherscan, rpc);
18
19/// CLI arguments for `cast artifact`.
20#[derive(Parser)]
21pub struct ArtifactArgs {
22    /// An Ethereum address, for which the artifact will be produced.
23    contract: Address,
24
25    /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is
26    /// not verified on Etherscan.
27    #[arg(long)]
28    abi_path: Option<String>,
29
30    /// The path to the output file.
31    ///
32    /// If not specified, the artifact will be output to stdout.
33    #[arg(
34        short,
35        long,
36        value_hint = clap::ValueHint::FilePath,
37        value_name = "PATH",
38    )]
39    output: Option<PathBuf>,
40
41    #[command(flatten)]
42    etherscan: EtherscanOpts,
43
44    #[command(flatten)]
45    rpc: RpcOpts,
46}
47
48impl ArtifactArgs {
49    pub async fn run(self) -> Result<()> {
50        let mut config = self.load_config()?;
51
52        let Self { contract, output: output_location, abi_path, etherscan: _, rpc: _ } = self;
53
54        let provider = utils::get_provider(&config)?;
55        let chain = provider.get_chain_id().await?;
56        config.chain = Some(chain.into());
57
58        let abi = if let Some(ref abi_path) = abi_path {
59            load_abi_from_file(abi_path, None)?
60        } else {
61            fetch_abi_from_etherscan(contract, &config).await?
62        };
63
64        let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?;
65
66        let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?;
67        let bytecode =
68            parse_code_output(bytecode, contract, &config, abi_path.as_deref(), true, false)
69                .await?;
70
71        let artifact = json!({
72            "abi": abi,
73            "bytecode": {
74              "object": bytecode
75            }
76        });
77
78        let artifact = serde_json::to_string_pretty(&artifact)?;
79
80        if let Some(loc) = output_location {
81            if let Some(parent) = loc.parent() {
82                fs::create_dir_all(parent)?;
83            }
84            fs::write(&loc, artifact)?;
85            sh_println!("Saved artifact at {}", loc.display())?;
86        } else {
87            sh_println!("{artifact}")?;
88        }
89
90        Ok(())
91    }
92}