mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-24 22:48:04 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c33803d427 | |||
| 09d56f5177 | |||
| a59e287fa1 |
Generated
+3
@@ -4093,6 +4093,7 @@ dependencies = [
|
||||
"moka",
|
||||
"once_cell",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -4159,6 +4160,7 @@ dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-types",
|
||||
"anyhow",
|
||||
"regex",
|
||||
"revive-common",
|
||||
"revive-dt-common",
|
||||
"semver 1.0.26",
|
||||
@@ -4201,6 +4203,7 @@ name = "revive-dt-report"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"revive-dt-common",
|
||||
"revive-dt-compiler",
|
||||
"revive-dt-config",
|
||||
"revive-dt-format",
|
||||
|
||||
@@ -29,6 +29,7 @@ clap = { version = "4", features = ["derive"] }
|
||||
foundry-compilers-artifacts = { version = "0.18.0" }
|
||||
futures = { version = "0.3.31" }
|
||||
hex = "0.4.3"
|
||||
regex = "1"
|
||||
moka = "0.12.10"
|
||||
reqwest = { version = "0.12.15", features = ["json"] }
|
||||
once_cell = "1.21"
|
||||
@@ -44,6 +45,7 @@ sp-core = "36.1.0"
|
||||
sp-runtime = "41.1.0"
|
||||
temp-dir = { version = "0.1.16" }
|
||||
tempfile = "3.3"
|
||||
thiserror = "2"
|
||||
tokio = { version = "1.47.0", default-features = false, features = [
|
||||
"rt-multi-thread",
|
||||
"process",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"modes": [
|
||||
"Y >=0.8.9",
|
||||
"E",
|
||||
"I"
|
||||
"E"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
|
||||
@@ -13,4 +13,5 @@ anyhow = { workspace = true }
|
||||
moka = { workspace = true, features = ["sync"] }
|
||||
once_cell = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true, default-features = false, features = ["time"] }
|
||||
|
||||
@@ -47,27 +47,3 @@ pub fn read_dir(path: impl AsRef<Path>) -> Result<Box<dyn Iterator<Item = Result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PathExt {
|
||||
fn cached_canonicalize(&self) -> Result<PathBuf>;
|
||||
}
|
||||
|
||||
impl<T> PathExt for T
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
fn cached_canonicalize(&self) -> Result<PathBuf> {
|
||||
static CANONICALIZATION_CACHE: Lazy<Cache<PathBuf, PathBuf>> =
|
||||
Lazy::new(|| Cache::new(10_000));
|
||||
|
||||
let path = self.as_ref().to_path_buf();
|
||||
match CANONICALIZATION_CACHE.get(&path) {
|
||||
Some(canonicalized) => Ok(canonicalized),
|
||||
None => {
|
||||
let canonicalized = path.canonicalize()?;
|
||||
CANONICALIZATION_CACHE.insert(path, canonicalized.clone());
|
||||
Ok(canonicalized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
mod mode;
|
||||
mod version_or_requirement;
|
||||
|
||||
pub use mode::*;
|
||||
pub use version_or_requirement::*;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
use crate::types::VersionOrRequirement;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// This represents a mode that a given test should be run with, if possible.
|
||||
///
|
||||
/// We obtain this by taking a [`ParsedMode`], which may be looser or more strict
|
||||
/// in its requirements, and then expanding it out into a list of [`Mode`]s.
|
||||
///
|
||||
/// Use [`ParsedMode::to_test_modes()`] to do this.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
|
||||
pub struct Mode {
|
||||
pub pipeline: ModePipeline,
|
||||
pub optimize_setting: ModeOptimizerSetting,
|
||||
pub version: Option<semver::VersionReq>,
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.pipeline.fmt(f)?;
|
||||
f.write_str(" ")?;
|
||||
self.optimize_setting.fmt(f)?;
|
||||
|
||||
if let Some(version) = &self.version {
|
||||
f.write_str(" ")?;
|
||||
version.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
/// Return all of the available mode combinations.
|
||||
pub fn all() -> impl Iterator<Item = Mode> {
|
||||
ModePipeline::test_cases().flat_map(|pipeline| {
|
||||
ModeOptimizerSetting::test_cases().map(move |optimize_setting| Mode {
|
||||
pipeline,
|
||||
optimize_setting,
|
||||
version: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves the [`Mode`]'s solidity version requirement into a [`VersionOrRequirement`] if
|
||||
/// the requirement is present on the object. Otherwise, the passed default version is used.
|
||||
pub fn compiler_version_to_use(&self, default: Version) -> VersionOrRequirement {
|
||||
match self.version {
|
||||
Some(ref requirement) => requirement.clone().into(),
|
||||
None => default.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What do we want the compiler to do?
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||
pub enum ModePipeline {
|
||||
/// Compile Solidity code via Yul IR
|
||||
ViaYulIR,
|
||||
/// Compile Solidity direct to assembly
|
||||
ViaEVMAssembly,
|
||||
}
|
||||
|
||||
impl FromStr for ModePipeline {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
// via Yul IR
|
||||
"Y" => Ok(ModePipeline::ViaYulIR),
|
||||
// Don't go via Yul IR
|
||||
"E" => Ok(ModePipeline::ViaEVMAssembly),
|
||||
// Anything else that we see isn't a mode at all
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Unsupported pipeline '{s}': expected 'Y' or 'E'"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ModePipeline {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ModePipeline::ViaYulIR => f.write_str("Y"),
|
||||
ModePipeline::ViaEVMAssembly => f.write_str("E"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModePipeline {
|
||||
/// Should we go via Yul IR?
|
||||
pub fn via_yul_ir(&self) -> bool {
|
||||
matches!(self, ModePipeline::ViaYulIR)
|
||||
}
|
||||
|
||||
/// An iterator over the available pipelines that we'd like to test,
|
||||
/// when an explicit pipeline was not specified.
|
||||
pub fn test_cases() -> impl Iterator<Item = ModePipeline> + Clone {
|
||||
[ModePipeline::ViaYulIR, ModePipeline::ViaEVMAssembly].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||
pub enum ModeOptimizerSetting {
|
||||
/// 0 / -: Don't apply any optimizations
|
||||
M0,
|
||||
/// 1: Apply less than default optimizations
|
||||
M1,
|
||||
/// 2: Apply the default optimizations
|
||||
M2,
|
||||
/// 3 / +: Apply aggressive optimizations
|
||||
M3,
|
||||
/// s: Optimize for size
|
||||
Ms,
|
||||
/// z: Aggressively optimize for size
|
||||
Mz,
|
||||
}
|
||||
|
||||
impl FromStr for ModeOptimizerSetting {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"M0" => Ok(ModeOptimizerSetting::M0),
|
||||
"M1" => Ok(ModeOptimizerSetting::M1),
|
||||
"M2" => Ok(ModeOptimizerSetting::M2),
|
||||
"M3" => Ok(ModeOptimizerSetting::M3),
|
||||
"Ms" => Ok(ModeOptimizerSetting::Ms),
|
||||
"Mz" => Ok(ModeOptimizerSetting::Mz),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Unsupported optimizer setting '{s}': expected 'M0', 'M1', 'M2', 'M3', 'Ms' or 'Mz'"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ModeOptimizerSetting {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ModeOptimizerSetting::M0 => f.write_str("M0"),
|
||||
ModeOptimizerSetting::M1 => f.write_str("M1"),
|
||||
ModeOptimizerSetting::M2 => f.write_str("M2"),
|
||||
ModeOptimizerSetting::M3 => f.write_str("M3"),
|
||||
ModeOptimizerSetting::Ms => f.write_str("Ms"),
|
||||
ModeOptimizerSetting::Mz => f.write_str("Mz"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeOptimizerSetting {
|
||||
/// An iterator over the available optimizer settings that we'd like to test,
|
||||
/// when an explicit optimizer setting was not specified.
|
||||
pub fn test_cases() -> impl Iterator<Item = ModeOptimizerSetting> + Clone {
|
||||
[
|
||||
// No optimizations:
|
||||
ModeOptimizerSetting::M0,
|
||||
// Aggressive optimizations:
|
||||
ModeOptimizerSetting::M3,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
/// Are any optimizations enabled?
|
||||
pub fn optimizations_enabled(&self) -> bool {
|
||||
!matches!(self, ModeOptimizerSetting::M0)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use anyhow::{Error, bail};
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VersionOrRequirement {
|
||||
Version(Version),
|
||||
Requirement(VersionReq),
|
||||
@@ -42,26 +39,3 @@ impl TryFrom<VersionOrRequirement> for VersionReq {
|
||||
Ok(requirement)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionOrRequirement {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(version) = Version::parse(s) {
|
||||
Ok(Self::Version(version))
|
||||
} else if let Ok(version_req) = VersionReq::parse(s) {
|
||||
Ok(Self::Requirement(version_req))
|
||||
} else {
|
||||
bail!("Not a valid version or version requirement")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for VersionOrRequirement {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VersionOrRequirement::Version(version) => version.fmt(f),
|
||||
VersionOrRequirement::Requirement(version_req) => version_req.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
use semver::Version;
|
||||
|
||||
/// This is the first version of solc that supports the `--via-ir` flag / "viaIR" input JSON.
|
||||
pub const SOLC_VERSION_SUPPORTING_VIA_YUL_IR: Version = Version::new(0, 8, 13);
|
||||
@@ -3,6 +3,8 @@
|
||||
//! - Polkadot revive resolc compiler
|
||||
//! - Polkadot revive Wasm compiler
|
||||
|
||||
mod constants;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::Hash,
|
||||
@@ -19,6 +21,9 @@ use revive_dt_common::cached_fs::read_to_string;
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::Arguments;
|
||||
|
||||
// Re-export this as it's a part of the compiler interface.
|
||||
pub use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
||||
|
||||
pub mod revive_js;
|
||||
pub mod revive_resolc;
|
||||
pub mod solc;
|
||||
@@ -43,13 +48,20 @@ pub trait SolidityCompiler {
|
||||
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
||||
|
||||
fn version(&self) -> anyhow::Result<Version>;
|
||||
|
||||
/// Does the compiler support the provided mode and version settings?
|
||||
fn supports_mode(
|
||||
compiler_version: &Version,
|
||||
optimize_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// The generic compilation input configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompilerInput {
|
||||
pub enable_optimization: Option<bool>,
|
||||
pub via_ir: Option<bool>,
|
||||
pub pipeline: Option<ModePipeline>,
|
||||
pub optimization: Option<ModeOptimizerSetting>,
|
||||
pub evm_version: Option<EVMVersion>,
|
||||
pub allow_paths: Vec<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
@@ -85,8 +97,8 @@ where
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
input: CompilerInput {
|
||||
enable_optimization: Default::default(),
|
||||
via_ir: Default::default(),
|
||||
pipeline: Default::default(),
|
||||
optimization: Default::default(),
|
||||
evm_version: Default::default(),
|
||||
allow_paths: Default::default(),
|
||||
base_path: Default::default(),
|
||||
@@ -98,13 +110,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_optimization(mut self, value: impl Into<Option<bool>>) -> Self {
|
||||
self.input.enable_optimization = value.into();
|
||||
pub fn with_optimization(mut self, value: impl Into<Option<ModeOptimizerSetting>>) -> Self {
|
||||
self.input.optimization = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_via_ir(mut self, value: impl Into<Option<bool>>) -> Self {
|
||||
self.input.via_ir = value.into();
|
||||
pub fn with_pipeline(mut self, value: impl Into<Option<ModePipeline>>) -> Self {
|
||||
self.input.pipeline = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
//! compiling contracts to PolkaVM (PVM) bytecode.
|
||||
|
||||
use std::{
|
||||
os::unix::process::CommandExt,
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
@@ -15,7 +14,8 @@ use revive_solc_json_interface::{
|
||||
SolcStandardJsonOutput,
|
||||
};
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
use super::constants::SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||
use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler};
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use anyhow::Context;
|
||||
@@ -40,9 +40,8 @@ impl SolidityCompiler for Resolc {
|
||||
async fn build(
|
||||
&self,
|
||||
CompilerInput {
|
||||
enable_optimization,
|
||||
// Ignored and not honored since this is required for the resolc compilation.
|
||||
via_ir: _via_ir,
|
||||
pipeline,
|
||||
optimization,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
@@ -54,6 +53,12 @@ impl SolidityCompiler for Resolc {
|
||||
}: CompilerInput,
|
||||
additional_options: Self::Options,
|
||||
) -> anyhow::Result<CompilerOutput> {
|
||||
if !matches!(pipeline, None | Some(ModePipeline::ViaYulIR)) {
|
||||
anyhow::bail!(
|
||||
"Resolc only supports the Y (via Yul IR) pipeline, but the provided pipeline is {pipeline:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let input = SolcStandardJsonInput {
|
||||
language: SolcStandardJsonInputLanguage::Solidity,
|
||||
sources: sources
|
||||
@@ -82,7 +87,9 @@ impl SolidityCompiler for Resolc {
|
||||
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
|
||||
via_ir: Some(true),
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer::new(
|
||||
enable_optimization.unwrap_or(false),
|
||||
optimization
|
||||
.unwrap_or(ModeOptimizerSetting::M0)
|
||||
.optimizations_enabled(),
|
||||
None,
|
||||
&Version::new(0, 0, 0),
|
||||
false,
|
||||
@@ -93,14 +100,11 @@ impl SolidityCompiler for Resolc {
|
||||
};
|
||||
|
||||
let mut command = AsyncCommand::new(&self.resolc_path);
|
||||
unsafe {
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json")
|
||||
.pre_exec(|| Ok(()))
|
||||
};
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
@@ -219,15 +223,12 @@ impl SolidityCompiler for Resolc {
|
||||
// Logic for parsing the resolc version from the following string:
|
||||
// Solidity frontend for the revive compiler version 0.3.0+commit.b238913.llvm-18.1.8
|
||||
|
||||
let output = unsafe {
|
||||
Command::new(self.resolc_path.as_path())
|
||||
.arg("--version")
|
||||
.stdout(Stdio::piped())
|
||||
.pre_exec(|| Ok(()))
|
||||
.spawn()?
|
||||
.wait_with_output()?
|
||||
.stdout
|
||||
};
|
||||
let output = Command::new(self.resolc_path.as_path())
|
||||
.arg("--version")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?
|
||||
.wait_with_output()?
|
||||
.stdout;
|
||||
let output = String::from_utf8_lossy(&output);
|
||||
let version_string = output
|
||||
.split("version ")
|
||||
@@ -239,6 +240,18 @@ impl SolidityCompiler for Resolc {
|
||||
|
||||
Version::parse(version_string).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn supports_mode(
|
||||
compiler_version: &Version,
|
||||
_optimize_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool {
|
||||
// We only support the Y (IE compile via Yul IR) mode here, which also means that we can
|
||||
// only use solc version 0.8.13 and above. We must always compile via Yul IR as resolc
|
||||
// needs this to translate to LLVM IR and then RISCV.
|
||||
pipeline == ModePipeline::ViaYulIR
|
||||
&& compiler_version >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+45
-39
@@ -2,7 +2,6 @@
|
||||
//! compiling contracts to EVM bytecode.
|
||||
|
||||
use std::{
|
||||
os::unix::process::CommandExt,
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
@@ -11,7 +10,8 @@ use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_solc_binaries::download_solc;
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
use super::constants::SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||
use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler};
|
||||
|
||||
use anyhow::Context;
|
||||
use foundry_compilers_artifacts::{
|
||||
@@ -32,12 +32,12 @@ pub struct Solc {
|
||||
impl SolidityCompiler for Solc {
|
||||
type Options = ();
|
||||
|
||||
#[tracing::instrument(level = "info", ret)]
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
async fn build(
|
||||
&self,
|
||||
CompilerInput {
|
||||
enable_optimization,
|
||||
via_ir,
|
||||
pipeline,
|
||||
optimization,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
@@ -47,6 +47,17 @@ impl SolidityCompiler for Solc {
|
||||
}: CompilerInput,
|
||||
_: Self::Options,
|
||||
) -> anyhow::Result<CompilerOutput> {
|
||||
let compiler_supports_via_ir = self.version()? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||
|
||||
// Be careful to entirely omit the viaIR field if the compiler does not support it,
|
||||
// as it will error if you provide fields it does not know about. Because
|
||||
// `supports_mode` is called prior to instantiating a compiler, we should never
|
||||
// ask for something which is invalid.
|
||||
let via_ir = match (pipeline, compiler_supports_via_ir) {
|
||||
(pipeline, true) => pipeline.map(|p| p.via_yul_ir()),
|
||||
(_pipeline, false) => None,
|
||||
};
|
||||
|
||||
let input = SolcInput {
|
||||
language: SolcLanguage::Solidity,
|
||||
sources: Sources(
|
||||
@@ -57,7 +68,7 @@ impl SolidityCompiler for Solc {
|
||||
),
|
||||
settings: Settings {
|
||||
optimizer: Optimizer {
|
||||
enabled: enable_optimization,
|
||||
enabled: optimization.map(|o| o.optimizations_enabled()),
|
||||
details: Some(Default::default()),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -103,14 +114,11 @@ impl SolidityCompiler for Solc {
|
||||
};
|
||||
|
||||
let mut command = AsyncCommand::new(&self.solc_path);
|
||||
unsafe {
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json")
|
||||
.pre_exec(|| Ok(()))
|
||||
};
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
@@ -124,20 +132,12 @@ impl SolidityCompiler for Solc {
|
||||
.join(","),
|
||||
);
|
||||
}
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.inspect_err(|err| tracing::error!(%err, "Failed to spawn the solc command"))?;
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let stdin = child.stdin.as_mut().expect("should be piped");
|
||||
let serialized_input = serde_json::to_vec(&input)?;
|
||||
stdin
|
||||
.write_all(&serialized_input)
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!(%err, "Failed to write standard JSON to stdin"))?;
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!(%err, "Failed to get the output of solc"))?;
|
||||
stdin.write_all(&serialized_input).await?;
|
||||
let output = child.wait_with_output().await?;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)?;
|
||||
@@ -174,13 +174,10 @@ impl SolidityCompiler for Solc {
|
||||
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (contract_path, contracts) in parsed.contracts {
|
||||
let map =
|
||||
compiler_output
|
||||
.contracts
|
||||
.entry(contract_path.canonicalize().inspect_err(
|
||||
|err| tracing::error!(%err, "Canonicalization of path failed"),
|
||||
)?)
|
||||
.or_default();
|
||||
let map = compiler_output
|
||||
.contracts
|
||||
.entry(contract_path.canonicalize()?)
|
||||
.or_default();
|
||||
for (contract_name, contract_info) in contracts.into_iter() {
|
||||
let source_code = contract_info
|
||||
.evm
|
||||
@@ -220,13 +217,10 @@ impl SolidityCompiler for Solc {
|
||||
// Version: 0.8.30+commit.73712a01.Darwin.appleclang
|
||||
// ```
|
||||
|
||||
let child = unsafe {
|
||||
Command::new(self.solc_path.as_path())
|
||||
.arg("--version")
|
||||
.stdout(Stdio::piped())
|
||||
.pre_exec(|| Ok(()))
|
||||
.spawn()?
|
||||
};
|
||||
let child = Command::new(self.solc_path.as_path())
|
||||
.arg("--version")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
let output = child.wait_with_output()?;
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
let version_line = output
|
||||
@@ -240,6 +234,18 @@ impl SolidityCompiler for Solc {
|
||||
|
||||
Version::parse(version_string).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn supports_mode(
|
||||
compiler_version: &Version,
|
||||
_optimize_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool {
|
||||
// solc 0.8.13 and above supports --via-ir, and less than that does not. Thus, we support mode E
|
||||
// (ie no Yul IR) in either case, but only support Y (via Yul IR) if the compiler is new enough.
|
||||
pipeline == ModePipeline::ViaEVMAssembly
|
||||
|| (pipeline == ModePipeline::ViaYulIR
|
||||
&& compiler_version >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -58,10 +58,6 @@ pub struct Arguments {
|
||||
#[arg(long = "geth-start-timeout", default_value = "5000")]
|
||||
pub geth_start_timeout: u64,
|
||||
|
||||
/// The test network chain ID.
|
||||
#[arg(short, long = "network-id", default_value = "420420420")]
|
||||
pub network_id: u64,
|
||||
|
||||
/// Configure nodes according to this genesis.json file.
|
||||
#[arg(long = "genesis", default_value = "genesis.json")]
|
||||
pub genesis_file: PathBuf,
|
||||
|
||||
+94
-100
@@ -13,15 +13,17 @@ use alloy::{
|
||||
};
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::futures_unordered::FuturesUnordered;
|
||||
use futures::{Stream, StreamExt};
|
||||
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use semver::Version;
|
||||
use temp_dir::TempDir;
|
||||
use tokio::sync::{Mutex, RwLock, mpsc};
|
||||
use tracing::{Instrument, Level, instrument};
|
||||
use tracing::{Instrument, Level};
|
||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||
|
||||
use revive_dt_common::types::Mode;
|
||||
use revive_dt_compiler::SolidityCompiler;
|
||||
use revive_dt_compiler::{Compiler, CompilerOutput};
|
||||
use revive_dt_config::*;
|
||||
@@ -34,7 +36,6 @@ use revive_dt_format::{
|
||||
corpus::Corpus,
|
||||
input::{Input, Step},
|
||||
metadata::{ContractInstance, ContractPathAndIdent, Metadata, MetadataFile},
|
||||
mode::{Mode, SolcMode},
|
||||
};
|
||||
use revive_dt_node::pool::NodePool;
|
||||
use revive_dt_report::reporter::{Report, Span};
|
||||
@@ -44,7 +45,7 @@ static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
||||
type CompilationCache = Arc<
|
||||
RwLock<
|
||||
HashMap<
|
||||
(PathBuf, SolcMode, TestingPlatform),
|
||||
(PathBuf, Mode, TestingPlatform),
|
||||
Arc<Mutex<Option<Arc<(Version, CompilerOutput)>>>>,
|
||||
>,
|
||||
>,
|
||||
@@ -55,8 +56,8 @@ type CompilationCache = Arc<
|
||||
struct Test {
|
||||
metadata: Metadata,
|
||||
path: PathBuf,
|
||||
mode: SolcMode,
|
||||
case_idx: usize,
|
||||
mode: Mode,
|
||||
case_idx: CaseIdx,
|
||||
case: Case,
|
||||
}
|
||||
|
||||
@@ -124,7 +125,7 @@ fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<Metad
|
||||
let corpus = Corpus::try_from_path(path)?;
|
||||
tracing::info!("found corpus: {}", path.display());
|
||||
let tests = corpus.enumerate_tests();
|
||||
tracing::info!("corpus '{}' contains {} tests", &corpus.name, tests.len());
|
||||
tracing::info!("corpus '{}' contains {} tests", &corpus.name(), tests.len());
|
||||
corpora.insert(corpus, tests);
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ where
|
||||
{
|
||||
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test, CaseResult)>();
|
||||
|
||||
let tests = prepare_tests::<L, F>(metadata_files);
|
||||
let tests = prepare_tests::<L, F>(args, metadata_files);
|
||||
let driver_task = start_driver_task::<L, F>(args, tests, span, report_tx)?;
|
||||
let status_reporter_task = start_reporter_task(report_rx);
|
||||
|
||||
@@ -153,7 +154,10 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_tests<L, F>(metadata_files: &[MetadataFile]) -> impl Iterator<Item = Test>
|
||||
fn prepare_tests<L, F>(
|
||||
args: &Arguments,
|
||||
metadata_files: &[MetadataFile],
|
||||
) -> impl Stream<Item = Test>
|
||||
where
|
||||
L: Platform,
|
||||
F: Platform,
|
||||
@@ -172,7 +176,8 @@ where
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(case_idx, case)| {
|
||||
SolcMode::ALL
|
||||
metadata
|
||||
.solc_modes()
|
||||
.into_iter()
|
||||
.map(move |solc_mode| (path, metadata, case_idx, case, solc_mode))
|
||||
})
|
||||
@@ -225,41 +230,58 @@ where
|
||||
}
|
||||
None => true,
|
||||
})
|
||||
.flat_map(|(path, metadata, case_idx, case, solc_mode)| {
|
||||
if let Some(ref case_modes) = case.modes {
|
||||
case_modes
|
||||
.iter()
|
||||
.filter_map(Mode::as_solc_mode)
|
||||
.filter(|case_mode| case_mode.matches(&solc_mode))
|
||||
.map(|case_mode| (path, metadata, case_idx, case, case_mode.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
} else if let Some(ref metadata_modes) = metadata.modes {
|
||||
metadata_modes
|
||||
.iter()
|
||||
.filter_map(Mode::as_solc_mode)
|
||||
.filter(|metadata_mode| metadata_mode.matches(&solc_mode))
|
||||
.map(|metadata_mode| (path, metadata, case_idx, case, metadata_mode.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
} else {
|
||||
vec![(path, metadata, case_idx, case, solc_mode)].into_iter()
|
||||
}
|
||||
})
|
||||
.map(|(metadata_file_path, metadata, case_idx, case, solc_mode)| {
|
||||
Test {
|
||||
metadata: metadata.clone(),
|
||||
path: metadata_file_path.to_path_buf(),
|
||||
mode: solc_mode,
|
||||
case_idx,
|
||||
case_idx: case_idx.into(),
|
||||
case: case.clone(),
|
||||
}
|
||||
})
|
||||
.map(async |test| test)
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.filter_map(async move |test| {
|
||||
// Check that both compilers support this test, else we skip it
|
||||
let is_supported = does_compiler_support_mode::<L>(args, &test.mode).await.ok().unwrap_or(false) &&
|
||||
does_compiler_support_mode::<F>(args, &test.mode).await.ok().unwrap_or(false);
|
||||
|
||||
tracing::warn!(
|
||||
metadata_file_path = %test.path.display(),
|
||||
case_idx = %test.case_idx,
|
||||
case_name = ?test.case.name,
|
||||
mode = %test.mode,
|
||||
"Skipping test as one or both of the compilers don't support it"
|
||||
);
|
||||
|
||||
// We filter_map to avoid needing to clone `test`, but return it as-is.
|
||||
if is_supported {
|
||||
Some(test)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn does_compiler_support_mode<P: Platform>(
|
||||
args: &Arguments,
|
||||
mode: &Mode,
|
||||
) -> anyhow::Result<bool> {
|
||||
let compiler_version_or_requirement = mode.compiler_version_to_use(args.solc.clone());
|
||||
let compiler_path =
|
||||
P::Compiler::get_compiler_executable(args, compiler_version_or_requirement).await?;
|
||||
let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
|
||||
|
||||
Ok(P::Compiler::supports_mode(
|
||||
&compiler_version,
|
||||
mode.optimize_setting,
|
||||
mode.pipeline,
|
||||
))
|
||||
}
|
||||
|
||||
fn start_driver_task<L, F>(
|
||||
args: &Arguments,
|
||||
tests: impl Iterator<Item = Test>,
|
||||
tests: impl Stream<Item = Test>,
|
||||
span: Span,
|
||||
report_tx: mpsc::UnboundedSender<(Test, CaseResult)>,
|
||||
) -> anyhow::Result<impl Future<Output = ()>>
|
||||
@@ -274,7 +296,7 @@ where
|
||||
let compilation_cache = Arc::new(RwLock::new(HashMap::new()));
|
||||
let number_concurrent_tasks = args.number_of_concurrent_tasks();
|
||||
|
||||
Ok(futures::stream::iter(tests).for_each_concurrent(
|
||||
Ok(tests.for_each_concurrent(
|
||||
// We want to limit the concurrent tasks here because:
|
||||
//
|
||||
// 1. We don't want to overwhelm the nodes with too many requests, leading to responses timing out.
|
||||
@@ -298,13 +320,13 @@ where
|
||||
"Running driver",
|
||||
metadata_file_path = %test.path.display(),
|
||||
case_idx = ?test.case_idx,
|
||||
solc_mode = %test.mode,
|
||||
solc_mode = ?test.mode,
|
||||
);
|
||||
|
||||
let result = handle_case_driver::<L, F>(
|
||||
&test.path,
|
||||
&test.metadata,
|
||||
test.case_idx.into(),
|
||||
test.case_idx,
|
||||
&test.case,
|
||||
test.mode.clone(),
|
||||
args,
|
||||
@@ -329,7 +351,7 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
|
||||
|
||||
const GREEN: &str = "\x1B[32m";
|
||||
const RED: &str = "\x1B[31m";
|
||||
const COLOR_RESET: &str = "\x1B[0m";
|
||||
const COLOUR_RESET: &str = "\x1B[0m";
|
||||
const BOLD: &str = "\x1B[1m";
|
||||
const BOLD_RESET: &str = "\x1B[22m";
|
||||
|
||||
@@ -348,13 +370,13 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
|
||||
Ok(_inputs) => {
|
||||
number_of_successes += 1;
|
||||
eprintln!(
|
||||
"{GREEN}Case Succeeded:{COLOR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||
"{GREEN}Case Succeeded:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
number_of_failures += 1;
|
||||
eprintln!(
|
||||
"{RED}Case Failed:{COLOR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||
"{RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||
);
|
||||
failures.push((test, err));
|
||||
}
|
||||
@@ -377,14 +399,14 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
|
||||
let test_mode = test.mode.clone();
|
||||
|
||||
eprintln!(
|
||||
"---- {RED}Case Failed:{COLOR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode}) ----\n\n{err}\n"
|
||||
"---- {RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode}) ----\n\n{err}\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Summary at the end.
|
||||
eprintln!(
|
||||
"{} cases: {GREEN}{number_of_successes}{COLOR_RESET} cases succeeded, {RED}{number_of_failures}{COLOR_RESET} cases failed in {} seconds",
|
||||
"{} cases: {GREEN}{number_of_successes}{COLOUR_RESET} cases succeeded, {RED}{number_of_failures}{COLOUR_RESET} cases failed in {} seconds",
|
||||
number_of_successes + number_of_failures,
|
||||
elapsed.as_secs()
|
||||
);
|
||||
@@ -396,7 +418,7 @@ async fn handle_case_driver<L, F>(
|
||||
metadata: &Metadata,
|
||||
case_idx: CaseIdx,
|
||||
case: &Case,
|
||||
mode: SolcMode,
|
||||
mode: Mode,
|
||||
config: &Arguments,
|
||||
compilation_cache: CompilationCache,
|
||||
leader_node: &L::Blockchain,
|
||||
@@ -637,7 +659,7 @@ where
|
||||
async fn get_or_build_contracts<P: Platform>(
|
||||
metadata: &Metadata,
|
||||
metadata_file_path: &Path,
|
||||
mode: SolcMode,
|
||||
mode: Mode,
|
||||
config: &Arguments,
|
||||
compilation_cache: CompilationCache,
|
||||
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
@@ -656,16 +678,16 @@ async fn get_or_build_contracts<P: Platform>(
|
||||
}
|
||||
None => {
|
||||
tracing::debug!(?key, "Compiled contracts cache miss");
|
||||
let compiled_contracts = Arc::new(
|
||||
compile_contracts::<P>(
|
||||
metadata,
|
||||
metadata_file_path,
|
||||
&mode,
|
||||
config,
|
||||
deployed_libraries,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
let compiled_contracts = compile_contracts::<P>(
|
||||
metadata,
|
||||
metadata_file_path,
|
||||
&mode,
|
||||
config,
|
||||
deployed_libraries,
|
||||
)
|
||||
.await?;
|
||||
let compiled_contracts = Arc::new(compiled_contracts);
|
||||
|
||||
*compilation_artifact = Some(compiled_contracts.clone());
|
||||
return Ok(compiled_contracts.clone());
|
||||
}
|
||||
@@ -680,59 +702,44 @@ async fn get_or_build_contracts<P: Platform>(
|
||||
mutex
|
||||
};
|
||||
let mut compilation_artifact = mutex.lock().await;
|
||||
let compiled_contracts = Arc::new(
|
||||
compile_contracts::<P>(
|
||||
metadata,
|
||||
metadata_file_path,
|
||||
&mode,
|
||||
config,
|
||||
deployed_libraries,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let compiled_contracts = compile_contracts::<P>(
|
||||
metadata,
|
||||
metadata_file_path,
|
||||
&mode,
|
||||
config,
|
||||
deployed_libraries,
|
||||
)
|
||||
.await?;
|
||||
let compiled_contracts = Arc::new(compiled_contracts);
|
||||
|
||||
*compilation_artifact = Some(compiled_contracts.clone());
|
||||
Ok(compiled_contracts.clone())
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(
|
||||
metadata_file_path = %metadata_file_path.display(),
|
||||
mode = %mode,
|
||||
deployed_libraries = deployed_libraries.len(),
|
||||
),
|
||||
err
|
||||
)]
|
||||
async fn compile_contracts<P: Platform>(
|
||||
metadata: &Metadata,
|
||||
metadata_file_path: &Path,
|
||||
mode: &SolcMode,
|
||||
mode: &Mode,
|
||||
config: &Arguments,
|
||||
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
) -> anyhow::Result<(Version, CompilerOutput)> {
|
||||
let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone());
|
||||
let compiler_path =
|
||||
P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?;
|
||||
let compiler_version = P::Compiler::new(compiler_path.clone())
|
||||
.version()
|
||||
.inspect_err(|err| tracing::error!(%err, "Failed to get compiler version"))?;
|
||||
let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
|
||||
|
||||
tracing::info!(
|
||||
%compiler_version,
|
||||
metadata_file_path = %metadata_file_path.display(),
|
||||
mode = %mode,
|
||||
mode = ?mode,
|
||||
"Compiling contracts"
|
||||
);
|
||||
|
||||
let compiler = Compiler::<P::Compiler>::new()
|
||||
.with_allow_path(
|
||||
metadata
|
||||
.directory()
|
||||
.inspect_err(|err| tracing::error!(%err, "Failed to get the metadata directory"))?,
|
||||
)
|
||||
.with_optimization(mode.optimize)
|
||||
.with_via_ir(mode.via_ir);
|
||||
.with_allow_path(metadata.directory()?)
|
||||
.with_optimization(mode.optimize_setting)
|
||||
.with_pipeline(mode.pipeline);
|
||||
let mut compiler = metadata
|
||||
.files_to_compile()?
|
||||
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
|
||||
@@ -749,10 +756,7 @@ async fn compile_contracts<P: Platform>(
|
||||
// yet more compute intensive route, of telling solc that all of the files need to link the
|
||||
// library and it will only perform the linking for the files that do actually need the
|
||||
// library.
|
||||
compiler =
|
||||
FilesWithExtensionIterator::new(metadata.directory().inspect_err(
|
||||
|err| tracing::error!(%err, "Failed to get the metadata directory"),
|
||||
)?)
|
||||
compiler = FilesWithExtensionIterator::new(metadata.directory()?)
|
||||
.with_allowed_extension("sol")
|
||||
.with_use_cached_fs(true)
|
||||
.fold(compiler, |compiler, path| {
|
||||
@@ -760,17 +764,7 @@ async fn compile_contracts<P: Platform>(
|
||||
});
|
||||
}
|
||||
|
||||
let compiler_output = compiler
|
||||
.try_build(compiler_path)
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!(%err, "Contract compilation failed"))?;
|
||||
|
||||
tracing::info!(
|
||||
%compiler_version,
|
||||
metadata_file_path = %metadata_file_path.display(),
|
||||
mode = %mode,
|
||||
"Compiled contracts"
|
||||
);
|
||||
let compiler_output = compiler.try_build(compiler_path).await?;
|
||||
|
||||
Ok((compiler_version, compiler_output))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ alloy = { workspace = true }
|
||||
alloy-primitives = { workspace = true }
|
||||
alloy-sol-types = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
+10
-11
@@ -1,13 +1,10 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_common::macros::define_wrapper_type;
|
||||
|
||||
use crate::{
|
||||
input::{Expected, Step},
|
||||
metadata::{deserialize_compilation_modes, serialize_compilation_modes},
|
||||
mode::Mode,
|
||||
mode::ParsedMode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
@@ -18,13 +15,8 @@ pub struct Case {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "deserialize_compilation_modes",
|
||||
serialize_with = "serialize_compilation_modes"
|
||||
)]
|
||||
pub modes: Option<HashSet<Mode>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub modes: Option<Vec<ParsedMode>>,
|
||||
|
||||
#[serde(rename = "inputs")]
|
||||
pub steps: Vec<Step>,
|
||||
@@ -40,6 +32,7 @@ pub struct Case {
|
||||
}
|
||||
|
||||
impl Case {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
|
||||
let steps_len = self.steps.len();
|
||||
self.steps
|
||||
@@ -74,3 +67,9 @@ define_wrapper_type!(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CaseIdx(usize);
|
||||
);
|
||||
|
||||
impl std::fmt::Display for CaseIdx {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
+56
-25
@@ -8,43 +8,74 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::metadata::MetadataFile;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
pub struct Corpus {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Corpus {
|
||||
SinglePath { name: String, path: PathBuf },
|
||||
MultiplePaths { name: String, paths: Vec<PathBuf> },
|
||||
}
|
||||
|
||||
impl Corpus {
|
||||
/// Try to read and parse the corpus definition file at given `path`.
|
||||
pub fn try_from_path(path: &Path) -> anyhow::Result<Self> {
|
||||
let file = File::open(path)?;
|
||||
let mut corpus: Corpus = serde_json::from_reader(file)?;
|
||||
pub fn try_from_path(file_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let mut corpus = File::open(file_path.as_ref())
|
||||
.map_err(Into::<anyhow::Error>::into)
|
||||
.and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into))?;
|
||||
|
||||
// Ensure that the path mentioned in the corpus is relative to the corpus file.
|
||||
// Canonicalizing also helps make the path in any errors unambiguous.
|
||||
corpus.path = path
|
||||
.parent()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Corpus path '{}' does not point to a file", path.display())
|
||||
})?
|
||||
.canonicalize()
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to canonicalize path to corpus '{}': {error}",
|
||||
path.display()
|
||||
)
|
||||
})?
|
||||
.join(corpus.path);
|
||||
for path in corpus.paths_iter_mut() {
|
||||
*path = file_path
|
||||
.as_ref()
|
||||
.parent()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Corpus path '{}' does not point to a file", path.display())
|
||||
})?
|
||||
.canonicalize()
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to canonicalize path to corpus '{}': {error}",
|
||||
path.display()
|
||||
)
|
||||
})?
|
||||
.join(path.as_path())
|
||||
}
|
||||
|
||||
Ok(corpus)
|
||||
}
|
||||
|
||||
/// Scan the corpus base directory and return all tests found.
|
||||
pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
|
||||
let mut tests = Vec::new();
|
||||
collect_metadata(&self.path, &mut tests);
|
||||
for path in self.paths_iter() {
|
||||
collect_metadata(path, &mut tests);
|
||||
}
|
||||
tests
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Corpus::SinglePath { name, .. } | Corpus::MultiplePaths { name, .. } => name.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paths_iter(&self) -> impl Iterator<Item = &Path> {
|
||||
match self {
|
||||
Corpus::SinglePath { path, .. } => {
|
||||
Box::new(std::iter::once(path.as_path())) as Box<dyn Iterator<Item = _>>
|
||||
}
|
||||
Corpus::MultiplePaths { paths, .. } => {
|
||||
Box::new(paths.iter().map(|path| path.as_path())) as Box<dyn Iterator<Item = _>>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paths_iter_mut(&mut self) -> impl Iterator<Item = &mut PathBuf> {
|
||||
match self {
|
||||
Corpus::SinglePath { path, .. } => {
|
||||
Box::new(std::iter::once(path)) as Box<dyn Iterator<Item = _>>
|
||||
}
|
||||
Corpus::MultiplePaths { paths, .. } => {
|
||||
Box::new(paths.iter_mut()) as Box<dyn Iterator<Item = _>>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively walks `path` and parses any JSON or Solidity file into a test
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, HashSet},
|
||||
collections::BTreeMap,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
ops::Deref,
|
||||
@@ -8,17 +8,15 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_common::EVMVersion;
|
||||
use revive_dt_common::{
|
||||
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
|
||||
types::Mode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
case::Case,
|
||||
mode::{Mode, SolcMode},
|
||||
};
|
||||
use crate::{case::Case, mode::ParsedMode};
|
||||
|
||||
pub const METADATA_FILE_EXTENSION: &str = "json";
|
||||
pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol";
|
||||
@@ -67,13 +65,8 @@ pub struct Metadata {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "deserialize_compilation_modes",
|
||||
serialize_with = "serialize_compilation_modes"
|
||||
)]
|
||||
pub modes: Option<HashSet<Mode>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub modes: Option<Vec<ParsedMode>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file_path: Option<PathBuf>,
|
||||
@@ -91,21 +84,12 @@ pub struct Metadata {
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Returns the solc modes of this metadata, inserting a default mode if not present.
|
||||
pub fn solc_modes(&self) -> Vec<SolcMode> {
|
||||
self.modes
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| SolcMode::ALL.map(Mode::Solidity).iter().cloned().collect())
|
||||
.iter()
|
||||
.filter_map(|mode| match mode {
|
||||
Mode::Solidity(solc_mode) => Some(solc_mode),
|
||||
Mode::Unknown(mode) => {
|
||||
tracing::debug!("compiler: ignoring unknown mode '{mode}'");
|
||||
None
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
/// Returns the modes that we should test from this metadata.
|
||||
pub fn solc_modes(&self) -> Vec<Mode> {
|
||||
match &self.modes {
|
||||
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
||||
None => Mode::all().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the base directory of this metadata.
|
||||
@@ -274,37 +258,6 @@ impl Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_compilation_modes<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<HashSet<Mode>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let maybe_strings = Option::<Vec<String>>::deserialize(deserializer)?;
|
||||
Ok(maybe_strings.map(|strings| {
|
||||
strings
|
||||
.into_iter()
|
||||
.flat_map(Mode::parse_from_string)
|
||||
.collect()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn serialize_compilation_modes<S>(
|
||||
value: &Option<HashSet<Mode>>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match value {
|
||||
None => serializer.serialize_none(),
|
||||
Some(modes) => {
|
||||
let strings: Vec<String> = modes.iter().cloned().map(Into::<String>::into).collect();
|
||||
serializer.serialize_some(&strings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_wrapper_type!(
|
||||
/// Represents a contract instance found a metadata file.
|
||||
///
|
||||
|
||||
+221
-352
@@ -1,392 +1,261 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use regex::Regex;
|
||||
use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use semver::Version;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Specifies a compilation mode for the test artifact that it requires. This is used as a filter
|
||||
/// when used in the [`Metadata`] and is used as a directive when used in the core crate.
|
||||
/// This represents a mode that has been parsed from test metadata.
|
||||
///
|
||||
/// [`Metadata`]: crate::metadata::Metadata
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Mode {
|
||||
/// A compilation mode that's been parsed from a String and into its contents.
|
||||
Solidity(SolcMode),
|
||||
/// An unknown compilation mode.
|
||||
Unknown(String),
|
||||
/// Mode strings can take the following form (in pseudo-regex):
|
||||
///
|
||||
/// ```text
|
||||
/// [YEILV][+-]? (M[0123sz])? <semver>?
|
||||
/// ```
|
||||
///
|
||||
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct ParsedMode {
|
||||
pub pipeline: Option<ModePipeline>,
|
||||
pub optimize_flag: Option<bool>,
|
||||
pub optimize_setting: Option<ModeOptimizerSetting>,
|
||||
pub version: Option<semver::VersionReq>,
|
||||
}
|
||||
|
||||
impl From<Mode> for String {
|
||||
fn from(value: Mode) -> Self {
|
||||
value.to_string()
|
||||
impl FromStr for ParsedMode {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?x)
|
||||
^
|
||||
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
|
||||
\s*
|
||||
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
|
||||
\s*
|
||||
(?P<version>[>=<]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
|
||||
$
|
||||
").unwrap()
|
||||
});
|
||||
|
||||
let Some(caps) = REGEX.captures(s) else {
|
||||
anyhow::bail!("Cannot parse mode '{s}' from string");
|
||||
};
|
||||
|
||||
let pipeline = match caps.name("pipeline") {
|
||||
Some(m) => Some(ModePipeline::from_str(m.as_str())?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+");
|
||||
|
||||
let optimize_setting = match caps.name("optimize_setting") {
|
||||
Some(m) => Some(ModeOptimizerSetting::from_str(m.as_str())?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let version = match caps.name("version") {
|
||||
Some(m) => Some(semver::VersionReq::parse(m.as_str()).map_err(|e| {
|
||||
anyhow::anyhow!("Cannot parse the version requirement '{}': {e}", m.as_str())
|
||||
})?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(ParsedMode {
|
||||
pipeline,
|
||||
optimize_flag,
|
||||
optimize_setting,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
impl Display for ParsedMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Mode::Solidity(mode) => mode.fmt(f),
|
||||
Mode::Unknown(string) => string.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut has_written = false;
|
||||
|
||||
impl Mode {
|
||||
pub fn parse_from_string(str: impl AsRef<str>) -> Vec<Self> {
|
||||
let mut chars = str.as_ref().chars().peekable();
|
||||
|
||||
let compile_via_ir = match chars.next() {
|
||||
Some('Y') => true,
|
||||
Some('E') => false,
|
||||
_ => {
|
||||
tracing::warn!("Encountered an unknown mode {}", str.as_ref());
|
||||
return vec![Self::Unknown(str.as_ref().to_string())];
|
||||
if let Some(pipeline) = self.pipeline {
|
||||
pipeline.fmt(f)?;
|
||||
if let Some(optimize_flag) = self.optimize_flag {
|
||||
f.write_str(if optimize_flag { "+" } else { "-" })?;
|
||||
}
|
||||
};
|
||||
|
||||
let optimize_flag = match chars.peek() {
|
||||
Some('+') => {
|
||||
let _ = chars.next();
|
||||
Some(true)
|
||||
}
|
||||
Some('-') => {
|
||||
let _ = chars.next();
|
||||
Some(false)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut chars = chars.skip_while(|char| *char == ' ').peekable();
|
||||
|
||||
let version_requirement = match chars.peek() {
|
||||
Some('=' | '>' | '<' | '~' | '^' | '*' | '0'..='9') => {
|
||||
let version_requirement = chars.take_while(|char| *char != ' ').collect::<String>();
|
||||
let Ok(version_requirement) = VersionOrRequirement::from_str(&version_requirement)
|
||||
else {
|
||||
return vec![Self::Unknown(str.as_ref().to_string())];
|
||||
};
|
||||
Some(version_requirement)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match optimize_flag {
|
||||
Some(flag) => {
|
||||
vec![Self::Solidity(SolcMode {
|
||||
via_ir: compile_via_ir,
|
||||
optimize: flag,
|
||||
compiler_version_requirement: version_requirement,
|
||||
})]
|
||||
}
|
||||
None => {
|
||||
vec![
|
||||
Self::Solidity(SolcMode {
|
||||
via_ir: compile_via_ir,
|
||||
optimize: true,
|
||||
compiler_version_requirement: version_requirement.clone(),
|
||||
}),
|
||||
Self::Solidity(SolcMode {
|
||||
via_ir: compile_via_ir,
|
||||
optimize: false,
|
||||
compiler_version_requirement: version_requirement,
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: self_via_ir,
|
||||
optimize: self_optimize,
|
||||
compiler_version_requirement: self_compiler_version_requirement,
|
||||
}),
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: other_via_ir,
|
||||
optimize: other_optimize,
|
||||
compiler_version_requirement: other_compiler_version_requirement,
|
||||
}),
|
||||
) => {
|
||||
let mut matches = true;
|
||||
matches &= self_via_ir == other_via_ir;
|
||||
matches &= self_optimize == other_optimize;
|
||||
match (
|
||||
self_compiler_version_requirement,
|
||||
other_compiler_version_requirement,
|
||||
) {
|
||||
(
|
||||
Some(VersionOrRequirement::Version(self_version)),
|
||||
Some(VersionOrRequirement::Version(other_version)),
|
||||
) => {
|
||||
matches &= self_version == other_version;
|
||||
}
|
||||
(
|
||||
Some(VersionOrRequirement::Version(version)),
|
||||
Some(VersionOrRequirement::Requirement(requirement)),
|
||||
)
|
||||
| (
|
||||
Some(VersionOrRequirement::Requirement(requirement)),
|
||||
Some(VersionOrRequirement::Version(version)),
|
||||
) => matches &= requirement.matches(version),
|
||||
(
|
||||
Some(VersionOrRequirement::Requirement(..)),
|
||||
Some(VersionOrRequirement::Requirement(..)),
|
||||
) => matches = false,
|
||||
(Some(_), None) | (None, Some(_)) | (None, None) => {}
|
||||
}
|
||||
matches
|
||||
}
|
||||
(Mode::Solidity { .. }, Mode::Unknown(_))
|
||||
| (Mode::Unknown(_), Mode::Solidity { .. })
|
||||
| (Mode::Unknown(_), Mode::Unknown(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_solc_mode(&self) -> Option<&SolcMode> {
|
||||
if let Self::Solidity(mode) = self {
|
||||
Some(mode)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
|
||||
#[serde(into = "String")]
|
||||
pub struct SolcMode {
|
||||
pub via_ir: bool,
|
||||
pub optimize: bool,
|
||||
pub compiler_version_requirement: Option<VersionOrRequirement>,
|
||||
}
|
||||
|
||||
impl SolcMode {
|
||||
pub const ALL: [Self; 4] = [
|
||||
SolcMode {
|
||||
via_ir: false,
|
||||
optimize: false,
|
||||
compiler_version_requirement: None,
|
||||
},
|
||||
SolcMode {
|
||||
via_ir: false,
|
||||
optimize: true,
|
||||
compiler_version_requirement: None,
|
||||
},
|
||||
SolcMode {
|
||||
via_ir: true,
|
||||
optimize: false,
|
||||
compiler_version_requirement: None,
|
||||
},
|
||||
SolcMode {
|
||||
via_ir: true,
|
||||
optimize: true,
|
||||
compiler_version_requirement: None,
|
||||
},
|
||||
];
|
||||
|
||||
pub fn matches(&self, other: &Self) -> bool {
|
||||
Mode::Solidity(self.clone()).matches(&Mode::Solidity(other.clone()))
|
||||
}
|
||||
|
||||
/// Resolves the [`SolcMode`]'s solidity version requirement into a [`VersionOrRequirement`] if
|
||||
/// the requirement is present on the object. Otherwise, the passed default version is used.
|
||||
pub fn compiler_version_to_use(&self, default: Version) -> VersionOrRequirement {
|
||||
match self.compiler_version_requirement {
|
||||
Some(ref requirement) => requirement.clone(),
|
||||
None => default.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SolcMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self {
|
||||
via_ir,
|
||||
optimize,
|
||||
compiler_version_requirement,
|
||||
} = self;
|
||||
|
||||
if *via_ir {
|
||||
write!(f, "Y")?;
|
||||
} else {
|
||||
write!(f, "E")?;
|
||||
has_written = true;
|
||||
}
|
||||
|
||||
if *optimize {
|
||||
write!(f, "+")?;
|
||||
} else {
|
||||
write!(f, "-")?;
|
||||
if let Some(optimize_setting) = self.optimize_setting {
|
||||
if has_written {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
optimize_setting.fmt(f)?;
|
||||
has_written = true;
|
||||
}
|
||||
|
||||
if let Some(req) = compiler_version_requirement {
|
||||
write!(f, " {req}")?;
|
||||
if let Some(version) = &self.version {
|
||||
if has_written {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
version.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SolcMode> for String {
|
||||
fn from(value: SolcMode) -> Self {
|
||||
value.to_string()
|
||||
impl From<ParsedMode> for String {
|
||||
fn from(parsed_mode: ParsedMode) -> Self {
|
||||
parsed_mode.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ParsedMode {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
ParsedMode::from_str(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParsedMode {
|
||||
/// This takes a [`ParsedMode`] and expands it into a list of [`Mode`]s that we should try.
|
||||
pub fn to_modes(&self) -> impl Iterator<Item = Mode> {
|
||||
let pipeline_iter = self.pipeline.as_ref().map_or_else(
|
||||
|| EitherIter::A(ModePipeline::test_cases()),
|
||||
|p| EitherIter::B(std::iter::once(*p)),
|
||||
);
|
||||
|
||||
let optimize_flag_setting = self.optimize_flag.map(|flag| {
|
||||
if flag {
|
||||
ModeOptimizerSetting::M3
|
||||
} else {
|
||||
ModeOptimizerSetting::M0
|
||||
}
|
||||
});
|
||||
|
||||
let optimize_flag_iter = match optimize_flag_setting {
|
||||
Some(setting) => EitherIter::A(std::iter::once(setting)),
|
||||
None => EitherIter::B(ModeOptimizerSetting::test_cases()),
|
||||
};
|
||||
|
||||
let optimize_settings_iter = self.optimize_setting.as_ref().map_or_else(
|
||||
|| EitherIter::A(optimize_flag_iter),
|
||||
|s| EitherIter::B(std::iter::once(*s)),
|
||||
);
|
||||
|
||||
pipeline_iter.flat_map(move |pipeline| {
|
||||
optimize_settings_iter
|
||||
.clone()
|
||||
.map(move |optimize_setting| Mode {
|
||||
pipeline,
|
||||
optimize_setting,
|
||||
version: self.version.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a set of [`Mode`]s that correspond to the given [`ParsedMode`]s.
|
||||
/// This avoids any duplicate entries.
|
||||
pub fn many_to_modes<'a>(
|
||||
parsed: impl Iterator<Item = &'a ParsedMode>,
|
||||
) -> impl Iterator<Item = Mode> {
|
||||
let modes: HashSet<_> = parsed.flat_map(|p| p.to_modes()).collect();
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator that could be either of two iterators.
|
||||
#[derive(Clone, Debug)]
|
||||
enum EitherIter<A, B> {
|
||||
A(A),
|
||||
B(B),
|
||||
}
|
||||
|
||||
impl<A, B> Iterator for EitherIter<A, B>
|
||||
where
|
||||
A: Iterator,
|
||||
B: Iterator<Item = A::Item>,
|
||||
{
|
||||
type Item = A::Item;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
EitherIter::A(iter) => iter.next(),
|
||||
EitherIter::B(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use semver::Version;
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mode_can_be_parsed_as_expected() {
|
||||
// Arrange
|
||||
let fixtures = [
|
||||
(
|
||||
"Y",
|
||||
vec![
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: true,
|
||||
compiler_version_requirement: None,
|
||||
}),
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: false,
|
||||
compiler_version_requirement: None,
|
||||
}),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Y+",
|
||||
vec![Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: true,
|
||||
compiler_version_requirement: None,
|
||||
})],
|
||||
),
|
||||
(
|
||||
"Y-",
|
||||
vec![Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: false,
|
||||
compiler_version_requirement: None,
|
||||
})],
|
||||
),
|
||||
(
|
||||
"E",
|
||||
vec![
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: false,
|
||||
optimize: true,
|
||||
compiler_version_requirement: None,
|
||||
}),
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: false,
|
||||
optimize: false,
|
||||
compiler_version_requirement: None,
|
||||
}),
|
||||
],
|
||||
),
|
||||
(
|
||||
"E+",
|
||||
vec![Mode::Solidity(SolcMode {
|
||||
via_ir: false,
|
||||
optimize: true,
|
||||
compiler_version_requirement: None,
|
||||
})],
|
||||
),
|
||||
(
|
||||
"E-",
|
||||
vec![Mode::Solidity(SolcMode {
|
||||
via_ir: false,
|
||||
optimize: false,
|
||||
compiler_version_requirement: None,
|
||||
})],
|
||||
),
|
||||
(
|
||||
"Y >=0.8.3",
|
||||
vec![
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: true,
|
||||
compiler_version_requirement: Some(VersionOrRequirement::Requirement(
|
||||
">=0.8.3".parse().unwrap(),
|
||||
)),
|
||||
}),
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: false,
|
||||
compiler_version_requirement: Some(VersionOrRequirement::Requirement(
|
||||
">=0.8.3".parse().unwrap(),
|
||||
)),
|
||||
}),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Y 0.8.3",
|
||||
vec![
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: true,
|
||||
compiler_version_requirement: Some(VersionOrRequirement::Version(
|
||||
Version {
|
||||
major: 0,
|
||||
minor: 8,
|
||||
patch: 3,
|
||||
pre: Default::default(),
|
||||
build: Default::default(),
|
||||
},
|
||||
)),
|
||||
}),
|
||||
Mode::Solidity(SolcMode {
|
||||
via_ir: true,
|
||||
optimize: false,
|
||||
compiler_version_requirement: Some(VersionOrRequirement::Version(
|
||||
Version {
|
||||
major: 0,
|
||||
minor: 8,
|
||||
patch: 3,
|
||||
pre: Default::default(),
|
||||
build: Default::default(),
|
||||
},
|
||||
)),
|
||||
}),
|
||||
],
|
||||
),
|
||||
fn test_parsed_mode_from_str() {
|
||||
let strings = vec![
|
||||
("Mz", "Mz"),
|
||||
("Y", "Y"),
|
||||
("Y+", "Y+"),
|
||||
("Y-", "Y-"),
|
||||
("E", "E"),
|
||||
("E+", "E+"),
|
||||
("E-", "E-"),
|
||||
("Y M0", "Y M0"),
|
||||
("Y M1", "Y M1"),
|
||||
("Y M2", "Y M2"),
|
||||
("Y M3", "Y M3"),
|
||||
("Y Ms", "Y Ms"),
|
||||
("Y Mz", "Y Mz"),
|
||||
("E M0", "E M0"),
|
||||
("E M1", "E M1"),
|
||||
("E M2", "E M2"),
|
||||
("E M3", "E M3"),
|
||||
("E Ms", "E Ms"),
|
||||
("E Mz", "E Mz"),
|
||||
// When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
|
||||
("Y 0.8.0", "Y ^0.8.0"),
|
||||
("E+ 0.8.0", "E+ ^0.8.0"),
|
||||
("Y M3 >=0.8.0", "Y M3 >=0.8.0"),
|
||||
("E Mz <0.7.0", "E Mz <0.7.0"),
|
||||
// We can parse +- _and_ M1/M2 but the latter takes priority.
|
||||
("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
|
||||
("E- M2 0.7.0", "E- M2 ^0.7.0"),
|
||||
// We don't see this in the wild but it is parsed.
|
||||
("<=0.8", "<=0.8"),
|
||||
];
|
||||
|
||||
for (string, expectation) in fixtures {
|
||||
// Act
|
||||
let actual = Mode::parse_from_string(string);
|
||||
|
||||
// Assert
|
||||
for (actual, expected) in strings {
|
||||
let parsed = ParsedMode::from_str(actual)
|
||||
.expect(format!("Failed to parse mode string '{actual}'").as_str());
|
||||
assert_eq!(
|
||||
actual, expectation,
|
||||
"Parsed {string} into {actual:?} but expected {expectation:?}"
|
||||
)
|
||||
expected,
|
||||
parsed.to_string(),
|
||||
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::uninlined_format_args)]
|
||||
fn mode_matches_as_expected() {
|
||||
// Arrange
|
||||
let fixtures = [("Y+", "Y+", true), ("Y+ >=0.8.3", "Y+", true)];
|
||||
fn test_parsed_mode_to_test_modes() {
|
||||
let strings = vec![
|
||||
("Mz", vec!["Y Mz", "E Mz"]),
|
||||
("Y", vec!["Y M0", "Y M3"]),
|
||||
("E", vec!["E M0", "E M3"]),
|
||||
("Y+", vec!["Y M3"]),
|
||||
("Y-", vec!["Y M0"]),
|
||||
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
|
||||
(
|
||||
"<=0.8",
|
||||
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
|
||||
),
|
||||
];
|
||||
|
||||
for (self_mode, other_mode, expected_result) in fixtures {
|
||||
let self_mode = Mode::parse_from_string(self_mode).pop().unwrap();
|
||||
let other_mode = Mode::parse_from_string(other_mode).pop().unwrap();
|
||||
for (actual, expected) in strings {
|
||||
let parsed = ParsedMode::from_str(actual)
|
||||
.expect(format!("Failed to parse mode string '{actual}'").as_str());
|
||||
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
|
||||
let actual_set: HashSet<_> = parsed.to_modes().map(|m| m.to_string()).collect();
|
||||
|
||||
// Act
|
||||
let actual = self_mode.matches(&other_mode);
|
||||
|
||||
// Assert
|
||||
assert_eq!(
|
||||
actual, expected_result,
|
||||
"Match of {} and {} failed. Expected {} but got {}",
|
||||
self_mode, other_mode, expected_result, actual
|
||||
expected_set, actual_set,
|
||||
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-25
@@ -33,7 +33,7 @@ use alloy::{
|
||||
};
|
||||
use anyhow::Context;
|
||||
use revive_common::EVMVersion;
|
||||
use tracing::Instrument;
|
||||
use tracing::{Instrument, Level};
|
||||
|
||||
use revive_dt_common::{fs::clear_directory, futures::poll};
|
||||
use revive_dt_config::Arguments;
|
||||
@@ -48,7 +48,7 @@ static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
///
|
||||
/// Implements helpers to initialize, spawn and wait the node.
|
||||
///
|
||||
/// Assumes dev mode and IPC only (`P2P`, `http` etc. are kept disabled).
|
||||
/// Assumes dev mode and IPC only (`P2P`, `http`` etc. are kept disabled).
|
||||
///
|
||||
/// Prunes the child process and the base directory on drop.
|
||||
#[derive(Debug)]
|
||||
@@ -60,7 +60,6 @@ pub struct GethNode {
|
||||
geth: PathBuf,
|
||||
id: u32,
|
||||
handle: Option<Child>,
|
||||
network_id: u64,
|
||||
start_timeout: u64,
|
||||
wallet: EthereumWallet,
|
||||
nonce_manager: CachedNonceManager,
|
||||
@@ -165,8 +164,6 @@ impl GethNode {
|
||||
.arg(&self.data_directory)
|
||||
.arg("--ipcpath")
|
||||
.arg(&self.connection_string)
|
||||
.arg("--networkid")
|
||||
.arg(self.network_id.to_string())
|
||||
.arg("--nodiscover")
|
||||
.arg("--maxpeers")
|
||||
.arg("0")
|
||||
@@ -213,6 +210,7 @@ impl GethNode {
|
||||
|
||||
let maximum_wait_time = Duration::from_millis(self.start_timeout);
|
||||
let mut stderr = BufReader::new(logs_file).lines();
|
||||
let mut lines = vec![];
|
||||
loop {
|
||||
if let Some(Ok(line)) = stderr.next() {
|
||||
if line.contains(Self::ERROR_MARKER) {
|
||||
@@ -221,19 +219,24 @@ impl GethNode {
|
||||
if line.contains(Self::READY_MARKER) {
|
||||
return Ok(self);
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
if Instant::now().duration_since(start_time) > maximum_wait_time {
|
||||
anyhow::bail!("Timeout in starting geth");
|
||||
anyhow::bail!(
|
||||
"Timeout in starting geth: took longer than {}ms. stdout:\n\n{}\n",
|
||||
self.start_timeout,
|
||||
lines.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = "trace")]
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = Level::TRACE)]
|
||||
fn geth_stdout_log_file_path(&self) -> PathBuf {
|
||||
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = "trace")]
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = Level::TRACE)]
|
||||
fn geth_stderr_log_file_path(&self) -> PathBuf {
|
||||
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
|
||||
}
|
||||
@@ -259,7 +262,7 @@ impl GethNode {
|
||||
.disable_recommended_fillers()
|
||||
.filler(FallbackGasFiller::new(
|
||||
25_000_000,
|
||||
100_000_000_000,
|
||||
1_000_000_000,
|
||||
1_000_000_000,
|
||||
))
|
||||
.filler(ChainIdFiller::default())
|
||||
@@ -268,7 +271,6 @@ impl GethNode {
|
||||
.connect(&connection_string)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.inspect_err(|err| tracing::error!(%err, "Failed to create the alloy provider"))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -282,26 +284,27 @@ impl EthereumNode for GethNode {
|
||||
let span = tracing::debug_span!("Submitting transaction", ?transaction);
|
||||
let _guard = span.enter();
|
||||
|
||||
let provider = self.provider().await.map(Arc::new)?;
|
||||
let provider = Arc::new(self.provider().await?);
|
||||
let transaction_hash = *provider.send_transaction(transaction).await?.tx_hash();
|
||||
|
||||
// The following is a fix for the "transaction indexing is in progress" error that we used
|
||||
// to get. You can find more information on this in the following GH issue in geth
|
||||
// The following is a fix for the "transaction indexing is in progress" error that we
|
||||
// used to get. You can find more information on this in the following GH issue in geth
|
||||
// https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on,
|
||||
// before we can get the receipt of the transaction it needs to have been indexed by the
|
||||
// node's indexer. Just because the transaction has been confirmed it doesn't mean that it
|
||||
// has been indexed. When we call alloy's `get_receipt` it checks if the transaction was
|
||||
// confirmed. If it has been, then it will call `eth_getTransactionReceipt` method which
|
||||
// _might_ return the above error if the tx has not yet been indexed yet. So, we need to
|
||||
// implement a retry mechanism for the receipt to keep retrying to get it until it
|
||||
// eventually works, but we only do that if the error we get back is the "transaction
|
||||
// node's indexer. Just because the transaction has been confirmed it doesn't mean that
|
||||
// it has been indexed. When we call alloy's `get_receipt` it checks if the transaction
|
||||
// was confirmed. If it has been, then it will call `eth_getTransactionReceipt` method
|
||||
// which _might_ return the above error if the tx has not yet been indexed yet. So, we
|
||||
// need to implement a retry mechanism for the receipt to keep retrying to get it until
|
||||
// it eventually works, but we only do that if the error we get back is the "transaction
|
||||
// indexing is in progress" error or if the receipt is None.
|
||||
//
|
||||
// Getting the transaction indexed and taking a receipt can take a long time especially
|
||||
// when a lot of transactions are being submitted to the node. Thus, while initially we only
|
||||
// allowed for 60 seconds of waiting with a 1 second delay in polling, we need to allow for
|
||||
// a larger wait time. Therefore, in here we allow for 5 minutes of waiting with exponential
|
||||
// backoff each time we attempt to get the receipt and find that it's not available.
|
||||
// when a lot of transactions are being submitted to the node. Thus, while initially we
|
||||
// only allowed for 60 seconds of waiting with a 1 second delay in polling, we need to
|
||||
// allow for a larger wait time. Therefore, in here we allow for 5 minutes of waiting
|
||||
// with exponential backoff each time we attempt to get the receipt and find that it's
|
||||
// not available.
|
||||
poll(
|
||||
Self::RECEIPT_POLLING_DURATION,
|
||||
Default::default(),
|
||||
@@ -327,7 +330,6 @@ impl EthereumNode for GethNode {
|
||||
?transaction_hash
|
||||
))
|
||||
.await
|
||||
.inspect(|receipt| tracing::info!(gas_used = receipt.gas_used, "Gas used on transaction"))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
||||
@@ -522,7 +524,6 @@ impl Node for GethNode {
|
||||
geth: config.geth.clone(),
|
||||
id,
|
||||
handle: None,
|
||||
network_id: config.network_id,
|
||||
start_timeout: config.geth_start_timeout,
|
||||
wallet,
|
||||
// We know that we only need to be storing 2 files so we can specify that when creating
|
||||
|
||||
@@ -367,9 +367,9 @@ impl KitchensinkNode {
|
||||
.disable_recommended_fillers()
|
||||
.network::<KitchenSinkNetwork>()
|
||||
.filler(FallbackGasFiller::new(
|
||||
30_000_000,
|
||||
200_000_000_000,
|
||||
3_000_000_000,
|
||||
25_000_000,
|
||||
1_000_000_000,
|
||||
1_000_000_000,
|
||||
))
|
||||
.filler(ChainIdFiller::default())
|
||||
.filler(NonceFiller::new(nonce_manager))
|
||||
|
||||
@@ -8,6 +8,7 @@ repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
revive-dt-common = { workspace = true }
|
||||
revive-dt-config = { workspace = true }
|
||||
revive-dt-format = { workspace = true }
|
||||
revive-dt-compiler = { workspace = true }
|
||||
|
||||
@@ -12,11 +12,12 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
||||
use serde::Serialize;
|
||||
|
||||
use revive_dt_common::types::Mode;
|
||||
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
||||
use revive_dt_config::{Arguments, TestingPlatform};
|
||||
use revive_dt_format::{corpus::Corpus, mode::SolcMode};
|
||||
use revive_dt_format::corpus::Corpus;
|
||||
|
||||
use crate::analyzer::CompilerStatistics;
|
||||
|
||||
@@ -48,7 +49,7 @@ pub struct CompilationTask {
|
||||
/// The observed compiler output.
|
||||
pub json_output: Option<CompilerOutput>,
|
||||
/// The observed compiler mode.
|
||||
pub mode: SolcMode,
|
||||
pub mode: Mode,
|
||||
/// The observed compiler version.
|
||||
pub compiler_version: String,
|
||||
/// The observed error, if any.
|
||||
|
||||
Reference in New Issue
Block a user