From 0c67265f97a950b987868eeca3c277a5060db7f2 Mon Sep 17 00:00:00 2001 From: Zhuo Zhang Date: Wed, 27 Mar 2024 21:57:07 -0500 Subject: [PATCH 1/2] feat: disable git by default for forge clone --- crates/forge/bin/cmd/clone.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index fb179780c162..eb9af8a3f999 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -14,6 +14,7 @@ use foundry_config::Config; use toml_edit; use super::init::InitArgs; +use super::install::DependencyInstallOpts; /// CLI arguments for `forge clone`. #[derive(Clone, Debug, Parser)] @@ -25,13 +26,17 @@ pub struct CloneArgs { #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] root: PathBuf, + /// Enable git for the cloned project, default is false. + #[arg(long)] + pub enable_git: bool, + #[command(flatten)] etherscan: EtherscanOpts, } impl CloneArgs { pub async fn run(self) -> Result<()> { - let CloneArgs { address, root, etherscan } = self; + let CloneArgs { address, root, enable_git, etherscan } = self; // parse the contract address let contract_address: Address = address.parse()?; @@ -53,7 +58,9 @@ impl CloneArgs { } // let's try to init the project with default init args - let init_args = InitArgs { root: root.clone(), ..Default::default() }; + let opts = + DependencyInstallOpts { no_git: !enable_git, ..Default::default() }; + let init_args = InitArgs { root: root.clone(), opts, ..Default::default() }; init_args.run().map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; // canonicalize the root path @@ -85,10 +92,12 @@ impl CloneArgs { update_config_by_metadata(config, doc, &meta).is_ok() })?; - // Git add and commit the changes - let git = Git::new(&root).quiet(true); - git.add(Some("--all"))?; - git.commit("chore: forge clone")?; + // Git add and commit the changes if enabled + if enable_git { + let git = Git::new(&root).quiet(true); + git.add(Some("--all"))?; + git.commit("chore: forge clone")?; + } Ok(()) } @@ -340,6 +349,7 @@ mod tests { address: "0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193".to_string(), root: project_root.clone(), etherscan: Default::default(), + enable_git: false, }; let (contract_name, stripped_creation_code) = pick_creation_info(&args.address).expect("creation code not found"); @@ -356,6 +366,7 @@ mod tests { address: "0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545".to_string(), root: project_root.clone(), etherscan: Default::default(), + enable_git: false, }; let (contract_name, stripped_creation_code) = pick_creation_info(&args.address).expect("creation code not found"); @@ -372,6 +383,7 @@ mod tests { address: "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec".to_string(), root: project_root.clone(), etherscan: Default::default(), + enable_git: false, }; let (contract_name, stripped_creation_code) = pick_creation_info(&args.address).expect("creation code not found"); @@ -388,6 +400,7 @@ mod tests { address: "0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05".to_string(), root: project_root.clone(), etherscan: Default::default(), + enable_git: false, }; let (contract_name, stripped_creation_code) = pick_creation_info(&args.address).expect("creation code not found"); From 58a27582fc542a603119ff66f8cb6112329273ba Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Wed, 27 Mar 2024 22:54:08 -0500 Subject: [PATCH 2/2] dump clone.toml metadata in cloned projects --- crates/forge/bin/cmd/clone.rs | 86 +++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index eb9af8a3f999..76b378847cd1 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -1,15 +1,17 @@ +use std::time::Duration; use std::{fs::read_dir, path::PathBuf}; -use alloy_primitives::Address; +use alloy_primitives::{Address, ChainId, TxHash}; use clap::{Parser, ValueHint}; use eyre::Result; use foundry_block_explorers::{contract::Metadata, Client}; use foundry_cli::opts::EtherscanOpts; use foundry_cli::utils::Git; +use foundry_common::compile::ProjectCompiler; use foundry_common::fs; use foundry_compilers::artifacts::Settings; use foundry_compilers::remappings::{RelativeRemapping, Remapping}; -use foundry_compilers::ProjectPathsConfig; +use foundry_compilers::{ProjectCompileOutput, ProjectPathsConfig}; use foundry_config::Config; use toml_edit; @@ -34,6 +36,25 @@ pub struct CloneArgs { etherscan: EtherscanOpts, } +/// CloneMetadata stores the metadata that are not included by `foundry.toml` but necessary for a cloned contract. +/// The metadata can be serialized to the `clone.toml` file in the cloned project root. +#[derive(Debug, Clone, serde::Serialize)] +pub struct CloneMetadata { + /// The path to the source file that contains the contract declaration. + /// The path is relative to the root directory of the project. + pub path: PathBuf, + /// The name of the contract in the file. + pub target_contract: String, + /// The address of the contract on the blockchian. + pub address: Address, + /// The chain id. + pub chain_id: ChainId, + /// The transaction hash of the creation transaction. + pub creation_transaction: TxHash, + /// The address of the deployer (caller of the CREATE/CREATE2). + pub deployer: Address, +} + impl CloneArgs { pub async fn run(self) -> Result<()> { let CloneArgs { address, root, enable_git, etherscan } = self; @@ -45,6 +66,13 @@ impl CloneArgs { let config = Config::from(ðerscan); let chain = config.chain.unwrap_or_default(); let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let etherscan_call_interval = if etherscan_api_key.is_empty() { + // if the etherscan api key is not set, we need to wait for 1 seconds between calls + Duration::from_secs(5) + } else { + // if the etherscan api key is set, we can call etherscan more frequently + Duration::from_secs(1) + }; // get the contract code let client = Client::new(chain, etherscan_api_key)?; @@ -58,8 +86,7 @@ impl CloneArgs { } // let's try to init the project with default init args - let opts = - DependencyInstallOpts { no_git: !enable_git, ..Default::default() }; + let opts = DependencyInstallOpts { no_git: !enable_git, ..Default::default() }; let init_args = InitArgs { root: root.clone(), opts, ..Default::default() }; init_args.run().map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; @@ -92,6 +119,25 @@ impl CloneArgs { update_config_by_metadata(config, doc, &meta).is_ok() })?; + // compile the cloned contract + let compile_output = compile_project(&root)?; + let main_file = find_main_file(&compile_output, &meta.contract_name)?; + let main_file = main_file.strip_prefix(&root)?.to_path_buf(); + + // dump the metadata to the root directory + std::thread::sleep(etherscan_call_interval); + let creation_tx = client.contract_creation_data(contract_address).await?; + let clone_meta = CloneMetadata { + path: main_file, + target_contract: meta.contract_name, + address: contract_address, + chain_id: etherscan.chain.unwrap_or_default().id(), + creation_transaction: creation_tx.transaction_hash, + deployer: creation_tx.contract_creator, + }; + let metadata_content = toml::to_string(&clone_meta)?; + fs::write(root.join("clone.toml"), metadata_content)?; + // Git add and commit the changes if enabled if enable_git { let git = Git::new(&root).quiet(true); @@ -294,14 +340,34 @@ fn dump_sources(meta: &Metadata, root: &PathBuf) -> Result Result { + std::env::set_current_dir(root)?; + let config = Config::load(); + let project = config.project()?; + let compiler = ProjectCompiler::new(); + compiler.compile(&project) +} + +/// Find the file path that contains the contract with the specified name. +/// The returned path is absolute path. +pub fn find_main_file(compile_output: &ProjectCompileOutput, contract: &str) -> Result { + for (f, c, _) in compile_output.artifacts_with_files() { + if contract == c { + return Ok(PathBuf::from(f)); + } + } + Err(eyre::eyre!("contract not found")) +} + #[cfg(test)] mod tests { use std::{path::PathBuf, thread::sleep, time::Duration}; + use crate::cmd::clone::compile_project; + use super::CloneArgs; - use foundry_common::compile::ProjectCompiler; use foundry_compilers::{Artifact, ProjectCompileOutput}; - use foundry_config::Config; use hex::ToHex; use serial_test::serial; use tempfile; @@ -312,13 +378,7 @@ mod tests { sleep(Duration::from_secs(5)); println!("project_root: {:#?}", root); - - // change directory to the root - std::env::set_current_dir(root).unwrap(); - let config = Config::load(); - let project = config.project().unwrap(); - let compiler = ProjectCompiler::new(); - compiler.compile(&project).expect("compilation failure") + compile_project(root).expect("compilation failure") } fn assert_compilation_result(