diff --git a/Cargo.lock b/Cargo.lock index 16d671c33c55f..5523c4e5c77c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4354,6 +4354,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", + "solar-compiler", "thiserror 2.0.16", "toml 0.9.5", "tracing", @@ -4684,6 +4685,7 @@ dependencies = [ "revm-inspectors", "serde", "serde_json", + "solar-compiler", "thiserror 2.0.16", "tracing", "uuid 1.18.1", diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index caafb92464ce8..f9f30f093c78d 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -25,6 +25,8 @@ foundry-evm-traces.workspace = true foundry-wallets.workspace = true forge-script-sequence.workspace = true +solar.workspace = true + alloy-dyn-abi.workspace = true alloy-evm.workspace = true alloy-json-abi.workspace = true diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 7280a22991a46..5828a7927470d 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -8,7 +8,7 @@ use crate::{ mock::{MockCallDataContext, MockCallReturnData}, prank::Prank, }, - inspector::utils::CommonCreateInput, + inspector::{analysis::CheatcodeAnalysis, utils::CommonCreateInput}, script::{Broadcast, Wallets}, test::{ assume::AssumeNoRevert, @@ -75,6 +75,7 @@ use std::{ sync::Arc, }; +mod analysis; mod utils; pub type Ecx<'a, 'b, 'c> = &'a mut EthEvmContext<&'b mut (dyn DatabaseExt + 'c)>; @@ -367,6 +368,9 @@ pub type BroadcastableTransactions = VecDeque; /// allowed to execute cheatcodes #[derive(Clone, Debug)] pub struct Cheatcodes { + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities + pub analysis: Option, + /// The block environment /// /// Used in the cheatcode handler to overwrite the block environment separately from the diff --git a/crates/cheatcodes/src/inspector/analysis.rs b/crates/cheatcodes/src/inspector/analysis.rs new file mode 100644 index 0000000000000..94ad7e104b658 --- /dev/null +++ b/crates/cheatcodes/src/inspector/analysis.rs @@ -0,0 +1,161 @@ +//! Cheatcode information, extracted from the syntactic and semantic analysis of the sources. + +use eyre::{OptionExt, Result}; +use solar::sema::{self, Compiler, Gcx, hir}; +use std::{cell::OnceCell, collections::BTreeMap, sync::Arc}; + +/// Provides cached, on-demand syntactic and semantic analysis of a completed `Compiler` instance. +/// +/// This struct acts as a facade over the `Compiler`, offering lazy-loaded analysis +/// for tools like cheatcode inspectors. It assumes the compiler has already +/// completed parsing and lowering. +/// +/// # Extending with New Analyses +/// +/// To add support for a new type of cached analysis, follow this pattern: +/// +/// 1. Add a new `pub OnceCell>` field to `CheatcodeAnalysis`, where `T` is the type of +/// the data that you are adding support for. +/// +/// 2. Implement a getter method for the new field. Inside the getter, use +/// `self.field.get_or_init()` to compute and cache the value on the first call. +/// +/// 3. Inside the closure passed to `get_or_init()`, create a dedicated visitor to traverse the HIR +/// using `self.compiler.enter()` and collect the required data. +/// +/// This ensures all analyses remain lazy, efficient, and consistent with the existing design. +#[derive(Clone)] +pub struct CheatcodeAnalysis { + /// A shared, thread-safe reference to solar's `Compiler` instance. + pub compiler: Arc, + + /// Cached struct definitions in the sources. + /// Used to keep field order when parsing JSON values. + pub struct_defs: OnceCell>, +} + +pub type StructDefinitions = BTreeMap>; + +impl std::fmt::Debug for CheatcodeAnalysis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CheatcodeAnalysis") + .field("compiler", &"") + .field("struct_defs", &self.struct_defs) + .finish() + } +} + +impl CheatcodeAnalysis { + pub fn new(compiler: Arc) -> Self { + Self { compiler, struct_defs: OnceCell::new() } + } + + /// Lazily initializes and returns the struct definitions. + pub fn struct_defs(&self) -> Result<&StructDefinitions> { + self.struct_defs + .get_or_init(|| { + self.compiler.enter(|compiler| { + let gcx = compiler.gcx(); + + StructDefinitionResolver::new(gcx).process().ok() + }) + }) + .as_ref() + .ok_or_eyre("unable to resolve struct definitions") + } +} + +/// Generates a map of all struct definitions from the HIR using the resolved `Ty` system. +pub struct StructDefinitionResolver<'hir> { + gcx: Gcx<'hir>, + struct_defs: StructDefinitions, +} + +impl<'hir> StructDefinitionResolver<'hir> { + /// Constructs a new generator. + pub fn new(gcx: Gcx<'hir>) -> Self { + Self { gcx, struct_defs: BTreeMap::new() } + } + + /// Processes the HIR to generate all the struct definitions. + pub fn process(mut self) -> Result { + for id in self.hir().strukt_ids() { + self.resolve_struct_definition(id)?; + } + Ok(self.struct_defs) + } + + #[inline] + fn hir(&self) -> &'hir hir::Hir<'hir> { + &self.gcx.hir + } + + /// The recursive core of the generator. Resolves a single struct and adds it to the cache. + fn resolve_struct_definition(&mut self, id: hir::StructId) -> Result<()> { + let qualified_name = self.get_fully_qualified_name(id); + if self.struct_defs.contains_key(&qualified_name) { + return Ok(()); + } + + let hir = self.hir(); + let strukt = hir.strukt(id); + let mut fields = Vec::with_capacity(strukt.fields.len()); + + for &field_id in strukt.fields { + let var = hir.variable(field_id); + let name = + var.name.ok_or_else(|| eyre::eyre!("struct field is missing a name"))?.to_string(); + if let Some(ty_str) = self.ty_to_string(self.gcx.type_of_hir_ty(&var.ty)) { + fields.push((name, ty_str)); + } + } + + // Only insert if there are fields, to avoid adding empty entries + if !fields.is_empty() { + self.struct_defs.insert(qualified_name, fields); + } + + Ok(()) + } + + /// Converts a resolved `Ty` into its canonical string representation. + fn ty_to_string(&mut self, ty: sema::Ty<'hir>) -> Option { + let ty = ty.peel_refs(); + let res = match ty.kind { + sema::ty::TyKind::Elementary(e) => e.to_string(), + sema::ty::TyKind::Array(ty, size) => { + let inner_type = self.ty_to_string(ty)?; + format!("{inner_type}[{size}]") + } + sema::ty::TyKind::DynArray(ty) => { + let inner_type = self.ty_to_string(ty)?; + format!("{inner_type}[]") + } + sema::ty::TyKind::Struct(id) => { + // Ensure the nested struct is resolved before proceeding. + self.resolve_struct_definition(id).ok()?; + self.get_fully_qualified_name(id) + } + sema::ty::TyKind::Udvt(ty, _) => self.ty_to_string(ty)?, + // For now, map enums to `uint8` + sema::ty::TyKind::Enum(_) => "uint8".to_string(), + // For now, map contracts to `address` + sema::ty::TyKind::Contract(_) => "address".to_string(), + // Explicitly disallow unsupported types + _ => return None, + }; + + Some(res) + } + + /// Helper to get the fully qualified name `Contract.Struct`. + fn get_fully_qualified_name(&self, id: hir::StructId) -> String { + let hir = self.hir(); + let strukt = hir.strukt(id); + if let Some(contract_id) = strukt.contract { + format!("{}.{}", hir.contract(contract_id).name.as_str(), strukt.name.as_str()) + } else { + strukt.name.as_str().into() + } + } +} diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 7124c9d0bb0ac..4f8691ab29e00 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -23,6 +23,8 @@ foundry-evm-coverage.workspace = true foundry-evm-fuzz.workspace = true foundry-evm-traces.workspace = true +solar.workspace = true + alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-evm.workspace = true alloy-json-abi.workspace = true diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 8407f9aa09ff9..e36a75391d003 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -2,6 +2,9 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use foundry_evm_core::{Env, backend::Backend}; use revm::primitives::hardfork::SpecId; +// TODO(rusowsky): impl dummy `Debug` trait on `solar::sema::Compiler` +// #[derive(Clone, Debug)] + /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. /// @@ -9,7 +12,7 @@ use revm::primitives::hardfork::SpecId; /// /// [`Cheatcodes`]: super::Cheatcodes /// [`InspectorStack`]: super::InspectorStack -#[derive(Clone, Debug)] +#[derive(Clone)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct ExecutorBuilder { /// The configuration used to build an `InspectorStack`. diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 40ebcb8c9ada6..f250b2a92e5ee 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -34,9 +34,13 @@ use std::{ sync::Arc, }; -#[derive(Clone, Debug, Default)] +// TODO(rusowsky): impl dummy `Debug` trait for solar `Compiler` +// #[derive(Clone, Debug, Default)] +#[derive(Clone, Default)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct InspectorStackBuilder { + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities + pub analysis: Option>, /// The block environment. /// /// Used in the cheatcode handler to overwrite the block environment separately from the @@ -80,6 +84,13 @@ impl InspectorStackBuilder { Self::default() } + /// Set the solar compiler instance that grants syntactic and semantic analysis capabilities + #[inline] + pub fn set_analysis(mut self, analysis: Arc) -> Self { + self.analysis = Some(analysis); + self + } + /// Set the block environment. #[inline] pub fn block(mut self, block: BlockEnv) -> Self { @@ -178,6 +189,7 @@ impl InspectorStackBuilder { /// Builds the stack of inspectors to use when transacting/committing on the EVM. pub fn build(self) -> InspectorStack { let Self { + analysis, block, gas_price, cheatcodes, @@ -204,6 +216,9 @@ impl InspectorStackBuilder { stack.set_cheatcodes(cheatcodes); } + if let Some(analysis) = analysis { + stack.set_analysis(analysis); + } if let Some(fuzzer) = fuzzer { stack.set_fuzzer(fuzzer); } diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index 4c84c798883ac..2556f5b4e8a15 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -1,6 +1,6 @@ use super::{install, test::TestArgs, watch::WatchArgs}; use crate::{ - MultiContractRunnerBuilder, + ConfigAndProject, MultiContractRunnerBuilder, coverage::{ BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, DebugReporter, ItemAnchor, LcovReporter, @@ -103,18 +103,21 @@ impl CoverageArgs { // Coverage analysis requires the Solc AST output. config.ast = true; - let (paths, output) = { - let (project, output) = self.build(&config)?; - (project.paths, output) - }; + let (project, output) = self.build(&config)?; - self.populate_reporters(&paths.root); + self.populate_reporters(&project.paths.root); sh_println!("Analysing contracts...")?; - let report = self.prepare(&paths, &output)?; + let report = self.prepare(&project.paths, &output)?; sh_println!("Running tests...")?; - self.collect(&paths.root, &output, report, Arc::new(config), evm_opts).await + self.collect( + &output, + report, + ConfigAndProject::new(Arc::new(config), Arc::new(project)), + evm_opts, + ) + .await } fn populate_reporters(&mut self, root: &Path) { @@ -261,23 +264,23 @@ impl CoverageArgs { #[instrument(name = "Coverage::collect", skip_all)] async fn collect( mut self, - root: &Path, output: &ProjectCompileOutput, mut report: CoverageReport, - config: Arc, + config_and_project: ConfigAndProject, evm_opts: EvmOpts, ) -> Result<()> { let verbosity = evm_opts.verbosity; + let config = config_and_project.config.clone(); // Build the contract runner let env = evm_opts.evm_env().await?; - let runner = MultiContractRunnerBuilder::new(config.clone()) + let runner = MultiContractRunnerBuilder::new(config_and_project) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) .set_coverage(true) - .build::(root, output, env, evm_opts)?; + .build::(output, env, evm_opts)?; let known_contracts = runner.known_contracts.clone(); diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index d33df4a107fa5..13448b379cff1 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -3,7 +3,7 @@ use crate::{ MultiContractRunner, MultiContractRunnerBuilder, decode::decode_console_logs, gas_report::GasReport, - multi_runner::matches_artifact, + multi_runner::{ConfigAndProject, matches_artifact}, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ CallTraceDecoderBuilder, InternalTraceMode, TraceKind, @@ -17,7 +17,7 @@ use chrono::Utc; use clap::{Parser, ValueHint}; use eyre::{Context, OptionExt, Result, bail}; use foundry_cli::{ - opts::{BuildOpts, GlobalArgs}, + opts::{BuildOpts, GlobalArgs, configure_pcx}, utils::{self, LoadConfig}, }; use foundry_common::{ @@ -272,15 +272,16 @@ impl TestArgs { let filter = self.filter(&config)?; trace!(target: "forge::test", ?filter, "using filter"); + let files = self.get_sources_to_compile(&config, &filter)?; + let analysis_files = files.iter().map(|f| f.to_path_buf()).collect::>(); + let compiler = ProjectCompiler::new() .dynamic_test_linking(config.dynamic_test_linking) .quiet(shell::is_json() || self.junit) - .files(self.get_sources_to_compile(&config, &filter)?); + .files(files); let output = compiler.compile(&project)?; // Create test options from general project settings and compiler output. - let project_root = &project.paths.root; - let should_debug = self.debug; let should_draw = self.flamegraph || self.flamechart; @@ -306,19 +307,40 @@ impl TestArgs { InternalTraceMode::None }; + // Initialize and configure the solar compiler. + let mut analysis = solar::sema::Compiler::new( + solar::interface::Session::builder().with_stderr_emitter().build(), + ); + let dcx = analysis.dcx_mut(); + dcx.set_emitter(Box::new( + solar::interface::diagnostics::HumanEmitter::stderr(Default::default()) + .source_map(Some(dcx.source_map().unwrap().clone())), + )); + dcx.set_flags_mut(|f| f.track_diagnostics = false); + // Populate solar's global context by parsing and lowering the sources. + analysis.enter_mut(|compiler| -> Result<()> { + let mut pcx = compiler.parse(); + configure_pcx(&mut pcx, &config, Some(&project), Some(&analysis_files))?; + pcx.parse(); + let _ = compiler.lower_asts(); + Ok(()) + })?; + // Prepare the test builder. let config = Arc::new(config); - let runner = MultiContractRunnerBuilder::new(config.clone()) - .set_debug(should_debug) - .set_decode_internal(decode_internal) - .initial_balance(evm_opts.initial_balance) - .evm_spec(config.evm_spec_id()) - .sender(evm_opts.sender) - .with_fork(evm_opts.get_fork(&config, env.clone())) - .enable_isolation(evm_opts.isolate) - .fail_fast(self.fail_fast) - .odyssey(evm_opts.odyssey) - .build::(project_root, &output, env, evm_opts)?; + let project = Arc::new(project); + let runner = + MultiContractRunnerBuilder::new(ConfigAndProject::new(config.clone(), project.clone())) + .set_debug(should_debug) + .set_decode_internal(decode_internal) + .initial_balance(evm_opts.initial_balance) + .evm_spec(config.evm_spec_id()) + .sender(evm_opts.sender) + .with_fork(evm_opts.get_fork(&config, env.clone())) + .enable_isolation(evm_opts.isolate) + .fail_fast(self.fail_fast) + .odyssey(evm_opts.odyssey) + .build::(&output, env, evm_opts)?; let libraries = runner.libraries.clone(); let mut outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index fdbad8e4e6b2e..576e813c17ca1 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -18,7 +18,7 @@ pub mod coverage; pub mod gas_report; pub mod multi_runner; -pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder}; +pub use multi_runner::{ConfigAndProject, MultiContractRunner, MultiContractRunnerBuilder}; mod runner; pub use runner::ContractRunner; diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 28717b3700196..2722bb25cb3a1 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -7,14 +7,14 @@ use crate::{ use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; +use foundry_cli::opts::configure_pcx; use foundry_common::{ ContractsByArtifact, ContractsByArtifactBuilder, TestFunctionExt, get_contract_name, shell::verbosity, }; use foundry_compilers::{ - Artifact, ArtifactId, ProjectCompileOutput, + Artifact, ArtifactId, Compiler, Project, ProjectCompileOutput, artifacts::{Contract, Libraries}, - compilers::Compiler, }; use foundry_config::{Config, InlineConfig}; use foundry_evm::{ @@ -61,6 +61,8 @@ pub struct MultiContractRunner { pub libs_to_deploy: Vec, /// Library addresses used to link contracts. pub libraries: Libraries, + /// Solar compiler instance, to grant syntactic and semantic analysis capabilities + pub analysis: Arc, /// The fork to use at launch pub fork: Option, @@ -252,7 +254,12 @@ impl MultiContractRunner { debug!("start executing all tests in contract"); - let executor = self.tcfg.executor(self.known_contracts.clone(), artifact_id, db.clone()); + let executor = self.tcfg.executor( + self.known_contracts.clone(), + self.analysis.clone(), + artifact_id, + db.clone(), + ); let runner = ContractRunner::new( &identifier, contract, @@ -350,6 +357,7 @@ impl TestRunnerConfig { pub fn executor( &self, known_contracts: ContractsByArtifact, + analysis: Arc, artifact_id: &ArtifactId, db: Backend, ) -> Executor { @@ -384,8 +392,20 @@ impl TestRunnerConfig { } } +#[derive(Clone)] +pub struct ConfigAndProject { + pub config: Arc, + pub project: Arc, +} + +impl ConfigAndProject { + pub fn new(config: Arc, project: Arc) -> Self { + Self { config, project } + } +} + /// Builder used for instantiating the multi-contract runner -#[derive(Clone, Debug)] +#[derive(Clone)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct MultiContractRunnerBuilder { /// The address which will be used to deploy the initial contracts and send all @@ -398,7 +418,7 @@ pub struct MultiContractRunnerBuilder { /// The fork to use at launch pub fork: Option, /// Project config. - pub config: Arc, + pub config_and_project: ConfigAndProject, /// Whether or not to collect line coverage info pub line_coverage: bool, /// Whether or not to collect debug info @@ -414,9 +434,9 @@ pub struct MultiContractRunnerBuilder { } impl MultiContractRunnerBuilder { - pub fn new(config: Arc) -> Self { + pub fn new(config_and_project: ConfigAndProject) -> Self { Self { - config, + config_and_project, sender: Default::default(), initial_balance: Default::default(), evm_spec: Default::default(), @@ -430,6 +450,14 @@ impl MultiContractRunnerBuilder { } } + fn config(&self) -> &Arc { + &self.config_and_project.config + } + + fn project(&self) -> &Arc { + &self.config_and_project.project + } + pub fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self @@ -484,11 +512,11 @@ impl MultiContractRunnerBuilder { /// against that evm pub fn build>( self, - root: &Path, output: &ProjectCompileOutput, env: Env, evm_opts: EvmOpts, ) -> Result { + let root = self.project().root(); let contracts = output .artifact_ids() .map(|(id, v)| (id.with_stripped_file_prefixes(root), v)) @@ -539,29 +567,52 @@ impl MultiContractRunnerBuilder { .with_storage_layouts(output.clone().with_stripped_file_prefixes(root)) .build(); + // Initialize and configure the solar compiler. + let mut analysis = solar::sema::Compiler::new( + solar::interface::Session::builder().with_stderr_emitter().build(), + ); + let dcx = analysis.dcx_mut(); + dcx.set_emitter(Box::new( + solar::interface::diagnostics::HumanEmitter::stderr(Default::default()) + .source_map(Some(dcx.source_map().unwrap().clone())), + )); + dcx.set_flags_mut(|f| f.track_diagnostics = false); + + // Populate solar's global context by parsing and lowering the sources. + let files: Vec<_> = + output.output().sources.as_ref().iter().map(|(path, _)| path.to_path_buf()).collect(); + analysis.enter_mut(|compiler| -> Result<()> { + let mut pcx = compiler.parse(); + configure_pcx(&mut pcx, self.config(), Some(self.project()), Some(&files))?; + pcx.parse(); + let _ = compiler.lower_asts(); + Ok(()) + })?; + Ok(MultiContractRunner { contracts: deployable_contracts, revert_decoder, known_contracts, libs_to_deploy, libraries, - - fork: self.fork, + analysis: Arc::new(analysis), tcfg: TestRunnerConfig { evm_opts, env, - spec_id: self.evm_spec.unwrap_or_else(|| self.config.evm_spec_id()), - sender: self.sender.unwrap_or(self.config.sender), + spec_id: self.evm_spec.unwrap_or_else(|| self.config().evm_spec_id()), + sender: self.sender.unwrap_or(self.config().sender), line_coverage: self.line_coverage, debug: self.debug, decode_internal: self.decode_internal, - inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), + inline_config: Arc::new(InlineConfig::new_parsed(output, self.config())?), isolation: self.isolation, odyssey: self.odyssey, - config: self.config, + config: self.config().clone(), fail_fast: FailFast::new(self.fail_fast), }, + + fork: self.fork, }) } } diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 7891f8c81b625..8781eeab45306 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -1,4 +1,6 @@ //! Forge tests for cheatcodes. +use std::sync::Arc; + use crate::{ config::*, test_helpers::{ @@ -76,7 +78,7 @@ async fn test_state_diff_storage_layout() { let mut project = config.project().unwrap(); // Compile with StorageLayout let output = get_compiled(&mut project); - ForgeTestData { project, output, config: config.into(), profile } + ForgeTestData { project: Arc::new(project), output, config: config.into(), profile } }; let filter = Filter::new( ".*", diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index b891ebb057d04..e687b25ae7a9a 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -2,7 +2,7 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; -use forge::{MultiContractRunner, MultiContractRunnerBuilder}; +use forge::{ConfigAndProject, MultiContractRunner, MultiContractRunnerBuilder}; use foundry_cli::utils::install_crypto_provider; use foundry_compilers::{ Project, ProjectCompileOutput, SolcConfig, Vyper, @@ -165,7 +165,7 @@ impl ForgeTestProfile { /// Container for test data for a specific test profile. pub struct ForgeTestData { - pub project: Project, + pub project: Arc, pub output: ProjectCompileOutput, pub config: Arc, pub profile: ForgeTestProfile, @@ -181,14 +181,18 @@ impl ForgeTestData { let config = Arc::new(profile.config()); let mut project = config.project().unwrap(); let output = get_compiled(&mut project); - Self { project, output, config, profile } + Self { project: Arc::new(project), output, config, profile } } /// Builds a base runner pub fn base_runner(&self) -> MultiContractRunnerBuilder { init_tracing(); let config = self.config.clone(); - let mut runner = MultiContractRunnerBuilder::new(config).sender(self.config.sender); + let mut runner = MultiContractRunnerBuilder::new(ConfigAndProject { + config, + project: self.project.clone(), + }) + .sender(self.config.sender); if self.profile.is_paris() { runner = runner.evm_spec(SpecId::MERGE); } @@ -220,12 +224,12 @@ impl ForgeTestData { let mut builder = self.base_runner(); let config = Arc::new(config); - let root = self.project.root(); - builder.config = config.clone(); + builder.config_and_project = + ConfigAndProject { config: config.clone(), project: self.project.clone() }; builder .enable_isolation(opts.isolate) .sender(config.sender) - .build::(root, &self.output, opts.local_evm_env(), opts) + .build::(&self.output, opts.local_evm_env(), opts) .unwrap() } @@ -233,9 +237,7 @@ impl ForgeTestData { pub fn tracing_runner(&self) -> MultiContractRunner { let mut opts = config_evm_opts(&self.config); opts.verbosity = 5; - self.base_runner() - .build::(self.project.root(), &self.output, opts.local_evm_env(), opts) - .unwrap() + self.base_runner().build::(&self.output, opts.local_evm_env(), opts).unwrap() } /// Builds a runner that runs against forked state @@ -248,10 +250,7 @@ impl ForgeTestData { let env = opts.evm_env().await.expect("Could not instantiate fork environment"); let fork = opts.get_fork(&Default::default(), env.clone()); - self.base_runner() - .with_fork(fork) - .build::(self.project.root(), &self.output, env, opts) - .unwrap() + self.base_runner().with_fork(fork).build::(&self.output, env, opts).unwrap() } }