Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor forge for library use #10117

Merged
merged 7 commits into from
Mar 20, 2025
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 1 addition & 153 deletions crates/forge/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,4 @@
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use eyre::Result;
use foundry_cli::{handler, utils};
use foundry_common::shell;
use foundry_evm::inspectors::cheatcodes::{set_execution_context, ForgeContext};

mod cmd;
use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch};

mod opts;
use opts::{Forge, ForgeSubcommand};

#[macro_use]
extern crate foundry_common;

#[macro_use]
extern crate tracing;
use forge::args::run;

#[cfg(all(feature = "jemalloc", unix))]
#[global_allocator]
@@ -27,138 +10,3 @@ fn main() {
std::process::exit(1);
}
}

fn run() -> Result<()> {
handler::install();
utils::load_dotenv();
utils::subscriber();
utils::enable_paint();

let args = Forge::parse();
args.global.init()?;
init_execution_context(&args.cmd);

match args.cmd {
ForgeSubcommand::Test(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_test(cmd))
} else {
let silent = cmd.junit || shell::is_json();
let outcome = utils::block_on(cmd.run())?;
outcome.ensure_ok(silent)
}
}
ForgeSubcommand::Script(cmd) => utils::block_on(cmd.run_script()),
ForgeSubcommand::Coverage(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_coverage(cmd))
} else {
utils::block_on(cmd.run())
}
}
ForgeSubcommand::Bind(cmd) => cmd.run(),
ForgeSubcommand::Build(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_build(cmd))
} else {
cmd.run().map(drop)
}
}
ForgeSubcommand::VerifyContract(args) => utils::block_on(args.run()),
ForgeSubcommand::VerifyCheck(args) => utils::block_on(args.run()),
ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Clone(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Cache(cmd) => match cmd.sub {
CacheSubcommands::Clean(cmd) => cmd.run(),
CacheSubcommands::Ls(cmd) => cmd.run(),
},
ForgeSubcommand::Create(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Update(cmd) => cmd.run(),
ForgeSubcommand::Install(cmd) => cmd.run(),
ForgeSubcommand::Remove(cmd) => cmd.run(),
ForgeSubcommand::Remappings(cmd) => cmd.run(),
ForgeSubcommand::Init(cmd) => cmd.run(),
ForgeSubcommand::Completions { shell } => {
generate(shell, &mut Forge::command(), "forge", &mut std::io::stdout());
Ok(())
}
ForgeSubcommand::GenerateFigSpec => {
clap_complete::generate(
clap_complete_fig::Fig,
&mut Forge::command(),
"forge",
&mut std::io::stdout(),
);
Ok(())
}
ForgeSubcommand::Clean { root } => {
let config = utils::load_config_with_root(root.as_deref())?;
let project = config.project()?;
config.cleanup(&project)?;
Ok(())
}
ForgeSubcommand::Snapshot(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_gas_snapshot(cmd))
} else {
utils::block_on(cmd.run())
}
}
ForgeSubcommand::Fmt(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_fmt(cmd))
} else {
cmd.run()
}
}
ForgeSubcommand::Config(cmd) => cmd.run(),
ForgeSubcommand::Flatten(cmd) => cmd.run(),
ForgeSubcommand::Inspect(cmd) => cmd.run(),
ForgeSubcommand::Tree(cmd) => cmd.run(),
ForgeSubcommand::Geiger(cmd) => {
let n = cmd.run()?;
if n > 0 {
std::process::exit(n as i32);
}
Ok(())
}
ForgeSubcommand::Doc(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_doc(cmd))
} else {
utils::block_on(cmd.run())?;
Ok(())
}
}
ForgeSubcommand::Selectors { command } => utils::block_on(command.run()),
ForgeSubcommand::Generate(cmd) => match cmd.sub {
GenerateSubcommands::Test(cmd) => cmd.run(),
},
ForgeSubcommand::Compiler(cmd) => cmd.run(),
ForgeSubcommand::Soldeer(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Eip712(cmd) => cmd.run(),
ForgeSubcommand::BindJson(cmd) => cmd.run(),
}
}

