Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat(solc): add hardhat artifact support #677

Merged
merged 6 commits into from
Dec 11, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

### Unreleased

- Add support for hardhat artifacts [#677](https://github.com/gakonst/ethers-rs/pull/677)
- Add more utility functions to the `Artifact` trait [#673](https://github.com/gakonst/ethers-rs/pull/673)
- Return cached artifacts from project `compile` when the cache only contains
some files
Expand Down
7 changes: 7 additions & 0 deletions ethers-solc/src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,13 @@ impl BytecodeObject {
}
}

// Returns a not deployable bytecode by default as "0x"
impl Default for BytecodeObject {
fn default() -> Self {
BytecodeObject::Unlinked("0x".to_string())
}
}

impl AsRef<[u8]> for BytecodeObject {
fn as_ref(&self) -> &[u8] {
match self {
Expand Down
21 changes: 19 additions & 2 deletions ethers-solc/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ use std::{
/// Hardhat format version
const HH_FORMAT_VERSION: &str = "hh-sol-cache-2";

/// ethers-rs format version
///
/// `ethers-solc` uses a different format version id, but the actual format is consistent with
/// hardhat This allows ethers-solc to detect if the cache file was written by hardhat or
/// `ethers-solc`
const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-cache-1";

/// The file name of the default cache file
pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json";

Expand All @@ -42,6 +49,16 @@ impl SolFilesCache {
SolFilesCacheBuilder::default()
}

/// Whether this cache's format is the hardhat format identifier
pub fn is_hardhat_format(&self) -> bool {
self.format == HH_FORMAT_VERSION
}

/// Whether this cache's format is our custom format identifier
pub fn is_ethers_format(&self) -> bool {
self.format == ETHERS_FORMAT_VERSION
}

/// Reads the cache json file from the given path
#[tracing::instrument(skip_all, name = "sol-files-cache::read")]
pub fn read(path: impl AsRef<Path>) -> Result<Self> {
Expand Down Expand Up @@ -161,7 +178,7 @@ impl SolFilesCache {
})
}

/// Reads all cached artifacts from disk
/// Reads all cached artifacts from disk using the given ArtifactOutput handler
pub fn read_artifacts<T: ArtifactOutput>(
&self,
artifacts_root: &Path,
Expand Down Expand Up @@ -215,7 +232,7 @@ impl SolFilesCacheBuilder {
}

pub fn insert_files(self, sources: Sources, dest: Option<PathBuf>) -> Result<SolFilesCache> {
let format = self.format.unwrap_or_else(|| HH_FORMAT_VERSION.to_string());
let format = self.format.unwrap_or_else(|| ETHERS_FORMAT_VERSION.to_string());
let solc_config =
self.solc_config.map(Ok).unwrap_or_else(|| SolcConfig::builder().build())?;

Expand Down
109 changes: 58 additions & 51 deletions ethers-solc/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::{
artifacts::{CompactContract, CompactContractRef, Contract, Settings},
cache::SOLIDITY_FILES_CACHE_FILENAME,
error::Result,
error::{Result, SolcError},
hh::HardhatArtifact,
remappings::Remapping,
CompilerOutput,
};
use ethers_core::{abi::Abi, types::Bytes};
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
collections::BTreeMap,
convert::TryFrom,
Expand Down Expand Up @@ -225,37 +226,27 @@ pub trait Artifact {
fn into_compact_contract(self) -> CompactContract;

/// Returns the contents of this type as a single tuple of abi, bytecode and deployed bytecode
fn into_parts(self) -> (Option<Abi>, Option<Bytes>, Option<Bytes>)
where
Self: Sized,
{
self.into_compact_contract().into_parts()
}
fn into_parts(self) -> (Option<Abi>, Option<Bytes>, Option<Bytes>);
}

impl Artifact for CompactContract {
impl<T: Into<CompactContract>> Artifact for T {
fn into_inner(self) -> (Option<Abi>, Option<Bytes>) {
(self.abi, self.bin.and_then(|bin| bin.into_bytes()))
let artifact = self.into_compact_contract();
(artifact.abi, artifact.bin.and_then(|bin| bin.into_bytes()))
}

fn into_compact_contract(self) -> CompactContract {
self
}
}

impl Artifact for serde_json::Value {
fn into_inner(self) -> (Option<Abi>, Option<Bytes>) {
self.into_compact_contract().into_inner()
self.into()
}

fn into_compact_contract(self) -> CompactContract {
self.into()
fn into_parts(self) -> (Option<Abi>, Option<Bytes>, Option<Bytes>) {
self.into_compact_contract().into_parts()
}
}

pub trait ArtifactOutput {
/// How Artifacts are stored
type Artifact: Artifact;
type Artifact: Artifact + DeserializeOwned;

/// Handle the compiler output.
fn on_output(output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()>;
Expand Down Expand Up @@ -295,7 +286,11 @@ pub trait ArtifactOutput {
root.as_ref().join(Self::output_file(contract_file, name)).exists()
}

fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact>;
fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact> {
let file = fs::File::open(path.as_ref())?;
let file = io::BufReader::new(file);
Ok(serde_json::from_reader(file)?)
}

/// Read the cached artifacts from disk
fn read_cached_artifacts<T, I>(files: I) -> Result<BTreeMap<PathBuf, Self::Artifact>>
Expand All @@ -313,21 +308,22 @@ pub trait ArtifactOutput {
}

/// Convert a contract to the artifact type
fn contract_to_artifact(contract: Contract) -> Self::Artifact;
fn contract_to_artifact(file: &str, name: &str, contract: Contract) -> Self::Artifact;

/// Convert the compiler output into a set of artifacts
fn output_to_artifacts(output: CompilerOutput) -> Artifacts<Self::Artifact> {
output
.contracts
.into_iter()
.map(|(s, contracts)| {
(
s,
contracts
.into_iter()
.map(|(s, c)| (s, Self::contract_to_artifact(c)))
.collect(),
)
.map(|(file, contracts)| {
let contracts = contracts
.into_iter()
.map(|(name, c)| {
let contract = Self::contract_to_artifact(&file, &name, c);
(name, contract)
})
.collect();
(file, contracts)
})
.collect()
}
Expand All @@ -350,49 +346,60 @@ impl ArtifactOutput for MinimalCombinedArtifacts {
type Artifact = CompactContract;

fn on_output(output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
fs::create_dir_all(&layout.artifacts)?;
fs::create_dir_all(&layout.artifacts)
.map_err(|err| SolcError::msg(format!("Failed to create artifacts dir: {}", err)))?;
for (file, contracts) in output.contracts.iter() {
for (name, contract) in contracts {
let artifact = Self::output_file(file, name);
let file = layout.artifacts.join(artifact);
if let Some(parent) = file.parent() {
fs::create_dir_all(parent)?;
fs::create_dir_all(parent).map_err(|err| {
SolcError::msg(format!(
"Failed to create artifact parent folder \"{}\": {}",
parent.display(),
err
))
})?;
}
let min = CompactContractRef::from(contract);
fs::write(file, serde_json::to_vec_pretty(&min)?)?
fs::write(&file, serde_json::to_vec_pretty(&min)?)?
}
}
Ok(())
}

fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact> {
let file = fs::File::open(path.as_ref())?;
let file = io::BufReader::new(file);
Ok(serde_json::from_reader(file)?)
}

fn contract_to_artifact(contract: Contract) -> Self::Artifact {
CompactContract::from(contract)
fn contract_to_artifact(_file: &str, _name: &str, contract: Contract) -> Self::Artifact {
Self::Artifact::from(contract)
}
}

/// Hardhat style artifacts
/// An Artifacts handler implementation that works the same as `MinimalCombinedArtifacts` but also
/// supports reading hardhat artifacts if an initial attempt to deserialize an artifact failed
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct HardhatArtifacts;
pub struct MinimalCombinedArtifactsHardhatFallback;

impl ArtifactOutput for HardhatArtifacts {
type Artifact = serde_json::Value;
impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
type Artifact = CompactContract;

fn on_output(_output: &CompilerOutput, _layout: &ProjectPathsConfig) -> Result<()> {
todo!("Hardhat style artifacts not yet implemented")
fn on_output(output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
MinimalCombinedArtifacts::on_output(output, layout)
}

fn read_cached_artifact(_path: impl AsRef<Path>) -> Result<Self::Artifact> {
todo!("Hardhat style artifacts not yet implemented")
fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact> {
let content = fs::read_to_string(path)?;
if let Ok(a) = serde_json::from_str(&content) {
Ok(a)
} else {
tracing::error!("Failed to deserialize compact artifact");
tracing::trace!("Fallback to hardhat artifact deserialization");
let artifact = serde_json::from_str::<HardhatArtifact>(&content)?;
tracing::trace!("successfully deserialized hardhat artifact");
Ok(artifact.into_compact_contract())
}
}

fn contract_to_artifact(_contract: Contract) -> Self::Artifact {
todo!("Hardhat style artifacts not yet implemented")
fn contract_to_artifact(file: &str, name: &str, contract: Contract) -> Self::Artifact {
MinimalCombinedArtifacts::contract_to_artifact(file, name, contract)
}
}

Expand Down
6 changes: 6 additions & 0 deletions ethers-solc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ pub enum SolcError {
NoContracts(String),
#[error(transparent)]
PatternError(#[from] glob::PatternError),
/// General purpose message
#[error("{0}")]
Message(String),
}

impl SolcError {
pub(crate) fn solc(msg: impl Into<String>) -> Self {
SolcError::SolcError(msg.into())
}
pub(crate) fn msg(msg: impl Into<String>) -> Self {
SolcError::Message(msg.into())
}
}
Loading