Skip to content

Commit

Permalink
refactor: make Contract generic for Compiler and add metadata to Comp…
Browse files Browse the repository at this point in the history
…ilerOutput (#224)

Right now the compiler abstraction has two couplings that force
foundry-zksync to implement it's [own fork of
compilers](https://github.com/Moonsong-Labs/compilers/tree/zksync-v0.11.6):
1. `Contract` is specific to `solc`/EVM contracts. Era VM contracts,
while requiring different fields, still could use most of the
functionality of the `compilers` pipeline.
2. `CompilerOutput` has `solc` specific fields. `zksolc` compilation has
relevant information that is useful to have later on, for example when
storing `BuildInfo`

This PR implements changes to address this. If implemented, it would
allow `foundry-zksync` (and potentially other non EVM implementations of
foundry) to get rid of all overrides and only maintain ZKsync specific
data structures/trait implementations. See [sample
PR](Moonsong-Labs#42). Changes
include:
1. Make `Compiler` generic over `Contract` (using `CompilerContract` as
a trait type).
2. Add `metadata` field to `CompilerOutput` in order to add arbitrary
data to compilation output.

---------

Co-authored-by: Nisheeth Barthwal <[email protected]>
Co-authored-by: Arsenii Kulikov <[email protected]>
  • Loading branch information
3 people authored Dec 4, 2024
1 parent 88b1892 commit a087dbf
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 144 deletions.
3 changes: 2 additions & 1 deletion crates/compilers/src/artifact_output/configurable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,12 @@ impl ConfigurableArtifacts {

impl ArtifactOutput for ConfigurableArtifacts {
type Artifact = ConfigurableContractArtifact;
type CompilerContract = Contract;

/// Writes extra files for compiled artifact based on [Self::additional_files]
fn handle_artifacts(
&self,
contracts: &crate::VersionedContracts,
contracts: &crate::VersionedContracts<Contract>,
artifacts: &crate::Artifacts<Self::Artifact>,
) -> Result<(), SolcError> {
for (file, contracts) in contracts.as_ref().iter() {
Expand Down
1 change: 1 addition & 0 deletions crates/compilers/src/artifact_output/hh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct HardhatArtifacts {

impl ArtifactOutput for HardhatArtifacts {
type Artifact = HardhatArtifact;
type CompilerContract = Contract;

fn contract_to_artifact(
&self,
Expand Down
29 changes: 16 additions & 13 deletions crates/compilers/src/artifact_output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::{
contracts::VersionedContracts,
sources::{VersionedSourceFile, VersionedSourceFiles},
},
ProjectPathsConfig,
CompilerContract, ProjectPathsConfig,
};

/// Represents unique artifact metadata for identifying artifacts on output
Expand Down Expand Up @@ -110,7 +110,7 @@ impl ArtifactId {
}
}

/// Represents an artifact file representing a [`crate::Contract`]
/// Represents an artifact file representing a [`crate::compilers::CompilerContract`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtifactFile<T> {
/// The Artifact that was written
Expand Down Expand Up @@ -428,7 +428,7 @@ impl<T> Artifacts<T> {
}
}

/// A trait representation for a [`crate::Contract`] artifact
/// A trait representation for a [`crate::compilers::CompilerContract`] artifact
pub trait Artifact {
/// Returns the artifact's [`JsonAbi`] and bytecode.
fn into_inner(self) -> (Option<JsonAbi>, Option<Bytes>);
Expand Down Expand Up @@ -596,9 +596,9 @@ where

/// Handler invoked with the output of `solc`
///
/// Implementers of this trait are expected to take care of [`crate::Contract`] to
/// [`crate::ArtifactOutput::Artifact`] conversion and how that `Artifact` type is stored on disk,
/// this includes artifact file location and naming.
/// Implementers of this trait are expected to take care of [`crate::compilers::CompilerContract`]
/// to [`crate::ArtifactOutput::Artifact`] conversion and how that `Artifact` type is stored on
/// disk, this includes artifact file location and naming.
///
/// Depending on the [`crate::Project`] contracts and their compatible versions,
/// The project compiler may invoke different `solc` executables on the same
Expand All @@ -609,16 +609,17 @@ where
pub trait ArtifactOutput {
/// Represents the artifact that will be stored for a `Contract`
type Artifact: Artifact + DeserializeOwned + Serialize + fmt::Debug + Send + Sync;
type CompilerContract: CompilerContract;

/// Handle the aggregated set of compiled contracts from the solc [`crate::CompilerOutput`].
///
/// This will be invoked with all aggregated contracts from (multiple) solc `CompilerOutput`.
/// See [`crate::AggregatedCompilerOutput`]
fn on_output<C>(
fn on_output<L>(
&self,
contracts: &VersionedContracts,
contracts: &VersionedContracts<Self::CompilerContract>,
sources: &VersionedSourceFiles,
layout: &ProjectPathsConfig<C>,
layout: &ProjectPathsConfig<L>,
ctx: OutputContext<'_>,
) -> Result<Artifacts<Self::Artifact>> {
let mut artifacts = self.output_to_artifacts(contracts, sources, ctx, layout);
Expand All @@ -638,7 +639,7 @@ pub trait ArtifactOutput {
/// Invoked after artifacts has been written to disk for additional processing.
fn handle_artifacts(
&self,
_contracts: &VersionedContracts,
_contracts: &VersionedContracts<Self::CompilerContract>,
_artifacts: &Artifacts<Self::Artifact>,
) -> Result<()> {
Ok(())
Expand Down Expand Up @@ -800,7 +801,7 @@ pub trait ArtifactOutput {
&self,
_file: &Path,
_name: &str,
contract: Contract,
contract: Self::CompilerContract,
source_file: Option<&SourceFile>,
) -> Self::Artifact;

Expand Down Expand Up @@ -845,7 +846,7 @@ pub trait ArtifactOutput {
/// [`Self::on_output()`]
fn output_to_artifacts<C>(
&self,
contracts: &VersionedContracts,
contracts: &VersionedContracts<Self::CompilerContract>,
sources: &VersionedSourceFiles,
ctx: OutputContext<'_>,
layout: &ProjectPathsConfig<C>,
Expand Down Expand Up @@ -1083,6 +1084,7 @@ pub struct MinimalCombinedArtifacts {

impl ArtifactOutput for MinimalCombinedArtifacts {
type Artifact = CompactContractBytecode;
type CompilerContract = Contract;

fn contract_to_artifact(
&self,
Expand Down Expand Up @@ -1112,10 +1114,11 @@ pub struct MinimalCombinedArtifactsHardhatFallback {

impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
type Artifact = CompactContractBytecode;
type CompilerContract = Contract;

fn on_output<C>(
&self,
output: &VersionedContracts,
output: &VersionedContracts<Contract>,
sources: &VersionedSourceFiles,
layout: &ProjectPathsConfig<C>,
ctx: OutputContext<'_>,
Expand Down
16 changes: 9 additions & 7 deletions crates/compilers/src/buildinfo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Represents an entire build
use crate::compilers::{CompilationError, CompilerInput, CompilerOutput, Language};
use crate::compilers::{
CompilationError, CompilerContract, CompilerInput, CompilerOutput, Language,
};
use alloy_primitives::hex;
use foundry_compilers_core::{error::Result, utils};
use md5::Digest;
Expand Down Expand Up @@ -43,7 +45,7 @@ pub struct BuildContext<L> {
}

impl<L: Language> BuildContext<L> {
pub fn new<I, E>(input: &I, output: &CompilerOutput<E>) -> Result<Self>
pub fn new<I, E, C>(input: &I, output: &CompilerOutput<E, C>) -> Result<Self>
where
I: CompilerInput<Language = L>,
{
Expand Down Expand Up @@ -87,9 +89,9 @@ pub struct RawBuildInfo<L> {

impl<L: Language> RawBuildInfo<L> {
/// Serializes a `BuildInfo` object
pub fn new<I: CompilerInput<Language = L>, E: CompilationError>(
pub fn new<I: CompilerInput<Language = L>, E: CompilationError, C: CompilerContract>(
input: &I,
output: &CompilerOutput<E>,
output: &CompilerOutput<E, C>,
full_build_info: bool,
) -> Result<Self> {
let version = input.version().clone();
Expand Down Expand Up @@ -130,7 +132,7 @@ impl<L: Language> RawBuildInfo<L> {
mod tests {
use super::*;
use crate::compilers::solc::SolcVersionedInput;
use foundry_compilers_artifacts::{sources::Source, Error, SolcLanguage, Sources};
use foundry_compilers_artifacts::{sources::Source, Contract, Error, SolcLanguage, Sources};
use std::path::PathBuf;

#[test]
Expand All @@ -142,9 +144,9 @@ mod tests {
SolcLanguage::Solidity,
v,
);
let output = CompilerOutput::<Error>::default();
let output = CompilerOutput::<Error, Contract>::default();
let raw_info = RawBuildInfo::new(&input, &output, true).unwrap();
let _info: BuildInfo<SolcVersionedInput, CompilerOutput<Error>> =
let _info: BuildInfo<SolcVersionedInput, CompilerOutput<Error, Contract>> =
serde_json::from_str(&serde_json::to_string(&raw_info).unwrap()).unwrap();
}
}
22 changes: 17 additions & 5 deletions crates/compilers/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,11 @@ impl GroupedSources {
/// A helper abstraction over the [`CompilerCache`] used to determine what files need to compiled
/// and which `Artifacts` can be reused.
#[derive(Debug)]
pub(crate) struct ArtifactsCacheInner<'a, T: ArtifactOutput, C: Compiler> {
pub(crate) struct ArtifactsCacheInner<
'a,
T: ArtifactOutput<CompilerContract = C::CompilerContract>,
C: Compiler,
> {
/// The preexisting cache file.
pub cache: CompilerCache<C::Settings>,

Expand Down Expand Up @@ -652,7 +656,9 @@ pub(crate) struct ArtifactsCacheInner<'a, T: ArtifactOutput, C: Compiler> {
pub content_hashes: HashMap<PathBuf, String>,
}

impl<T: ArtifactOutput, C: Compiler> ArtifactsCacheInner<'_, T, C> {
impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
ArtifactsCacheInner<'_, T, C>
{
/// Creates a new cache entry for the file
fn create_cache_entry(&mut self, file: PathBuf, source: &Source) {
let imports = self
Expand Down Expand Up @@ -905,20 +911,26 @@ impl<T: ArtifactOutput, C: Compiler> ArtifactsCacheInner<'_, T, C> {
/// Abstraction over configured caching which can be either non-existent or an already loaded cache
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub(crate) enum ArtifactsCache<'a, T: ArtifactOutput, C: Compiler> {
pub(crate) enum ArtifactsCache<
'a,
T: ArtifactOutput<CompilerContract = C::CompilerContract>,
C: Compiler,
> {
/// Cache nothing on disk
Ephemeral(GraphEdges<C::ParsedSource>, &'a Project<C, T>),
/// Handles the actual cached artifacts, detects artifacts that can be reused
Cached(ArtifactsCacheInner<'a, T, C>),
}

impl<'a, T: ArtifactOutput, C: Compiler> ArtifactsCache<'a, T, C> {
impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
ArtifactsCache<'a, T, C>
{
/// Create a new cache instance with the given files
pub fn new(project: &'a Project<C, T>, edges: GraphEdges<C::ParsedSource>) -> Result<Self> {
/// Returns the [CompilerCache] to use
///
/// Returns a new empty cache if the cache does not exist or `invalidate_cache` is set.
fn get_cache<T: ArtifactOutput, C: Compiler>(
fn get_cache<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>(
project: &Project<C, T>,
invalidate_cache: bool,
) -> CompilerCache<C::Settings> {
Expand Down
Loading

0 comments on commit a087dbf

Please sign in to comment.