/// Set the program execution context based on `forge` subcommand used.
/// The execution context can be set only once per program, and it can be checked by using
/// cheatcodes.
fn init_execution_context(subcommand: &ForgeSubcommand) {
let context = match subcommand {
ForgeSubcommand::Test(_) => ForgeContext::Test,
ForgeSubcommand::Coverage(_) => ForgeContext::Coverage,
ForgeSubcommand::Snapshot(_) => ForgeContext::Snapshot,
ForgeSubcommand::Script(cmd) => {
if cmd.broadcast {
ForgeContext::ScriptBroadcast
} else if cmd.resume {
ForgeContext::ScriptResume
} else {
ForgeContext::ScriptDryRun
}
}
_ => ForgeContext::Unknown,
};
set_execution_context(context);
}
143 changes: 143 additions & 0 deletions crates/forge/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::{
cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch},
opts::{Forge, ForgeSubcommand},
};
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use eyre::Result;
use foundry_cli::{handler, utils};
use foundry_common::shell;
use foundry_evm::inspectors::cheatcodes::{set_execution_context, ForgeContext};

/// Run the `forge` command line interface.
pub fn run() -> Result<()> {
// Initialize the global logger and other utilities.
handler::install();
utils::load_dotenv();
utils::subscriber();
utils::enable_paint();

let args = Forge::parse();
args.global.init()?;

// Set the execution context based on the subcommand.
let context = match &args.cmd {
ForgeSubcommand::Test(_) => ForgeContext::Test,
ForgeSubcommand::Coverage(_) => ForgeContext::Coverage,
ForgeSubcommand::Snapshot(_) => ForgeContext::Snapshot,
ForgeSubcommand::Script(cmd) => {
if cmd.broadcast {
ForgeContext::ScriptBroadcast
} else if cmd.resume {
ForgeContext::ScriptResume
} else {
ForgeContext::ScriptDryRun
}
}
_ => ForgeContext::Unknown,
};
set_execution_context(context);

// Run the subcommand.
match args.cmd {
ForgeSubcommand::Test(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_test(cmd))
} else {
let silent = cmd.junit || shell::is_json();
let outcome = utils::block_on(cmd.run())?;
outcome.ensure_ok(silent)
}
}
ForgeSubcommand::Script(cmd) => utils::block_on(cmd.run_script()),
ForgeSubcommand::Coverage(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_coverage(cmd))
} else {
utils::block_on(cmd.run())
}
}
ForgeSubcommand::Bind(cmd) => cmd.run(),
ForgeSubcommand::Build(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_build(cmd))
} else {
cmd.run().map(drop)
}
}
ForgeSubcommand::VerifyContract(args) => utils::block_on(args.run()),
ForgeSubcommand::VerifyCheck(args) => utils::block_on(args.run()),
ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Clone(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Cache(cmd) => match cmd.sub {
CacheSubcommands::Clean(cmd) => cmd.run(),
CacheSubcommands::Ls(cmd) => cmd.run(),
},
ForgeSubcommand::Create(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Update(cmd) => cmd.run(),
ForgeSubcommand::Install(cmd) => cmd.run(),
ForgeSubcommand::Remove(cmd) => cmd.run(),
ForgeSubcommand::Remappings(cmd) => cmd.run(),
ForgeSubcommand::Init(cmd) => cmd.run(),
ForgeSubcommand::Completions { shell } => {
generate(shell, &mut Forge::command(), "forge", &mut std::io::stdout());
Ok(())
}
ForgeSubcommand::GenerateFigSpec => {
clap_complete::generate(
clap_complete_fig::Fig,
&mut Forge::command(),
"forge",
&mut std::io::stdout(),
);
Ok(())
}
ForgeSubcommand::Clean { root } => {
let config = utils::load_config_with_root(root.as_deref())?;
let project = config.project()?;
config.cleanup(&project)?;
Ok(())
}
ForgeSubcommand::Snapshot(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_gas_snapshot(cmd))
} else {
utils::block_on(cmd.run())
}
}
ForgeSubcommand::Fmt(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_fmt(cmd))
} else {
cmd.run()
}
}
ForgeSubcommand::Config(cmd) => cmd.run(),
ForgeSubcommand::Flatten(cmd) => cmd.run(),
ForgeSubcommand::Inspect(cmd) => cmd.run(),
ForgeSubcommand::Tree(cmd) => cmd.run(),
ForgeSubcommand::Geiger(cmd) => {
let n = cmd.run()?;
if n > 0 {
std::process::exit(n as i32);
}
Ok(())
}
ForgeSubcommand::Doc(cmd) => {
if cmd.is_watch() {
utils::block_on(watch::watch_doc(cmd))
} else {
utils::block_on(cmd.run())?;
Ok(())
}
}
ForgeSubcommand::Selectors { command } => utils::block_on(command.run()),
ForgeSubcommand::Generate(cmd) => match cmd.sub {
GenerateSubcommands::Test(cmd) => cmd.run(),
},
ForgeSubcommand::Compiler(cmd) => cmd.run(),
ForgeSubcommand::Soldeer(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::Eip712(cmd) => cmd.run(),
ForgeSubcommand::BindJson(cmd) => cmd.run(),
}
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -109,8 +109,8 @@ impl BuildArgs {
/// Returns the `Project` for the current workspace
///
/// This loads the `foundry_config::Config` for the current workspace (see
/// [`utils::find_project_root`] and merges the cli `BuildArgs` into it before returning
/// [`foundry_config::Config::project()`]
/// [`foundry_config::utils::find_project_root`] and merges the cli `BuildArgs` into it before
/// returning [`foundry_config::Config::project()`]
pub fn project(&self) -> Result<Project> {
self.build.project()
}
@@ -120,8 +120,7 @@ impl BuildArgs {
self.watch.watch.is_some()
}

/// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to
/// bootstrap a new [`watchexe::Watchexec`] loop.
/// Returns the [`watchexec::Config`] necessary to bootstrap a new watch loop.
pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
// Use the path arguments or if none where provided the `src`, `test` and `script`
// directories as well as the `foundry.toml` configuration file.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -306,8 +306,8 @@ impl CloneArgs {
/// - `model_checker`, `debug`, and `output_selection` are ignored for now
///
/// Detailed information can be found from the following link:
/// - https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
/// - https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description
/// - <https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options>
/// - <https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description>
fn update_config_by_metadata(
config: &Config,
doc: &mut toml_edit::DocumentMut,
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use super::{install, test::TestArgs, watch::WatchArgs};
use alloy_primitives::{map::HashMap, Address, Bytes, U256};
use clap::{Parser, ValueEnum, ValueHint};
use eyre::{Context, Result};
use forge::{
use crate::{
coverage::{
analysis::{SourceAnalysis, SourceFile, SourceFiles},
anchors::find_anchors,
BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter,
DebugReporter, ItemAnchor, LcovReporter,
},
opts::EvmOpts,
utils::IcPcMap,
MultiContractRunnerBuilder,
};
use alloy_primitives::{map::HashMap, Address, Bytes, U256};
use clap::{Parser, ValueEnum, ValueHint};
use eyre::{Context, Result};
use foundry_cli::utils::{LoadConfig, STATIC_FUZZ_SEED};
use foundry_common::compile::ProjectCompiler;
use foundry_compilers::{
@@ -23,6 +22,7 @@ use foundry_compilers::{
Artifact, ArtifactId, Project, ProjectCompileOutput, ProjectPathsConfig,
};
use foundry_config::Config;
use foundry_evm::opts::EvmOpts;
use rayon::prelude::*;
use semver::{Version, VersionReq};
use std::{
@@ -330,11 +330,11 @@ impl CoverageArgs {
Ok(())
}

pub(crate) fn is_watch(&self) -> bool {
pub fn is_watch(&self) -> bool {
self.test.is_watch()
}

pub(crate) fn watch(&self) -> &WatchArgs {
pub fn watch(&self) -> &WatchArgs {
&self.test.watch
}
}
Original file line number Diff line number Diff line change
@@ -475,8 +475,6 @@ pub type ContractFactory<P> = DeploymentTxFactory<P>;
/// Helper which manages the deployment transaction of a smart contract. It
/// wraps a deployment transaction, and retrieves the contract address output
/// by it.
///
/// Currently, we recommend using the [`ContractDeployer`] type alias.
#[derive(Debug)]
#[must_use = "ContractDeploymentTx does nothing unless you `send` it"]
pub struct ContractDeploymentTx<P, C> {
@@ -513,9 +511,8 @@ pub struct Deployer<P> {

impl<P: Provider<AnyNetwork>> Deployer<P> {
/// Broadcasts the contract deployment transaction and after waiting for it to
/// be sufficiently confirmed (default: 1), it returns a tuple with
/// the [`Contract`](crate::Contract) struct at the deployed contract's address
/// and the corresponding [`AnyReceipt`].
/// be sufficiently confirmed (default: 1), it returns a tuple with the [`Address`] at the
/// deployed contract's address and the corresponding [`AnyTransactionReceipt`].
pub async fn send_with_receipt(
self,
) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> {
@@ -536,42 +533,9 @@ impl<P: Provider<AnyNetwork>> Deployer<P> {
}
}

/// To deploy a contract to the Ethereum network, a `ContractFactory` can be
/// To deploy a contract to the Ethereum network, a [`ContractFactory`] can be
/// created which manages the Contract bytecode and Application Binary Interface
/// (ABI), usually generated from the Solidity compiler.
///
/// Once the factory's deployment transaction is mined with sufficient confirmations,
/// the [`Contract`](crate::Contract) object is returned.
///
/// # Example
///
/// ```
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// use alloy_primitives::Bytes;
/// use ethers_contract::ContractFactory;
/// use ethers_providers::{Provider, Http};
///
/// // get the contract ABI and bytecode
/// let abi = Default::default();
/// let bytecode = Bytes::from_static(b"...");
///
/// // connect to the network
/// let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
/// let client = std::sync::Arc::new(client);
///
/// // create a factory which will be used to deploy instances of the contract
/// let factory = ContractFactory::new(abi, bytecode, client);
///
/// // The deployer created by the `deploy` call exposes a builder which gets consumed
/// // by the async `send` call
/// let contract = factory
/// .deploy("initial value".to_string())?
/// .confirmations(0usize)
/// .send()
/// .await?;
/// println!("{}", contract.address());
/// # Ok(())
/// # }
#[derive(Clone, Debug)]
pub struct DeploymentTxFactory<P> {
client: P,
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -176,7 +176,8 @@ impl Resolver {
Ok(Some(result))
}

/// Converts given [TypeName] into a type which can be converted to [DynSolType].
/// Converts given [TypeName] into a type which can be converted to
/// [`alloy_dyn_abi::DynSolType`].
///
/// Returns `None` if the type is not supported for EIP712 encoding.
pub fn resolve_type(
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::revm::primitives::Eof;
use alloy_json_abi::{EventParam, InternalType, JsonAbi, Param};
use alloy_primitives::{hex, keccak256, Address};
use clap::Parser;
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table};
use eyre::{Context, Result};
use forge::revm::primitives::Eof;
use foundry_cli::opts::{BuildOpts, CompilerOpts};
use foundry_common::{
compile::{PathOrContractInfo, ProjectCompiler},
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::test;
use crate::result::{SuiteTestResult, TestKindReport, TestOutcome};
use alloy_primitives::{map::HashMap, U256};
use clap::{builder::RangedU64ValueParser, Parser, ValueHint};
use eyre::{Context, Result};
use forge::result::{SuiteTestResult, TestKindReport, TestOutcome};
use foundry_cli::utils::STATIC_FUZZ_SEED;
use regex::Regex;
use std::{
@@ -85,8 +85,7 @@ impl GasSnapshotArgs {
self.test.is_watch()
}

/// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to
/// bootstrap a new [`watchexe::Watchexec`] loop.
/// Returns the [`watchexec::Config`] necessary to bootstrap a new watch loop.
pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
self.test.watchexec_config()
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use super::{install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs};
use alloy_primitives::U256;
use chrono::Utc;
use clap::{Parser, ValueHint};
use eyre::{bail, Context, OptionExt, Result};
use forge::{
use crate::{
decode::decode_console_logs,
gas_report::GasReport,
multi_runner::matches_contract,
@@ -16,6 +12,10 @@ use forge::{
},
MultiContractRunner, MultiContractRunnerBuilder, TestFilter,
};
use alloy_primitives::U256;
use chrono::Utc;
use clap::{Parser, ValueHint};
use eyre::{bail, Context, OptionExt, Result};
use foundry_cli::{
opts::{BuildOpts, GlobalArgs},
utils::{self, LoadConfig},
@@ -53,8 +53,8 @@ use yansi::Paint;

mod filter;
mod summary;
use crate::{result::TestKind, traces::render_trace_arena_inner};
pub use filter::FilterArgs;
use forge::{result::TestKind, traces::render_trace_arena_inner};
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
use summary::{format_invariant_metrics_table, TestSummaryReport};

@@ -835,8 +835,7 @@ impl TestArgs {
self.watch.watch.is_some()
}

/// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to
/// bootstrap a new [`watchexe::Watchexec`] loop.
/// Returns the [`watchexec::Config`] necessary to bootstrap a new watch loop.
pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
self.watch.watchexec_config(|| {
let config = self.load_config()?;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions crates/forge/src/lib.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,10 @@ extern crate foundry_common;
#[macro_use]
extern crate tracing;

pub mod args;
pub mod cmd;
pub mod opts;

pub mod coverage;

pub mod gas_report;
File renamed without changes.