Compare commits

..

2 Commits

Author SHA1 Message Date
Omar Abdulla e77962ee66 Attempt to improve the geth tx indexing issue.
We're facing an issue where Geth transaction indexing can sometimes stall
on some of the nodes we're running. The logs show that for all transactions
we always need 1 second of waiting time. However, during certain runs we
sometimes run into an issue with some of the nodes where it seems like
their transaction indexer fails (either at the start or after some amount
of time) which leads us to never get the receipts back from these specific
nodes.

This is not a load issue as it appears like all of the other nodes handle
it just fine. However, it looks like once a node gets into this state it
can not get out of it and its bricked for the entire run.

This commit adds some more command line arguments to the geth command in
hopes of improving this issue.
2025-07-28 17:45:58 +03:00
Omar Abdulla f6aa7c9109 Allow for files to be specified in the corpus file 2025-07-28 17:45:50 +03:00
28 changed files with 462 additions and 1069 deletions
-16
View File
@@ -99,12 +99,9 @@ jobs:
- name: Install Geth on Ubuntu - name: Install Geth on Ubuntu
if: matrix.os == 'ubuntu-24.04' if: matrix.os == 'ubuntu-24.04'
run: | run: |
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update sudo apt-get update
sudo apt-get install -y protobuf-compiler sudo apt-get install -y protobuf-compiler
sudo apt-get install -y solc
# We were facing some issues in CI with the 1.16.* versions of geth, and specifically on # We were facing some issues in CI with the 1.16.* versions of geth, and specifically on
# Ubuntu. Eventually, we found out that the last version of geth that worked in our CI was # Ubuntu. Eventually, we found out that the last version of geth that worked in our CI was
# version 1.15.11. Thus, this is the version that we want to use in CI. The PPA sadly does # version 1.15.11. Thus, this is the version that we want to use in CI. The PPA sadly does
@@ -125,22 +122,12 @@ jobs:
wget -qO- "$URL" | sudo tar xz -C /usr/local/bin --strip-components=1 wget -qO- "$URL" | sudo tar xz -C /usr/local/bin --strip-components=1
geth --version geth --version
curl -sL https://github.com/paritytech/revive/releases/download/v0.3.0/resolc-x86_64-unknown-linux-musl -o resolc
chmod +x resolc
sudo mv resolc /usr/local/bin
- name: Install Geth on macOS - name: Install Geth on macOS
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
run: | run: |
brew tap ethereum/ethereum brew tap ethereum/ethereum
brew install ethereum protobuf brew install ethereum protobuf
brew install solidity
curl -sL https://github.com/paritytech/revive/releases/download/v0.3.0/resolc-universal-apple-darwin -o resolc
chmod +x resolc
sudo mv resolc /usr/local/bin
- name: Machete - name: Machete
uses: bnjbvr/cargo-machete@v0.7.1 uses: bnjbvr/cargo-machete@v0.7.1
@@ -156,8 +143,5 @@ jobs:
- name: Check eth-rpc version - name: Check eth-rpc version
run: eth-rpc --version run: eth-rpc --version
- name: Check resolc version
run: resolc --version
- name: Test cargo workspace - name: Test cargo workspace
run: make test run: make test
Generated
+3 -84
View File
@@ -339,7 +339,6 @@ dependencies = [
"const-hex", "const-hex",
"derive_more 2.0.1", "derive_more 2.0.1",
"foldhash", "foldhash",
"getrandom 0.3.3",
"hashbrown 0.15.3", "hashbrown 0.15.3",
"indexmap 2.10.0", "indexmap 2.10.0",
"itoa", "itoa",
@@ -2228,66 +2227,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "foundry-compilers-artifacts"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2676d70082ed23680fe2d08c0b750d5f7f2438c6d946f1cb140a76c5e5e0392"
dependencies = [
"foundry-compilers-artifacts-solc",
"foundry-compilers-artifacts-vyper",
]
[[package]]
name = "foundry-compilers-artifacts-solc"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3ada94dc5946334bb08df574855ba345ab03ba8c6f233560c72c8d61fa9db80"
dependencies = [
"alloy-json-abi",
"alloy-primitives",
"foundry-compilers-core",
"path-slash",
"regex",
"semver 1.0.26",
"serde",
"serde_json",
"thiserror 2.0.12",
"tracing",
"yansi",
]
[[package]]
name = "foundry-compilers-artifacts-vyper"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372052af72652e375a6e7eed22179bd8935114e25e1c5a8cca7f00e8f20bd94c"
dependencies = [
"alloy-json-abi",
"alloy-primitives",
"foundry-compilers-artifacts-solc",
"foundry-compilers-core",
"path-slash",
"semver 1.0.26",
"serde",
]
[[package]]
name = "foundry-compilers-core"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0962c46855979300f6526ed57f987ccf6a025c2b92ce574b281d9cb2ef666b"
dependencies = [
"alloy-primitives",
"cfg-if",
"dunce",
"path-slash",
"semver 1.0.26",
"serde",
"serde_json",
"thiserror 2.0.12",
]
[[package]] [[package]]
name = "fs-err" name = "fs-err"
version = "2.11.0" version = "2.11.0"
@@ -3517,12 +3456,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "path-slash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
[[package]] [[package]]
name = "pbkdf2" name = "pbkdf2"
version = "0.12.2" version = "0.12.2"
@@ -4022,7 +3955,6 @@ dependencies = [
"anyhow", "anyhow",
"futures", "futures",
"once_cell", "once_cell",
"semver 1.0.26",
"tokio", "tokio",
"tracing", "tracing",
] ]
@@ -4031,17 +3963,13 @@ dependencies = [
name = "revive-dt-compiler" name = "revive-dt-compiler"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"alloy",
"alloy-primitives", "alloy-primitives",
"anyhow", "anyhow",
"foundry-compilers-artifacts",
"revive-common", "revive-common",
"revive-dt-common",
"revive-dt-config", "revive-dt-config",
"revive-dt-solc-binaries", "revive-dt-solc-binaries",
"revive-solc-json-interface", "revive-solc-json-interface",
"semver 1.0.26", "semver 1.0.26",
"serde",
"serde_json", "serde_json",
"tracing", "tracing",
] ]
@@ -4073,7 +4001,8 @@ dependencies = [
"revive-dt-node", "revive-dt-node",
"revive-dt-node-interaction", "revive-dt-node-interaction",
"revive-dt-report", "revive-dt-report",
"semver 1.0.26", "revive-solc-json-interface",
"serde_json",
"temp-dir", "temp-dir",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -4126,9 +4055,9 @@ name = "revive-dt-report"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"revive-dt-compiler",
"revive-dt-config", "revive-dt-config",
"revive-dt-format", "revive-dt-format",
"revive-solc-json-interface",
"serde", "serde",
"serde_json", "serde_json",
"tracing", "tracing",
@@ -4141,7 +4070,6 @@ dependencies = [
"anyhow", "anyhow",
"hex", "hex",
"reqwest", "reqwest",
"revive-dt-common",
"semver 1.0.26", "semver 1.0.26",
"serde", "serde",
"sha2 0.10.9", "sha2 0.10.9",
@@ -4239,9 +4167,6 @@ name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
dependencies = [
"rand 0.8.5",
]
[[package]] [[package]]
name = "rustc-hex" name = "rustc-hex"
@@ -6292,12 +6217,6 @@ dependencies = [
"tap", "tap",
] ]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.0" version = "0.8.0"
-1
View File
@@ -26,7 +26,6 @@ alloy-primitives = "1.2.1"
alloy-sol-types = "1.2.1" alloy-sol-types = "1.2.1"
anyhow = "1.0" anyhow = "1.0"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
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"
reqwest = { version = "0.12.15", features = ["blocking", "json"] } reqwest = { version = "0.12.15", features = ["blocking", "json"] }
-1
View File
@@ -11,7 +11,6 @@ rust-version.workspace = true
[dependencies] [dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
semver = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
-1
View File
@@ -4,4 +4,3 @@
pub mod concepts; pub mod concepts;
pub mod iterators; pub mod iterators;
pub mod macros; pub mod macros;
pub mod types;
-3
View File
@@ -1,3 +0,0 @@
mod version_or_requirement;
pub use version_or_requirement::*;
@@ -1,41 +0,0 @@
use semver::{Version, VersionReq};
#[derive(Clone, Debug)]
pub enum VersionOrRequirement {
Version(Version),
Requirement(VersionReq),
}
impl From<Version> for VersionOrRequirement {
fn from(value: Version) -> Self {
Self::Version(value)
}
}
impl From<VersionReq> for VersionOrRequirement {
fn from(value: VersionReq) -> Self {
Self::Requirement(value)
}
}
impl TryFrom<VersionOrRequirement> for Version {
type Error = anyhow::Error;
fn try_from(value: VersionOrRequirement) -> Result<Self, Self::Error> {
let VersionOrRequirement::Version(version) = value else {
anyhow::bail!("Version or requirement was not a version");
};
Ok(version)
}
}
impl TryFrom<VersionOrRequirement> for VersionReq {
type Error = anyhow::Error;
fn try_from(value: VersionOrRequirement) -> Result<Self, Self::Error> {
let VersionOrRequirement::Requirement(requirement) = value else {
anyhow::bail!("Version or requirement was not a requirement");
};
Ok(requirement)
}
}
-4
View File
@@ -10,16 +10,12 @@ rust-version.workspace = true
[dependencies] [dependencies]
revive-solc-json-interface = { workspace = true } revive-solc-json-interface = { workspace = true }
revive-dt-common = { workspace = true }
revive-dt-config = { workspace = true } revive-dt-config = { workspace = true }
revive-dt-solc-binaries = { workspace = true } revive-dt-solc-binaries = { workspace = true }
revive-common = { workspace = true } revive-common = { workspace = true }
alloy = { workspace = true }
alloy-primitives = { workspace = true } alloy-primitives = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
foundry-compilers-artifacts = { workspace = true }
semver = { workspace = true } semver = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
+113 -75
View File
@@ -4,20 +4,21 @@
//! - Polkadot revive Wasm compiler //! - Polkadot revive Wasm compiler
use std::{ use std::{
collections::HashMap,
fs::read_to_string, fs::read_to_string,
hash::Hash, hash::Hash,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use alloy::json_abi::JsonAbi;
use alloy_primitives::Address; use alloy_primitives::Address;
use semver::Version; use revive_dt_config::Arguments;
use serde::{Deserialize, Serialize};
use revive_common::EVMVersion; use revive_common::EVMVersion;
use revive_dt_common::types::VersionOrRequirement; use revive_solc_json_interface::{
use revive_dt_config::Arguments; SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
SolcStandardJsonOutput,
};
use semver::Version;
pub mod revive_js; pub mod revive_js;
pub mod revive_resolc; pub mod revive_resolc;
@@ -31,44 +32,63 @@ pub trait SolidityCompiler {
/// The low-level compiler interface. /// The low-level compiler interface.
fn build( fn build(
&self, &self,
input: CompilerInput, input: CompilerInput<Self::Options>,
additional_options: Self::Options, ) -> anyhow::Result<CompilerOutput<Self::Options>>;
) -> anyhow::Result<CompilerOutput>;
fn new(solc_executable: PathBuf) -> Self; fn new(solc_executable: PathBuf) -> Self;
fn get_compiler_executable( fn get_compiler_executable(config: &Arguments, version: Version) -> anyhow::Result<PathBuf>;
config: &Arguments,
version: impl Into<VersionOrRequirement>,
) -> anyhow::Result<PathBuf>;
fn version(&self) -> anyhow::Result<Version>;
} }
/// The generic compilation input configuration. /// The generic compilation input configuration.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug)]
pub struct CompilerInput { pub struct CompilerInput<T: PartialEq + Eq + Hash> {
pub enable_optimization: Option<bool>, pub extra_options: T,
pub via_ir: Option<bool>, pub input: SolcStandardJsonInput,
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>,
pub sources: HashMap<PathBuf, String>,
pub libraries: HashMap<PathBuf, HashMap<String, Address>>,
} }
/// The generic compilation output configuration. /// The generic compilation output configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug)]
pub struct CompilerOutput { pub struct CompilerOutput<T: PartialEq + Eq + Hash> {
/// The compiled contracts. The bytecode of the contract is kept as a string incase linking is /// The solc standard JSON input.
/// required and the compiled source has placeholders. pub input: CompilerInput<T>,
pub contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>, /// The produced solc standard JSON output.
pub output: SolcStandardJsonOutput,
/// The error message in case the compiler returns abnormally.
pub error: Option<String>,
} }
/// A generic builder style interface for configuring the supported compiler options. impl<T> PartialEq for CompilerInput<T>
where
T: PartialEq + Eq + Hash,
{
fn eq(&self, other: &Self) -> bool {
let self_input = serde_json::to_vec(&self.input).unwrap_or_default();
let other_input = serde_json::to_vec(&self.input).unwrap_or_default();
self.extra_options.eq(&other.extra_options) && self_input == other_input
}
}
impl<T> Eq for CompilerInput<T> where T: PartialEq + Eq + Hash {}
impl<T> Hash for CompilerInput<T>
where
T: PartialEq + Eq + Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.extra_options.hash(state);
state.write(&serde_json::to_vec(&self.input).unwrap_or_default());
}
}
/// A generic builder style interface for configuring all compiler options.
pub struct Compiler<T: SolidityCompiler> { pub struct Compiler<T: SolidityCompiler> {
input: CompilerInput, input: SolcStandardJsonInput,
additional_options: T::Options, extra_options: T::Options,
allow_paths: Vec<PathBuf>,
base_path: Option<PathBuf>,
} }
impl Default for Compiler<solc::Solc> { impl Default for Compiler<solc::Solc> {
@@ -83,75 +103,93 @@ where
{ {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
input: CompilerInput { input: SolcStandardJsonInput {
enable_optimization: Default::default(), language: SolcStandardJsonInputLanguage::Solidity,
via_ir: Default::default(),
evm_version: Default::default(),
allow_paths: Default::default(),
base_path: Default::default(),
sources: Default::default(), sources: Default::default(),
libraries: Default::default(), settings: SolcStandardJsonInputSettings::new(
None,
Default::default(),
None,
SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new(
false,
None,
&Version::new(0, 0, 0),
false,
),
None,
None,
),
}, },
additional_options: T::Options::default(), extra_options: Default::default(),
allow_paths: Default::default(),
base_path: None,
} }
} }
pub fn with_optimization(mut self, value: impl Into<Option<bool>>) -> Self { pub fn solc_optimizer(mut self, enabled: bool) -> Self {
self.input.enable_optimization = value.into(); self.input.settings.optimizer.enabled = enabled;
self self
} }
pub fn with_via_ir(mut self, value: impl Into<Option<bool>>) -> Self { pub fn with_source(mut self, path: &Path) -> anyhow::Result<Self> {
self.input.via_ir = value.into();
self
}
pub fn with_evm_version(mut self, version: impl Into<Option<EVMVersion>>) -> Self {
self.input.evm_version = version.into();
self
}
pub fn with_allow_path(mut self, path: impl AsRef<Path>) -> Self {
self.input.allow_paths.push(path.as_ref().into());
self
}
pub fn with_base_path(mut self, path: impl Into<Option<PathBuf>>) -> Self {
self.input.base_path = path.into();
self
}
pub fn with_source(mut self, path: impl AsRef<Path>) -> anyhow::Result<Self> {
self.input self.input
.sources .sources
.insert(path.as_ref().to_path_buf(), read_to_string(path.as_ref())?); .insert(path.display().to_string(), read_to_string(path)?.into());
Ok(self) Ok(self)
} }
pub fn evm_version(mut self, evm_version: EVMVersion) -> Self {
self.input.settings.evm_version = Some(evm_version);
self
}
pub fn extra_options(mut self, extra_options: T::Options) -> Self {
self.extra_options = extra_options;
self
}
pub fn allow_path(mut self, path: PathBuf) -> Self {
self.allow_paths.push(path);
self
}
pub fn base_path(mut self, base_path: PathBuf) -> Self {
self.base_path = Some(base_path);
self
}
pub fn with_library( pub fn with_library(
mut self, mut self,
path: impl AsRef<Path>, scope: impl AsRef<Path>,
name: impl AsRef<str>, library_ident: impl AsRef<str>,
address: Address, library_address: Address,
) -> Self { ) -> Self {
self.input self.input
.settings
.libraries .libraries
.entry(path.as_ref().to_path_buf()) .get_or_insert_with(Default::default)
.entry(scope.as_ref().display().to_string())
.or_default() .or_default()
.insert(name.as_ref().into(), address); .insert(
library_ident.as_ref().to_owned(),
library_address.to_string(),
);
self self
} }
pub fn with_additional_options(mut self, options: impl Into<T::Options>) -> Self { pub fn try_build(self, solc_path: PathBuf) -> anyhow::Result<CompilerOutput<T::Options>> {
self.additional_options = options.into(); T::new(solc_path).build(CompilerInput {
self extra_options: self.extra_options,
input: self.input,
allow_paths: self.allow_paths,
base_path: self.base_path,
})
} }
pub fn try_build(self, compiler_path: impl AsRef<Path>) -> anyhow::Result<CompilerOutput> { /// Returns the compiler JSON input.
T::new(compiler_path.as_ref().to_path_buf()).build(self.input, self.additional_options) pub fn input(&self) -> SolcStandardJsonInput {
}
pub fn input(&self) -> CompilerInput {
self.input.clone() self.input.clone()
} }
} }
+79 -170
View File
@@ -6,19 +6,9 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use alloy::json_abi::JsonAbi;
use revive_dt_common::types::VersionOrRequirement;
use revive_dt_config::Arguments;
use revive_solc_json_interface::{
SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
SolcStandardJsonOutput,
};
use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
use revive_dt_config::Arguments;
use anyhow::Context; use revive_solc_json_interface::SolcStandardJsonOutput;
use semver::Version;
// TODO: I believe that we need to also pass the solc compiler to resolc so that resolc uses the // TODO: I believe that we need to also pass the solc compiler to resolc so that resolc uses the
// specified solc compiler. I believe that currently we completely ignore the specified solc binary // specified solc compiler. I believe that currently we completely ignore the specified solc binary
@@ -37,56 +27,8 @@ impl SolidityCompiler for Resolc {
#[tracing::instrument(level = "debug", ret)] #[tracing::instrument(level = "debug", ret)]
fn build( fn build(
&self, &self,
CompilerInput { input: CompilerInput<Self::Options>,
enable_optimization, ) -> anyhow::Result<CompilerOutput<Self::Options>> {
// Ignored and not honored since this is required for the resolc compilation.
via_ir: _via_ir,
evm_version,
allow_paths,
base_path,
sources,
libraries,
}: CompilerInput,
additional_options: Self::Options,
) -> anyhow::Result<CompilerOutput> {
let input = SolcStandardJsonInput {
language: SolcStandardJsonInputLanguage::Solidity,
sources: sources
.into_iter()
.map(|(path, source)| (path.display().to_string(), source.into()))
.collect(),
settings: SolcStandardJsonInputSettings {
evm_version,
libraries: Some(
libraries
.into_iter()
.map(|(source_code, libraries_map)| {
(
source_code.display().to_string(),
libraries_map
.into_iter()
.map(|(library_ident, library_address)| {
(library_ident, library_address.to_string())
})
.collect(),
)
})
.collect(),
),
remappings: None,
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
via_ir: Some(true),
optimizer: SolcStandardJsonInputSettingsOptimizer::new(
enable_optimization.unwrap_or(false),
None,
&Version::new(0, 0, 0),
false,
),
metadata: None,
polkavm: None,
},
};
let mut command = Command::new(&self.resolc_path); let mut command = Command::new(&self.resolc_path);
command command
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@@ -94,12 +36,13 @@ impl SolidityCompiler for Resolc {
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.arg("--standard-json"); .arg("--standard-json");
if let Some(ref base_path) = base_path { if let Some(ref base_path) = input.base_path {
command.arg("--base-path").arg(base_path); command.arg("--base-path").arg(base_path);
} }
if !allow_paths.is_empty() { if !input.allow_paths.is_empty() {
command.arg("--allow-paths").arg( command.arg("--allow-paths").arg(
allow_paths input
.allow_paths
.iter() .iter()
.map(|path| path.display().to_string()) .map(|path| path.display().to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -109,86 +52,93 @@ impl SolidityCompiler for Resolc {
let mut child = command.spawn()?; let mut child = command.spawn()?;
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped"); let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
serde_json::to_writer(stdin_pipe, &input)?; serde_json::to_writer(stdin_pipe, &input.input)?;
let json_in = serde_json::to_string_pretty(&input.input)?;
let output = child.wait_with_output()?; let output = child.wait_with_output()?;
let stdout = output.stdout; let stdout = output.stdout;
let stderr = output.stderr; let stderr = output.stderr;
if !output.status.success() { if !output.status.success() {
let json_in = serde_json::to_string_pretty(&input)?;
let message = String::from_utf8_lossy(&stderr); let message = String::from_utf8_lossy(&stderr);
tracing::error!( tracing::error!(
status = %output.status, "resolc failed exit={} stderr={} JSON-in={} ",
message = %message, output.status,
json_input = json_in, &message,
"Compilation using resolc failed" json_in,
); );
anyhow::bail!("Compilation failed with an error: {message}"); return Ok(CompilerOutput {
input,
output: Default::default(),
error: Some(message.into()),
});
} }
let parsed = serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| { let mut parsed =
anyhow::anyhow!( serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| {
"failed to parse resolc JSON output: {e}\nstderr: {}", anyhow::anyhow!(
String::from_utf8_lossy(&stderr) "failed to parse resolc JSON output: {e}\nstderr: {}",
) String::from_utf8_lossy(&stderr)
})?; )
})?;
// Detecting if the compiler output contained errors and reporting them through logs and
// errors instead of returning the compiler output that might contain errors.
for error in parsed.errors.iter().flatten() {
if error.severity == "error" {
tracing::error!(?error, ?input, "Encountered an error in the compilation");
anyhow::bail!("Encountered an error in the compilation: {error}")
}
}
// We need to do some post processing on the output to make it in the same format that solc
// outputs. More specifically, for each contract, the `.metadata` field should be replaced
// with the `.metadata.solc_metadata` field which contains the ABI and other information
// about the compiled contracts. We do this because we do not want any downstream logic to
// need to differentiate between which compiler is being used when extracting the ABI of the
// contracts.
if let Some(ref mut contracts) = parsed.contracts {
for (contract_path, contracts_map) in contracts.iter_mut() {
for (contract_name, contract_info) in contracts_map.iter_mut() {
let Some(metadata) = contract_info.metadata.take() else {
continue;
};
// Get the `solc_metadata` in the metadata of the contract.
let Some(solc_metadata) = metadata
.get("solc_metadata")
.and_then(|metadata| metadata.as_str())
else {
tracing::error!(
contract_path,
contract_name,
metadata = serde_json::to_string(&metadata).unwrap(),
"Encountered a contract compiled with resolc that has no solc_metadata"
);
anyhow::bail!(
"Contract {} compiled with resolc that has no solc_metadata",
contract_name
);
};
// Replace the original metadata with the new solc_metadata.
contract_info.metadata =
Some(serde_json::Value::String(solc_metadata.to_string()));
}
}
}
tracing::debug!( tracing::debug!(
output = %serde_json::to_string(&parsed).unwrap(), output = %serde_json::to_string(&parsed).unwrap(),
"Compiled successfully" "Compiled successfully"
); );
// Detecting if the compiler output contained errors and reporting them through logs and Ok(CompilerOutput {
// errors instead of returning the compiler output that might contain errors. input,
for error in parsed.errors.iter().flatten() { output: parsed,
if error.severity == "error" { error: None,
tracing::error!( })
?error,
?input,
output = %serde_json::to_string(&parsed).unwrap(),
"Encountered an error in the compilation"
);
anyhow::bail!("Encountered an error in the compilation: {error}")
}
}
let Some(contracts) = parsed.contracts else {
anyhow::bail!("Unexpected error - resolc output doesn't have a contracts section");
};
let mut compiler_output = CompilerOutput::default();
for (source_path, contracts) in contracts.into_iter() {
let source_path = PathBuf::from(source_path).canonicalize()?;
let map = compiler_output.contracts.entry(source_path).or_default();
for (contract_name, contract_information) in contracts.into_iter() {
let bytecode = contract_information
.evm
.and_then(|evm| evm.bytecode.clone())
.context("Unexpected - Contract compiled with resolc has no bytecode")?;
let abi = contract_information
.metadata
.as_ref()
.and_then(|metadata| metadata.as_object())
.and_then(|metadata| metadata.get("solc_metadata"))
.and_then(|solc_metadata| solc_metadata.as_str())
.and_then(|metadata| serde_json::from_str::<serde_json::Value>(metadata).ok())
.and_then(|metadata| {
metadata.get("output").and_then(|output| {
output
.get("abi")
.and_then(|abi| serde_json::from_value::<JsonAbi>(abi.clone()).ok())
})
})
.context(
"Unexpected - Failed to get the ABI for a contract compiled with resolc",
)?;
map.insert(contract_name, (bytecode.object, abi));
}
}
Ok(compiler_output)
} }
fn new(resolc_path: PathBuf) -> Self { fn new(resolc_path: PathBuf) -> Self {
@@ -197,7 +147,7 @@ impl SolidityCompiler for Resolc {
fn get_compiler_executable( fn get_compiler_executable(
config: &Arguments, config: &Arguments,
_version: impl Into<VersionOrRequirement>, _version: semver::Version,
) -> anyhow::Result<PathBuf> { ) -> anyhow::Result<PathBuf> {
if !config.resolc.as_os_str().is_empty() { if !config.resolc.as_os_str().is_empty() {
return Ok(config.resolc.clone()); return Ok(config.resolc.clone());
@@ -205,45 +155,4 @@ impl SolidityCompiler for Resolc {
Ok(PathBuf::from("resolc")) Ok(PathBuf::from("resolc"))
} }
fn version(&self) -> anyhow::Result<semver::Version> {
// 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 = 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 ")
.nth(1)
.context("Version parsing failed")?
.split("+")
.next()
.context("Version parsing failed")?;
Version::parse(version_string).map_err(Into::into)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn compiler_version_can_be_obtained() {
// Arrange
let args = Arguments::default();
let path = Resolc::get_compiler_executable(&args, Version::new(0, 7, 6)).unwrap();
let compiler = Resolc::new(path);
// Act
let version = compiler.version();
// Assert
let _ = version.expect("Failed to get version");
}
} }
+30 -162
View File
@@ -6,21 +6,10 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use revive_dt_common::types::VersionOrRequirement; use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
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 revive_solc_json_interface::SolcStandardJsonOutput;
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
use anyhow::Context;
use foundry_compilers_artifacts::{
output_selection::{
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection,
},
solc::CompilerOutput as SolcOutput,
solc::*,
};
use semver::Version;
#[derive(Debug)] #[derive(Debug)]
pub struct Solc { pub struct Solc {
@@ -33,63 +22,8 @@ impl SolidityCompiler for Solc {
#[tracing::instrument(level = "debug", ret)] #[tracing::instrument(level = "debug", ret)]
fn build( fn build(
&self, &self,
CompilerInput { input: CompilerInput<Self::Options>,
enable_optimization, ) -> anyhow::Result<CompilerOutput<Self::Options>> {
via_ir,
evm_version,
allow_paths,
base_path,
sources,
libraries,
}: CompilerInput,
_: Self::Options,
) -> anyhow::Result<CompilerOutput> {
let input = SolcInput {
language: SolcLanguage::Solidity,
sources: Sources(
sources
.into_iter()
.map(|(source_path, source_code)| (source_path, Source::new(source_code)))
.collect(),
),
settings: Settings {
optimizer: Optimizer {
enabled: enable_optimization,
details: Some(Default::default()),
..Default::default()
},
output_selection: OutputSelection::common_output_selection(
[
ContractOutputSelection::Abi,
ContractOutputSelection::Evm(EvmOutputSelection::ByteCode(
BytecodeOutputSelection::Object,
)),
]
.into_iter()
.map(|item| item.to_string()),
),
evm_version: evm_version.map(|version| version.to_string().parse().unwrap()),
via_ir,
libraries: Libraries {
libs: libraries
.into_iter()
.map(|(file_path, libraries)| {
(
file_path,
libraries
.into_iter()
.map(|(library_name, library_address)| {
(library_name, library_address.to_string())
})
.collect(),
)
})
.collect(),
},
..Default::default()
},
};
let mut command = Command::new(&self.solc_path); let mut command = Command::new(&self.solc_path);
command command
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@@ -97,12 +31,13 @@ impl SolidityCompiler for Solc {
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.arg("--standard-json"); .arg("--standard-json");
if let Some(ref base_path) = base_path { if let Some(ref base_path) = input.base_path {
command.arg("--base-path").arg(base_path); command.arg("--base-path").arg(base_path);
} }
if !allow_paths.is_empty() { if !input.allow_paths.is_empty() {
command.arg("--allow-paths").arg( command.arg("--allow-paths").arg(
allow_paths input
.allow_paths
.iter() .iter()
.map(|path| path.display().to_string()) .map(|path| path.display().to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -112,32 +47,31 @@ impl SolidityCompiler for Solc {
let mut child = command.spawn()?; let mut child = command.spawn()?;
let stdin = child.stdin.as_mut().expect("should be piped"); let stdin = child.stdin.as_mut().expect("should be piped");
serde_json::to_writer(stdin, &input)?; serde_json::to_writer(stdin, &input.input)?;
let output = child.wait_with_output()?; let output = child.wait_with_output()?;
if !output.status.success() { if !output.status.success() {
let json_in = serde_json::to_string_pretty(&input)?;
let message = String::from_utf8_lossy(&output.stderr); let message = String::from_utf8_lossy(&output.stderr);
tracing::error!( tracing::error!("solc failed exit={} stderr={}", output.status, &message);
status = %output.status, return Ok(CompilerOutput {
message = %message, input,
json_input = json_in, output: Default::default(),
"Compilation using solc failed" error: Some(message.into()),
); });
anyhow::bail!("Compilation failed with an error: {message}");
} }
let parsed = serde_json::from_slice::<SolcOutput>(&output.stdout).map_err(|e| { let parsed =
anyhow::anyhow!( serde_json::from_slice::<SolcStandardJsonOutput>(&output.stdout).map_err(|e| {
"failed to parse resolc JSON output: {e}\nstderr: {}", anyhow::anyhow!(
String::from_utf8_lossy(&output.stdout) "failed to parse resolc JSON output: {e}\nstderr: {}",
) String::from_utf8_lossy(&output.stdout)
})?; )
})?;
// Detecting if the compiler output contained errors and reporting them through logs and // Detecting if the compiler output contained errors and reporting them through logs and
// errors instead of returning the compiler output that might contain errors. // errors instead of returning the compiler output that might contain errors.
for error in parsed.errors.iter() { for error in parsed.errors.iter().flatten() {
if error.severity == Severity::Error { if error.severity == "error" {
tracing::error!(?error, ?input, "Encountered an error in the compilation"); tracing::error!(?error, ?input, "Encountered an error in the compilation");
anyhow::bail!("Encountered an error in the compilation: {error}") anyhow::bail!("Encountered an error in the compilation: {error}")
} }
@@ -148,29 +82,11 @@ impl SolidityCompiler for Solc {
"Compiled successfully" "Compiled successfully"
); );
let mut compiler_output = CompilerOutput::default(); Ok(CompilerOutput {
for (contract_path, contracts) in parsed.contracts { input,
let map = compiler_output output: parsed,
.contracts error: None,
.entry(contract_path.canonicalize()?) })
.or_default();
for (contract_name, contract_info) in contracts.into_iter() {
let source_code = contract_info
.evm
.and_then(|evm| evm.bytecode)
.map(|bytecode| match bytecode.object {
BytecodeObject::Bytecode(bytecode) => bytecode.to_string(),
BytecodeObject::Unlinked(unlinked) => unlinked,
})
.context("Unexpected - contract compiled with solc has no source code")?;
let abi = contract_info
.abi
.context("Unexpected - contract compiled with solc as no ABI")?;
map.insert(contract_name, (source_code, abi));
}
}
Ok(compiler_output)
} }
fn new(solc_path: PathBuf) -> Self { fn new(solc_path: PathBuf) -> Self {
@@ -179,57 +95,9 @@ impl SolidityCompiler for Solc {
fn get_compiler_executable( fn get_compiler_executable(
config: &Arguments, config: &Arguments,
version: impl Into<VersionOrRequirement>, version: semver::Version,
) -> anyhow::Result<PathBuf> { ) -> anyhow::Result<PathBuf> {
let path = download_solc(config.directory(), version, config.wasm)?; let path = download_solc(config.directory(), version, config.wasm)?;
Ok(path) Ok(path)
} }
fn version(&self) -> anyhow::Result<semver::Version> {
// The following is the parsing code for the version from the solc version strings which
// look like the following:
// ```
// solc, the solidity compiler commandline interface
// Version: 0.8.30+commit.73712a01.Darwin.appleclang
// ```
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
.split("Version: ")
.nth(1)
.context("Version parsing failed")?;
let version_string = version_line
.split("+")
.next()
.context("Version parsing failed")?;
Version::parse(version_string).map_err(Into::into)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn compiler_version_can_be_obtained() {
// Arrange
let args = Arguments::default();
let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6)).unwrap();
let compiler = Solc::new(path);
// Act
let version = compiler.version();
// Assert
assert_eq!(
version.expect("Failed to get version"),
Version::new(0, 7, 6)
)
}
} }
@@ -1,9 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.9;
contract Callable {
function f(uint[1] memory p1) public pure returns(uint) {
return p1[0];
}
}
@@ -1,13 +0,0 @@
// SPDX-License-Identifier: MIT
// Report https://linear.app/matterlabs/issue/CPR-269/call-with-calldata-variable-bug
pragma solidity >=0.6.9;
import "./callable.sol";
contract Main {
function main(uint[1] calldata p1, Callable callable) public returns(uint) {
return callable.f(p1);
}
}
@@ -1,21 +0,0 @@
{ "cases": [ {
"name": "first",
"inputs": [
{
"instance": "Main",
"method": "main",
"calldata": [
"1",
"Callable.address"
]
}
],
"expected": [
"1"
]
} ],
"contracts": {
"Main": "main.sol:Main",
"Callable": "callable.sol:Callable"
}
}
-81
View File
@@ -1,81 +0,0 @@
use std::path::PathBuf;
use revive_dt_compiler::{Compiler, SolidityCompiler, revive_resolc::Resolc, solc::Solc};
use revive_dt_config::Arguments;
use semver::Version;
#[test]
fn contracts_can_be_compiled_with_solc() {
// Arrange
let args = Arguments::default();
let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30)).unwrap();
// Act
let output = Compiler::<Solc>::new()
.with_source("./tests/assets/array_one_element/callable.sol")
.unwrap()
.with_source("./tests/assets/array_one_element/main.sol")
.unwrap()
.try_build(compiler_path);
// Assert
let output = output.expect("Failed to compile");
assert_eq!(output.contracts.len(), 2);
let main_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/main.sol")
.canonicalize()
.unwrap(),
)
.unwrap();
let callable_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
.canonicalize()
.unwrap(),
)
.unwrap();
assert!(main_file_contracts.contains_key("Main"));
assert!(callable_file_contracts.contains_key("Callable"));
}
#[test]
fn contracts_can_be_compiled_with_resolc() {
// Arrange
let args = Arguments::default();
let compiler_path = Resolc::get_compiler_executable(&args, Version::new(0, 8, 30)).unwrap();
// Act
let output = Compiler::<Resolc>::new()
.with_source("./tests/assets/array_one_element/callable.sol")
.unwrap()
.with_source("./tests/assets/array_one_element/main.sol")
.unwrap()
.try_build(compiler_path);
// Assert
let output = output.expect("Failed to compile");
assert_eq!(output.contracts.len(), 2);
let main_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/main.sol")
.canonicalize()
.unwrap(),
)
.unwrap();
let callable_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
.canonicalize()
.unwrap(),
)
.unwrap();
assert!(main_file_contracts.contains_key("Main"));
assert!(callable_file_contracts.contains_key("Callable"));
}
+2 -10
View File
@@ -3,7 +3,6 @@
use std::{ use std::{
fmt::Display, fmt::Display,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::LazyLock,
}; };
use alloy::{network::EthereumWallet, signers::local::PrivateKeySigner}; use alloy::{network::EthereumWallet, signers::local::PrivateKeySigner};
@@ -55,7 +54,7 @@ pub struct Arguments {
pub geth: PathBuf, pub geth: PathBuf,
/// The maximum time in milliseconds to wait for geth to start. /// The maximum time in milliseconds to wait for geth to start.
#[arg(long = "geth-start-timeout", default_value = "5000")] #[arg(long = "geth-start-timeout", default_value = "2000")]
pub geth_start_timeout: u64, pub geth_start_timeout: u64,
/// The test network chain ID. /// The test network chain ID.
@@ -145,14 +144,7 @@ impl Arguments {
impl Default for Arguments { impl Default for Arguments {
fn default() -> Self { fn default() -> Self {
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap()); Arguments::parse_from(["retester"])
let default = Arguments::parse_from(["retester"]);
Arguments {
temp_dir: Some(&TEMP_DIR),
..default
}
} }
} }
+2 -1
View File
@@ -28,5 +28,6 @@ indexmap = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
semver = { workspace = true } revive-solc-json-interface = { workspace = true }
serde_json = { workspace = true }
temp-dir = { workspace = true } temp-dir = { workspace = true }
+153 -178
View File
@@ -3,11 +3,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::PathBuf; use std::str::FromStr;
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use alloy::network::{Ethereum, TransactionBuilder}; use alloy::network::{Ethereum, TransactionBuilder};
use alloy::primitives::U256;
use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::{ use alloy::rpc::types::trace::geth::{
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace, CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
@@ -22,7 +21,7 @@ use alloy::{
}; };
use anyhow::Context; use anyhow::Context;
use indexmap::IndexMap; use indexmap::IndexMap;
use semver::Version; use serde_json::Value;
use revive_dt_common::iterators::FilesWithExtensionIterator; use revive_dt_common::iterators::FilesWithExtensionIterator;
use revive_dt_compiler::{Compiler, SolidityCompiler}; use revive_dt_compiler::{Compiler, SolidityCompiler};
@@ -34,6 +33,7 @@ use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
use revive_dt_node::Node; use revive_dt_node::Node;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::reporter::{CompilationTask, Report, Span}; use revive_dt_report::reporter::{CompilationTask, Report, Span};
use revive_solc_json_interface::SolcStandardJsonOutput;
use crate::Platform; use crate::Platform;
@@ -47,27 +47,23 @@ pub struct State<'a, T: Platform> {
/// The [`Span`] used in reporting. /// The [`Span`] used in reporting.
span: Span, span: Span,
/// A map of all of the compiled contracts for the given metadata file. /// A vector of all of the compiled contracts. Each call to [`build_contracts`] adds a new entry
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>, /// to this vector.
///
/// [`build_contracts`]: State::build_contracts
contracts: Vec<SolcStandardJsonOutput>,
/// This map stores the contracts deployments that have been made for each case within a /// This map stores the contracts deployments that have been made for each case within a
/// metadata file. Note, this means that the state can't be reused between different metadata /// metadata file. Note, this means that the state can't be reused between different metadata
/// files. /// files.
deployed_contracts: HashMap<CaseIdx, HashMap<ContractInstance, (Address, JsonAbi)>>, deployed_contracts: HashMap<CaseIdx, HashMap<ContractInstance, (Address, JsonAbi)>>,
/// This map stores the variables used for each one of the cases contained in the metadata
/// file.
variables: HashMap<CaseIdx, HashMap<String, U256>>,
/// This is a map of the deployed libraries. /// This is a map of the deployed libraries.
/// ///
/// This map is not per case, but rather, per metadata file. This means that we do not redeploy /// This map is not per case, but rather, per metadata file. This means that we do not redeploy
/// the libraries with each case. /// the libraries with each case.
deployed_libraries: HashMap<ContractInstance, (Address, JsonAbi)>, deployed_libraries: HashMap<ContractInstance, (Address, JsonAbi)>,
/// Stores the version of the compiler used for the given Solc mode.
compiler_version: HashMap<&'a SolcMode, Version>,
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
@@ -79,11 +75,9 @@ where
Self { Self {
config, config,
span, span,
compiled_contracts: Default::default(), contracts: Default::default(),
deployed_contracts: Default::default(), deployed_contracts: Default::default(),
variables: Default::default(),
deployed_libraries: Default::default(), deployed_libraries: Default::default(),
compiler_version: Default::default(),
phantom: Default::default(), phantom: Default::default(),
} }
} }
@@ -93,11 +87,7 @@ where
self.span self.span
} }
pub fn build_contracts( pub fn build_contracts(&mut self, mode: &SolcMode, metadata: &Metadata) -> anyhow::Result<()> {
&mut self,
mode: &'a SolcMode,
metadata: &Metadata,
) -> anyhow::Result<()> {
let mut span = self.span(); let mut span = self.span();
span.next_metadata( span.next_metadata(
metadata metadata
@@ -107,21 +97,34 @@ where
.clone(), .clone(),
); );
let compiler_version_or_requirement = let Some(version) = mode.last_patch_version(&self.config.solc) else {
mode.compiler_version_to_use(self.config.solc.clone()); anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version);
let compiler_path = };
T::Compiler::get_compiler_executable(self.config, compiler_version_or_requirement)?;
let compiler_version = T::Compiler::new(compiler_path.clone()).version()?;
self.compiler_version.insert(mode, compiler_version.clone());
tracing::info!(%compiler_version, "Resolved the compiler version to use"); // Note: if the metadata is contained within a solidity file then this is the only file that
// we wish to compile since this is a self-contained test. Otherwise, if it's a JSON file
// then we need to compile all of the contracts that are in the directory since imports are
// allowed in there.
let Some(ref metadata_file_path) = metadata.file_path else {
anyhow::bail!("The metadata file path is not defined");
};
let mut files_to_compile = if metadata_file_path
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("sol"))
{
Box::new(std::iter::once(metadata_file_path.clone())) as Box<dyn Iterator<Item = _>>
} else {
Box::new(
FilesWithExtensionIterator::new(metadata.directory()?)
.with_allowed_extension("sol"),
)
};
let compiler = Compiler::<T::Compiler>::new() let compiler = Compiler::<T::Compiler>::new()
.with_allow_path(metadata.directory()?) .allow_path(metadata.directory()?)
.with_optimization(mode.solc_optimize()); .solc_optimizer(mode.solc_optimize());
let mut compiler = metadata let mut compiler =
.files_to_compile()? files_to_compile.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
for (library_instance, (library_address, _)) in self.deployed_libraries.iter() { for (library_instance, (library_address, _)) in self.deployed_libraries.iter() {
let library_ident = &metadata let library_ident = &metadata
.contracts .contracts
@@ -146,27 +149,28 @@ where
json_input: compiler.input(), json_input: compiler.input(),
json_output: None, json_output: None,
mode: mode.clone(), mode: mode.clone(),
compiler_version: format!("{}", &compiler_version), compiler_version: format!("{}", &version),
error: None, error: None,
}; };
let compiler_path = T::Compiler::get_compiler_executable(self.config, version)?;
match compiler.try_build(compiler_path) { match compiler.try_build(compiler_path) {
Ok(output) => { Ok(output) => {
task.json_output = Some(output.clone()); task.json_output = Some(output.output.clone());
task.error = output.error;
self.contracts.push(output.output);
for (contract_path, contracts) in output.contracts.into_iter() { if let Some(last_output) = self.contracts.last() {
let map = self if let Some(contracts) = &last_output.contracts {
.compiled_contracts for (file, contracts_map) in contracts {
.entry(contract_path.clone()) for contract_name in contracts_map.keys() {
.or_default(); tracing::debug!(
for (contract_name, contract_info) in contracts.into_iter() { "Compiled contract: {contract_name} from file: {file}"
tracing::debug!( );
contract_path = %contract_path.display(), }
contract_name = contract_name, }
"Compiled contract" } else {
); tracing::warn!("Compiled contracts field is None");
map.insert(contract_name, contract_info);
} }
} }
@@ -184,7 +188,7 @@ where
pub fn build_and_publish_libraries( pub fn build_and_publish_libraries(
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
mode: &'a SolcMode, mode: &SolcMode,
node: &T::Blockchain, node: &T::Blockchain,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.build_contracts(mode, metadata)?; self.build_contracts(mode, metadata)?;
@@ -221,16 +225,7 @@ where
self.handle_contract_deployment(metadata, case_idx, input, node)?; self.handle_contract_deployment(metadata, case_idx, input, node)?;
let execution_receipt = let execution_receipt =
self.handle_input_execution(case_idx, input, deployment_receipts, node)?; self.handle_input_execution(case_idx, input, deployment_receipts, node)?;
let tracing_result = self.handle_input_call_frame_tracing(&execution_receipt, node)?; self.handle_input_expectations(case_idx, input, &execution_receipt, node, mode)?;
self.handle_input_variable_assignment(case_idx, input, &tracing_result)?;
self.handle_input_expectations(
case_idx,
input,
&execution_receipt,
node,
mode,
&tracing_result,
)?;
self.handle_input_diff(case_idx, execution_receipt, node) self.handle_input_diff(case_idx, execution_receipt, node)
} }
@@ -251,12 +246,7 @@ where
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new(); let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
for instance in input.find_all_contract_instances().into_iter() { for instance in input.find_all_contract_instances().into_iter() {
if !self if !self.deployed_contracts(case_idx).contains_key(&instance) {
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone())
.contains_key(&instance)
{
instances_we_must_deploy.entry(instance).or_insert(false); instances_we_must_deploy.entry(instance).or_insert(false);
} }
} }
@@ -308,13 +298,7 @@ where
.remove(&input.instance) .remove(&input.instance)
.context("Failed to find deployment receipt"), .context("Failed to find deployment receipt"),
Method::Fallback | Method::FunctionName(_) => { Method::Fallback | Method::FunctionName(_) => {
let tx = match input.legacy_transaction( let tx = match input.legacy_transaction(self.deployed_contracts(case_idx), node) {
self.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone()),
&*self.variables.entry(case_idx).or_default(),
node,
) {
Ok(tx) => { Ok(tx) => {
tracing::debug!("Legacy transaction data: {tx:#?}"); tracing::debug!("Legacy transaction data: {tx:#?}");
tx tx
@@ -342,56 +326,6 @@ where
} }
} }
fn handle_input_call_frame_tracing(
&self,
execution_receipt: &TransactionReceipt,
node: &T::Blockchain,
) -> anyhow::Result<CallFrame> {
node.trace_transaction(
execution_receipt,
GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
},
)
.map(|trace| {
trace
.try_into_call_frame()
.expect("Impossible - we requested a callframe trace so we must get it back")
})
}
fn handle_input_variable_assignment(
&mut self,
case_idx: CaseIdx,
input: &Input,
tracing_result: &CallFrame,
) -> anyhow::Result<()> {
let Some(ref assignments) = input.variable_assignments else {
return Ok(());
};
// Handling the return data variable assignments.
for (variable_name, output_word) in assignments.return_data.iter().zip(
tracing_result
.output
.as_ref()
.unwrap_or_default()
.to_vec()
.chunks(32),
) {
let value = U256::from_be_slice(output_word);
self.variables
.entry(case_idx)
.or_default()
.insert(variable_name.clone(), value);
}
Ok(())
}
fn handle_input_expectations( fn handle_input_expectations(
&mut self, &mut self,
case_idx: CaseIdx, case_idx: CaseIdx,
@@ -399,7 +333,6 @@ where
execution_receipt: &TransactionReceipt, execution_receipt: &TransactionReceipt,
node: &T::Blockchain, node: &T::Blockchain,
mode: &SolcMode, mode: &SolcMode,
tracing_result: &CallFrame,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let span = tracing::info_span!("Handling input expectations"); let span = tracing::info_span!("Handling input expectations");
let _guard = span.enter(); let _guard = span.enter();
@@ -432,13 +365,29 @@ where
} }
} }
// Note: we need to do assertions and checks on the output of the last call and this isn't
// available in the receipt. The only way to get this information is through tracing on the
// node.
let tracing_result = node
.trace_transaction(
execution_receipt,
GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
},
)?
.try_into_call_frame()
.expect("Impossible - we requested a callframe trace so we must get it back");
for expectation in expectations.iter() { for expectation in expectations.iter() {
self.handle_input_expectation_item( self.handle_input_expectation_item(
case_idx, case_idx,
execution_receipt, execution_receipt,
node, node,
expectation, expectation,
tracing_result, &tracing_result,
mode, mode,
)?; )?;
} }
@@ -456,33 +405,22 @@ where
mode: &SolcMode, mode: &SolcMode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if let Some(ref version_requirement) = expectation.compiler_version { if let Some(ref version_requirement) = expectation.compiler_version {
let compiler_version = self let Some(compiler_version) = mode.last_patch_version(&self.config.solc) else {
.compiler_version anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version);
.get(mode) };
.context("Failed to find the compiler version fo the solc mode")?; if !version_requirement.matches(&compiler_version) {
if !version_requirement.matches(compiler_version) {
return Ok(()); return Ok(());
} }
} }
let deployed_contracts = self let deployed_contracts = self.deployed_contracts(case_idx);
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone());
let variables = self.variables.entry(case_idx).or_default();
let chain_state_provider = node; let chain_state_provider = node;
// Handling the receipt state assertion. // Handling the receipt state assertion.
let expected = !expectation.exception; let expected = !expectation.exception;
let actual = execution_receipt.status(); let actual = execution_receipt.status();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(expected, actual, "Transaction status assertion failed",);
expected,
actual,
?execution_receipt,
?tracing_result,
"Transaction status assertion failed"
);
anyhow::bail!( anyhow::bail!(
"Transaction status assertion failed - Expected {expected} but got {actual}", "Transaction status assertion failed - Expected {expected} but got {actual}",
); );
@@ -492,12 +430,7 @@ where
if let Some(ref expected_calldata) = expectation.return_data { if let Some(ref expected_calldata) = expectation.return_data {
let expected = expected_calldata; let expected = expected_calldata;
let actual = &tracing_result.output.as_ref().unwrap_or_default(); let actual = &tracing_result.output.as_ref().unwrap_or_default();
if !expected.is_equivalent( if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
actual,
deployed_contracts,
&*variables,
chain_state_provider,
)? {
tracing::error!( tracing::error!(
?execution_receipt, ?execution_receipt,
?expected, ?expected,
@@ -526,12 +459,17 @@ where
{ {
// Handling the emitter assertion. // Handling the emitter assertion.
if let Some(ref expected_address) = expected_event.address { if let Some(ref expected_address) = expected_event.address {
let expected = Address::from_slice( let expected = if let Some(contract_instance) = expected_address
Calldata::new_compound([expected_address]) .strip_suffix(".address")
.calldata(deployed_contracts, &*variables, node)? .map(ContractInstance::new)
.get(12..32) {
.expect("Can't fail"), deployed_contracts
); .get(&contract_instance)
.map(|(address, _)| *address)
} else {
Address::from_str(expected_address).ok()
}
.context("Failed to get the address of the event")?;
let actual = actual_event.address(); let actual = actual_event.address();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(
@@ -556,7 +494,6 @@ where
if !expected.is_equivalent( if !expected.is_equivalent(
&actual.0, &actual.0,
deployed_contracts, deployed_contracts,
&*variables,
chain_state_provider, chain_state_provider,
)? { )? {
tracing::error!( tracing::error!(
@@ -574,12 +511,7 @@ where
// Handling the values assertion. // Handling the values assertion.
let expected = &expected_event.values; let expected = &expected_event.values;
let actual = &actual_event.data().data; let actual = &actual_event.data().data;
if !expected.is_equivalent( if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
&actual.0,
deployed_contracts,
&*variables,
chain_state_provider,
)? {
tracing::error!( tracing::error!(
?execution_receipt, ?execution_receipt,
?expected, ?expected,
@@ -617,6 +549,19 @@ where
Ok((execution_receipt, trace, diff)) Ok((execution_receipt, trace, diff))
} }
fn deployed_contracts(
&mut self,
case_idx: impl Into<Option<CaseIdx>>,
) -> &mut HashMap<ContractInstance, (Address, JsonAbi)> {
match case_idx.into() {
Some(case_idx) => self
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone()),
None => &mut self.deployed_libraries,
}
}
/// Gets the information of a deployed contract or library from the state. If it's found to not /// Gets the information of a deployed contract or library from the state. If it's found to not
/// be deployed then it will be deployed. /// be deployed then it will be deployed.
/// ///
@@ -635,17 +580,18 @@ where
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> { ) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
let case_idx = case_idx.into(); let case_idx = case_idx.into();
let deployed_contracts = match case_idx { if let Some((address, abi)) = self.deployed_libraries.get(contract_instance) {
Some(case_idx) => self
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone()),
None => &mut self.deployed_libraries,
};
if let Some((address, abi)) = deployed_contracts.get(contract_instance) {
return Ok((*address, abi.clone(), None)); return Ok((*address, abi.clone(), None));
} }
if let Some(case_idx) = case_idx {
if let Some((address, abi)) = self
.deployed_contracts
.get(&case_idx)
.and_then(|contracts| contracts.get(contract_instance))
{
return Ok((*address, abi.clone(), None));
}
}
let Some(ContractPathAndIdent { let Some(ContractPathAndIdent {
contract_source_path, contract_source_path,
@@ -659,24 +605,30 @@ where
) )
}; };
let Some((code, abi)) = self let compiled_contract = self.contracts.iter().rev().find_map(|output| {
.compiled_contracts output
.get(&contract_source_path) .contracts
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref())) .as_ref()?
.cloned() .get(&contract_source_path.display().to_string())
.and_then(|source_file_contracts| {
source_file_contracts.get(contract_ident.as_ref())
})
});
let Some(code) = compiled_contract
.and_then(|contract| contract.evm.as_ref().and_then(|evm| evm.bytecode.as_ref()))
else { else {
tracing::error!( tracing::error!(
contract_source_path = contract_source_path.display().to_string(), contract_source_path = contract_source_path.display().to_string(),
contract_ident = contract_ident.as_ref(), contract_ident = contract_ident.as_ref(),
"Failed to find information for contract" "Failed to find bytecode for contract"
); );
anyhow::bail!( anyhow::bail!(
"Failed to find information for contract {:?}", "Failed to find bytecode for contract {:?}",
contract_instance contract_instance
) )
}; };
let mut code = match alloy::hex::decode(&code) { let mut code = match alloy::hex::decode(&code.object) {
Ok(code) => code, Ok(code) => code,
Err(error) => { Err(error) => {
tracing::error!( tracing::error!(
@@ -689,8 +641,30 @@ where
} }
}; };
let Some(Value::String(metadata)) =
compiled_contract.and_then(|contract| contract.metadata.as_ref())
else {
tracing::error!("Contract does not have a metadata field");
anyhow::bail!("Contract does not have a metadata field");
};
let Ok(metadata) = serde_json::from_str::<Value>(metadata) else {
tracing::error!(%metadata, "Failed to parse solc metadata into a structured value");
anyhow::bail!("Failed to parse solc metadata into a structured value {metadata}");
};
let Some(abi) = metadata.get("output").and_then(|value| value.get("abi")) else {
tracing::error!(%metadata, "Failed to access the .output.abi field of the solc metadata");
anyhow::bail!("Failed to access the .output.abi field of the solc metadata {metadata}");
};
let Ok(abi) = serde_json::from_value::<JsonAbi>(abi.clone()) else {
tracing::error!(%metadata, "Failed to deserialize ABI into a structured format");
anyhow::bail!("Failed to deserialize ABI into a structured format {metadata}");
};
if let Some(calldata) = calldata { if let Some(calldata) = calldata {
let calldata = calldata.calldata(deployed_contracts, None, node)?; let calldata = calldata.calldata(self.deployed_contracts(case_idx), node)?;
code.extend(calldata); code.extend(calldata);
} }
@@ -725,7 +699,8 @@ where
"Deployed contract" "Deployed contract"
); );
deployed_contracts.insert(contract_instance.clone(), (address, abi.clone())); self.deployed_contracts(case_idx)
.insert(contract_instance.clone(), (address, abi.clone()));
Ok((address, abi, Some(receipt))) Ok((address, abi, Some(receipt)))
} }
+1 -19
View File
@@ -17,25 +17,7 @@ impl Corpus {
/// Try to read and parse the corpus definition file at given `path`. /// Try to read and parse the corpus definition file at given `path`.
pub fn try_from_path(path: &Path) -> anyhow::Result<Self> { pub fn try_from_path(path: &Path) -> anyhow::Result<Self> {
let file = File::open(path)?; let file = File::open(path)?;
let mut corpus: Corpus = serde_json::from_reader(file)?; Ok(serde_json::from_reader(file)?)
// 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);
Ok(corpus)
} }
/// Scan the corpus base directory and return all tests found. /// Scan the corpus base directory and return all tests found.
+28 -88
View File
@@ -2,7 +2,6 @@ use std::collections::HashMap;
use alloy::{ use alloy::{
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
hex::ToHexExt,
json_abi::JsonAbi, json_abi::JsonAbi,
network::TransactionBuilder, network::TransactionBuilder,
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
@@ -31,7 +30,6 @@ pub struct Input {
pub expected: Option<Expected>, pub expected: Option<Expected>,
pub value: Option<EtherValue>, pub value: Option<EtherValue>,
pub storage: Option<HashMap<String, Calldata>>, pub storage: Option<HashMap<String, Calldata>>,
pub variable_assignments: Option<VariableAssignments>,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
@@ -138,8 +136,6 @@ enum Operation {
BitwiseAnd, BitwiseAnd,
BitwiseOr, BitwiseOr,
BitwiseXor, BitwiseXor,
ShiftLeft,
ShiftRight,
} }
/// Specify how the contract is called. /// Specify how the contract is called.
@@ -168,14 +164,6 @@ define_wrapper_type!(
pub struct EtherValue(U256); pub struct EtherValue(U256);
); );
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct VariableAssignments {
/// A vector of the variable names to assign to the return data.
///
/// Example: `UniswapV3PoolAddress`
pub return_data: Vec<String>,
}
impl Input { impl Input {
pub const fn default_caller() -> Address { pub const fn default_caller() -> Address {
Address(FixedBytes(alloy::hex!( Address(FixedBytes(alloy::hex!(
@@ -198,17 +186,16 @@ impl Input {
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed")) .ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
} }
pub fn encoded_input<'a>( pub fn encoded_input(
&'a self, &self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
match self.method { match self.method {
Method::Deployer | Method::Fallback => { Method::Deployer | Method::Fallback => {
let calldata = let calldata = self
self.calldata .calldata
.calldata(deployed_contracts, variables, chain_state_provider)?; .calldata(deployed_contracts, chain_state_provider)?;
Ok(calldata.into()) Ok(calldata.into())
} }
@@ -257,7 +244,6 @@ impl Input {
self.calldata.calldata_into_slice( self.calldata.calldata_into_slice(
&mut calldata, &mut calldata,
deployed_contracts, deployed_contracts,
variables,
chain_state_provider, chain_state_provider,
)?; )?;
@@ -267,13 +253,12 @@ impl Input {
} }
/// Parse this input into a legacy transaction. /// Parse this input into a legacy transaction.
pub fn legacy_transaction<'a>( pub fn legacy_transaction(
&'a self, &self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = self.encoded_input(deployed_contracts, variables, chain_state_provider)?; let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default().from(self.caller).value( let transaction_request = TransactionRequest::default().from(self.caller).value(
self.value self.value
.map(|value| value.into_inner()) .map(|value| value.into_inner())
@@ -351,27 +336,20 @@ impl Calldata {
} }
} }
pub fn calldata<'a>( pub fn calldata(
&'a self, &self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement()); let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice( self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?;
&mut buffer,
deployed_contracts,
variables,
chain_state_provider,
)?;
Ok(buffer) Ok(buffer)
} }
pub fn calldata_into_slice<'a>( pub fn calldata_into_slice(
&'a self, &self,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
@@ -380,7 +358,7 @@ impl Calldata {
} }
Calldata::Compound(items) => { Calldata::Compound(items) => {
for (arg_idx, arg) in items.iter().enumerate() { for (arg_idx, arg) in items.iter().enumerate() {
match arg.resolve(deployed_contracts, variables.clone(), chain_state_provider) { match arg.resolve(deployed_contracts, chain_state_provider) {
Ok(resolved) => { Ok(resolved) => {
buffer.extend(resolved.to_be_bytes::<32>()); buffer.extend(resolved.to_be_bytes::<32>());
} }
@@ -403,11 +381,10 @@ impl Calldata {
} }
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes. /// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
pub fn is_equivalent<'a>( pub fn is_equivalent(
&'a self, &self,
other: &[u8], other: &[u8],
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
match self { match self {
@@ -430,8 +407,7 @@ impl Calldata {
std::borrow::Cow::Borrowed(other) std::borrow::Cow::Borrowed(other)
}; };
let this = let this = this.resolve(deployed_contracts, chain_state_provider)?;
this.resolve(deployed_contracts, variables.clone(), chain_state_provider)?;
let other = U256::from_be_slice(&other); let other = U256::from_be_slice(&other);
if this != other { if this != other {
return Ok(false); return Ok(false);
@@ -444,17 +420,16 @@ impl Calldata {
} }
impl CalldataItem { impl CalldataItem {
fn resolve<'a>( fn resolve(
&'a self, &self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
let mut stack = Vec::<CalldataToken<U256>>::new(); let mut stack = Vec::<CalldataToken<U256>>::new();
for token in self for token in self
.calldata_tokens() .calldata_tokens()
.map(|token| token.resolve(deployed_contracts, variables.clone(), chain_state_provider)) .map(|token| token.resolve(deployed_contracts, chain_state_provider))
{ {
let token = token?; let token = token?;
let new_token = match token { let new_token = match token {
@@ -477,14 +452,8 @@ impl CalldataItem {
Operation::BitwiseAnd => Some(left_operand & right_operand), Operation::BitwiseAnd => Some(left_operand & right_operand),
Operation::BitwiseOr => Some(left_operand | right_operand), Operation::BitwiseOr => Some(left_operand | right_operand),
Operation::BitwiseXor => Some(left_operand ^ right_operand), Operation::BitwiseXor => Some(left_operand ^ right_operand),
Operation::ShiftLeft => {
Some(left_operand << usize::try_from(right_operand)?)
}
Operation::ShiftRight => {
Some(left_operand >> usize::try_from(right_operand)?)
}
} }
.context("Invalid calldata arithmetic operation - Invalid operation")?; .context("Invalid calldata arithmetic operation")?;
CalldataToken::Item(result) CalldataToken::Item(result)
} }
@@ -495,17 +464,8 @@ impl CalldataItem {
match stack.as_slice() { match stack.as_slice() {
// Empty stack means that we got an empty compound calldata which we resolve to zero. // Empty stack means that we got an empty compound calldata which we resolve to zero.
[] => Ok(U256::ZERO), [] => Ok(U256::ZERO),
[CalldataToken::Item(item)] => { [CalldataToken::Item(item)] => Ok(*item),
tracing::debug!( _ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation")),
original = self.0,
resolved = item.to_be_bytes::<32>().encode_hex(),
"Resolved a Calldata item"
);
Ok(*item)
}
_ => Err(anyhow::anyhow!(
"Invalid calldata arithmetic operation - Invalid stack"
)),
} }
} }
@@ -518,8 +478,6 @@ impl CalldataItem {
"&" => CalldataToken::Operation(Operation::BitwiseAnd), "&" => CalldataToken::Operation(Operation::BitwiseAnd),
"|" => CalldataToken::Operation(Operation::BitwiseOr), "|" => CalldataToken::Operation(Operation::BitwiseOr),
"^" => CalldataToken::Operation(Operation::BitwiseXor), "^" => CalldataToken::Operation(Operation::BitwiseXor),
"<<" => CalldataToken::Operation(Operation::ShiftLeft),
">>" => CalldataToken::Operation(Operation::ShiftRight),
_ => CalldataToken::Item(item), _ => CalldataToken::Item(item),
}) })
} }
@@ -536,7 +494,6 @@ impl<T> CalldataToken<T> {
const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH"; const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH";
const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER"; const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER";
const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP"; const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP";
const VARIABLE_PREFIX: &str = "$VARIABLE:";
fn into_item(self) -> Option<T> { fn into_item(self) -> Option<T> {
match self { match self {
@@ -555,10 +512,9 @@ impl<T: AsRef<str>> CalldataToken<T> {
/// This piece of code is taken from the matter-labs-tester repository which is licensed under /// This piece of code is taken from the matter-labs-tester repository which is licensed under
/// MIT or Apache. The original source code can be found here: /// MIT or Apache. The original source code can be found here:
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 /// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
fn resolve<'a>( fn resolve(
self, self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<CalldataToken<U256>> { ) -> anyhow::Result<CalldataToken<U256>> {
match self { match self {
@@ -623,16 +579,6 @@ impl<T: AsRef<str>> CalldataToken<T> {
let timestamp = let timestamp =
chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?; chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?;
Ok(U256::from(timestamp)) Ok(U256::from(timestamp))
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
let Some(variables) = variables.into() else {
anyhow::bail!(
"Variable resolution required but no variables were passed in"
);
};
let Some(variable) = variables.get(variable_name) else {
anyhow::bail!("No variable found with the name {}", variable_name)
};
Ok(*variable)
} else { } else {
Ok(U256::from_str_radix(item, 10) Ok(U256::from_str_radix(item, 10)
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?) .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?)
@@ -753,9 +699,7 @@ mod tests {
(Address::ZERO, parsed_abi), (Address::ZERO, parsed_abi),
); );
let encoded = input let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
.encoded_input(&contracts, None, &MockResolver)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (u64,); type T = (u64,);
@@ -797,9 +741,7 @@ mod tests {
(Address::ZERO, parsed_abi), (Address::ZERO, parsed_abi),
); );
let encoded = input let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
.encoded_input(&contracts, None, &MockResolver)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,); type T = (alloy_primitives::Address,);
@@ -844,9 +786,7 @@ mod tests {
(Address::ZERO, parsed_abi), (Address::ZERO, parsed_abi),
); );
let encoded = input let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
.encoded_input(&contracts, None, &MockResolver)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,); type T = (alloy_primitives::Address,);
@@ -862,7 +802,7 @@ mod tests {
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
CalldataItem::new(input).resolve(deployed_contracts, None, chain_state_provider) CalldataItem::new(input).resolve(deployed_contracts, chain_state_provider)
} }
#[test] #[test]
+1 -24
View File
@@ -9,7 +9,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revive_dt_common::{iterators::FilesWithExtensionIterator, macros::define_wrapper_type}; use revive_dt_common::macros::define_wrapper_type;
use crate::{ use crate::{
case::Case, case::Case,
@@ -212,29 +212,6 @@ impl Metadata {
} }
} }
} }
/// Returns an iterator over all of the solidity files that needs to be compiled for this
/// [`Metadata`] object
///
/// Note: if the metadata is contained within a solidity file then this is the only file that
/// we wish to compile since this is a self-contained test. Otherwise, if it's a JSON file
/// then we need to compile all of the contracts that are in the directory since imports are
/// allowed in there.
pub fn files_to_compile(&self) -> anyhow::Result<Box<dyn Iterator<Item = PathBuf>>> {
let Some(ref metadata_file_path) = self.file_path else {
anyhow::bail!("The metadata file path is not defined");
};
if metadata_file_path
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("sol"))
{
Ok(Box::new(std::iter::once(metadata_file_path.clone())))
} else {
Ok(Box::new(
FilesWithExtensionIterator::new(self.directory()?).with_allowed_extension("sol"),
))
}
}
} }
define_wrapper_type!( define_wrapper_type!(
-10
View File
@@ -1,4 +1,3 @@
use revive_dt_common::types::VersionOrRequirement;
use semver::Version; use semver::Version;
use serde::de::Deserializer; use serde::de::Deserializer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -79,15 +78,6 @@ impl SolcMode {
None None
} }
/// 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.solc_version {
Some(ref requirement) => requirement.clone().into(),
None => default.into(),
}
}
} }
impl<'de> Deserialize<'de> for Mode { impl<'de> Deserialize<'de> for Mode {
+1 -1
View File
@@ -10,9 +10,9 @@ rust-version.workspace = true
[dependencies] [dependencies]
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 }
anyhow = { workspace = true } anyhow = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
revive-solc-json-interface = { workspace = true }
+21 -8
View File
@@ -1,6 +1,5 @@
//! The report analyzer enriches the raw report data. //! The report analyzer enriches the raw report data.
use revive_dt_compiler::CompilerOutput;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::reporter::CompilationTask; use crate::reporter::CompilationTask;
@@ -14,27 +13,41 @@ pub struct CompilerStatistics {
pub mean_code_size: usize, pub mean_code_size: usize,
/// The mean size of the optimized YUL IR. /// The mean size of the optimized YUL IR.
pub mean_yul_size: usize, pub mean_yul_size: usize,
/// Is a proxy because the YUL also contains a lot of comments. /// Is a proxy because the YUL also containes a lot of comments.
pub yul_to_bytecode_size_ratio: f32, pub yul_to_bytecode_size_ratio: f32,
} }
impl CompilerStatistics { impl CompilerStatistics {
/// Cumulatively update the statistics with the next compiler task. /// Cumulatively update the statistics with the next compiler task.
pub fn sample(&mut self, compilation_task: &CompilationTask) { pub fn sample(&mut self, compilation_task: &CompilationTask) {
let Some(CompilerOutput { contracts }) = &compilation_task.json_output else { let Some(output) = &compilation_task.json_output else {
return;
};
let Some(contracts) = &output.contracts else {
return; return;
}; };
for (_solidity, contracts) in contracts.iter() { for (_solidity, contracts) in contracts.iter() {
for (_name, (bytecode, _)) in contracts.iter() { for (_name, contract) in contracts.iter() {
let Some(evm) = &contract.evm else {
continue;
};
let Some(deploy_code) = &evm.deployed_bytecode else {
continue;
};
// The EVM bytecode can be unlinked and thus is not necessarily a decodable hex // The EVM bytecode can be unlinked and thus is not necessarily a decodable hex
// string; for our statistics this is a good enough approximation. // string; for our statistics this is a good enough approximation.
let bytecode_size = bytecode.len() / 2; let bytecode_size = deploy_code.object.len() / 2;
// TODO: for the time being we set the yul_size to be zero. We need to change this let yul_size = contract
// when we overhaul the reporting. .ir_optimized
.as_ref()
.expect("if the contract has a deploy code it should also have the opimized IR")
.len();
self.update_sizes(bytecode_size, 0); self.update_sizes(bytecode_size, yul_size);
} }
} }
} }
+12 -4
View File
@@ -12,11 +12,11 @@ use std::{
}; };
use anyhow::Context; use anyhow::Context;
use revive_dt_compiler::{CompilerInput, CompilerOutput};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
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, mode::SolcMode};
use revive_solc_json_interface::{SolcStandardJsonInput, SolcStandardJsonOutput};
use crate::analyzer::CompilerStatistics; use crate::analyzer::CompilerStatistics;
@@ -44,9 +44,9 @@ pub struct Report {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompilationTask { pub struct CompilationTask {
/// The observed compiler input. /// The observed compiler input.
pub json_input: CompilerInput, pub json_input: SolcStandardJsonInput,
/// The observed compiler output. /// The observed compiler output.
pub json_output: Option<CompilerOutput>, pub json_output: Option<SolcStandardJsonOutput>,
/// The observed compiler mode. /// The observed compiler mode.
pub mode: SolcMode, pub mode: SolcMode,
/// The observed compiler version. /// The observed compiler version.
@@ -152,7 +152,15 @@ impl Report {
for (platform, results) in self.compiler_results.iter() { for (platform, results) in self.compiler_results.iter() {
for result in results { for result in results {
// ignore if there were no errors // ignore if there were no errors
if result.compilation_task.error.is_none() { if result.compilation_task.error.is_none()
&& result
.compilation_task
.json_output
.as_ref()
.and_then(|output| output.errors.as_ref())
.map(|errors| errors.is_empty())
.unwrap_or(true)
{
continue; continue;
} }
-2
View File
@@ -9,8 +9,6 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
revive-dt-common = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
+13 -38
View File
@@ -5,8 +5,6 @@ use std::{
sync::{LazyLock, Mutex}, sync::{LazyLock, Mutex},
}; };
use revive_dt_common::types::VersionOrRequirement;
use semver::Version; use semver::Version;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@@ -54,50 +52,27 @@ impl GHDownloader {
pub const WINDOWS_NAME: &str = "solc-windows.exe"; pub const WINDOWS_NAME: &str = "solc-windows.exe";
pub const WASM_NAME: &str = "soljson.js"; pub const WASM_NAME: &str = "soljson.js";
fn new( fn new(version: Version, target: &'static str, list: &'static str) -> Self {
version: impl Into<VersionOrRequirement>, Self {
target: &'static str, version,
list: &'static str, target,
) -> anyhow::Result<Self> { list,
let version_or_requirement = version.into();
match version_or_requirement {
VersionOrRequirement::Version(version) => Ok(Self {
version,
target,
list,
}),
VersionOrRequirement::Requirement(requirement) => {
let Some(version) = List::download(list)?
.builds
.into_iter()
.map(|build| build.version)
.filter(|version| requirement.matches(version))
.max()
else {
anyhow::bail!("Failed to find a version that satisfies {requirement:?}");
};
Ok(Self {
version,
target,
list,
})
}
} }
} }
pub fn linux(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> { pub fn linux(version: Version) -> Self {
Self::new(version, Self::LINUX_NAME, List::LINUX_URL) Self::new(version, Self::LINUX_NAME, List::LINUX_URL)
} }
pub fn macosx(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> { pub fn macosx(version: Version) -> Self {
Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL) Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL)
} }
pub fn windows(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> { pub fn windows(version: Version) -> Self {
Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL) Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL)
} }
pub fn wasm(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> { pub fn wasm(version: Version) -> Self {
Self::new(version, Self::WASM_NAME, List::WASM_URL) Self::new(version, Self::WASM_NAME, List::WASM_URL)
} }
@@ -136,24 +111,24 @@ mod tests {
#[test] #[test]
fn try_get_windows() { fn try_get_windows() {
let version = List::download(List::WINDOWS_URL).unwrap().latest_release; let version = List::download(List::WINDOWS_URL).unwrap().latest_release;
GHDownloader::windows(version).unwrap().download().unwrap(); GHDownloader::windows(version).download().unwrap();
} }
#[test] #[test]
fn try_get_macosx() { fn try_get_macosx() {
let version = List::download(List::MACOSX_URL).unwrap().latest_release; let version = List::download(List::MACOSX_URL).unwrap().latest_release;
GHDownloader::macosx(version).unwrap().download().unwrap(); GHDownloader::macosx(version).download().unwrap();
} }
#[test] #[test]
fn try_get_linux() { fn try_get_linux() {
let version = List::download(List::LINUX_URL).unwrap().latest_release; let version = List::download(List::LINUX_URL).unwrap().latest_release;
GHDownloader::linux(version).unwrap().download().unwrap(); GHDownloader::linux(version).download().unwrap();
} }
#[test] #[test]
fn try_get_wasm() { fn try_get_wasm() {
let version = List::download(List::WASM_URL).unwrap().latest_release; let version = List::download(List::WASM_URL).unwrap().latest_release;
GHDownloader::wasm(version).unwrap().download().unwrap(); GHDownloader::wasm(version).download().unwrap();
} }
} }
+3 -4
View File
@@ -7,8 +7,7 @@ use std::path::{Path, PathBuf};
use cache::get_or_download; use cache::get_or_download;
use download::GHDownloader; use download::GHDownloader;
use semver::Version;
use revive_dt_common::types::VersionOrRequirement;
pub mod cache; pub mod cache;
pub mod download; pub mod download;
@@ -21,7 +20,7 @@ pub mod list;
/// and not download it again. /// and not download it again.
pub fn download_solc( pub fn download_solc(
cache_directory: &Path, cache_directory: &Path,
version: impl Into<VersionOrRequirement>, version: Version,
wasm: bool, wasm: bool,
) -> anyhow::Result<PathBuf> { ) -> anyhow::Result<PathBuf> {
let downloader = if wasm { let downloader = if wasm {
@@ -34,7 +33,7 @@ pub fn download_solc(
GHDownloader::windows(version) GHDownloader::windows(version)
} else { } else {
unimplemented!() unimplemented!()
}?; };
get_or_download(cache_directory, &downloader) get_or_download(cache_directory, &downloader)
} }