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

refactor: decouple Foundry project layout and build system #572

Merged
merged 2 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 1 addition & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/edr_napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ mimalloc = { version = "0.1.39", default-features = false, features = ["local_dy
# Solidity tests
alloy-primitives = { workspace = true, features = ["serde"] }
forge.workspace = true
foundry-compilers = { workspace = true, features = ["full"] }
foundry-common.workspace = true
foundry-config.workspace = true
tempfile = "3.10.1"

Expand Down
74 changes: 37 additions & 37 deletions crates/edr_napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,44 @@ export interface ExecutionResult {
/** Optional contract address if the transaction created a new contract. */
contractAddress?: Buffer
}
/** A compilation artifact. */
export interface Artifact {
/** The identifier of the artifact. */
id: ArtifactId
/** The test contract. */
contract: ContractData
}
/** The identifier of a Solidity contract. */
export interface ArtifactId {
/** The name of the contract. */
name: string
/** Original source file path. */
source: string
/** The solc semver string. */
solcVersion: string
}
/** A test contract to execute. */
export interface ContractData {
/** Contract ABI as json string. */
abi: string
/**
* Contract creation code as hex string. It can be missing if the contract
* is ABI only.
*/
bytecode?: string
/**
* Contract runtime code as hex string. It can be missing if the contract
* is ABI only.
*/
deployedBytecode?: string
}
/** See [forge::result::SuiteResult] */
export interface SuiteResult {
/** See [forge::result::SuiteResult::name] */
readonly name: string
/**
* The artifact id can be used to match input to result in the progress
* callback
*/
readonly id: ArtifactId
/** See [forge::result::SuiteResult::duration] */
readonly durationMs: bigint
/** See [forge::result::SuiteResult::test_results] */
Expand Down Expand Up @@ -427,40 +461,6 @@ export interface BaseCounterExample {
/** See [forge::fuzz::BaseCounterExample::args] */
readonly args?: string
}
/** A test suite is a contract and its test methods. */
export interface TestSuite {
/** The identifier of the test suite. */
id: ArtifactId
/** The test contract. */
contract: TestContract
}
/** The identifier of a Solidity test contract. */
export interface ArtifactId {
/** The name of the contract. */
name: string
/** Original source file path. */
source: string
/** The solc semver string. */
solcVersion: string
/** The artifact cache path. Currently unused. */
artifactCachePath: string
}
/** A test contract to execute. */
export interface TestContract {
/** The contract ABI as a JSON string. */
abi: string
/** The contract bytecode including all libraries as a hex string. */
bytecode: string
/** Vector of library bytecodes to deploy as hex string. */
libsToDeploy: Array<string>
/**
* Vector of library specifications of the form corresponding to libs to
* deploy, example item:
* `"src/DssSpell.sol:DssExecLib:
* 0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4"`
*/
libraries: Array<string>
}
/**
* Executes Solidity tests.
*
Expand All @@ -469,7 +469,7 @@ export interface TestContract {
* It is up to the caller to track how many times the callback is called to
* know when all tests are done.
*/
export function runSolidityTests(testSuites: Array<TestSuite>, gasReport: boolean, progressCallback: (result: SuiteResult) => void): void
export function runSolidityTests(artifacts: Array<Artifact>, testSuites: Array<ArtifactId>, gasReport: boolean, progressCallback: (result: SuiteResult) => void): void
export interface SubscriptionEvent {
filterId: bigint
result: any
Expand Down
36 changes: 26 additions & 10 deletions crates/edr_napi/src/solidity_tests.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod artifact;
mod config;
mod runner;
mod test_results;
mod test_suite;

use std::{path::Path, sync::Arc};
use std::{collections::BTreeMap, path::Path, sync::Arc};

use artifact::Artifact;
use forge::TestFilter;
use foundry_common::{ContractData, ContractsByArtifact};
use napi::{
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
Expand All @@ -17,7 +19,7 @@ use napi::{
use napi_derive::napi;

use crate::solidity_tests::{
runner::build_runner, test_results::SuiteResult, test_suite::TestSuite,
artifact::ArtifactId, runner::build_runner, test_results::SuiteResult,
};

/// Executes Solidity tests.
Expand All @@ -30,7 +32,8 @@ use crate::solidity_tests::{
#[allow(dead_code)]
#[napi]
pub fn run_solidity_tests(
test_suites: Vec<TestSuite>,
artifacts: Vec<Artifact>,
test_suites: Vec<ArtifactId>,
gas_report: bool,
#[napi(ts_arg_type = "(result: SuiteResult) => void")] progress_callback: JsFunction,
) -> napi::Result<()> {
Expand All @@ -41,14 +44,23 @@ pub fn run_solidity_tests(
|ctx: ThreadSafeCallContext<SuiteResult>| Ok(vec![ctx.value]),
)?;

let test_suites = test_suites
let known_contracts: ContractsByArtifact = artifacts
.into_iter()
.map(|item| Ok((item.id.try_into()?, item.contract.try_into()?)))
.collect::<Result<Vec<_>, napi::Error>>()?;
let runner = build_runner(test_suites, gas_report)?;
.collect::<Result<BTreeMap<foundry_common::ArtifactId, ContractData>, napi::Error>>()?
.into();

let test_suites = test_suites
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?;

let runner = build_runner(&known_contracts, test_suites, gas_report)?;

let (tx_results, mut rx_results) =
tokio::sync::mpsc::unbounded_channel::<(String, forge::result::SuiteResult)>();
let (tx_results, mut rx_results) = tokio::sync::mpsc::unbounded_channel::<(
foundry_common::ArtifactId,
forge::result::SuiteResult,
)>();

let runtime = runtime::Handle::current();
runtime.spawn(async move {
Expand All @@ -68,7 +80,11 @@ pub fn run_solidity_tests(
});

// Returns immediately after test suite execution is started
runner.test_hardhat(Arc::new(EverythingFilter), tx_results);
runner.test_hardhat(
Arc::new(known_contracts),
Arc::new(EverythingFilter),
tx_results,
);

Ok(())
}
Expand Down
97 changes: 97 additions & 0 deletions crates/edr_napi/src/solidity_tests/artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use napi_derive::napi;

/// A compilation artifact.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct Artifact {
/// The identifier of the artifact.
pub id: ArtifactId,
/// The test contract.
pub contract: ContractData,
}

/// The identifier of a Solidity contract.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct ArtifactId {
/// The name of the contract.
pub name: String,
/// Original source file path.
pub source: String,
/// The solc semver string.
pub solc_version: String,
}

impl From<foundry_common::contracts::ArtifactId> for ArtifactId {
fn from(value: foundry_common::ArtifactId) -> Self {
Self {
name: value.name,
source: value.source.to_string_lossy().to_string(),
solc_version: value.version.to_string(),
}
}
}

impl TryFrom<ArtifactId> for foundry_common::contracts::ArtifactId {
type Error = napi::Error;

fn try_from(value: ArtifactId) -> napi::Result<Self> {
Ok(foundry_common::contracts::ArtifactId {
name: value.name,
source: value.source.parse().map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid source path")
})?,
version: value.solc_version.parse().map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid solc semver string")
})?,
})
}
}

/// A test contract to execute.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct ContractData {
/// Contract ABI as json string.
pub abi: String,
/// Contract creation code as hex string. It can be missing if the contract
/// is ABI only.
pub bytecode: Option<String>,
/// Contract runtime code as hex string. It can be missing if the contract
/// is ABI only.
pub deployed_bytecode: Option<String>,
}

impl TryFrom<ContractData> for foundry_common::contracts::ContractData {
type Error = napi::Error;

fn try_from(contract: ContractData) -> napi::Result<Self> {
Ok(foundry_common::contracts::ContractData {
abi: serde_json::from_str(&contract.abi).map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid JSON ABI")
})?,
bytecode: contract
.bytecode
.map(|b| {
b.parse().map_err(|_err| {
napi::Error::new(
napi::Status::GenericFailure,
"Invalid hex bytecode for contract",
)
})
})
.transpose()?,
deployed_bytecode: contract
.deployed_bytecode
.map(|b| {
b.parse().map_err(|_err| {
napi::Error::new(
napi::Status::GenericFailure,
"Invalid hex deployed bytecode for contract",
)
})
})
.transpose()?,
})
}
}
18 changes: 4 additions & 14 deletions crates/edr_napi/src/solidity_tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ use forge::{
inspectors::cheatcodes::CheatsConfigOptions,
opts::{Env as EvmEnv, EvmOpts},
};
use foundry_compilers::ProjectPathsConfig;
use foundry_config::{
cache::StorageCachingConfig, fs_permissions::PathPermission, FsPermissions, FuzzConfig,
GasLimit, InvariantConfig, RpcEndpoint, RpcEndpoints,
cache::StorageCachingConfig, FsPermissions, FuzzConfig, GasLimit, InvariantConfig, RpcEndpoint,
RpcEndpoints,
};

/// Solidity tests configuration
Expand Down Expand Up @@ -66,16 +65,6 @@ impl SolidityTestsConfig {
"/../../crates/foundry/testdata"
));

// TODO https://github.com/NomicFoundation/edr/issues/487
let project_paths_config: ProjectPathsConfig<foundry_compilers::Solc> =
ProjectPathsConfig::builder().build_with_root(project_root.clone());

let artifacts: PathBuf = project_paths_config
.artifacts
.file_name()
.expect("artifacts are not relative")
.into();

// Matches Foundry config defaults
let cheats_config_options = CheatsConfigOptions {
rpc_endpoints: RpcEndpoints::new([(
Expand All @@ -85,7 +74,8 @@ impl SolidityTestsConfig {
unchecked_cheatcode_artifacts: false,
prompt_timeout: 0,
rpc_storage_caching: StorageCachingConfig::default(),
fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]),
// Hardhat doesn't support loading artifacts from disk
fs_permissions: FsPermissions::new(vec![]),
labels: HashMap::default(),
};

Expand Down
Loading
Loading