From 3b713ad2cb961bf8d014f602bef4920bf4248408 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 21 Mar 2025 18:10:17 +0100 Subject: [PATCH] building for EVM works with complex cases Signed-off-by: Cyrill Leutwiler --- Cargo.lock | 5 +++ Cargo.toml | 8 +--- crates/compiler/Cargo.toml | 1 + crates/compiler/src/solc.rs | 29 +++++++++++-- crates/core/Cargo.toml | 5 +++ crates/core/src/driver/compiler.rs | 67 ++++++++++++++++++++++++++++++ crates/core/src/driver/mod.rs | 24 +++++++++++ crates/core/src/main.rs | 17 +++++++- crates/format/Cargo.toml | 3 +- crates/format/src/metadata.rs | 7 ++-- 10 files changed, 149 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3916e3d..b6c2705 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2832,6 +2832,7 @@ dependencies = [ "revive-common", "revive-solc-json-interface", "semver 1.0.26", + "serde_json", ] [[package]] @@ -2842,7 +2843,11 @@ dependencies = [ "clap", "env_logger", "log", + "rayon", + "revive-dt-compiler", "revive-dt-format", + "revive-solc-json-interface", + "semver 1.0.26", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index b06ef33..24e1b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ hex = "0.4.3" reqwest = { version = "0.12.15", features = ["blocking", "json"] } log = "0.4.26" once_cell = "1.21" -semver = {version = "1.0", features = ["serde"] } +rayon = { version = "1.10" } +semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false, features = ["arbitrary_precision", "std"] } sha2 = { version = "0.10.8" } @@ -36,11 +37,6 @@ tokio = { version = "1", default-features = false, features = ["rt-multi-thread" revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "497dae2494dabe12d1af32d6d687122903cb2ada" } revive-common = { git = "https://github.com/paritytech/revive", rev = "497dae2494dabe12d1af32d6d687122903cb2ada" } -# polkadot-sdk and friends -codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } -scale-info = { version = "2.11.6", default-features = false } -polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "c29e72a8628835e34deb6aa7db9a78a2e4eabcee" } - [profile.bench] inherits = "release" lto = true diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index 3d32548..7c5af19 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -13,3 +13,4 @@ anyhow = { workspace = true } revive-solc-json-interface = { workspace = true } revive-common = { workspace = true } semver = { workspace = true } +serde_json = { workspace = true } diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index 7be5301..3d41c66 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -1,25 +1,46 @@ //! Implements the [SolidityCompiler] trait with solc for //! compiling contracts to EVM bytecode. +use std::{ + path::PathBuf, + process::{Command, Stdio}, +}; + use revive_solc_json_interface::{SolcStandardJsonInput, SolcStandardJsonOutput}; use semver::Version; use crate::SolidityCompiler; -pub struct Solc {} +pub struct Solc { + binary_path: PathBuf, +} impl SolidityCompiler for Solc { type Options = (); fn build( &self, - _input: &SolcStandardJsonInput, + input: &SolcStandardJsonInput, _extra_options: &Option, ) -> anyhow::Result { - todo!() + let mut child = Command::new(&self.binary_path) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("--standard-json") + .spawn()?; + + let stdin = child.stdin.as_mut().expect("should be piped"); + serde_json::to_writer(stdin, input)?; + + let output = child.wait_with_output()?.stdout; + + Ok(serde_json::from_slice(&output)?) } fn new(_solc_version: &Version) -> Self { - todo!() + Self { + binary_path: "solc".into(), + } } } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 18d8087..3012d76 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "revive-dt-core" +description = "revive differential testing core utility" version.workspace = true authors.workspace = true license.workspace = true @@ -12,11 +13,15 @@ name = "retester" path = "src/main.rs" [dependencies] +revive-dt-compiler = { workspace = true } revive-dt-format = { workspace = true } +revive-solc-json-interface = { workspace = true } anyhow = { workspace = true } clap = { workspace = true } log = { workspace = true } env_logger = { workspace = true } +rayon = { workspace = true } +semver = { workspace = true } serde = { workspace = true, features = [ "derive" ] } serde_json = { workspace = true } diff --git a/crates/core/src/driver/compiler.rs b/crates/core/src/driver/compiler.rs index cb7bc6e..5b717aa 100644 --- a/crates/core/src/driver/compiler.rs +++ b/crates/core/src/driver/compiler.rs @@ -1 +1,68 @@ //! The compiler driver builds the test case contracts for selected compilers. + +use std::collections::HashMap; + +use revive_dt_compiler::{Compiler, solc::Solc}; +use revive_dt_format::{ + metadata::Metadata, + mode::{Mode, SolcMode}, +}; +use revive_solc_json_interface::SolcStandardJsonOutput; +use semver::Version; + +#[derive(Hash, Eq, PartialEq)] +pub struct SolcSettings { + pub optimizer: bool, + pub solc_version: Version, +} + +pub fn build_evm( + metadata: &Metadata, +) -> anyhow::Result> { + let Some(metadata_path) = &metadata.path else { + anyhow::bail!("missing directory in metadata"); + }; + let Some(directory) = metadata_path.parent() else { + anyhow::bail!("metadata path has no parent: {}", metadata_path.display()); + }; + let Some(contracts) = &metadata.contracts else { + anyhow::bail!("missing contracts in metadata: {}", metadata_path.display()); + }; + let modes = metadata + .modes + .to_owned() + .unwrap_or_else(|| vec![Mode::Solidity(Default::default())]); + + let sources = super::contract_sources_from_metadata(directory, contracts)?; + + let mut result = HashMap::new(); + for mode in modes { + let mut compiler = Compiler::::new().base_path(directory.display().to_string()); + for (_, file) in &sources { + compiler = compiler.with_source(file)?; + } + + match mode { + Mode::Unknown(mode) => log::debug!("compiler: ignoring unknown mode '{mode}'"), + Mode::Solidity(SolcMode { + solc_version: _, + solc_optimize, + llvm_optimizer_settings: _, + }) => { + let optimizer = solc_optimize.unwrap_or(true); + let version = Version::new(0, 8, 29); + let out = compiler.solc_optimizer(optimizer).try_build(&version)?; + + result.insert( + SolcSettings { + optimizer: true, + solc_version: version, + }, + out, + ); + } + } + } + + Ok(result) +} diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index dd1b3c9..3c01a24 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -1,4 +1,28 @@ //! The test driver handles the compilation and execution of the test cases. +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; + pub mod compiler; pub mod input; + +pub(crate) fn contract_sources_from_metadata( + directory: &Path, + contracts: &BTreeMap, +) -> anyhow::Result> { + let mut sources = BTreeMap::new(); + for (name, contract) in contracts { + // TODO: broken if a colon is in the dir name.. + let Some(solidity_file_name) = contract.split(':').next() else { + anyhow::bail!("metadata contains invalid contract: {contract}"); + }; + + let mut file = directory.to_path_buf(); + file.push(solidity_file_name); + sources.insert(name.clone(), file); + } + + Ok(sources) +} diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 4f673ae..257c915 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -2,7 +2,8 @@ use std::collections::BTreeSet; use clap::Parser; -use revive_dt_core::arguments::Arguments; +use rayon::prelude::*; +use revive_dt_core::{arguments::Arguments, driver::compiler::build_evm}; use revive_dt_format::corpus::Corpus; fn main() -> anyhow::Result<()> { @@ -16,8 +17,20 @@ fn main() -> anyhow::Result<()> { log::info!("found corpus: {corpus:?}"); let tests = corpus.enumerate_tests(); + log::info!("found {} tests", tests.len()); - log::debug!("{tests:?}"); + tests + .par_iter() + .for_each(|metadata| match build_evm(&metadata) { + Ok(_) => log::info!( + "metadata {} compilation success", + metadata.path.as_ref().unwrap().display() + ), + Err(error) => log::warn!( + "metadata {} compilation failure: {error:?}", + metadata.path.as_ref().unwrap().display() + ), + }); } Ok(()) diff --git a/crates/format/Cargo.toml b/crates/format/Cargo.toml index aa5ae6c..d8fc345 100644 --- a/crates/format/Cargo.toml +++ b/crates/format/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "revive-dt-format" +description = "declarative test definition format" version.workspace = true authors.workspace = true license.workspace = true @@ -13,4 +14,4 @@ anyhow = { workspace = true } log = { workspace = true } semver = { workspace = true } serde = { workspace = true, features = [ "derive" ] } -serde_json = { workspace = true } \ No newline at end of file +serde_json = { workspace = true } diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index 73f84fa..395a055 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -19,7 +19,6 @@ pub struct Metadata { pub ignore: Option, pub modes: Option>, pub path: Option, - pub directory: Option, } impl Metadata { @@ -62,7 +61,7 @@ impl Metadata { match serde_json::from_reader::<_, Metadata>(file) { Ok(mut metadata) => { - metadata.directory = Some(path.to_path_buf()); + metadata.path = Some(path.to_path_buf()); Some(metadata) } Err(error) => { @@ -75,7 +74,7 @@ impl Metadata { } } - fn try_from_solidity(path: &Path) -> Option { - todo!() + fn try_from_solidity(_path: &Path) -> Option { + None } }