Skip to content

Commit d4ba7fc

Browse files
pawurbrplusq
authored andcommitted
feat(cast): add artifact method (foundry-rs#9249)
* feat(cast): add artifact method * Remove unneeded clone * Get chain info from provider * Rebase fix
1 parent 38d79e6 commit d4ba7fc

File tree

7 files changed

+148
-17
lines changed

7 files changed

+148
-17
lines changed

crates/cast/bin/args.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::cmd::{
2-
access_list::AccessListArgs, bind::BindArgs, call::CallArgs,
2+
access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs,
33
constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs,
44
estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs,
55
mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs,
@@ -927,6 +927,10 @@ pub enum CastSubcommand {
927927
#[command(visible_alias = "cc")]
928928
CreationCode(CreationCodeArgs),
929929

930+
/// Generate an artifact file, that can be used to deploy a contract locally.
931+
#[command(visible_alias = "ar")]
932+
Artifact(ArtifactArgs),
933+
930934
/// Display constructor arguments used for the contract initialization.
931935
#[command(visible_alias = "cra")]
932936
ConstructorArgs(ConstructorArgsArgs),

crates/cast/bin/cmd/artifact.rs

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

crates/cast/bin/cmd/constructor_args.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use alloy_dyn_abi::DynSolType;
22
use alloy_primitives::{Address, Bytes};
3+
use alloy_provider::Provider;
34
use clap::{command, Parser};
45
use eyre::{eyre, OptionExt, Result};
56
use foundry_block_explorers::Client;
@@ -36,13 +37,13 @@ impl ConstructorArgsArgs {
3637
pub async fn run(self) -> Result<()> {
3738
let Self { contract, etherscan, rpc, abi_path } = self;
3839

39-
let config = Config::from(&etherscan);
40-
let chain = config.chain.unwrap_or_default();
41-
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
42-
let client = Client::new(chain, api_key)?;
43-
40+
let mut etherscan = etherscan;
4441
let config = Config::from(&rpc);
4542
let provider = utils::get_provider(&config)?;
43+
let api_key = etherscan.key().unwrap_or_default();
44+
let chain = provider.get_chain_id().await?;
45+
etherscan.chain = Some(chain.into());
46+
let client = Client::new(chain.into(), api_key)?;
4647

4748
let bytecode = fetch_creation_code(contract, client, provider).await?;
4849

crates/cast/bin/cmd/creation_code.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,25 @@ impl CreationCodeArgs {
4949
let Self { contract, etherscan, rpc, disassemble, without_args, only_args, abi_path } =
5050
self;
5151

52-
let config = Config::from(&etherscan);
53-
let chain = config.chain.unwrap_or_default();
54-
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
55-
let client = Client::new(chain, api_key)?;
56-
52+
let mut etherscan = etherscan;
5753
let config = Config::from(&rpc);
5854
let provider = utils::get_provider(&config)?;
55+
let api_key = etherscan.key().unwrap_or_default();
56+
let chain = provider.get_chain_id().await?;
57+
etherscan.chain = Some(chain.into());
58+
let client = Client::new(chain.into(), api_key)?;
5959

6060
let bytecode = fetch_creation_code(contract, client, provider).await?;
6161

62-
let bytecode =
63-
parse_code_output(bytecode, contract, &etherscan, abi_path, without_args, only_args)
64-
.await?;
62+
let bytecode = parse_code_output(
63+
bytecode,
64+
contract,
65+
&etherscan,
66+
abi_path.as_deref(),
67+
without_args,
68+
only_args,
69+
)
70+
.await?;
6571

6672
if disassemble {
6773
let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
@@ -77,11 +83,11 @@ impl CreationCodeArgs {
7783
/// - The complete bytecode
7884
/// - The bytecode without constructor arguments
7985
/// - Only the constructor arguments
80-
async fn parse_code_output(
86+
pub async fn parse_code_output(
8187
bytecode: Bytes,
8288
contract: Address,
8389
etherscan: &EtherscanOpts,
84-
abi_path: Option<String>,
90+
abi_path: Option<&str>,
8591
without_args: bool,
8692
only_args: bool,
8793
) -> Result<Bytes> {
@@ -90,7 +96,7 @@ async fn parse_code_output(
9096
}
9197

9298
let abi = if let Some(abi_path) = abi_path {
93-
load_abi_from_file(&abi_path, None)?
99+
load_abi_from_file(abi_path, None)?
94100
} else {
95101
fetch_abi_from_etherscan(contract, etherscan).await?
96102
};

crates/cast/bin/cmd/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! [`foundry_config::Config`].
77
88
pub mod access_list;
9+
pub mod artifact;
910
pub mod bind;
1011
pub mod call;
1112
pub mod constructor_args;

crates/cast/bin/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ async fn main_args(args: CastArgs) -> Result<()> {
213213
CastSubcommand::Interface(cmd) => cmd.run().await?,
214214
CastSubcommand::CreationCode(cmd) => cmd.run().await?,
215215
CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
216+
CastSubcommand::Artifact(cmd) => cmd.run().await?,
216217
CastSubcommand::Bind(cmd) => cmd.run().await?,
217218
CastSubcommand::PrettyCalldata { calldata, offline } => {
218219
let calldata = stdin::unwrap_line(calldata)?;

crates/cast/tests/cli/main.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,3 +1593,26 @@ Traces:
15931593
15941594
"#]]);
15951595
});
1596+
1597+
// tests that displays a sample contract artifact
1598+
// <https://etherscan.io/address/0x0923cad07f06b2d0e5e49e63b8b35738d4156b95>
1599+
casttest!(fetch_artifact_from_etherscan, |_prj, cmd| {
1600+
let eth_rpc_url = next_http_rpc_endpoint();
1601+
cmd.args([
1602+
"artifact",
1603+
"--etherscan-api-key",
1604+
&next_mainnet_etherscan_api_key(),
1605+
"0x0923cad07f06b2d0e5e49e63b8b35738d4156b95",
1606+
"--rpc-url",
1607+
eth_rpc_url.as_str(),
1608+
])
1609+
.assert_success()
1610+
.stdout_eq(str![[r#"{
1611+
"abi": [],
1612+
"bytecode": {
1613+
"object": "0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033"
1614+
}
1615+
}
1616+
1617+
"#]]);
1618+
});

0 commit comments

Comments
 (0)