Skip to content

Commit

Permalink
feat: add standardjson compiler input type (gakonst#1169)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Apr 23, 2022
1 parent 35c29c8 commit ac3e12f
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 22 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions ethers-solc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dunce = "1.0.2"
solang-parser = { default-features = false, version = "0.1.12" }
rayon = "1.5.2"
rand = { version = "0.8.5", optional = true }
path-slash = "0.1.4"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
home = "0.5.3"
Expand Down
76 changes: 72 additions & 4 deletions ethers-solc/src/artifacts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub type VersionedSources = BTreeMap<Solc, (Version, Sources)>;
/// A set of different Solc installations with their version and the sources to be compiled
pub type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;

const SOLIDITY: &str = "Solidity";

/// Input type `solc` expects
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompilerInput {
Expand Down Expand Up @@ -77,7 +79,7 @@ impl CompilerInput {
let mut res = Vec::new();
if !solidity_sources.is_empty() {
res.push(Self {
language: "Solidity".to_string(),
language: SOLIDITY.to_string(),
sources: solidity_sources,
settings: Default::default(),
});
Expand Down Expand Up @@ -178,6 +180,52 @@ impl CompilerInput {
}
}

/// A `CompilerInput` representation used for verify
///
/// This type is an alternative `CompilerInput` but uses non-alphabetic ordering of the `sources`
/// and instead emits the (Path -> Source) path in the same order as the pairs in the `sources`
/// `Vec`. This is used over a map, so we can determine the order in which etherscan will display
/// the verified contracts
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StandardJsonCompilerInput {
pub language: String,
#[serde(with = "serde_helpers::tuple_vec_map")]
pub sources: Vec<(PathBuf, Source)>,
pub settings: Settings,
}

// === impl StandardJsonCompilerInput ===

impl StandardJsonCompilerInput {
pub fn new(sources: Vec<(PathBuf, Source)>, settings: Settings) -> Self {
Self { language: SOLIDITY.to_string(), sources, settings }
}

/// Normalizes the EVM version used in the settings to be up to the latest one
/// supported by the provided compiler version.
#[must_use]
pub fn normalize_evm_version(mut self, version: &Version) -> Self {
if let Some(ref mut evm_version) = self.settings.evm_version {
self.settings.evm_version = evm_version.normalize_version(version);
}
self
}
}

impl From<StandardJsonCompilerInput> for CompilerInput {
fn from(input: StandardJsonCompilerInput) -> Self {
let StandardJsonCompilerInput { language, sources, settings } = input;
CompilerInput { language, sources: sources.into_iter().collect(), settings }
}
}

impl From<CompilerInput> for StandardJsonCompilerInput {
fn from(input: CompilerInput) -> Self {
let CompilerInput { language, sources, settings } = input;
StandardJsonCompilerInput { language, sources: sources.into_iter().collect(), settings }
}
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Settings {
Expand Down Expand Up @@ -1486,9 +1534,29 @@ mod tests {

for path in fs::read_dir(dir).unwrap() {
let path = path.unwrap().path();
let compiler_output = fs::read_to_string(&path).unwrap();
serde_json::from_str::<CompilerInput>(&compiler_output).unwrap_or_else(|err| {
panic!("Failed to read compiler output of {} {}", path.display(), err)
let compiler_input = fs::read_to_string(&path).unwrap();
serde_json::from_str::<CompilerInput>(&compiler_input).unwrap_or_else(|err| {
panic!("Failed to read compiler input of {} {}", path.display(), err)
});
}
}

#[test]
fn can_parse_standard_json_compiler_input() {
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
dir.push("test-data/in");

for path in fs::read_dir(dir).unwrap() {
let path = path.unwrap().path();
let compiler_input = fs::read_to_string(&path).unwrap();
let val = serde_json::from_str::<StandardJsonCompilerInput>(&compiler_input)
.unwrap_or_else(|err| {
panic!("Failed to read compiler output of {} {}", path.display(), err)
});

let pretty = serde_json::to_string_pretty(&val).unwrap();
serde_json::from_str::<CompilerInput>(&pretty).unwrap_or_else(|err| {
panic!("Failed to read converted compiler input of {} {}", path.display(), err)
});
}
}
Expand Down
68 changes: 68 additions & 0 deletions ethers-solc/src/artifacts/serde_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,71 @@ pub mod display_from_str_opt {
}
}
}

/// (De)serialize vec of tuples as map
pub mod tuple_vec_map {
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};

pub fn serialize<K, V, S>(data: &[(K, V)], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
K: Serialize,
V: Serialize,
{
serializer.collect_map(data.iter().map(|x| (&x.0, &x.1)))
}

pub fn deserialize<'de, K, V, D>(deserializer: D) -> Result<Vec<(K, V)>, D::Error>
where
D: Deserializer<'de>,
K: DeserializeOwned,
V: DeserializeOwned,
{
use serde::de::{MapAccess, Visitor};
use std::{fmt, marker::PhantomData};

struct TupleVecMapVisitor<K, V> {
marker: PhantomData<Vec<(K, V)>>,
}

impl<K, V> TupleVecMapVisitor<K, V> {
pub fn new() -> Self {
TupleVecMapVisitor { marker: PhantomData }
}
}

impl<'de, K, V> Visitor<'de> for TupleVecMapVisitor<K, V>
where
K: Deserialize<'de>,
V: Deserialize<'de>,
{
type Value = Vec<(K, V)>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}

#[inline]
fn visit_unit<E>(self) -> Result<Vec<(K, V)>, E> {
Ok(Vec::new())
}

#[inline]
fn visit_map<T>(self, mut access: T) -> Result<Vec<(K, V)>, T::Error>
where
T: MapAccess<'de>,
{
let mut values =
Vec::with_capacity(std::cmp::min(access.size_hint().unwrap_or(0), 4096));

while let Some((key, value)) = access.next_entry()? {
values.push((key, value));
}

Ok(values)
}
}

deserializer.deserialize_map(TupleVecMapVisitor::new())
}
}
39 changes: 24 additions & 15 deletions ethers-solc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ mod config;
pub use config::{AllowedLibPaths, PathStyle, ProjectPathsConfig, SolcConfig};

pub mod remappings;
use crate::artifacts::{Source, SourceFile};
use crate::artifacts::{Source, SourceFile, StandardJsonCompilerInput};

pub mod error;
mod filter;
Expand Down Expand Up @@ -428,7 +428,12 @@ impl<T: ArtifactOutput> Project<T> {
}

/// Returns standard-json-input to compile the target contract
pub fn standard_json_input(&self, target: impl AsRef<Path>) -> Result<CompilerInput> {
pub fn standard_json_input(
&self,
target: impl AsRef<Path>,
) -> Result<StandardJsonCompilerInput> {
use path_slash::PathExt;

let target = target.as_ref();
tracing::trace!("Building standard-json-input for {:?}", target);
let graph = Graph::resolve(&self.paths)?;
Expand All @@ -442,28 +447,32 @@ impl<T: ArtifactOutput> Project<T> {
graph.all_imported_nodes(*target_index).map(|index| graph.node(index).unpack()),
);

let compiler_inputs = CompilerInput::with_sources(
sources.into_iter().map(|(s, p)| (s.clone(), p.clone())).collect(),
);

let root = self.root();
let sources = sources
.into_iter()
.map(|(path, source)| {
let path: PathBuf = if let Ok(stripped) = path.strip_prefix(root) {
stripped.to_slash_lossy().into()
} else {
path.to_slash_lossy().into()
};
(path, source.clone())
})
.collect();

let mut settings = self.solc_config.settings.clone();
// strip the path to the project root from all remappings
let remappings = self
settings.remappings = self
.paths
.remappings
.clone()
.into_iter()
.map(|r| r.into_relative(self.root()).to_relative_remapping())
.collect::<Vec<_>>();

let compiler_input = compiler_inputs
.first()
.ok_or_else(|| SolcError::msg("cannot get the compiler input"))?
.clone()
.settings(self.solc_config.settings.clone())
.with_remappings(remappings)
.strip_prefix(self.root());
let input = StandardJsonCompilerInput::new(sources, settings);

Ok(compiler_input)
Ok(input)
}
}

Expand Down
6 changes: 3 additions & 3 deletions ethers-solc/tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use ethers_solc::{
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
project_util::*,
remappings::Remapping,
ConfigurableArtifacts, ExtraOutputValues, Graph, Project, ProjectCompileOutput,
CompilerInput, ConfigurableArtifacts, ExtraOutputValues, Graph, Project, ProjectCompileOutput,
ProjectPathsConfig, Solc, TestFileFilter,
};
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -1023,11 +1023,11 @@ fn can_sanitize_bytecode_hash() {
fn can_compile_std_json_input() {
let tmp = TempProject::dapptools_init().unwrap();
tmp.assert_no_errors();
let source =
tmp.list_source_files().into_iter().filter(|p| p.ends_with("Dapp.t.sol")).next().unwrap();
let source = tmp.list_source_files().into_iter().find(|p| p.ends_with("Dapp.t.sol")).unwrap();
let input = tmp.project().standard_json_input(source).unwrap();

assert!(input.settings.remappings.contains(&"ds-test/=lib/ds-test/src/".parse().unwrap()));
let input: CompilerInput = input.into();
assert!(input.sources.contains_key(Path::new("lib/ds-test/src/test.sol")));

// should be installed
Expand Down

0 comments on commit ac3e12f

Please sign in to comment.