Compare commits

..

31 Commits

Author SHA1 Message Date
James Wilson 255de530c3 remove --networkid and arg, back to 5s timeout for geth 2025-08-15 16:32:41 +01:00
James Wilson c960f02417 fix line logging 2025-08-15 16:11:11 +01:00
James Wilson 5e19269124 Improve geth stdout logging on failure 2025-08-15 16:05:57 +01:00
James Wilson 458a0901d3 30s timeout 2025-08-15 15:54:41 +01:00
James Wilson 77f389bc74 Bump default geth timeout to 10s 2025-08-15 15:39:02 +01:00
James Wilson 897104857c Merge branch 'main' into jsdw-redo-modes 2025-08-15 14:02:47 +01:00
James Wilson 3837ab86d3 Update kitchensink weights too and fmt 2025-08-15 13:59:49 +01:00
James Wilson 9ae432b86a Update fallback tx weights to avoid out of gas errors 2025-08-15 13:58:13 +01:00
James Wilson 230d6729f4 clippy nits 2025-08-14 16:34:50 +01:00
Omar a59e287fa1 Add a cached fs abstraction (#141) 2025-08-14 15:21:05 +00:00
James Wilson 7e26e5ebcc Remove now-unused deps 2025-08-14 16:20:39 +01:00
James Wilson e5c7bf300b Move compile check to prepare_tests 2025-08-14 16:12:48 +01:00
James Wilson b2290b3177 Re-arrange Mode things; ParsedMode in format and Mode etc in common 2025-08-14 13:02:46 +01:00
James Wilson df6d485471 Rename ModePipeline::E/Y 2025-08-14 12:34:43 +01:00
James Wilson 30a656844f fmt 2025-08-14 10:59:58 +01:00
James Wilson 1c335e5709 constants.mod, and Display for CaseIdx to use it 2025-08-14 10:58:02 +01:00
James Wilson 95ea886ba7 Move Mode to common crate 2025-08-14 10:42:58 +01:00
James Wilson c94e97cfd0 Merge branch 'main' into jsdw-redo-modes 2025-08-14 09:05:26 +01:00
James Wilson 036579fb32 Merge branch 'main' into jsdw-redo-modes 2025-08-13 15:12:26 +01:00
James Wilson 0bc55cf400 Improve test output a little; string modes and list ignored tests 2025-08-13 15:10:03 +01:00
James Wilson ea29bb86c4 Merge branch 'main' into jsdw-redo-modes 2025-08-13 15:00:19 +01:00
James Wilson fe6a4f790a Elide viaIR input if compiler does not support it 2025-08-11 17:33:10 +01:00
James Wilson f98c15121d Add ability for compiler to opt out if it can't work with some Mode/version 2025-08-08 17:43:12 +01:00
James Wilson 7b5ffed288 Address nits 2025-08-08 12:40:41 +01:00
James Wilson 2ca220da0f Remove mode we no longer support from test metadata 2025-08-08 11:56:57 +01:00
James Wilson 4c9c001ad7 clippy 2025-08-08 11:02:16 +01:00
James Wilson 88f31348eb fmt 2025-08-08 11:00:41 +01:00
James Wilson cfbb5eefff First pass integrated new mode bits 2025-08-08 10:59:49 +01:00
James Wilson 2d01045ad2 WIP integrate new Mode/ParsedMode into rest of code 2025-08-08 10:50:40 +01:00
James Wilson bd966300a5 test expanding, too 2025-08-08 10:34:50 +01:00
James Wilson e8fa992a3b WIP redo how we parse and use modes 2025-08-07 16:54:26 +01:00
22 changed files with 653 additions and 679 deletions
Generated
+3
View File
@@ -4093,6 +4093,7 @@ dependencies = [
"moka", "moka",
"once_cell", "once_cell",
"semver 1.0.26", "semver 1.0.26",
"serde",
"tokio", "tokio",
] ]
@@ -4159,6 +4160,7 @@ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
"anyhow", "anyhow",
"regex",
"revive-common", "revive-common",
"revive-dt-common", "revive-dt-common",
"semver 1.0.26", "semver 1.0.26",
@@ -4201,6 +4203,7 @@ name = "revive-dt-report"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"revive-dt-common",
"revive-dt-compiler", "revive-dt-compiler",
"revive-dt-config", "revive-dt-config",
"revive-dt-format", "revive-dt-format",
+2
View File
@@ -29,6 +29,7 @@ clap = { version = "4", features = ["derive"] }
foundry-compilers-artifacts = { version = "0.18.0" } foundry-compilers-artifacts = { version = "0.18.0" }
futures = { version = "0.3.31" } futures = { version = "0.3.31" }
hex = "0.4.3" hex = "0.4.3"
regex = "1"
moka = "0.12.10" moka = "0.12.10"
reqwest = { version = "0.12.15", features = ["json"] } reqwest = { version = "0.12.15", features = ["json"] }
once_cell = "1.21" once_cell = "1.21"
@@ -44,6 +45,7 @@ sp-core = "36.1.0"
sp-runtime = "41.1.0" sp-runtime = "41.1.0"
temp-dir = { version = "0.1.16" } temp-dir = { version = "0.1.16" }
tempfile = "3.3" tempfile = "3.3"
thiserror = "2"
tokio = { version = "1.47.0", default-features = false, features = [ tokio = { version = "1.47.0", default-features = false, features = [
"rt-multi-thread", "rt-multi-thread",
"process", "process",
+1 -2
View File
@@ -1,8 +1,7 @@
{ {
"modes": [ "modes": [
"Y >=0.8.9", "Y >=0.8.9",
"E", "E"
"I"
], ],
"cases": [ "cases": [
{ {
+1
View File
@@ -13,4 +13,5 @@ anyhow = { workspace = true }
moka = { workspace = true, features = ["sync"] } moka = { workspace = true, features = ["sync"] }
once_cell = { workspace = true } once_cell = { workspace = true }
semver = { workspace = true } semver = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true, default-features = false, features = ["time"] } tokio = { workspace = true, default-features = false, features = ["time"] }
-24
View File
@@ -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)
}
}
}
}
+2
View File
@@ -1,3 +1,5 @@
mod mode;
mod version_or_requirement; mod version_or_requirement;
pub use mode::*;
pub use version_or_requirement::*; pub use version_or_requirement::*;
+167
View File
@@ -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}; use semver::{Version, VersionReq};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug)]
pub enum VersionOrRequirement { pub enum VersionOrRequirement {
Version(Version), Version(Version),
Requirement(VersionReq), Requirement(VersionReq),
@@ -42,26 +39,3 @@ impl TryFrom<VersionOrRequirement> for VersionReq {
Ok(requirement) 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),
}
}
}
+4
View File
@@ -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);
+20 -8
View File
@@ -3,6 +3,8 @@
//! - Polkadot revive resolc compiler //! - Polkadot revive resolc compiler
//! - Polkadot revive Wasm compiler //! - Polkadot revive Wasm compiler
mod constants;
use std::{ use std::{
collections::HashMap, collections::HashMap,
hash::Hash, hash::Hash,
@@ -19,6 +21,9 @@ use revive_dt_common::cached_fs::read_to_string;
use revive_dt_common::types::VersionOrRequirement; use revive_dt_common::types::VersionOrRequirement;
use revive_dt_config::Arguments; 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_js;
pub mod revive_resolc; pub mod revive_resolc;
pub mod solc; pub mod solc;
@@ -43,13 +48,20 @@ pub trait SolidityCompiler {
) -> impl Future<Output = anyhow::Result<PathBuf>>; ) -> impl Future<Output = anyhow::Result<PathBuf>>;
fn version(&self) -> anyhow::Result<Version>; 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. /// The generic compilation input configuration.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompilerInput { pub struct CompilerInput {
pub enable_optimization: Option<bool>, pub pipeline: Option<ModePipeline>,
pub via_ir: Option<bool>, pub optimization: Option<ModeOptimizerSetting>,
pub evm_version: Option<EVMVersion>, pub evm_version: Option<EVMVersion>,
pub allow_paths: Vec<PathBuf>, pub allow_paths: Vec<PathBuf>,
pub base_path: Option<PathBuf>, pub base_path: Option<PathBuf>,
@@ -85,8 +97,8 @@ where
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
input: CompilerInput { input: CompilerInput {
enable_optimization: Default::default(), pipeline: Default::default(),
via_ir: Default::default(), optimization: Default::default(),
evm_version: Default::default(), evm_version: Default::default(),
allow_paths: Default::default(), allow_paths: Default::default(),
base_path: Default::default(), base_path: Default::default(),
@@ -98,13 +110,13 @@ where
} }
} }
pub fn with_optimization(mut self, value: impl Into<Option<bool>>) -> Self { pub fn with_optimization(mut self, value: impl Into<Option<ModeOptimizerSetting>>) -> Self {
self.input.enable_optimization = value.into(); self.input.optimization = value.into();
self self
} }
pub fn with_via_ir(mut self, value: impl Into<Option<bool>>) -> Self { pub fn with_pipeline(mut self, value: impl Into<Option<ModePipeline>>) -> Self {
self.input.via_ir = value.into(); self.input.pipeline = value.into();
self self
} }
+36 -23
View File
@@ -2,7 +2,6 @@
//! compiling contracts to PolkaVM (PVM) bytecode. //! compiling contracts to PolkaVM (PVM) bytecode.
use std::{ use std::{
os::unix::process::CommandExt,
path::PathBuf, path::PathBuf,
process::{Command, Stdio}, process::{Command, Stdio},
}; };
@@ -15,7 +14,8 @@ use revive_solc_json_interface::{
SolcStandardJsonOutput, 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 alloy::json_abi::JsonAbi;
use anyhow::Context; use anyhow::Context;
@@ -40,9 +40,8 @@ impl SolidityCompiler for Resolc {
async fn build( async fn build(
&self, &self,
CompilerInput { CompilerInput {
enable_optimization, pipeline,
// Ignored and not honored since this is required for the resolc compilation. optimization,
via_ir: _via_ir,
evm_version, evm_version,
allow_paths, allow_paths,
base_path, base_path,
@@ -54,6 +53,12 @@ impl SolidityCompiler for Resolc {
}: CompilerInput, }: CompilerInput,
additional_options: Self::Options, additional_options: Self::Options,
) -> anyhow::Result<CompilerOutput> { ) -> 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 { let input = SolcStandardJsonInput {
language: SolcStandardJsonInputLanguage::Solidity, language: SolcStandardJsonInputLanguage::Solidity,
sources: sources sources: sources
@@ -82,7 +87,9 @@ impl SolidityCompiler for Resolc {
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()), output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
via_ir: Some(true), via_ir: Some(true),
optimizer: SolcStandardJsonInputSettingsOptimizer::new( optimizer: SolcStandardJsonInputSettingsOptimizer::new(
enable_optimization.unwrap_or(false), optimization
.unwrap_or(ModeOptimizerSetting::M0)
.optimizations_enabled(),
None, None,
&Version::new(0, 0, 0), &Version::new(0, 0, 0),
false, false,
@@ -93,14 +100,11 @@ impl SolidityCompiler for Resolc {
}; };
let mut command = AsyncCommand::new(&self.resolc_path); let mut command = AsyncCommand::new(&self.resolc_path);
unsafe { command
command .stdin(Stdio::piped())
.stdin(Stdio::piped()) .stdout(Stdio::piped())
.stdout(Stdio::piped()) .stderr(Stdio::piped())
.stderr(Stdio::piped()) .arg("--standard-json");
.arg("--standard-json")
.pre_exec(|| Ok(()))
};
if let Some(ref base_path) = base_path { if let Some(ref base_path) = base_path {
command.arg("--base-path").arg(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: // 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 // Solidity frontend for the revive compiler version 0.3.0+commit.b238913.llvm-18.1.8
let output = unsafe { let output = Command::new(self.resolc_path.as_path())
Command::new(self.resolc_path.as_path()) .arg("--version")
.arg("--version") .stdout(Stdio::piped())
.stdout(Stdio::piped()) .spawn()?
.pre_exec(|| Ok(())) .wait_with_output()?
.spawn()? .stdout;
.wait_with_output()?
.stdout
};
let output = String::from_utf8_lossy(&output); let output = String::from_utf8_lossy(&output);
let version_string = output let version_string = output
.split("version ") .split("version ")
@@ -239,6 +240,18 @@ impl SolidityCompiler for Resolc {
Version::parse(version_string).map_err(Into::into) 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)] #[cfg(test)]
+45 -39
View File
@@ -2,7 +2,6 @@
//! compiling contracts to EVM bytecode. //! compiling contracts to EVM bytecode.
use std::{ use std::{
os::unix::process::CommandExt,
path::PathBuf, path::PathBuf,
process::{Command, Stdio}, process::{Command, Stdio},
}; };
@@ -11,7 +10,8 @@ use revive_dt_common::types::VersionOrRequirement;
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_solc_binaries::download_solc; 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 anyhow::Context;
use foundry_compilers_artifacts::{ use foundry_compilers_artifacts::{
@@ -32,12 +32,12 @@ pub struct Solc {
impl SolidityCompiler for Solc { impl SolidityCompiler for Solc {
type Options = (); type Options = ();
#[tracing::instrument(level = "info", ret)] #[tracing::instrument(level = "debug", ret)]
async fn build( async fn build(
&self, &self,
CompilerInput { CompilerInput {
enable_optimization, pipeline,
via_ir, optimization,
evm_version, evm_version,
allow_paths, allow_paths,
base_path, base_path,
@@ -47,6 +47,17 @@ impl SolidityCompiler for Solc {
}: CompilerInput, }: CompilerInput,
_: Self::Options, _: Self::Options,
) -> anyhow::Result<CompilerOutput> { ) -> 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 { let input = SolcInput {
language: SolcLanguage::Solidity, language: SolcLanguage::Solidity,
sources: Sources( sources: Sources(
@@ -57,7 +68,7 @@ impl SolidityCompiler for Solc {
), ),
settings: Settings { settings: Settings {
optimizer: Optimizer { optimizer: Optimizer {
enabled: enable_optimization, enabled: optimization.map(|o| o.optimizations_enabled()),
details: Some(Default::default()), details: Some(Default::default()),
..Default::default() ..Default::default()
}, },
@@ -103,14 +114,11 @@ impl SolidityCompiler for Solc {
}; };
let mut command = AsyncCommand::new(&self.solc_path); let mut command = AsyncCommand::new(&self.solc_path);
unsafe { command
command .stdin(Stdio::piped())
.stdin(Stdio::piped()) .stdout(Stdio::piped())
.stdout(Stdio::piped()) .stderr(Stdio::piped())
.stderr(Stdio::piped()) .arg("--standard-json");
.arg("--standard-json")
.pre_exec(|| Ok(()))
};
if let Some(ref base_path) = base_path { if let Some(ref base_path) = base_path {
command.arg("--base-path").arg(base_path); command.arg("--base-path").arg(base_path);
@@ -124,20 +132,12 @@ impl SolidityCompiler for Solc {
.join(","), .join(","),
); );
} }
let mut child = command let mut child = command.spawn()?;
.spawn()
.inspect_err(|err| tracing::error!(%err, "Failed to spawn the solc command"))?;
let stdin = child.stdin.as_mut().expect("should be piped"); let stdin = child.stdin.as_mut().expect("should be piped");
let serialized_input = serde_json::to_vec(&input)?; let serialized_input = serde_json::to_vec(&input)?;
stdin stdin.write_all(&serialized_input).await?;
.write_all(&serialized_input) let output = child.wait_with_output().await?;
.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"))?;
if !output.status.success() { if !output.status.success() {
let json_in = serde_json::to_string_pretty(&input)?; let json_in = serde_json::to_string_pretty(&input)?;
@@ -174,13 +174,10 @@ impl SolidityCompiler for Solc {
let mut compiler_output = CompilerOutput::default(); let mut compiler_output = CompilerOutput::default();
for (contract_path, contracts) in parsed.contracts { for (contract_path, contracts) in parsed.contracts {
let map = let map = compiler_output
compiler_output .contracts
.contracts .entry(contract_path.canonicalize()?)
.entry(contract_path.canonicalize().inspect_err( .or_default();
|err| tracing::error!(%err, "Canonicalization of path failed"),
)?)
.or_default();
for (contract_name, contract_info) in contracts.into_iter() { for (contract_name, contract_info) in contracts.into_iter() {
let source_code = contract_info let source_code = contract_info
.evm .evm
@@ -220,13 +217,10 @@ impl SolidityCompiler for Solc {
// Version: 0.8.30+commit.73712a01.Darwin.appleclang // Version: 0.8.30+commit.73712a01.Darwin.appleclang
// ``` // ```
let child = unsafe { let child = Command::new(self.solc_path.as_path())
Command::new(self.solc_path.as_path()) .arg("--version")
.arg("--version") .stdout(Stdio::piped())
.stdout(Stdio::piped()) .spawn()?;
.pre_exec(|| Ok(()))
.spawn()?
};
let output = child.wait_with_output()?; let output = child.wait_with_output()?;
let output = String::from_utf8_lossy(&output.stdout); let output = String::from_utf8_lossy(&output.stdout);
let version_line = output let version_line = output
@@ -240,6 +234,18 @@ impl SolidityCompiler for Solc {
Version::parse(version_string).map_err(Into::into) 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)] #[cfg(test)]
-4
View File
@@ -58,10 +58,6 @@ pub struct Arguments {
#[arg(long = "geth-start-timeout", default_value = "5000")] #[arg(long = "geth-start-timeout", default_value = "5000")]
pub geth_start_timeout: u64, 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. /// Configure nodes according to this genesis.json file.
#[arg(long = "genesis", default_value = "genesis.json")] #[arg(long = "genesis", default_value = "genesis.json")]
pub genesis_file: PathBuf, pub genesis_file: PathBuf,
+93 -99
View File
@@ -13,15 +13,17 @@ use alloy::{
}; };
use anyhow::Context; use anyhow::Context;
use clap::Parser; 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_common::iterators::FilesWithExtensionIterator;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use semver::Version; use semver::Version;
use temp_dir::TempDir; use temp_dir::TempDir;
use tokio::sync::{Mutex, RwLock, mpsc}; use tokio::sync::{Mutex, RwLock, mpsc};
use tracing::{Instrument, Level, instrument}; use tracing::{Instrument, Level};
use tracing_subscriber::{EnvFilter, FmtSubscriber}; use tracing_subscriber::{EnvFilter, FmtSubscriber};
use revive_dt_common::types::Mode;
use revive_dt_compiler::SolidityCompiler; use revive_dt_compiler::SolidityCompiler;
use revive_dt_compiler::{Compiler, CompilerOutput}; use revive_dt_compiler::{Compiler, CompilerOutput};
use revive_dt_config::*; use revive_dt_config::*;
@@ -34,7 +36,6 @@ use revive_dt_format::{
corpus::Corpus, corpus::Corpus,
input::{Input, Step}, input::{Input, Step},
metadata::{ContractInstance, ContractPathAndIdent, Metadata, MetadataFile}, metadata::{ContractInstance, ContractPathAndIdent, Metadata, MetadataFile},
mode::{Mode, SolcMode},
}; };
use revive_dt_node::pool::NodePool; use revive_dt_node::pool::NodePool;
use revive_dt_report::reporter::{Report, Span}; use revive_dt_report::reporter::{Report, Span};
@@ -44,7 +45,7 @@ static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
type CompilationCache = Arc< type CompilationCache = Arc<
RwLock< RwLock<
HashMap< HashMap<
(PathBuf, SolcMode, TestingPlatform), (PathBuf, Mode, TestingPlatform),
Arc<Mutex<Option<Arc<(Version, CompilerOutput)>>>>, Arc<Mutex<Option<Arc<(Version, CompilerOutput)>>>>,
>, >,
>, >,
@@ -55,8 +56,8 @@ type CompilationCache = Arc<
struct Test { struct Test {
metadata: Metadata, metadata: Metadata,
path: PathBuf, path: PathBuf,
mode: SolcMode, mode: Mode,
case_idx: usize, case_idx: CaseIdx,
case: Case, case: Case,
} }
@@ -144,7 +145,7 @@ where
{ {
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test, CaseResult)>(); 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 driver_task = start_driver_task::<L, F>(args, tests, span, report_tx)?;
let status_reporter_task = start_reporter_task(report_rx); let status_reporter_task = start_reporter_task(report_rx);
@@ -153,7 +154,10 @@ where
Ok(()) 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 where
L: Platform, L: Platform,
F: Platform, F: Platform,
@@ -172,7 +176,8 @@ where
.iter() .iter()
.enumerate() .enumerate()
.flat_map(move |(case_idx, case)| { .flat_map(move |(case_idx, case)| {
SolcMode::ALL metadata
.solc_modes()
.into_iter() .into_iter()
.map(move |solc_mode| (path, metadata, case_idx, case, solc_mode)) .map(move |solc_mode| (path, metadata, case_idx, case, solc_mode))
}) })
@@ -225,41 +230,58 @@ where
} }
None => true, 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)| { .map(|(metadata_file_path, metadata, case_idx, case, solc_mode)| {
Test { Test {
metadata: metadata.clone(), metadata: metadata.clone(),
path: metadata_file_path.to_path_buf(), path: metadata_file_path.to_path_buf(),
mode: solc_mode, mode: solc_mode,
case_idx, case_idx: case_idx.into(),
case: case.clone(), 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>( fn start_driver_task<L, F>(
args: &Arguments, args: &Arguments,
tests: impl Iterator<Item = Test>, tests: impl Stream<Item = Test>,
span: Span, span: Span,
report_tx: mpsc::UnboundedSender<(Test, CaseResult)>, report_tx: mpsc::UnboundedSender<(Test, CaseResult)>,
) -> anyhow::Result<impl Future<Output = ()>> ) -> anyhow::Result<impl Future<Output = ()>>
@@ -274,7 +296,7 @@ where
let compilation_cache = Arc::new(RwLock::new(HashMap::new())); let compilation_cache = Arc::new(RwLock::new(HashMap::new()));
let number_concurrent_tasks = args.number_of_concurrent_tasks(); 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: // 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. // 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", "Running driver",
metadata_file_path = %test.path.display(), metadata_file_path = %test.path.display(),
case_idx = ?test.case_idx, case_idx = ?test.case_idx,
solc_mode = %test.mode, solc_mode = ?test.mode,
); );
let result = handle_case_driver::<L, F>( let result = handle_case_driver::<L, F>(
&test.path, &test.path,
&test.metadata, &test.metadata,
test.case_idx.into(), test.case_idx,
&test.case, &test.case,
test.mode.clone(), test.mode.clone(),
args, args,
@@ -329,7 +351,7 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
const GREEN: &str = "\x1B[32m"; const GREEN: &str = "\x1B[32m";
const RED: &str = "\x1B[31m"; const RED: &str = "\x1B[31m";
const COLOR_RESET: &str = "\x1B[0m"; const COLOUR_RESET: &str = "\x1B[0m";
const BOLD: &str = "\x1B[1m"; const BOLD: &str = "\x1B[1m";
const BOLD_RESET: &str = "\x1B[22m"; const BOLD_RESET: &str = "\x1B[22m";
@@ -348,13 +370,13 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
Ok(_inputs) => { Ok(_inputs) => {
number_of_successes += 1; number_of_successes += 1;
eprintln!( 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) => { Err(err) => {
number_of_failures += 1; number_of_failures += 1;
eprintln!( 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)); 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(); let test_mode = test.mode.clone();
eprintln!( 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. // Summary at the end.
eprintln!( 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, number_of_successes + number_of_failures,
elapsed.as_secs() elapsed.as_secs()
); );
@@ -396,7 +418,7 @@ async fn handle_case_driver<L, F>(
metadata: &Metadata, metadata: &Metadata,
case_idx: CaseIdx, case_idx: CaseIdx,
case: &Case, case: &Case,
mode: SolcMode, mode: Mode,
config: &Arguments, config: &Arguments,
compilation_cache: CompilationCache, compilation_cache: CompilationCache,
leader_node: &L::Blockchain, leader_node: &L::Blockchain,
@@ -637,7 +659,7 @@ where
async fn get_or_build_contracts<P: Platform>( async fn get_or_build_contracts<P: Platform>(
metadata: &Metadata, metadata: &Metadata,
metadata_file_path: &Path, metadata_file_path: &Path,
mode: SolcMode, mode: Mode,
config: &Arguments, config: &Arguments,
compilation_cache: CompilationCache, compilation_cache: CompilationCache,
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
@@ -656,16 +678,16 @@ async fn get_or_build_contracts<P: Platform>(
} }
None => { None => {
tracing::debug!(?key, "Compiled contracts cache miss"); tracing::debug!(?key, "Compiled contracts cache miss");
let compiled_contracts = Arc::new( let compiled_contracts = compile_contracts::<P>(
compile_contracts::<P>( metadata,
metadata, metadata_file_path,
metadata_file_path, &mode,
&mode, config,
config, deployed_libraries,
deployed_libraries, )
) .await?;
.await?, let compiled_contracts = Arc::new(compiled_contracts);
);
*compilation_artifact = Some(compiled_contracts.clone()); *compilation_artifact = Some(compiled_contracts.clone());
return Ok(compiled_contracts.clone()); return Ok(compiled_contracts.clone());
} }
@@ -680,59 +702,44 @@ async fn get_or_build_contracts<P: Platform>(
mutex mutex
}; };
let mut compilation_artifact = mutex.lock().await; let mut compilation_artifact = mutex.lock().await;
let compiled_contracts = Arc::new(
compile_contracts::<P>( let compiled_contracts = compile_contracts::<P>(
metadata, metadata,
metadata_file_path, metadata_file_path,
&mode, &mode,
config, config,
deployed_libraries, deployed_libraries,
) )
.await?, .await?;
); let compiled_contracts = Arc::new(compiled_contracts);
*compilation_artifact = Some(compiled_contracts.clone()); *compilation_artifact = Some(compiled_contracts.clone());
Ok(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>( async fn compile_contracts<P: Platform>(
metadata: &Metadata, metadata: &Metadata,
metadata_file_path: &Path, metadata_file_path: &Path,
mode: &SolcMode, mode: &Mode,
config: &Arguments, config: &Arguments,
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
) -> anyhow::Result<(Version, CompilerOutput)> { ) -> anyhow::Result<(Version, CompilerOutput)> {
let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone()); let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone());
let compiler_path = let compiler_path =
P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?; P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?;
let compiler_version = P::Compiler::new(compiler_path.clone()) let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
.version()
.inspect_err(|err| tracing::error!(%err, "Failed to get compiler version"))?;
tracing::info!( tracing::info!(
%compiler_version, %compiler_version,
metadata_file_path = %metadata_file_path.display(), metadata_file_path = %metadata_file_path.display(),
mode = %mode, mode = ?mode,
"Compiling contracts" "Compiling contracts"
); );
let compiler = Compiler::<P::Compiler>::new() let compiler = Compiler::<P::Compiler>::new()
.with_allow_path( .with_allow_path(metadata.directory()?)
metadata .with_optimization(mode.optimize_setting)
.directory() .with_pipeline(mode.pipeline);
.inspect_err(|err| tracing::error!(%err, "Failed to get the metadata directory"))?,
)
.with_optimization(mode.optimize)
.with_via_ir(mode.via_ir);
let mut compiler = metadata let mut compiler = metadata
.files_to_compile()? .files_to_compile()?
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?; .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 // 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 and it will only perform the linking for the files that do actually need the
// library. // library.
compiler = compiler = FilesWithExtensionIterator::new(metadata.directory()?)
FilesWithExtensionIterator::new(metadata.directory().inspect_err(
|err| tracing::error!(%err, "Failed to get the metadata directory"),
)?)
.with_allowed_extension("sol") .with_allowed_extension("sol")
.with_use_cached_fs(true) .with_use_cached_fs(true)
.fold(compiler, |compiler, path| { .fold(compiler, |compiler, path| {
@@ -760,17 +764,7 @@ async fn compile_contracts<P: Platform>(
}); });
} }
let compiler_output = compiler let compiler_output = compiler.try_build(compiler_path).await?;
.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"
);
Ok((compiler_version, compiler_output)) Ok((compiler_version, compiler_output))
} }
+1
View File
@@ -17,6 +17,7 @@ alloy = { workspace = true }
alloy-primitives = { workspace = true } alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true } alloy-sol-types = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
regex = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
semver = { workspace = true } semver = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
+10 -11
View File
@@ -1,13 +1,10 @@
use std::collections::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revive_dt_common::macros::define_wrapper_type; use revive_dt_common::macros::define_wrapper_type;
use crate::{ use crate::{
input::{Expected, Step}, input::{Expected, Step},
metadata::{deserialize_compilation_modes, serialize_compilation_modes}, mode::ParsedMode,
mode::Mode,
}; };
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] #[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
@@ -18,13 +15,8 @@ pub struct Case {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>, pub comment: Option<String>,
#[serde( #[serde(skip_serializing_if = "Option::is_none")]
default, pub modes: Option<Vec<ParsedMode>>,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_compilation_modes",
serialize_with = "serialize_compilation_modes"
)]
pub modes: Option<HashSet<Mode>>,
#[serde(rename = "inputs")] #[serde(rename = "inputs")]
pub steps: Vec<Step>, pub steps: Vec<Step>,
@@ -40,6 +32,7 @@ pub struct Case {
} }
impl Case { impl Case {
#[allow(irrefutable_let_patterns)]
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> { pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
let steps_len = self.steps.len(); let steps_len = self.steps.len();
self.steps self.steps
@@ -74,3 +67,9 @@ define_wrapper_type!(
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CaseIdx(usize); 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)
}
}
+12 -59
View File
@@ -1,6 +1,6 @@
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::{BTreeMap, HashSet}, collections::BTreeMap,
fmt::Display, fmt::Display,
fs::File, fs::File,
ops::Deref, ops::Deref,
@@ -8,17 +8,15 @@ use std::{
str::FromStr, str::FromStr,
}; };
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Serialize};
use revive_common::EVMVersion; use revive_common::EVMVersion;
use revive_dt_common::{ use revive_dt_common::{
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type, cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
types::Mode,
}; };
use crate::{ use crate::{case::Case, mode::ParsedMode};
case::Case,
mode::{Mode, SolcMode},
};
pub const METADATA_FILE_EXTENSION: &str = "json"; pub const METADATA_FILE_EXTENSION: &str = "json";
pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol"; pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol";
@@ -67,13 +65,8 @@ pub struct Metadata {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>, pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>,
#[serde( #[serde(skip_serializing_if = "Option::is_none")]
default, pub modes: Option<Vec<ParsedMode>>,
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")] #[serde(skip_serializing_if = "Option::is_none")]
pub file_path: Option<PathBuf>, pub file_path: Option<PathBuf>,
@@ -91,21 +84,12 @@ pub struct Metadata {
} }
impl Metadata { impl Metadata {
/// Returns the solc modes of this metadata, inserting a default mode if not present. /// Returns the modes that we should test from this metadata.
pub fn solc_modes(&self) -> Vec<SolcMode> { pub fn solc_modes(&self) -> Vec<Mode> {
self.modes match &self.modes {
.to_owned() Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
.unwrap_or_else(|| SolcMode::ALL.map(Mode::Solidity).iter().cloned().collect()) None => Mode::all().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 base directory of this metadata. /// 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!( define_wrapper_type!(
/// Represents a contract instance found a metadata file. /// Represents a contract instance found a metadata file.
/// ///
+221 -352
View File
@@ -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; /// This represents a mode that has been parsed from test metadata.
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.
/// ///
/// [`Metadata`]: crate::metadata::Metadata /// Mode strings can take the following form (in pseudo-regex):
#[derive(Clone, Debug, PartialEq, Eq, Hash)] ///
pub enum Mode { /// ```text
/// A compilation mode that's been parsed from a String and into its contents. /// [YEILV][+-]? (M[0123sz])? <semver>?
Solidity(SolcMode), /// ```
/// An unknown compilation mode. ///
Unknown(String), /// 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 { impl FromStr for ParsedMode {
fn from(value: Mode) -> Self { type Err = anyhow::Error;
value.to_string() 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { let mut has_written = false;
Mode::Solidity(mode) => mode.fmt(f),
Mode::Unknown(string) => string.fmt(f),
}
}
}
impl Mode { if let Some(pipeline) = self.pipeline {
pub fn parse_from_string(str: impl AsRef<str>) -> Vec<Self> { pipeline.fmt(f)?;
let mut chars = str.as_ref().chars().peekable(); if let Some(optimize_flag) = self.optimize_flag {
f.write_str(if optimize_flag { "+" } else { "-" })?;
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())];
} }
}; has_written = true;
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")?;
} }
if *optimize { if let Some(optimize_setting) = self.optimize_setting {
write!(f, "+")?; if has_written {
} else { f.write_str(" ")?;
write!(f, "-")?; }
optimize_setting.fmt(f)?;
has_written = true;
} }
if let Some(req) = compiler_version_requirement { if let Some(version) = &self.version {
write!(f, " {req}")?; if has_written {
f.write_str(" ")?;
}
version.fmt(f)?;
} }
Ok(()) Ok(())
} }
} }
impl From<SolcMode> for String { impl From<ParsedMode> for String {
fn from(value: SolcMode) -> Self { fn from(parsed_mode: ParsedMode) -> Self {
value.to_string() 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)] #[cfg(test)]
mod test { mod tests {
use semver::Version;
use super::*; use super::*;
#[test] #[test]
fn mode_can_be_parsed_as_expected() { fn test_parsed_mode_from_str() {
// Arrange let strings = vec![
let fixtures = [ ("Mz", "Mz"),
( ("Y", "Y"),
"Y", ("Y+", "Y+"),
vec![ ("Y-", "Y-"),
Mode::Solidity(SolcMode { ("E", "E"),
via_ir: true, ("E+", "E+"),
optimize: true, ("E-", "E-"),
compiler_version_requirement: None, ("Y M0", "Y M0"),
}), ("Y M1", "Y M1"),
Mode::Solidity(SolcMode { ("Y M2", "Y M2"),
via_ir: true, ("Y M3", "Y M3"),
optimize: false, ("Y Ms", "Y Ms"),
compiler_version_requirement: None, ("Y Mz", "Y Mz"),
}), ("E M0", "E M0"),
], ("E M1", "E M1"),
), ("E M2", "E M2"),
( ("E M3", "E M3"),
"Y+", ("E Ms", "E Ms"),
vec![Mode::Solidity(SolcMode { ("E Mz", "E Mz"),
via_ir: true, // When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
optimize: true, ("Y 0.8.0", "Y ^0.8.0"),
compiler_version_requirement: None, ("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-", ("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
vec![Mode::Solidity(SolcMode { ("E- M2 0.7.0", "E- M2 ^0.7.0"),
via_ir: true, // We don't see this in the wild but it is parsed.
optimize: false, ("<=0.8", "<=0.8"),
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(),
},
)),
}),
],
),
]; ];
for (string, expectation) in fixtures { for (actual, expected) in strings {
// Act let parsed = ParsedMode::from_str(actual)
let actual = Mode::parse_from_string(string); .expect(format!("Failed to parse mode string '{actual}'").as_str());
// Assert
assert_eq!( assert_eq!(
actual, expectation, expected,
"Parsed {string} into {actual:?} but expected {expectation:?}" parsed.to_string(),
) "Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
);
} }
} }
#[test] #[test]
#[allow(clippy::uninlined_format_args)] fn test_parsed_mode_to_test_modes() {
fn mode_matches_as_expected() { let strings = vec![
// Arrange ("Mz", vec!["Y Mz", "E Mz"]),
let fixtures = [("Y+", "Y+", true), ("Y+ >=0.8.3", "Y+", true)]; ("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 { for (actual, expected) in strings {
let self_mode = Mode::parse_from_string(self_mode).pop().unwrap(); let parsed = ParsedMode::from_str(actual)
let other_mode = Mode::parse_from_string(other_mode).pop().unwrap(); .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!( assert_eq!(
actual, expected_result, expected_set, actual_set,
"Match of {} and {} failed. Expected {} but got {}", "Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
self_mode, other_mode, expected_result, actual
); );
} }
} }
+26 -25
View File
@@ -33,7 +33,7 @@ use alloy::{
}; };
use anyhow::Context; use anyhow::Context;
use revive_common::EVMVersion; use revive_common::EVMVersion;
use tracing::Instrument; use tracing::{Instrument, Level};
use revive_dt_common::{fs::clear_directory, futures::poll}; use revive_dt_common::{fs::clear_directory, futures::poll};
use revive_dt_config::Arguments; 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. /// 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. /// Prunes the child process and the base directory on drop.
#[derive(Debug)] #[derive(Debug)]
@@ -60,7 +60,6 @@ pub struct GethNode {
geth: PathBuf, geth: PathBuf,
id: u32, id: u32,
handle: Option<Child>, handle: Option<Child>,
network_id: u64,
start_timeout: u64, start_timeout: u64,
wallet: EthereumWallet, wallet: EthereumWallet,
nonce_manager: CachedNonceManager, nonce_manager: CachedNonceManager,
@@ -165,8 +164,6 @@ impl GethNode {
.arg(&self.data_directory) .arg(&self.data_directory)
.arg("--ipcpath") .arg("--ipcpath")
.arg(&self.connection_string) .arg(&self.connection_string)
.arg("--networkid")
.arg(self.network_id.to_string())
.arg("--nodiscover") .arg("--nodiscover")
.arg("--maxpeers") .arg("--maxpeers")
.arg("0") .arg("0")
@@ -213,6 +210,7 @@ impl GethNode {
let maximum_wait_time = Duration::from_millis(self.start_timeout); let maximum_wait_time = Duration::from_millis(self.start_timeout);
let mut stderr = BufReader::new(logs_file).lines(); let mut stderr = BufReader::new(logs_file).lines();
let mut lines = vec![];
loop { loop {
if let Some(Ok(line)) = stderr.next() { if let Some(Ok(line)) = stderr.next() {
if line.contains(Self::ERROR_MARKER) { if line.contains(Self::ERROR_MARKER) {
@@ -221,19 +219,24 @@ impl GethNode {
if line.contains(Self::READY_MARKER) { if line.contains(Self::READY_MARKER) {
return Ok(self); return Ok(self);
} }
lines.push(line);
} }
if Instant::now().duration_since(start_time) > maximum_wait_time { 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 { fn geth_stdout_log_file_path(&self) -> PathBuf {
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME) 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 { fn geth_stderr_log_file_path(&self) -> PathBuf {
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME) self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
} }
@@ -259,7 +262,7 @@ impl GethNode {
.disable_recommended_fillers() .disable_recommended_fillers()
.filler(FallbackGasFiller::new( .filler(FallbackGasFiller::new(
25_000_000, 25_000_000,
100_000_000_000, 1_000_000_000,
1_000_000_000, 1_000_000_000,
)) ))
.filler(ChainIdFiller::default()) .filler(ChainIdFiller::default())
@@ -268,7 +271,6 @@ impl GethNode {
.connect(&connection_string) .connect(&connection_string)
.await .await
.map_err(Into::into) .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 span = tracing::debug_span!("Submitting transaction", ?transaction);
let _guard = span.enter(); 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(); 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 // The following is a fix for the "transaction indexing is in progress" error that we
// to get. You can find more information on this in the following GH issue in geth // 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, // 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 // 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 // node's indexer. Just because the transaction has been confirmed it doesn't mean that
// has been indexed. When we call alloy's `get_receipt` it checks if the transaction was // it has been indexed. When we call alloy's `get_receipt` it checks if the transaction
// confirmed. If it has been, then it will call `eth_getTransactionReceipt` method which // was confirmed. If it has been, then it will call `eth_getTransactionReceipt` method
// _might_ return the above error if the tx has not yet been indexed yet. So, we need to // which _might_ return the above error if the tx has not yet been indexed yet. So, we
// implement a retry mechanism for the receipt to keep retrying to get it until it // need to implement a retry mechanism for the receipt to keep retrying to get it until
// eventually works, but we only do that if the error we get back is the "transaction // 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. // 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 // 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 // when a lot of transactions are being submitted to the node. Thus, while initially we
// allowed for 60 seconds of waiting with a 1 second delay in polling, we need to allow for // only allowed for 60 seconds of waiting with a 1 second delay in polling, we need to
// a larger wait time. Therefore, in here we allow for 5 minutes of waiting with exponential // allow for a larger wait time. Therefore, in here we allow for 5 minutes of waiting
// backoff each time we attempt to get the receipt and find that it's not available. // with exponential backoff each time we attempt to get the receipt and find that it's
// not available.
poll( poll(
Self::RECEIPT_POLLING_DURATION, Self::RECEIPT_POLLING_DURATION,
Default::default(), Default::default(),
@@ -327,7 +330,6 @@ impl EthereumNode for GethNode {
?transaction_hash ?transaction_hash
)) ))
.await .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)] #[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(), geth: config.geth.clone(),
id, id,
handle: None, handle: None,
network_id: config.network_id,
start_timeout: config.geth_start_timeout, start_timeout: config.geth_start_timeout,
wallet, wallet,
// We know that we only need to be storing 2 files so we can specify that when creating // We know that we only need to be storing 2 files so we can specify that when creating
+3 -3
View File
@@ -367,9 +367,9 @@ impl KitchensinkNode {
.disable_recommended_fillers() .disable_recommended_fillers()
.network::<KitchenSinkNetwork>() .network::<KitchenSinkNetwork>()
.filler(FallbackGasFiller::new( .filler(FallbackGasFiller::new(
30_000_000, 25_000_000,
200_000_000_000, 1_000_000_000,
3_000_000_000, 1_000_000_000,
)) ))
.filler(ChainIdFiller::default()) .filler(ChainIdFiller::default())
.filler(NonceFiller::new(nonce_manager)) .filler(NonceFiller::new(nonce_manager))
+1
View File
@@ -8,6 +8,7 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
revive-dt-common = { workspace = true }
revive-dt-config = { workspace = true } revive-dt-config = { workspace = true }
revive-dt-format = { workspace = true } revive-dt-format = { workspace = true }
revive-dt-compiler = { workspace = true } revive-dt-compiler = { workspace = true }
+4 -3
View File
@@ -12,11 +12,12 @@ use std::{
}; };
use anyhow::Context; use anyhow::Context;
use revive_dt_compiler::{CompilerInput, CompilerOutput};
use serde::Serialize; use serde::Serialize;
use revive_dt_common::types::Mode;
use revive_dt_compiler::{CompilerInput, CompilerOutput};
use revive_dt_config::{Arguments, TestingPlatform}; use revive_dt_config::{Arguments, TestingPlatform};
use revive_dt_format::{corpus::Corpus, mode::SolcMode}; use revive_dt_format::corpus::Corpus;
use crate::analyzer::CompilerStatistics; use crate::analyzer::CompilerStatistics;
@@ -48,7 +49,7 @@ pub struct CompilationTask {
/// The observed compiler output. /// The observed compiler output.
pub json_output: Option<CompilerOutput>, pub json_output: Option<CompilerOutput>,
/// The observed compiler mode. /// The observed compiler mode.
pub mode: SolcMode, pub mode: Mode,
/// The observed compiler version. /// The observed compiler version.
pub compiler_version: String, pub compiler_version: String,
/// The observed error, if any. /// The observed error, if any.