diff --git a/cli/src/cast.rs b/cli/src/cast.rs index 64b5afe0bddf1..818f741082111 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -36,7 +36,7 @@ use std::{ use clap::{IntoApp, Parser}; use clap_complete::generate; -use crate::utils::read_secret; +use crate::{cmd::Cmd, utils::read_secret}; use eyre::WrapErr; use futures::join; @@ -307,6 +307,7 @@ async fn main() -> eyre::Result<()> { eyre::bail!("No wallet or sender address provided.") } } + Subcommands::Run(cmd) => cmd.run()?, Subcommands::PublishTx { eth, raw_tx, cast_async } => { let provider = Provider::try_from(eth.rpc_url()?)?; let cast = Cast::new(&provider); diff --git a/cli/src/cmd/cast/mod.rs b/cli/src/cmd/cast/mod.rs new file mode 100644 index 0000000000000..ad21ef1614900 --- /dev/null +++ b/cli/src/cmd/cast/mod.rs @@ -0,0 +1,40 @@ +//! Subcommands for cast +//! +//! All subcommands should respect the `foundry_config::Config`. +//! If a subcommand accepts values that are supported by the `Config`, then the subcommand should +//! implement `figment::Provider` which allows the subcommand to override the config's defaults, see +//! [`foundry_config::Config`]. +//! +//! See [`BuildArgs`] for a reference implementation. +//! And [`RunArgs`] for how to merge `Providers`. +//! +//! # Example +//! +//! create a `clap` subcommand into a `figment::Provider` and integrate it in the +//! `foundry_config::Config`: +//! +//! ```rust +//! use crate::{cmd::build::BuildArgs, opts::evm::EvmArgs}; +//! use clap::Parser; +//! use foundry_config::{figment::Figment, *}; +//! +//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` +//! #[derive(Debug, Clone, Parser)] +//! pub struct MyArgs { +//! #[clap(flatten)] +//! evm_opts: EvmArgs, +//! #[clap(flatten)] +//! opts: BuildArgs, +//! } +//! +//! // add `Figment` and `Config` converters +//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); +//! let args = MyArgs::parse_from(["build"]); +//! +//! let figment: Figment = From::from(&args); +//! let evm_opts = figment.extract::().unwrap(); +//! +//! let config: Config = From::from(&args); +//! ``` + +pub mod run; diff --git a/cli/src/cmd/cast/run.rs b/cli/src/cmd/cast/run.rs new file mode 100644 index 0000000000000..0a7f99a8d9bc8 --- /dev/null +++ b/cli/src/cmd/cast/run.rs @@ -0,0 +1,103 @@ +use crate::cmd::{forge::build::BuildArgs, Cmd}; +use clap::Parser; + +use forge::ContractRunner; +use foundry_utils::IntoFunction; + +use ethers::types::{Address, Bytes, U256}; +use sputnik::ExitReason; + +use crate::opts::evm::EvmArgs; +use ansi_term::Colour; +use evm_adapters::{ + evm_opts::{BackendKind, EvmOpts}, + sputnik::helpers::vm, + Evm, +}; +use foundry_config::{figment::Figment, Config}; + +// Loads project's figment and merges the build cli arguments into it +foundry_config::impl_figment_convert!(RunArgs, opts, evm_opts); + +#[derive(Debug, Clone, Parser)] +pub struct RunArgs { + #[clap(help = "the bytecode to execute")] + pub bytecode: String, + + #[clap(flatten)] + pub evm_opts: EvmArgs, + + #[clap(flatten)] + opts: BuildArgs, +} + +impl Cmd for RunArgs { + type Output = (); + + fn run(self) -> eyre::Result { + // Keeping it like this for simplicity. + #[cfg(not(feature = "sputnik-evm"))] + unimplemented!("`exec` does not work with EVMs other than Sputnik yet"); + + let figment: Figment = From::from(&self); + let mut evm_opts = figment.extract::()?; + let config = Config::from_provider(figment).sanitized(); + let evm_version = config.evm_version; + if evm_opts.debug { + evm_opts.verbosity = 3; + } + + let mut cfg = crate::utils::sputnik_cfg(&evm_version); + cfg.create_contract_limit = None; + let vicinity = evm_opts.vicinity()?; + let backend = evm_opts.backend(&vicinity)?; + + // Parse bytecode string + let bytecode_vec = self.bytecode.strip_prefix("0x").unwrap_or(&self.bytecode); + let parsed_bytecode = Bytes::from(hex::decode(bytecode_vec)?); + + // Create the evm executor + let mut evm = vm(); + + // Deploy our bytecode + let custVal = U256::from(0); + let (addr, _, _, _) = evm.deploy(Address::zero(), parsed_bytecode, custVal).unwrap(); + + // Configure EVM + evm.gas_limit = u64::MAX; + + // TODO: support arbitrary input + // let sig = ethers::utils::id("foo()").to_vec(); + + // Call the address with an empty input + let (retBytes, retReason, retU64, retVecStr) = + evm.call_raw(Address::zero(), addr, Default::default(), custVal, true)?; + + // Match on the return exit reason + match retReason { + ExitReason::Succeed(s) => { + println!("{}", Colour::Green.paint(format!("SUCCESS [{:?}]", s))); + println!(""); + println!("==== Execution Return Bytes ===="); + println!("{}", retBytes); + } + ExitReason::Error(e) => { + println!("{}", Colour::Red.paint(format!("ERROR [{:?}]", e))); + } + ExitReason::Revert(r) => { + println!("{}", Colour::Yellow.paint(format!("REVERT [{:?}]", r))); + } + ExitReason::Fatal(f) => { + println!("{}", Colour::Red.paint(format!("FATAL [{:?}]", f))); + } + } + + Ok(()) + } +} + +impl RunArgs { + pub fn build(&self, _: Config, _: &EvmOpts) -> eyre::Result<()> { + Ok(()) + } +} diff --git a/cli/src/cmd/bind.rs b/cli/src/cmd/forge/bind.rs similarity index 100% rename from cli/src/cmd/bind.rs rename to cli/src/cmd/forge/bind.rs diff --git a/cli/src/cmd/build.rs b/cli/src/cmd/forge/build.rs similarity index 98% rename from cli/src/cmd/build.rs rename to cli/src/cmd/forge/build.rs index 015946ea3d4e4..7ce4d13af268a 100644 --- a/cli/src/cmd/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use crate::{cmd::Cmd, opts::forge::CompilerArgs}; -use crate::cmd::watch::WatchArgs; +use crate::cmd::forge::watch::WatchArgs; use clap::{Parser, ValueHint}; use ethers::solc::remappings::Remapping; use foundry_config::{ @@ -184,7 +184,7 @@ impl Cmd for BuildArgs { type Output = ProjectCompileOutput; fn run(self) -> eyre::Result { let project = self.project()?; - super::compile(&project, self.names, self.sizes) + crate::cmd::compile(&project, self.names, self.sizes) } } @@ -207,7 +207,7 @@ impl BuildArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { - use crate::cmd::watch; + use crate::cmd::forge::watch; let init = watch::init()?; let mut runtime = watch::runtime(&self.watch)?; diff --git a/cli/src/cmd/config.rs b/cli/src/cmd/forge/config.rs similarity index 96% rename from cli/src/cmd/config.rs rename to cli/src/cmd/forge/config.rs index 060c333765202..840dabb71a38d 100644 --- a/cli/src/cmd/config.rs +++ b/cli/src/cmd/forge/config.rs @@ -1,7 +1,7 @@ //! config command use crate::{ - cmd::{build::BuildArgs, Cmd}, + cmd::{forge::build::BuildArgs, Cmd}, opts::evm::EvmArgs, }; use clap::Parser; diff --git a/cli/src/cmd/create.rs b/cli/src/cmd/forge/create.rs similarity index 96% rename from cli/src/cmd/create.rs rename to cli/src/cmd/forge/create.rs index a4bfa51a24635..cc857ac38fd9e 100644 --- a/cli/src/cmd/create.rs +++ b/cli/src/cmd/forge/create.rs @@ -1,7 +1,7 @@ //! Create command use crate::{ - cmd::{build::BuildArgs, Cmd}, + cmd::{forge::build::BuildArgs, Cmd}, opts::{EthereumOpts, WalletType}, utils::parse_u256, }; @@ -70,10 +70,10 @@ impl Cmd for CreateArgs { fn run(self) -> Result { // Find Project & Compile let project = self.opts.project()?; - let compiled = super::compile(&project, self.opts.names, self.opts.sizes)?; + let compiled = crate::cmd::compile(&project, self.opts.names, self.opts.sizes)?; // Get ABI and BIN - let (abi, bin, _) = super::read_artifact(&project, compiled, self.contract.clone())?; + let (abi, bin, _) = crate::cmd::read_artifact(&project, compiled, self.contract.clone())?; let bin = match bin.object { BytecodeObject::Bytecode(_) => bin.object, diff --git a/cli/src/cmd/flatten.rs b/cli/src/cmd/forge/flatten.rs similarity index 98% rename from cli/src/cmd/flatten.rs rename to cli/src/cmd/forge/flatten.rs index bacf0ca1e160f..179f2c87d477d 100644 --- a/cli/src/cmd/flatten.rs +++ b/cli/src/cmd/forge/flatten.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use ethers::solc::remappings::Remapping; -use crate::cmd::{build::BuildArgs, Cmd}; +use crate::cmd::{forge::build::BuildArgs, Cmd}; use clap::{Parser, ValueHint}; use foundry_config::Config; diff --git a/cli/src/cmd/fmt.rs b/cli/src/cmd/forge/fmt.rs similarity index 100% rename from cli/src/cmd/fmt.rs rename to cli/src/cmd/forge/fmt.rs diff --git a/cli/src/cmd/init.rs b/cli/src/cmd/forge/init.rs similarity index 95% rename from cli/src/cmd/init.rs rename to cli/src/cmd/forge/init.rs index 02c6e7c65abb6..4e8aabdd3f3d4 100644 --- a/cli/src/cmd/init.rs +++ b/cli/src/cmd/forge/init.rs @@ -1,14 +1,14 @@ //! init command use crate::{ - cmd::{install::install, Cmd}, + cmd::{forge::install::install, Cmd}, opts::forge::Dependency, utils::p_println, }; use clap::{Parser, ValueHint}; use foundry_config::Config; -use crate::cmd::{install::DependencyInstallOpts, remappings}; +use crate::cmd::forge::{install::DependencyInstallOpts, remappings}; use ansi_term::Colour; use ethers::solc::remappings::Remapping; use std::{ @@ -102,10 +102,13 @@ impl Cmd for InitArgs { // write the contract file let contract_path = src.join("Contract.sol"); - std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.sol"))?; + std::fs::write(contract_path, include_str!("../../../../assets/ContractTemplate.sol"))?; // write the tests let contract_path = test.join("Contract.t.sol"); - std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.t.sol"))?; + std::fs::write( + contract_path, + include_str!("../../../../assets/ContractTemplate.t.sol"), + )?; let dest = root.join(Config::FILE_NAME); if !dest.exists() { @@ -159,7 +162,7 @@ fn init_git_repo(root: &Path, no_commit: bool) -> eyre::Result<()> { if !is_git.success() { let gitignore_path = root.join(".gitignore"); - std::fs::write(gitignore_path, include_str!("../../../assets/.gitignoreTemplate"))?; + std::fs::write(gitignore_path, include_str!("../../../../assets/.gitignoreTemplate"))?; Command::new("git") .arg("init") diff --git a/cli/src/cmd/inspect.rs b/cli/src/cmd/forge/inspect.rs similarity index 99% rename from cli/src/cmd/inspect.rs rename to cli/src/cmd/forge/inspect.rs index aa9c80905880f..b322026999428 100644 --- a/cli/src/cmd/inspect.rs +++ b/cli/src/cmd/forge/inspect.rs @@ -2,7 +2,7 @@ use std::{fmt, str::FromStr}; use crate::{ cmd::{ - build::{self, BuildArgs}, + forge::build::{self, BuildArgs}, Cmd, }, opts::forge::CompilerArgs, @@ -156,7 +156,7 @@ impl Cmd for InspectArgs { // Build the project let project = modified_build_args.project()?; - let outcome = super::suppress_compile(&project)?; + let outcome = crate::cmd::suppress_compile(&project)?; // Find the artifact let found_artifact = outcome.find(&contract); diff --git a/cli/src/cmd/install.rs b/cli/src/cmd/forge/install.rs similarity index 100% rename from cli/src/cmd/install.rs rename to cli/src/cmd/forge/install.rs diff --git a/cli/src/cmd/forge/mod.rs b/cli/src/cmd/forge/mod.rs new file mode 100644 index 0000000000000..6f0cf893d46b7 --- /dev/null +++ b/cli/src/cmd/forge/mod.rs @@ -0,0 +1,55 @@ +//! Subcommands for forge +//! +//! All subcommands should respect the `foundry_config::Config`. +//! If a subcommand accepts values that are supported by the `Config`, then the subcommand should +//! implement `figment::Provider` which allows the subcommand to override the config's defaults, see +//! [`foundry_config::Config`]. +//! +//! See [`BuildArgs`] for a reference implementation. +//! And [`RunArgs`] for how to merge `Providers`. +//! +//! # Example +//! +//! create a `clap` subcommand into a `figment::Provider` and integrate it in the +//! `foundry_config::Config`: +//! +//! ```rust +//! use crate::{cmd::build::BuildArgs, opts::evm::EvmArgs}; +//! use clap::Parser; +//! use foundry_config::{figment::Figment, *}; +//! +//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` +//! #[derive(Debug, Clone, Parser)] +//! pub struct MyArgs { +//! #[clap(flatten)] +//! evm_opts: EvmArgs, +//! #[clap(flatten)] +//! opts: BuildArgs, +//! } +//! +//! // add `Figment` and `Config` converters +//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); +//! let args = MyArgs::parse_from(["build"]); +//! +//! let figment: Figment = From::from(&args); +//! let evm_opts = figment.extract::().unwrap(); +//! +//! let config: Config = From::from(&args); +//! ``` + +pub mod bind; +pub mod build; +pub mod config; +pub mod create; +pub mod flatten; +pub mod fmt; +pub mod init; +pub mod inspect; +pub mod install; +pub mod remappings; +pub mod run; +pub mod snapshot; +pub mod test; +pub mod tree; +pub mod verify; +pub mod watch; diff --git a/cli/src/cmd/remappings.rs b/cli/src/cmd/forge/remappings.rs similarity index 100% rename from cli/src/cmd/remappings.rs rename to cli/src/cmd/forge/remappings.rs diff --git a/cli/src/cmd/run.rs b/cli/src/cmd/forge/run.rs similarity index 99% rename from cli/src/cmd/run.rs rename to cli/src/cmd/forge/run.rs index b4f0603ee13d0..8d6389b0ecd28 100644 --- a/cli/src/cmd/run.rs +++ b/cli/src/cmd/forge/run.rs @@ -1,4 +1,4 @@ -use crate::cmd::{build::BuildArgs, compile_files, Cmd}; +use crate::cmd::{compile_files, forge::build::BuildArgs, Cmd}; use clap::{Parser, ValueHint}; use evm_adapters::sputnik::cheatcodes::{CONSOLE_ABI, HEVMCONSOLE_ABI, HEVM_ABI}; diff --git a/cli/src/cmd/snapshot.rs b/cli/src/cmd/forge/snapshot.rs similarity index 99% rename from cli/src/cmd/snapshot.rs rename to cli/src/cmd/forge/snapshot.rs index 70459b236267c..9c249b7be535d 100644 --- a/cli/src/cmd/snapshot.rs +++ b/cli/src/cmd/forge/snapshot.rs @@ -2,8 +2,10 @@ use crate::{ cmd::{ - test, - test::{Test, TestOutcome}, + forge::{ + test, + test::{Test, TestOutcome}, + }, Cmd, }, utils, diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/forge/test.rs similarity index 99% rename from cli/src/cmd/test.rs rename to cli/src/cmd/forge/test.rs index 28589c06da7ca..fad48ed12d713 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/forge/test.rs @@ -1,7 +1,7 @@ //! Test command use crate::{ - cmd::{build::BuildArgs, Cmd}, + cmd::{forge::build::BuildArgs, Cmd}, opts::evm::EvmArgs, }; use ansi_term::Colour; @@ -183,7 +183,7 @@ impl Cmd for TestArgs { // Set up the project let project = config.project()?; - let output = super::compile(&project, false, false)?; + let output = crate::cmd::compile(&project, false, false)?; // prepare the test builder let mut evm_cfg = crate::utils::sputnik_cfg(&config.evm_version); diff --git a/cli/src/cmd/tree.rs b/cli/src/cmd/forge/tree.rs similarity index 95% rename from cli/src/cmd/tree.rs rename to cli/src/cmd/forge/tree.rs index 49a269250c953..2e7b4b540c308 100644 --- a/cli/src/cmd/tree.rs +++ b/cli/src/cmd/forge/tree.rs @@ -1,6 +1,6 @@ //! tree command -use crate::cmd::{build::BuildArgs, Cmd}; +use crate::cmd::{forge::build::BuildArgs, Cmd}; use clap::Parser; use ethers::solc::Graph; use foundry_config::Config; diff --git a/cli/src/cmd/verify.rs b/cli/src/cmd/forge/verify.rs similarity index 98% rename from cli/src/cmd/verify.rs rename to cli/src/cmd/forge/verify.rs index 5a5a83c0f98d0..29334e5350aad 100644 --- a/cli/src/cmd/verify.rs +++ b/cli/src/cmd/forge/verify.rs @@ -1,7 +1,7 @@ //! Verify contract source on etherscan use crate::{ - cmd::{build::BuildArgs, flatten::CoreFlattenArgs}, + cmd::forge::{build::BuildArgs, flatten::CoreFlattenArgs}, opts::forge::ContractInfo, }; use clap::Parser; diff --git a/cli/src/cmd/watch.rs b/cli/src/cmd/forge/watch.rs similarity index 99% rename from cli/src/cmd/watch.rs rename to cli/src/cmd/forge/watch.rs index fa31c26fcd47d..03a0aff910f5a 100644 --- a/cli/src/cmd/watch.rs +++ b/cli/src/cmd/forge/watch.rs @@ -1,7 +1,7 @@ //! Watch mode support use crate::{ - cmd::{build::BuildArgs, test::TestArgs}, + cmd::forge::{build::BuildArgs, test::TestArgs}, utils::{self, FoundryPathExt}, }; use clap::Parser; diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index 19df860e39c03..01ab20f13257a 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -1,266 +1,13 @@ -//! Subcommands for forge +//! Subcommands //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should //! implement `figment::Provider` which allows the subcommand to override the config's defaults, see //! [`foundry_config::Config`]. -//! -//! See [`BuildArgs`] for a reference implementation. -//! And [`RunArgs`] for how to merge `Providers`. -//! -//! # Example -//! -//! create a `clap` subcommand into a `figment::Provider` and integrate it in the -//! `foundry_config::Config`: -//! -//! ```rust -//! use crate::{cmd::build::BuildArgs, opts::evm::EvmArgs}; -//! use clap::Parser; -//! use foundry_config::{figment::Figment, *}; -//! -//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` -//! #[derive(Debug, Clone, Parser)] -//! pub struct MyArgs { -//! #[clap(flatten)] -//! evm_opts: EvmArgs, -//! #[clap(flatten)] -//! opts: BuildArgs, -//! } -//! -//! // add `Figment` and `Config` converters -//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); -//! let args = MyArgs::parse_from(["build"]); -//! -//! let figment: Figment = From::from(&args); -//! let evm_opts = figment.extract::().unwrap(); -//! -//! let config: Config = From::from(&args); -//! ``` - -pub mod bind; -pub mod build; -pub mod config; -pub mod create; -pub mod flatten; -pub mod fmt; -pub mod init; -pub mod inspect; -pub mod install; -pub mod remappings; -pub mod run; -pub mod snapshot; -pub mod test; -pub mod tree; -pub mod verify; -pub mod watch; - -use crate::{opts::forge::ContractInfo, term}; -use ethers::{ - abi::Abi, - prelude::{ - artifacts::{CompactBytecode, CompactDeployedBytecode}, - report::NoReporter, - }, - solc::cache::SolFilesCache, -}; -use std::{collections::BTreeMap, path::PathBuf}; - -/// Common trait for all cli commands -pub trait Cmd: clap::Parser + Sized { - type Output; - fn run(self) -> eyre::Result; -} - -use ethers::solc::{artifacts::CompactContractBytecode, Project, ProjectCompileOutput}; - -use foundry_utils::to_table; - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -pub fn compile( - project: &Project, - print_names: bool, - print_sizes: bool, -) -> eyre::Result { - if !project.paths.sources.exists() { - eyre::bail!( - r#"no contracts to compile, contracts folder "{}" does not exist. -Check the configured workspace settings: -{} -If you are in a subdirectory in a Git repository, try adding `--root .`"#, - project.paths.sources.display(), - project.paths - ); - } - - let output = term::with_spinner_reporter(|| project.compile())?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } else if output.is_unchanged() { - println!("No files changed, compilation skipped"); - } else { - // print the compiler output / warnings - println!("{}", output); - - // print any sizes or names - if print_names { - let compiled_contracts = output.compiled_contracts_by_compiler_version(); - for (version, contracts) in compiled_contracts.into_iter() { - println!( - " compiler version: {}.{}.{}", - version.major, version.minor, version.patch - ); - for (name, _) in contracts { - println!(" - {}", name); - } - } - } - if print_sizes { - // add extra newline if names were already printed - if print_names { - println!(); - } - let compiled_contracts = output.compiled_contracts_by_compiler_version(); - let mut sizes = BTreeMap::new(); - for (_, contracts) in compiled_contracts.into_iter() { - for (name, contract) in contracts { - let bytecode: CompactContractBytecode = contract.into(); - let size = if let Some(code) = bytecode.bytecode { - if let Some(object) = code.object.as_bytes() { - object.to_vec().len() - } else { - 0 - } - } else { - 0 - }; - sizes.insert(name, size); - } - } - let json = serde_json::to_value(&sizes)?; - println!("name size (bytes)"); - println!("-----------------------------"); - println!("{}", to_table(json)); - } - } - - Ok(output) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// Doesn't print anything to stdout, thus is "suppressed". -pub fn suppress_compile(project: &Project) -> eyre::Result { - if !project.paths.sources.exists() { - eyre::bail!( - r#"no contracts to compile, contracts folder "{}" does not exist. -Check the configured workspace settings: -{} -If you are in a subdirectory in a Git repository, try adding `--root .`"#, - project.paths.sources.display(), - project.paths - ); - } - - let output = ethers::solc::report::with_scoped( - ðers::solc::report::Report::new(NoReporter::default()), - || project.compile(), - )?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - - Ok(output) -} - -/// Compile a set of files not necessarily included in the `project`'s source dir -pub fn compile_files(project: &Project, files: Vec) -> eyre::Result { - let output = term::with_spinner_reporter(|| project.compile_files(files))?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - println!("{}", output); - Ok(output) -} - -/// Given a project and its compiled artifacts, proceeds to return the ABI, Bytecode and -/// Runtime Bytecode of the given contract. -pub fn read_artifact( - project: &Project, - compiled: ProjectCompileOutput, - contract: ContractInfo, -) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - Ok(match contract.path { - Some(path) => get_artifact_from_path(project, path, contract.name)?, - None => get_artifact_from_name(contract, compiled)?, - }) -} - -/// Helper function for finding a contract by ContractName -// TODO: Is there a better / more ergonomic way to get the artifacts given a project and a -// contract name? -fn get_artifact_from_name( - contract: ContractInfo, - compiled: ProjectCompileOutput, -) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - let mut has_found_contract = false; - let mut contract_artifact = None; - - for (artifact_id, artifact) in compiled.into_artifacts() { - if artifact_id.name == contract.name { - if has_found_contract { - eyre::bail!("contract with duplicate name. pass path") - } - has_found_contract = true; - contract_artifact = Some(artifact); - } - } - - Ok(match contract_artifact { - Some(artifact) => ( - artifact - .abi - .map(Into::into) - .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract.name)))?, - artifact.bytecode.ok_or_else(|| { - eyre::Error::msg(format!("bytecode not found for {}", contract.name)) - })?, - artifact.deployed_bytecode.ok_or_else(|| { - eyre::Error::msg(format!("bytecode not found for {}", contract.name)) - })?, - ), - None => { - eyre::bail!("could not find artifact") - } - }) -} - -/// Find using src/ContractSource.sol:ContractName -fn get_artifact_from_path( - project: &Project, - contract_path: String, - contract_name: String, -) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - // Get sources from the requested location - let abs_path = dunce::canonicalize(PathBuf::from(contract_path))?; - - let cache = SolFilesCache::read_joined(&project.paths)?; - // Read the artifact from disk - let artifact: CompactContractBytecode = cache.read_artifact(abs_path, &contract_name)?; +pub mod cast; +pub mod forge; - Ok(( - artifact - .abi - .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract_name)))?, - artifact - .bytecode - .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, - artifact - .deployed_bytecode - .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, - )) -} +// Re-export our shared utilities +mod shared; +pub use shared::*; diff --git a/cli/src/cmd/shared/mod.rs b/cli/src/cmd/shared/mod.rs new file mode 100644 index 0000000000000..c6e9261b0e552 --- /dev/null +++ b/cli/src/cmd/shared/mod.rs @@ -0,0 +1,209 @@ +use crate::{opts::forge::ContractInfo, term}; +use ethers::{ + abi::Abi, + prelude::{ + artifacts::{CompactBytecode, CompactDeployedBytecode}, + report::NoReporter, + }, + solc::cache::SolFilesCache, +}; +use std::{collections::BTreeMap, path::PathBuf}; + +/// Common trait for all cli commands +pub trait Cmd: clap::Parser + Sized { + type Output; + fn run(self) -> eyre::Result; +} + +use ethers::solc::{artifacts::CompactContractBytecode, Project, ProjectCompileOutput}; +use foundry_utils::to_table; + +/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether +/// compilation was successful or if there was a cache hit. +pub fn compile( + project: &Project, + print_names: bool, + print_sizes: bool, +) -> eyre::Result { + if !project.paths.sources.exists() { + eyre::bail!( + r#"no contracts to compile, contracts folder "{}" does not exist. +Check the configured workspace settings: +{} +If you are in a subdirectory in a Git repository, try adding `--root .`"#, + project.paths.sources.display(), + project.paths + ); + } + + let output = term::with_spinner_reporter(|| project.compile())?; + + if output.has_compiler_errors() { + eyre::bail!(output.to_string()) + } else if output.is_unchanged() { + println!("No files changed, compilation skipped"); + } else { + // print the compiler output / warnings + println!("{}", output); + + // print any sizes or names + if print_names { + let compiled_contracts = output.compiled_contracts_by_compiler_version(); + for (version, contracts) in compiled_contracts.into_iter() { + println!( + " compiler version: {}.{}.{}", + version.major, version.minor, version.patch + ); + for (name, _) in contracts { + println!(" - {}", name); + } + } + } + if print_sizes { + // add extra newline if names were already printed + if print_names { + println!(); + } + let compiled_contracts = output.compiled_contracts_by_compiler_version(); + let mut sizes = BTreeMap::new(); + for (_, contracts) in compiled_contracts.into_iter() { + for (name, contract) in contracts { + let bytecode: CompactContractBytecode = contract.into(); + let size = if let Some(code) = bytecode.bytecode { + if let Some(object) = code.object.as_bytes() { + object.to_vec().len() + } else { + 0 + } + } else { + 0 + }; + sizes.insert(name, size); + } + } + let json = serde_json::to_value(&sizes)?; + println!("name size (bytes)"); + println!("-----------------------------"); + println!("{}", to_table(json)); + } + } + + Ok(output) +} + +/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether +/// compilation was successful or if there was a cache hit. +/// Doesn't print anything to stdout, thus is "suppressed". +pub fn suppress_compile(project: &Project) -> eyre::Result { + if !project.paths.sources.exists() { + eyre::bail!( + r#"no contracts to compile, contracts folder "{}" does not exist. +Check the configured workspace settings: +{} +If you are in a subdirectory in a Git repository, try adding `--root .`"#, + project.paths.sources.display(), + project.paths + ); + } + + let output = ethers::solc::report::with_scoped( + ðers::solc::report::Report::new(NoReporter::default()), + || project.compile(), + )?; + + if output.has_compiler_errors() { + eyre::bail!(output.to_string()) + } + + Ok(output) +} + +/// Compile a set of files not necessarily included in the `project`'s source dir +pub fn compile_files(project: &Project, files: Vec) -> eyre::Result { + let output = term::with_spinner_reporter(|| project.compile_files(files))?; + + if output.has_compiler_errors() { + eyre::bail!(output.to_string()) + } + println!("{}", output); + Ok(output) +} + +/// Given a project and its compiled artifacts, proceeds to return the ABI, Bytecode and +/// Runtime Bytecode of the given contract. +pub fn read_artifact( + project: &Project, + compiled: ProjectCompileOutput, + contract: ContractInfo, +) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { + Ok(match contract.path { + Some(path) => get_artifact_from_path(project, path, contract.name)?, + None => get_artifact_from_name(contract, compiled)?, + }) +} + +/// Helper function for finding a contract by ContractName +// TODO: Is there a better / more ergonomic way to get the artifacts given a project and a +// contract name? +fn get_artifact_from_name( + contract: ContractInfo, + compiled: ProjectCompileOutput, +) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { + let mut has_found_contract = false; + let mut contract_artifact = None; + + for (artifact_id, artifact) in compiled.into_artifacts() { + if artifact_id.name == contract.name { + if has_found_contract { + eyre::bail!("contract with duplicate name. pass path") + } + has_found_contract = true; + contract_artifact = Some(artifact); + } + } + + Ok(match contract_artifact { + Some(artifact) => ( + artifact + .abi + .map(Into::into) + .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract.name)))?, + artifact.bytecode.ok_or_else(|| { + eyre::Error::msg(format!("bytecode not found for {}", contract.name)) + })?, + artifact.deployed_bytecode.ok_or_else(|| { + eyre::Error::msg(format!("bytecode not found for {}", contract.name)) + })?, + ), + None => { + eyre::bail!("could not find artifact") + } + }) +} + +/// Find using src/ContractSource.sol:ContractName +fn get_artifact_from_path( + project: &Project, + contract_path: String, + contract_name: String, +) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { + // Get sources from the requested location + let abs_path = dunce::canonicalize(PathBuf::from(contract_path))?; + + let cache = SolFilesCache::read_joined(&project.paths)?; + + // Read the artifact from disk + let artifact: CompactContractBytecode = cache.read_artifact(abs_path, &contract_name)?; + + Ok(( + artifact + .abi + .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract_name)))?, + artifact + .bytecode + .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, + artifact + .deployed_bytecode + .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, + )) +} diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 5a778c0af4db0..877ba7b646daa 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -3,7 +3,10 @@ mod opts; mod term; mod utils; -use crate::cmd::{watch, Cmd}; +use crate::cmd::{ + forge::{verify, watch}, + Cmd, +}; use ethers::solc::{Project, ProjectPathsConfig}; use opts::forge::{Dependency, Opts, Subcommands}; @@ -31,7 +34,7 @@ fn main() -> eyre::Result<()> { } Subcommands::Build(cmd) => { if cmd.is_watch() { - utils::block_on(crate::cmd::watch::watch_build(cmd))?; + utils::block_on(watch::watch_build(cmd))?; } else { cmd.run()?; } @@ -40,10 +43,10 @@ fn main() -> eyre::Result<()> { cmd.run()?; } Subcommands::VerifyContract(args) => { - utils::block_on(cmd::verify::run_verify(&args))?; + utils::block_on(verify::run_verify(&args))?; } Subcommands::VerifyCheck(args) => { - utils::block_on(cmd::verify::run_verify_check(&args))?; + utils::block_on(verify::run_verify_check(&args))?; } Subcommands::Create(cmd) => { cmd.run()?; diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index e04f9d94d616f..317a221347980 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -7,7 +7,7 @@ use ethers::{ }; use super::{ClapChain, EthereumOpts, Wallet}; -use crate::utils::parse_u256; +use crate::{cmd::cast::run::RunArgs, utils::parse_u256}; #[derive(Debug, Subcommand)] #[clap(about = "Perform Ethereum RPC calls from the comfort of your command line.")] @@ -241,6 +241,8 @@ pub enum Subcommands { #[clap(long = "json", short = 'j')] to_json: bool, }, + #[clap(alias = "r", about = "Executes arbitrary bytecode in hex format")] + Run(RunArgs), #[clap(name = "publish")] #[clap(about = "Publish a raw transaction to the network")] PublishTx { diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 69b051bbc9344..840b72b877ddb 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -3,7 +3,7 @@ use clap::{Parser, Subcommand, ValueHint}; use ethers::solc::{artifacts::output_selection::ContractOutputSelection, EvmVersion}; use std::{path::PathBuf, str::FromStr}; -use crate::cmd::{ +use crate::cmd::forge::{ bind::BindArgs, build::BuildArgs, config, diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 6607606fb083d..613acdd9aa115 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -210,20 +210,8 @@ pub trait Evm { } // Check Success output: Should Fail vs Success - // - // Success - // ----------------------- - // | | false | true | - // | ----------------------| - // Should Fail | false | false | true | - // | ----------------------| - // | true | true | false | - // ----------------------- - (should_fail && !success) || (!should_fail && success) + should_fail ^ success } - - // TODO: Should we add a "deploy contract" function as well, or should we assume that - // the EVM is instantiated with a DB that includes any needed contracts? } // Test helpers which are generic over EVM implementation