Skip to content

Commit

Permalink
feat: cast etherscan-source --flatten (foundry-rs#8159)
Browse files Browse the repository at this point in the history
  • Loading branch information
klkvr authored Jun 18, 2024
1 parent 9e271d0 commit a131937
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 26 deletions.
11 changes: 7 additions & 4 deletions crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,18 +530,21 @@ async fn main() -> Result<()> {
CastSubcommand::RightShift { value, bits, base_in, base_out } => {
println!("{}", SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)?);
}
CastSubcommand::EtherscanSource { address, directory, etherscan } => {
CastSubcommand::EtherscanSource { address, directory, etherscan, flatten } => {
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();
match directory {
Some(dir) => {
match (directory, flatten) {
(Some(dir), false) => {
SimpleCast::expand_etherscan_source_to_directory(chain, address, api_key, dir)
.await?
}
None => {
(None, false) => {
println!("{}", SimpleCast::etherscan_source(chain, address, api_key).await?);
}
(dir, true) => {
SimpleCast::etherscan_source_flatten(chain, address, api_key, dir).await?;
}
}
}
CastSubcommand::Create2(cmd) => {
Expand Down
8 changes: 6 additions & 2 deletions crates/cast/bin/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,8 +808,12 @@ pub enum CastSubcommand {
/// The contract's address.
address: String,

/// The output directory to expand source tree into.
#[arg(short, value_hint = ValueHint::DirPath)]
/// Whether to flatten the source code.
#[arg(long, short)]
flatten: bool,

/// The output directory/file to expand source tree into.
#[arg(short, value_hint = ValueHint::DirPath, alias = "path")]
directory: Option<PathBuf>,

#[command(flatten)]
Expand Down
35 changes: 34 additions & 1 deletion crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ use eyre::{Context, ContextCompat, Result};
use foundry_block_explorers::Client;
use foundry_common::{
abi::{encode_function_args, get_func},
compile::etherscan_project,
fmt::*,
TransactionReceiptWithRevertReason,
fs, TransactionReceiptWithRevertReason,
};
use foundry_compilers::flatten::Flattener;
use foundry_config::Chain;
use futures::{future::Either, FutureExt, StreamExt};
use rayon::prelude::*;
Expand Down Expand Up @@ -1847,6 +1849,37 @@ impl SimpleCast {
Ok(())
}

/// Fetches the source code of verified contracts from etherscan, flattens it and writes it to
/// the given path or stdout.
pub async fn etherscan_source_flatten(
chain: Chain,
contract_address: String,
etherscan_api_key: String,
output_path: Option<PathBuf>,
) -> Result<()> {
let client = Client::new(chain, etherscan_api_key)?;
let metadata = client.contract_source_code(contract_address.parse()?).await?;
let Some(metadata) = metadata.items.first() else {
eyre::bail!("Empty contract source code")
};

let tmp = tempfile::tempdir()?;
let project = etherscan_project(metadata, tmp.path())?;
let target_path = project.find_contract_path(&metadata.contract_name)?;

let flattened = Flattener::new(project, &target_path)?.flatten();

if let Some(path) = output_path {
fs::create_dir_all(path.parent().unwrap())?;
fs::write(&path, flattened)?;
println!("Flattened file written at {}", path.display());
} else {
println!("{flattened}");
}

Ok(())
}

/// Disassembles hex encoded bytecode into individual / human readable opcodes
///
/// # Example
Expand Down
31 changes: 17 additions & 14 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,18 +165,8 @@ impl ProjectCompiler {
{
let quiet = self.quiet.unwrap_or(false);
let bail = self.bail.unwrap_or(true);
#[allow(clippy::collapsible_else_if)]
let reporter = if quiet {
Report::new(NoReporter::default())
} else {
if std::io::stdout().is_terminal() {
Report::new(SpinnerReporter::spawn())
} else {
Report::new(BasicStdoutReporter::default())
}
};

let output = foundry_compilers::report::with_scoped(&reporter, || {
let output = with_compilation_reporter(self.quiet.unwrap_or(false), || {
tracing::debug!("compiling project");

let timer = Instant::now();
Expand All @@ -187,9 +177,6 @@ impl ProjectCompiler {
r
})?;

// need to drop the reporter here, so that the spinner terminates
drop(reporter);

if bail && output.has_compiler_errors() {
eyre::bail!("{output}")
}
Expand Down Expand Up @@ -554,3 +541,19 @@ pub fn etherscan_project(
.no_artifacts()
.build(compiler)?)
}

/// Configures the reporter and runs the given closure.
pub fn with_compilation_reporter<O>(quiet: bool, f: impl FnOnce() -> O) -> O {
#[allow(clippy::collapsible_else_if)]
let reporter = if quiet {
Report::new(NoReporter::default())
} else {
if std::io::stdout().is_terminal() {
Report::new(SpinnerReporter::spawn())
} else {
Report::new(BasicStdoutReporter::default())
}
};

foundry_compilers::report::with_scoped(&reporter, f)
}
11 changes: 6 additions & 5 deletions crates/forge/bin/cmd/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use foundry_cli::{
opts::{CoreBuildArgs, ProjectPathsArgs},
utils::LoadConfig,
};
use foundry_common::fs;
use foundry_common::{compile::with_compilation_reporter, fs};
use foundry_compilers::{
compilers::solc::SolcLanguage,
error::SolcError,
Expand Down Expand Up @@ -40,13 +40,14 @@ impl FlattenArgs {

// flatten is a subset of `BuildArgs` so we can reuse that to get the config
let build_args = CoreBuildArgs { project_paths, ..Default::default() };
let mut config = build_args.try_load_config_emit_warnings()?;
// `Flattener` uses the typed AST for better flattening results.
config.ast = true;
let config = build_args.try_load_config_emit_warnings()?;
let project = config.create_project(false, true)?;

let target_path = dunce::canonicalize(target_path)?;
let flattener = Flattener::new(project.clone(), &target_path);

let flattener = with_compilation_reporter(build_args.silent, || {
Flattener::new(project.clone(), &target_path)
});

let flattened = match flattener {
Ok(flattener) => Ok(flattener.flatten()),
Expand Down

0 comments on commit a131937

Please sign in to comment.