cast/cmd/
artifact.rs

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