Skip to content

Commit

Permalink
feat(cast): add artifact method (#9249)
Browse files Browse the repository at this point in the history
* feat(cast): add artifact method

* Remove unneeded clone

* Get chain info from provider

* Rebase fix
  • Loading branch information
pawurb authored Nov 11, 2024
1 parent 8c01706 commit f8d9234
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 17 deletions.
6 changes: 5 additions & 1 deletion crates/cast/bin/args.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::cmd::{
access_list::AccessListArgs, bind::BindArgs, call::CallArgs,
access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs,
constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs,
estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs,
mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs,
Expand Down Expand Up @@ -927,6 +927,10 @@ pub enum CastSubcommand {
#[command(visible_alias = "cc")]
CreationCode(CreationCodeArgs),

/// Generate an artifact file, that can be used to deploy a contract locally.
#[command(visible_alias = "ar")]
Artifact(ArtifactArgs),

/// Display constructor arguments used for the contract initialization.
#[command(visible_alias = "cra")]
ConstructorArgs(ConstructorArgsArgs),
Expand Down
95 changes: 95 additions & 0 deletions crates/cast/bin/cmd/artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use alloy_primitives::Address;
use alloy_provider::Provider;
use clap::{command, Parser};
use eyre::Result;
use foundry_block_explorers::Client;
use foundry_cli::{
opts::{EtherscanOpts, RpcOpts},
utils,
};
use foundry_common::fs;
use foundry_config::Config;
use serde_json::json;
use std::path::PathBuf;

use super::{
creation_code::{fetch_creation_code, parse_code_output},
interface::{fetch_abi_from_etherscan, load_abi_from_file},
};

/// CLI arguments for `cast artifact`.
#[derive(Parser)]
pub struct ArtifactArgs {
/// An Ethereum address, for which the artifact will be produced.
contract: Address,

/// Path to file containing the contract's JSON ABI. It's necessary if the target contract is
/// not verified on Etherscan.
#[arg(long)]
abi_path: Option<String>,

/// The path to the output file.
///
/// If not specified, the artifact will be output to stdout.
#[arg(
short,
long,
value_hint = clap::ValueHint::FilePath,
value_name = "PATH",
)]
output: Option<PathBuf>,

#[command(flatten)]
etherscan: EtherscanOpts,

#[command(flatten)]
rpc: RpcOpts,
}

impl ArtifactArgs {
pub async fn run(self) -> Result<()> {
let Self { contract, etherscan, rpc, output: output_location, abi_path } = self;

let mut etherscan = etherscan;
let config = Config::from(&rpc);
let provider = utils::get_provider(&config)?;
let api_key = etherscan.key().unwrap_or_default();
let chain = provider.get_chain_id().await?;
etherscan.chain = Some(chain.into());
let client = Client::new(chain.into(), api_key)?;

let abi = if let Some(ref abi_path) = abi_path {
load_abi_from_file(abi_path, None)?
} else {
fetch_abi_from_etherscan(contract, &etherscan).await?
};

let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?;

let bytecode = fetch_creation_code(contract, client, provider).await?;
let bytecode =
parse_code_output(bytecode, contract, &etherscan, abi_path.as_deref(), true, false)
.await?;

let artifact = json!({
"abi": abi,
"bytecode": {
"object": bytecode
}
});

let artifact = serde_json::to_string_pretty(&artifact)?;

if let Some(loc) = output_location {
if let Some(parent) = loc.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&loc, artifact)?;
sh_println!("Saved artifact at {}", loc.display())?;
} else {
sh_println!("{artifact}")?;
}

Ok(())
}
}
11 changes: 6 additions & 5 deletions crates/cast/bin/cmd/constructor_args.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use alloy_dyn_abi::DynSolType;
use alloy_primitives::{Address, Bytes};
use alloy_provider::Provider;
use clap::{command, Parser};
use eyre::{eyre, OptionExt, Result};
use foundry_block_explorers::Client;
Expand Down Expand Up @@ -36,13 +37,13 @@ impl ConstructorArgsArgs {
pub async fn run(self) -> Result<()> {
let Self { contract, etherscan, rpc, abi_path } = self;

let config = Config::from(&etherscan);
let chain = config.chain.unwrap_or_default();
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
let client = Client::new(chain, api_key)?;

let mut etherscan = etherscan;
let config = Config::from(&rpc);
let provider = utils::get_provider(&config)?;
let api_key = etherscan.key().unwrap_or_default();
let chain = provider.get_chain_id().await?;
etherscan.chain = Some(chain.into());
let client = Client::new(chain.into(), api_key)?;

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

Expand Down
28 changes: 17 additions & 11 deletions crates/cast/bin/cmd/creation_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,25 @@ impl CreationCodeArgs {
let Self { contract, etherscan, rpc, disassemble, without_args, only_args, abi_path } =
self;

let config = Config::from(&etherscan);
let chain = config.chain.unwrap_or_default();
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
let client = Client::new(chain, api_key)?;

let mut etherscan = etherscan;
let config = Config::from(&rpc);
let provider = utils::get_provider(&config)?;
let api_key = etherscan.key().unwrap_or_default();
let chain = provider.get_chain_id().await?;
etherscan.chain = Some(chain.into());
let client = Client::new(chain.into(), api_key)?;

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

let bytecode =
parse_code_output(bytecode, contract, &etherscan, abi_path, without_args, only_args)
.await?;
let bytecode = parse_code_output(
bytecode,
contract,
&etherscan,
abi_path.as_deref(),
without_args,
only_args,
)
.await?;

if disassemble {
let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
Expand All @@ -77,11 +83,11 @@ impl CreationCodeArgs {
/// - The complete bytecode
/// - The bytecode without constructor arguments
/// - Only the constructor arguments
async fn parse_code_output(
pub async fn parse_code_output(
bytecode: Bytes,
contract: Address,
etherscan: &EtherscanOpts,
abi_path: Option<String>,
abi_path: Option<&str>,
without_args: bool,
only_args: bool,
) -> Result<Bytes> {
Expand All @@ -90,7 +96,7 @@ async fn parse_code_output(
}

let abi = if let Some(abi_path) = abi_path {
load_abi_from_file(&abi_path, None)?
load_abi_from_file(abi_path, None)?
} else {
fetch_abi_from_etherscan(contract, etherscan).await?
};
Expand Down
1 change: 1 addition & 0 deletions crates/cast/bin/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! [`foundry_config::Config`].
pub mod access_list;
pub mod artifact;
pub mod bind;
pub mod call;
pub mod constructor_args;
Expand Down
1 change: 1 addition & 0 deletions crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ async fn main_args(args: CastArgs) -> Result<()> {
CastSubcommand::Interface(cmd) => cmd.run().await?,
CastSubcommand::CreationCode(cmd) => cmd.run().await?,
CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
CastSubcommand::Artifact(cmd) => cmd.run().await?,
CastSubcommand::Bind(cmd) => cmd.run().await?,
CastSubcommand::PrettyCalldata { calldata, offline } => {
let calldata = stdin::unwrap_line(calldata)?;
Expand Down
23 changes: 23 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1528,3 +1528,26 @@ Traces:
"#]]);
});

// tests that displays a sample contract artifact
// <https://etherscan.io/address/0x0923cad07f06b2d0e5e49e63b8b35738d4156b95>
casttest!(fetch_artifact_from_etherscan, |_prj, cmd| {
let eth_rpc_url = next_http_rpc_endpoint();
cmd.args([
"artifact",
"--etherscan-api-key",
&next_mainnet_etherscan_api_key(),
"0x0923cad07f06b2d0e5e49e63b8b35738d4156b95",
"--rpc-url",
eth_rpc_url.as_str(),
])
.assert_success()
.stdout_eq(str![[r#"{
"abi": [],
"bytecode": {
"object": "0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033"
}
}
"#]]);
});

0 comments on commit f8d9234

Please sign in to comment.