mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 07:57:58 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b715228c35 | |||
| f51693cb9f | |||
| 4db7009640 | |||
| 5a36e242ec | |||
| 33329632b5 | |||
| 429f2e92a2 | |||
| 65f41f2038 | |||
| 3ed8a1ca1c |
@@ -99,9 +99,12 @@ jobs:
|
||||
- name: Install Geth on Ubuntu
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:ethereum/ethereum
|
||||
sudo apt-get update
|
||||
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
|
||||
# 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
|
||||
@@ -122,12 +125,22 @@ jobs:
|
||||
wget -qO- "$URL" | sudo tar xz -C /usr/local/bin --strip-components=1
|
||||
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
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
brew tap ethereum/ethereum
|
||||
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
|
||||
uses: bnjbvr/cargo-machete@v0.7.1
|
||||
|
||||
@@ -143,5 +156,8 @@ jobs:
|
||||
- name: Check eth-rpc version
|
||||
run: eth-rpc --version
|
||||
|
||||
- name: Check resolc version
|
||||
run: resolc --version
|
||||
|
||||
- name: Test cargo workspace
|
||||
run: make test
|
||||
|
||||
Generated
+84
-3
@@ -339,6 +339,7 @@ dependencies = [
|
||||
"const-hex",
|
||||
"derive_more 2.0.1",
|
||||
"foldhash",
|
||||
"getrandom 0.3.3",
|
||||
"hashbrown 0.15.3",
|
||||
"indexmap 2.10.0",
|
||||
"itoa",
|
||||
@@ -2227,6 +2228,66 @@ dependencies = [
|
||||
"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]]
|
||||
name = "fs-err"
|
||||
version = "2.11.0"
|
||||
@@ -3456,6 +3517,12 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
@@ -3955,6 +4022,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"once_cell",
|
||||
"semver 1.0.26",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -3963,13 +4031,17 @@ dependencies = [
|
||||
name = "revive-dt-compiler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"alloy-primitives",
|
||||
"anyhow",
|
||||
"foundry-compilers-artifacts",
|
||||
"revive-common",
|
||||
"revive-dt-common",
|
||||
"revive-dt-config",
|
||||
"revive-dt-solc-binaries",
|
||||
"revive-solc-json-interface",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
@@ -4001,8 +4073,7 @@ dependencies = [
|
||||
"revive-dt-node",
|
||||
"revive-dt-node-interaction",
|
||||
"revive-dt-report",
|
||||
"revive-solc-json-interface",
|
||||
"serde_json",
|
||||
"semver 1.0.26",
|
||||
"temp-dir",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -4055,9 +4126,9 @@ name = "revive-dt-report"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"revive-dt-compiler",
|
||||
"revive-dt-config",
|
||||
"revive-dt-format",
|
||||
"revive-solc-json-interface",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
@@ -4070,6 +4141,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"hex",
|
||||
"reqwest",
|
||||
"revive-dt-common",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
@@ -4167,6 +4239,9 @@ name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
dependencies = [
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
@@ -6217,6 +6292,12 @@ dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
|
||||
@@ -26,6 +26,7 @@ alloy-primitives = "1.2.1"
|
||||
alloy-sol-types = "1.2.1"
|
||||
anyhow = "1.0"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
foundry-compilers-artifacts = { version = "0.18.0" }
|
||||
futures = { version = "0.3.31" }
|
||||
hex = "0.4.3"
|
||||
reqwest = { version = "0.12.15", features = ["blocking", "json"] }
|
||||
|
||||
@@ -11,6 +11,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
pub mod concepts;
|
||||
pub mod iterators;
|
||||
pub mod macros;
|
||||
pub mod types;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mod version_or_requirement;
|
||||
|
||||
pub use version_or_requirement::*;
|
||||
@@ -0,0 +1,41 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,16 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
revive-solc-json-interface = { workspace = true }
|
||||
revive-dt-common = { workspace = true }
|
||||
revive-dt-config = { workspace = true }
|
||||
revive-dt-solc-binaries = { workspace = true }
|
||||
revive-common = { workspace = true }
|
||||
|
||||
alloy = { workspace = true }
|
||||
alloy-primitives = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
foundry-compilers-artifacts = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
+75
-113
@@ -4,21 +4,20 @@
|
||||
//! - Polkadot revive Wasm compiler
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::read_to_string,
|
||||
hash::Hash,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use alloy_primitives::Address;
|
||||
use revive_dt_config::Arguments;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_common::EVMVersion;
|
||||
use revive_solc_json_interface::{
|
||||
SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
|
||||
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
|
||||
SolcStandardJsonOutput,
|
||||
};
|
||||
use semver::Version;
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::Arguments;
|
||||
|
||||
pub mod revive_js;
|
||||
pub mod revive_resolc;
|
||||
@@ -32,63 +31,44 @@ pub trait SolidityCompiler {
|
||||
/// The low-level compiler interface.
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput<Self::Options>,
|
||||
) -> anyhow::Result<CompilerOutput<Self::Options>>;
|
||||
input: CompilerInput,
|
||||
additional_options: Self::Options,
|
||||
) -> anyhow::Result<CompilerOutput>;
|
||||
|
||||
fn new(solc_executable: PathBuf) -> Self;
|
||||
|
||||
fn get_compiler_executable(config: &Arguments, version: Version) -> anyhow::Result<PathBuf>;
|
||||
fn get_compiler_executable(
|
||||
config: &Arguments,
|
||||
version: impl Into<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf>;
|
||||
|
||||
fn version(&self) -> anyhow::Result<Version>;
|
||||
}
|
||||
|
||||
/// The generic compilation input configuration.
|
||||
#[derive(Debug)]
|
||||
pub struct CompilerInput<T: PartialEq + Eq + Hash> {
|
||||
pub extra_options: T,
|
||||
pub input: SolcStandardJsonInput,
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompilerInput {
|
||||
pub enable_optimization: Option<bool>,
|
||||
pub via_ir: Option<bool>,
|
||||
pub evm_version: Option<EVMVersion>,
|
||||
pub allow_paths: Vec<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub sources: HashMap<PathBuf, String>,
|
||||
pub libraries: HashMap<PathBuf, HashMap<String, Address>>,
|
||||
}
|
||||
|
||||
/// The generic compilation output configuration.
|
||||
#[derive(Debug)]
|
||||
pub struct CompilerOutput<T: PartialEq + Eq + Hash> {
|
||||
/// The solc standard JSON input.
|
||||
pub input: CompilerInput<T>,
|
||||
/// The produced solc standard JSON output.
|
||||
pub output: SolcStandardJsonOutput,
|
||||
/// The error message in case the compiler returns abnormally.
|
||||
pub error: Option<String>,
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct CompilerOutput {
|
||||
/// The compiled contracts. The bytecode of the contract is kept as a string incase linking is
|
||||
/// required and the compiled source has placeholders.
|
||||
pub contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||
}
|
||||
|
||||
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.
|
||||
/// A generic builder style interface for configuring the supported compiler options.
|
||||
pub struct Compiler<T: SolidityCompiler> {
|
||||
input: SolcStandardJsonInput,
|
||||
extra_options: T::Options,
|
||||
allow_paths: Vec<PathBuf>,
|
||||
base_path: Option<PathBuf>,
|
||||
input: CompilerInput,
|
||||
additional_options: T::Options,
|
||||
}
|
||||
|
||||
impl Default for Compiler<solc::Solc> {
|
||||
@@ -103,93 +83,75 @@ where
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
input: SolcStandardJsonInput {
|
||||
language: SolcStandardJsonInputLanguage::Solidity,
|
||||
input: CompilerInput {
|
||||
enable_optimization: Default::default(),
|
||||
via_ir: Default::default(),
|
||||
evm_version: Default::default(),
|
||||
allow_paths: Default::default(),
|
||||
base_path: Default::default(),
|
||||
sources: Default::default(),
|
||||
settings: SolcStandardJsonInputSettings::new(
|
||||
None,
|
||||
Default::default(),
|
||||
None,
|
||||
SolcStandardJsonInputSettingsSelection::new_required(),
|
||||
SolcStandardJsonInputSettingsOptimizer::new(
|
||||
false,
|
||||
None,
|
||||
&Version::new(0, 0, 0),
|
||||
false,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
libraries: Default::default(),
|
||||
},
|
||||
extra_options: Default::default(),
|
||||
allow_paths: Default::default(),
|
||||
base_path: None,
|
||||
additional_options: T::Options::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn solc_optimizer(mut self, enabled: bool) -> Self {
|
||||
self.input.settings.optimizer.enabled = enabled;
|
||||
pub fn with_optimization(mut self, value: impl Into<Option<bool>>) -> Self {
|
||||
self.input.enable_optimization = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, path: &Path) -> anyhow::Result<Self> {
|
||||
pub fn with_via_ir(mut self, value: impl Into<Option<bool>>) -> 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
|
||||
.sources
|
||||
.insert(path.display().to_string(), read_to_string(path)?.into());
|
||||
.insert(path.as_ref().to_path_buf(), read_to_string(path.as_ref())?);
|
||||
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(
|
||||
mut self,
|
||||
scope: impl AsRef<Path>,
|
||||
library_ident: impl AsRef<str>,
|
||||
library_address: Address,
|
||||
path: impl AsRef<Path>,
|
||||
name: impl AsRef<str>,
|
||||
address: Address,
|
||||
) -> Self {
|
||||
self.input
|
||||
.settings
|
||||
.libraries
|
||||
.get_or_insert_with(Default::default)
|
||||
.entry(scope.as_ref().display().to_string())
|
||||
.entry(path.as_ref().to_path_buf())
|
||||
.or_default()
|
||||
.insert(
|
||||
library_ident.as_ref().to_owned(),
|
||||
library_address.to_string(),
|
||||
);
|
||||
|
||||
.insert(name.as_ref().into(), address);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_build(self, solc_path: PathBuf) -> anyhow::Result<CompilerOutput<T::Options>> {
|
||||
T::new(solc_path).build(CompilerInput {
|
||||
extra_options: self.extra_options,
|
||||
input: self.input,
|
||||
allow_paths: self.allow_paths,
|
||||
base_path: self.base_path,
|
||||
})
|
||||
pub fn with_additional_options(mut self, options: impl Into<T::Options>) -> Self {
|
||||
self.additional_options = options.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the compiler JSON input.
|
||||
pub fn input(&self) -> SolcStandardJsonInput {
|
||||
pub fn try_build(self, compiler_path: impl AsRef<Path>) -> anyhow::Result<CompilerOutput> {
|
||||
T::new(compiler_path.as_ref().to_path_buf()).build(self.input, self.additional_options)
|
||||
}
|
||||
|
||||
pub fn input(&self) -> CompilerInput {
|
||||
self.input.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,23 @@ use std::{
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
use revive_solc_json_interface::{
|
||||
SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
|
||||
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
|
||||
SolcStandardJsonOutput,
|
||||
};
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
|
||||
use anyhow::Context;
|
||||
use semver::Version;
|
||||
|
||||
// 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
|
||||
// when invoking resolc which doesn't seem right if we're using solc as a compiler frontend.
|
||||
|
||||
/// A wrapper around the `resolc` binary, emitting PVM-compatible bytecode.
|
||||
#[derive(Debug)]
|
||||
@@ -23,8 +37,56 @@ impl SolidityCompiler for Resolc {
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput<Self::Options>,
|
||||
) -> anyhow::Result<CompilerOutput<Self::Options>> {
|
||||
CompilerInput {
|
||||
enable_optimization,
|
||||
// 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);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
@@ -32,13 +94,12 @@ impl SolidityCompiler for Resolc {
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = input.base_path {
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !input.allow_paths.is_empty() {
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
input
|
||||
.allow_paths
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
@@ -48,93 +109,86 @@ impl SolidityCompiler for Resolc {
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
|
||||
serde_json::to_writer(stdin_pipe, &input.input)?;
|
||||
|
||||
let json_in = serde_json::to_string_pretty(&input.input)?;
|
||||
serde_json::to_writer(stdin_pipe, &input)?;
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
let stdout = output.stdout;
|
||||
let stderr = output.stderr;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)?;
|
||||
let message = String::from_utf8_lossy(&stderr);
|
||||
tracing::error!(
|
||||
"resolc failed exit={} stderr={} JSON-in={} ",
|
||||
output.status,
|
||||
&message,
|
||||
json_in,
|
||||
status = %output.status,
|
||||
message = %message,
|
||||
json_input = json_in,
|
||||
"Compilation using resolc failed"
|
||||
);
|
||||
return Ok(CompilerOutput {
|
||||
input,
|
||||
output: Default::default(),
|
||||
error: Some(message.into()),
|
||||
});
|
||||
anyhow::bail!("Compilation failed with an error: {message}");
|
||||
}
|
||||
|
||||
let mut parsed =
|
||||
serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
let parsed = serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstderr: {}",
|
||||
String::from_utf8_lossy(&stderr)
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::debug!(
|
||||
output = %serde_json::to_string(&parsed).unwrap(),
|
||||
"Compiled successfully"
|
||||
);
|
||||
|
||||
Ok(CompilerOutput {
|
||||
input,
|
||||
output: parsed,
|
||||
error: None,
|
||||
})
|
||||
// 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,
|
||||
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 {
|
||||
@@ -143,7 +197,7 @@ impl SolidityCompiler for Resolc {
|
||||
|
||||
fn get_compiler_executable(
|
||||
config: &Arguments,
|
||||
_version: semver::Version,
|
||||
_version: impl Into<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
if !config.resolc.as_os_str().is_empty() {
|
||||
return Ok(config.resolc.clone());
|
||||
@@ -151,4 +205,45 @@ impl SolidityCompiler for 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");
|
||||
}
|
||||
}
|
||||
|
||||
+162
-30
@@ -6,10 +6,21 @@ use std::{
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::Arguments;
|
||||
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)]
|
||||
pub struct Solc {
|
||||
@@ -22,8 +33,63 @@ impl SolidityCompiler for Solc {
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput<Self::Options>,
|
||||
) -> anyhow::Result<CompilerOutput<Self::Options>> {
|
||||
CompilerInput {
|
||||
enable_optimization,
|
||||
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);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
@@ -31,13 +97,12 @@ impl SolidityCompiler for Solc {
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = input.base_path {
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !input.allow_paths.is_empty() {
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
input
|
||||
.allow_paths
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
@@ -47,31 +112,32 @@ impl SolidityCompiler for Solc {
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let stdin = child.stdin.as_mut().expect("should be piped");
|
||||
serde_json::to_writer(stdin, &input.input)?;
|
||||
serde_json::to_writer(stdin, &input)?;
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)?;
|
||||
let message = String::from_utf8_lossy(&output.stderr);
|
||||
tracing::error!("solc failed exit={} stderr={}", output.status, &message);
|
||||
return Ok(CompilerOutput {
|
||||
input,
|
||||
output: Default::default(),
|
||||
error: Some(message.into()),
|
||||
});
|
||||
tracing::error!(
|
||||
status = %output.status,
|
||||
message = %message,
|
||||
json_input = json_in,
|
||||
"Compilation using solc failed"
|
||||
);
|
||||
anyhow::bail!("Compilation failed with an error: {message}");
|
||||
}
|
||||
|
||||
let parsed =
|
||||
serde_json::from_slice::<SolcStandardJsonOutput>(&output.stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})?;
|
||||
let parsed = serde_json::from_slice::<SolcOutput>(&output.stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"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
|
||||
// errors instead of returning the compiler output that might contain errors.
|
||||
for error in parsed.errors.iter().flatten() {
|
||||
if error.severity == "error" {
|
||||
for error in parsed.errors.iter() {
|
||||
if error.severity == Severity::Error {
|
||||
tracing::error!(?error, ?input, "Encountered an error in the compilation");
|
||||
anyhow::bail!("Encountered an error in the compilation: {error}")
|
||||
}
|
||||
@@ -82,11 +148,29 @@ impl SolidityCompiler for Solc {
|
||||
"Compiled successfully"
|
||||
);
|
||||
|
||||
Ok(CompilerOutput {
|
||||
input,
|
||||
output: parsed,
|
||||
error: None,
|
||||
})
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (contract_path, contracts) in parsed.contracts {
|
||||
let map = compiler_output
|
||||
.contracts
|
||||
.entry(contract_path.canonicalize()?)
|
||||
.or_default();
|
||||
for (contract_name, contract_info) in contracts.into_iter() {
|
||||
let source_code = contract_info
|
||||
.evm
|
||||
.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 {
|
||||
@@ -95,9 +179,57 @@ impl SolidityCompiler for Solc {
|
||||
|
||||
fn get_compiler_executable(
|
||||
config: &Arguments,
|
||||
version: semver::Version,
|
||||
version: impl Into<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let path = download_solc(config.directory(), version, config.wasm)?;
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.6.9;
|
||||
|
||||
contract Callable {
|
||||
function f(uint[1] memory p1) public pure returns(uint) {
|
||||
return p1[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{ "cases": [ {
|
||||
"name": "first",
|
||||
"inputs": [
|
||||
{
|
||||
"instance": "Main",
|
||||
"method": "main",
|
||||
"calldata": [
|
||||
"1",
|
||||
"Callable.address"
|
||||
]
|
||||
}
|
||||
],
|
||||
"expected": [
|
||||
"1"
|
||||
]
|
||||
} ],
|
||||
"contracts": {
|
||||
"Main": "main.sol:Main",
|
||||
"Callable": "callable.sol:Callable"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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"));
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use alloy::{network::EthereumWallet, signers::local::PrivateKeySigner};
|
||||
@@ -54,7 +55,7 @@ pub struct Arguments {
|
||||
pub geth: PathBuf,
|
||||
|
||||
/// The maximum time in milliseconds to wait for geth to start.
|
||||
#[arg(long = "geth-start-timeout", default_value = "2000")]
|
||||
#[arg(long = "geth-start-timeout", default_value = "5000")]
|
||||
pub geth_start_timeout: u64,
|
||||
|
||||
/// The test network chain ID.
|
||||
@@ -144,7 +145,14 @@ impl Arguments {
|
||||
|
||||
impl Default for Arguments {
|
||||
fn default() -> Self {
|
||||
Arguments::parse_from(["retester"])
|
||||
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
||||
|
||||
let default = Arguments::parse_from(["retester"]);
|
||||
|
||||
Arguments {
|
||||
temp_dir: Some(&TEMP_DIR),
|
||||
..default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,5 @@ indexmap = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
revive-solc-json-interface = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
temp-dir = { workspace = true }
|
||||
|
||||
+195
-127
@@ -3,9 +3,11 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use alloy::network::{Ethereum, TransactionBuilder};
|
||||
use alloy::primitives::U256;
|
||||
use alloy::rpc::types::TransactionReceipt;
|
||||
use alloy::rpc::types::trace::geth::{
|
||||
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
|
||||
@@ -20,7 +22,7 @@ use alloy::{
|
||||
};
|
||||
use anyhow::Context;
|
||||
use indexmap::IndexMap;
|
||||
use serde_json::Value;
|
||||
use semver::Version;
|
||||
|
||||
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||
use revive_dt_compiler::{Compiler, SolidityCompiler};
|
||||
@@ -32,7 +34,6 @@ use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
|
||||
use revive_dt_node::Node;
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use revive_dt_report::reporter::{CompilationTask, Report, Span};
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
|
||||
use crate::Platform;
|
||||
|
||||
@@ -46,23 +47,27 @@ pub struct State<'a, T: Platform> {
|
||||
/// The [`Span`] used in reporting.
|
||||
span: Span,
|
||||
|
||||
/// A vector of all of the compiled contracts. Each call to [`build_contracts`] adds a new entry
|
||||
/// to this vector.
|
||||
///
|
||||
/// [`build_contracts`]: State::build_contracts
|
||||
contracts: Vec<SolcStandardJsonOutput>,
|
||||
/// A map of all of the compiled contracts for the given metadata file.
|
||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||
|
||||
/// 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
|
||||
/// files.
|
||||
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 map is not per case, but rather, per metadata file. This means that we do not redeploy
|
||||
/// the libraries with each case.
|
||||
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>,
|
||||
}
|
||||
|
||||
@@ -74,9 +79,11 @@ where
|
||||
Self {
|
||||
config,
|
||||
span,
|
||||
contracts: Default::default(),
|
||||
compiled_contracts: Default::default(),
|
||||
deployed_contracts: Default::default(),
|
||||
variables: Default::default(),
|
||||
deployed_libraries: Default::default(),
|
||||
compiler_version: Default::default(),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -86,7 +93,11 @@ where
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn build_contracts(&mut self, mode: &SolcMode, metadata: &Metadata) -> anyhow::Result<()> {
|
||||
pub fn build_contracts(
|
||||
&mut self,
|
||||
mode: &'a SolcMode,
|
||||
metadata: &Metadata,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut span = self.span();
|
||||
span.next_metadata(
|
||||
metadata
|
||||
@@ -96,15 +107,20 @@ where
|
||||
.clone(),
|
||||
);
|
||||
|
||||
let Some(version) = mode.last_patch_version(&self.config.solc) else {
|
||||
anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version);
|
||||
};
|
||||
let compiler_version_or_requirement =
|
||||
mode.compiler_version_to_use(self.config.solc.clone());
|
||||
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");
|
||||
|
||||
let compiler = Compiler::<T::Compiler>::new()
|
||||
.allow_path(metadata.directory()?)
|
||||
.solc_optimizer(mode.solc_optimize());
|
||||
let mut compiler = FilesWithExtensionIterator::new(metadata.directory()?)
|
||||
.with_allowed_extension("sol")
|
||||
.with_allow_path(metadata.directory()?)
|
||||
.with_optimization(mode.solc_optimize());
|
||||
let mut compiler = metadata
|
||||
.files_to_compile()?
|
||||
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
|
||||
for (library_instance, (library_address, _)) in self.deployed_libraries.iter() {
|
||||
let library_ident = &metadata
|
||||
@@ -130,28 +146,27 @@ where
|
||||
json_input: compiler.input(),
|
||||
json_output: None,
|
||||
mode: mode.clone(),
|
||||
compiler_version: format!("{}", &version),
|
||||
compiler_version: format!("{}", &compiler_version),
|
||||
error: None,
|
||||
};
|
||||
|
||||
let compiler_path = T::Compiler::get_compiler_executable(self.config, version)?;
|
||||
match compiler.try_build(compiler_path) {
|
||||
Ok(output) => {
|
||||
task.json_output = Some(output.output.clone());
|
||||
task.error = output.error;
|
||||
self.contracts.push(output.output);
|
||||
task.json_output = Some(output.clone());
|
||||
|
||||
if let Some(last_output) = self.contracts.last() {
|
||||
if let Some(contracts) = &last_output.contracts {
|
||||
for (file, contracts_map) in contracts {
|
||||
for contract_name in contracts_map.keys() {
|
||||
tracing::debug!(
|
||||
"Compiled contract: {contract_name} from file: {file}"
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Compiled contracts field is None");
|
||||
for (contract_path, contracts) in output.contracts.into_iter() {
|
||||
let map = self
|
||||
.compiled_contracts
|
||||
.entry(contract_path.clone())
|
||||
.or_default();
|
||||
for (contract_name, contract_info) in contracts.into_iter() {
|
||||
tracing::debug!(
|
||||
contract_path = %contract_path.display(),
|
||||
contract_name = contract_name,
|
||||
"Compiled contract"
|
||||
);
|
||||
|
||||
map.insert(contract_name, contract_info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +184,7 @@ where
|
||||
pub fn build_and_publish_libraries(
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
mode: &SolcMode,
|
||||
mode: &'a SolcMode,
|
||||
node: &T::Blockchain,
|
||||
) -> anyhow::Result<()> {
|
||||
self.build_contracts(mode, metadata)?;
|
||||
@@ -200,12 +215,22 @@ where
|
||||
case_idx: CaseIdx,
|
||||
input: &Input,
|
||||
node: &T::Blockchain,
|
||||
mode: &SolcMode,
|
||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||
let deployment_receipts =
|
||||
self.handle_contract_deployment(metadata, case_idx, input, node)?;
|
||||
let execution_receipt =
|
||||
self.handle_input_execution(case_idx, input, deployment_receipts, node)?;
|
||||
self.handle_input_expectations(case_idx, input, &execution_receipt, node)?;
|
||||
let tracing_result = self.handle_input_call_frame_tracing(&execution_receipt, node)?;
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -226,7 +251,12 @@ where
|
||||
|
||||
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
||||
for instance in input.find_all_contract_instances().into_iter() {
|
||||
if !self.deployed_contracts(case_idx).contains_key(&instance) {
|
||||
if !self
|
||||
.deployed_contracts
|
||||
.entry(case_idx)
|
||||
.or_insert_with(|| self.deployed_libraries.clone())
|
||||
.contains_key(&instance)
|
||||
{
|
||||
instances_we_must_deploy.entry(instance).or_insert(false);
|
||||
}
|
||||
}
|
||||
@@ -278,7 +308,13 @@ where
|
||||
.remove(&input.instance)
|
||||
.context("Failed to find deployment receipt"),
|
||||
Method::Fallback | Method::FunctionName(_) => {
|
||||
let tx = match input.legacy_transaction(self.deployed_contracts(case_idx), node) {
|
||||
let tx = match input.legacy_transaction(
|
||||
self.deployed_contracts
|
||||
.entry(case_idx)
|
||||
.or_insert_with(|| self.deployed_libraries.clone()),
|
||||
&*self.variables.entry(case_idx).or_default(),
|
||||
node,
|
||||
) {
|
||||
Ok(tx) => {
|
||||
tracing::debug!("Legacy transaction data: {tx:#?}");
|
||||
tx
|
||||
@@ -306,12 +342,64 @@ 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(
|
||||
&mut self,
|
||||
case_idx: CaseIdx,
|
||||
input: &Input,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
node: &T::Blockchain,
|
||||
mode: &SolcMode,
|
||||
tracing_result: &CallFrame,
|
||||
) -> anyhow::Result<()> {
|
||||
let span = tracing::info_span!("Handling input expectations");
|
||||
let _guard = span.enter();
|
||||
@@ -344,29 +432,14 @@ 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() {
|
||||
self.handle_input_expectation_item(
|
||||
case_idx,
|
||||
execution_receipt,
|
||||
node,
|
||||
expectation,
|
||||
&tracing_result,
|
||||
tracing_result,
|
||||
mode,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -380,20 +453,36 @@ where
|
||||
node: &T::Blockchain,
|
||||
expectation: &ExpectedOutput,
|
||||
tracing_result: &CallFrame,
|
||||
mode: &SolcMode,
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: We want to respect the compiler version filter on the expected output but would
|
||||
// require some changes to the interfaces of the compiler and such. So, we add it later.
|
||||
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the
|
||||
// transaction should succeed? Do we just ignore the expectation?
|
||||
if let Some(ref version_requirement) = expectation.compiler_version {
|
||||
let compiler_version = self
|
||||
.compiler_version
|
||||
.get(mode)
|
||||
.context("Failed to find the compiler version fo the solc mode")?;
|
||||
if !version_requirement.matches(compiler_version) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let deployed_contracts = self.deployed_contracts(case_idx);
|
||||
let deployed_contracts = self
|
||||
.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;
|
||||
|
||||
// Handling the receipt state assertion.
|
||||
let expected = !expectation.exception;
|
||||
let actual = execution_receipt.status();
|
||||
if actual != expected {
|
||||
tracing::error!(expected, actual, "Transaction status assertion failed",);
|
||||
tracing::error!(
|
||||
expected,
|
||||
actual,
|
||||
?execution_receipt,
|
||||
?tracing_result,
|
||||
"Transaction status assertion failed"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Transaction status assertion failed - Expected {expected} but got {actual}",
|
||||
);
|
||||
@@ -403,7 +492,12 @@ where
|
||||
if let Some(ref expected_calldata) = expectation.return_data {
|
||||
let expected = expected_calldata;
|
||||
let actual = &tracing_result.output.as_ref().unwrap_or_default();
|
||||
if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
|
||||
if !expected.is_equivalent(
|
||||
actual,
|
||||
deployed_contracts,
|
||||
&*variables,
|
||||
chain_state_provider,
|
||||
)? {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
@@ -431,8 +525,13 @@ where
|
||||
expected_events.iter().zip(execution_receipt.logs())
|
||||
{
|
||||
// Handling the emitter assertion.
|
||||
if let Some(expected_address) = expected_event.address {
|
||||
let expected = expected_address;
|
||||
if let Some(ref expected_address) = expected_event.address {
|
||||
let expected = Address::from_slice(
|
||||
Calldata::new_compound([expected_address])
|
||||
.calldata(deployed_contracts, &*variables, node)?
|
||||
.get(12..32)
|
||||
.expect("Can't fail"),
|
||||
);
|
||||
let actual = actual_event.address();
|
||||
if actual != expected {
|
||||
tracing::error!(
|
||||
@@ -457,6 +556,7 @@ where
|
||||
if !expected.is_equivalent(
|
||||
&actual.0,
|
||||
deployed_contracts,
|
||||
&*variables,
|
||||
chain_state_provider,
|
||||
)? {
|
||||
tracing::error!(
|
||||
@@ -474,7 +574,12 @@ where
|
||||
// Handling the values assertion.
|
||||
let expected = &expected_event.values;
|
||||
let actual = &actual_event.data().data;
|
||||
if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
|
||||
if !expected.is_equivalent(
|
||||
&actual.0,
|
||||
deployed_contracts,
|
||||
&*variables,
|
||||
chain_state_provider,
|
||||
)? {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
@@ -512,19 +617,6 @@ where
|
||||
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
|
||||
/// be deployed then it will be deployed.
|
||||
///
|
||||
@@ -543,17 +635,16 @@ where
|
||||
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
|
||||
let case_idx = case_idx.into();
|
||||
|
||||
if let Some((address, abi)) = self.deployed_libraries.get(contract_instance) {
|
||||
return Ok((*address, abi.clone(), None));
|
||||
}
|
||||
if let Some(case_idx) = case_idx {
|
||||
if let Some((address, abi)) = self
|
||||
let deployed_contracts = match case_idx {
|
||||
Some(case_idx) => self
|
||||
.deployed_contracts
|
||||
.get(&case_idx)
|
||||
.and_then(|contracts| contracts.get(contract_instance))
|
||||
{
|
||||
return Ok((*address, abi.clone(), None));
|
||||
}
|
||||
.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));
|
||||
}
|
||||
|
||||
let Some(ContractPathAndIdent {
|
||||
@@ -568,30 +659,24 @@ where
|
||||
)
|
||||
};
|
||||
|
||||
let compiled_contract = self.contracts.iter().rev().find_map(|output| {
|
||||
output
|
||||
.contracts
|
||||
.as_ref()?
|
||||
.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()))
|
||||
let Some((code, abi)) = self
|
||||
.compiled_contracts
|
||||
.get(&contract_source_path)
|
||||
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
|
||||
.cloned()
|
||||
else {
|
||||
tracing::error!(
|
||||
contract_source_path = contract_source_path.display().to_string(),
|
||||
contract_ident = contract_ident.as_ref(),
|
||||
"Failed to find bytecode for contract"
|
||||
"Failed to find information for contract"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Failed to find bytecode for contract {:?}",
|
||||
"Failed to find information for contract {:?}",
|
||||
contract_instance
|
||||
)
|
||||
};
|
||||
|
||||
let mut code = match alloy::hex::decode(&code.object) {
|
||||
let mut code = match alloy::hex::decode(&code) {
|
||||
Ok(code) => code,
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
@@ -604,30 +689,8 @@ 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 {
|
||||
let calldata = calldata.calldata(self.deployed_contracts(case_idx), node)?;
|
||||
let calldata = calldata.calldata(deployed_contracts, None, node)?;
|
||||
code.extend(calldata);
|
||||
}
|
||||
|
||||
@@ -662,8 +725,7 @@ where
|
||||
"Deployed contract"
|
||||
);
|
||||
|
||||
self.deployed_contracts(case_idx)
|
||||
.insert(contract_instance.clone(), (address, abi.clone()));
|
||||
deployed_contracts.insert(contract_instance.clone(), (address, abi.clone()));
|
||||
|
||||
Ok((address, abi, Some(receipt)))
|
||||
}
|
||||
@@ -865,8 +927,13 @@ where
|
||||
tracing::info_span!("Executing input", contract_name = ?input.instance)
|
||||
.in_scope(|| {
|
||||
let (leader_receipt, _, leader_diff) = match leader_state
|
||||
.handle_input(self.metadata, case_idx, &input, self.leader_node)
|
||||
{
|
||||
.handle_input(
|
||||
self.metadata,
|
||||
case_idx,
|
||||
&input,
|
||||
self.leader_node,
|
||||
&mode,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
@@ -895,6 +962,7 @@ where
|
||||
case_idx,
|
||||
&input,
|
||||
self.follower_node,
|
||||
&mode,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
|
||||
+53
-21
@@ -17,7 +17,25 @@ impl Corpus {
|
||||
/// Try to read and parse the corpus definition file at given `path`.
|
||||
pub fn try_from_path(path: &Path) -> anyhow::Result<Self> {
|
||||
let file = File::open(path)?;
|
||||
Ok(serde_json::from_reader(file)?)
|
||||
let mut corpus: Corpus = 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.
|
||||
@@ -35,33 +53,47 @@ impl Corpus {
|
||||
///
|
||||
/// `path` is expected to be a directory.
|
||||
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
||||
let dir_entry = match std::fs::read_dir(path) {
|
||||
Ok(dir_entry) => dir_entry,
|
||||
Err(error) => {
|
||||
tracing::error!("failed to read dir '{}': {error}", path.display());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in dir_entry {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
if path.is_dir() {
|
||||
let dir_entry = match std::fs::read_dir(path) {
|
||||
Ok(dir_entry) => dir_entry,
|
||||
Err(error) => {
|
||||
tracing::error!("error reading dir entry: {error}");
|
||||
continue;
|
||||
tracing::error!("failed to read dir '{}': {error}", path.display());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
collect_metadata(&path, tests);
|
||||
continue;
|
||||
}
|
||||
for entry in dir_entry {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(error) => {
|
||||
tracing::error!("error reading dir entry: {error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if path.is_file() {
|
||||
if let Some(metadata) = MetadataFile::try_from_file(&path) {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
collect_metadata(&path, tests);
|
||||
continue;
|
||||
}
|
||||
|
||||
if path.is_file() {
|
||||
if let Some(metadata) = MetadataFile::try_from_file(&path) {
|
||||
tests.push(metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(extension) = path.extension() else {
|
||||
tracing::error!("Failed to get file extension");
|
||||
return;
|
||||
};
|
||||
if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") {
|
||||
if let Some(metadata) = MetadataFile::try_from_file(path) {
|
||||
tests.push(metadata)
|
||||
}
|
||||
} else {
|
||||
tracing::error!(?extension, "Unsupported file extension");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+138
-29
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use alloy::{
|
||||
eips::BlockNumberOrTag,
|
||||
hex::ToHexExt,
|
||||
json_abi::JsonAbi,
|
||||
network::TransactionBuilder,
|
||||
primitives::{Address, Bytes, U256},
|
||||
@@ -30,6 +31,7 @@ pub struct Input {
|
||||
pub expected: Option<Expected>,
|
||||
pub value: Option<EtherValue>,
|
||||
pub storage: Option<HashMap<String, Calldata>>,
|
||||
pub variable_assignments: Option<VariableAssignments>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
@@ -51,7 +53,7 @@ pub struct ExpectedOutput {
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
pub struct Event {
|
||||
pub address: Option<Address>,
|
||||
pub address: Option<String>,
|
||||
pub topics: Vec<String>,
|
||||
pub values: Calldata,
|
||||
}
|
||||
@@ -136,6 +138,8 @@ enum Operation {
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BitwiseXor,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
}
|
||||
|
||||
/// Specify how the contract is called.
|
||||
@@ -164,6 +168,14 @@ define_wrapper_type!(
|
||||
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 {
|
||||
pub const fn default_caller() -> Address {
|
||||
Address(FixedBytes(alloy::hex!(
|
||||
@@ -186,16 +198,17 @@ impl Input {
|
||||
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
|
||||
}
|
||||
|
||||
pub fn encoded_input(
|
||||
&self,
|
||||
pub fn encoded_input<'a>(
|
||||
&'a self,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<Bytes> {
|
||||
match self.method {
|
||||
Method::Deployer | Method::Fallback => {
|
||||
let calldata = self
|
||||
.calldata
|
||||
.calldata(deployed_contracts, chain_state_provider)?;
|
||||
let calldata =
|
||||
self.calldata
|
||||
.calldata(deployed_contracts, variables, chain_state_provider)?;
|
||||
|
||||
Ok(calldata.into())
|
||||
}
|
||||
@@ -244,6 +257,7 @@ impl Input {
|
||||
self.calldata.calldata_into_slice(
|
||||
&mut calldata,
|
||||
deployed_contracts,
|
||||
variables,
|
||||
chain_state_provider,
|
||||
)?;
|
||||
|
||||
@@ -253,12 +267,13 @@ impl Input {
|
||||
}
|
||||
|
||||
/// Parse this input into a legacy transaction.
|
||||
pub fn legacy_transaction(
|
||||
&self,
|
||||
pub fn legacy_transaction<'a>(
|
||||
&'a self,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<TransactionRequest> {
|
||||
let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
|
||||
let input_data = self.encoded_input(deployed_contracts, variables, chain_state_provider)?;
|
||||
let transaction_request = TransactionRequest::default().from(self.caller).value(
|
||||
self.value
|
||||
.map(|value| value.into_inner())
|
||||
@@ -336,20 +351,27 @@ impl Calldata {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calldata(
|
||||
&self,
|
||||
pub fn calldata<'a>(
|
||||
&'a self,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
|
||||
self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?;
|
||||
self.calldata_into_slice(
|
||||
&mut buffer,
|
||||
deployed_contracts,
|
||||
variables,
|
||||
chain_state_provider,
|
||||
)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn calldata_into_slice(
|
||||
&self,
|
||||
pub fn calldata_into_slice<'a>(
|
||||
&'a self,
|
||||
buffer: &mut Vec<u8>,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<()> {
|
||||
match self {
|
||||
@@ -358,7 +380,7 @@ impl Calldata {
|
||||
}
|
||||
Calldata::Compound(items) => {
|
||||
for (arg_idx, arg) in items.iter().enumerate() {
|
||||
match arg.resolve(deployed_contracts, chain_state_provider) {
|
||||
match arg.resolve(deployed_contracts, variables.clone(), chain_state_provider) {
|
||||
Ok(resolved) => {
|
||||
buffer.extend(resolved.to_be_bytes::<32>());
|
||||
}
|
||||
@@ -381,10 +403,11 @@ impl Calldata {
|
||||
}
|
||||
|
||||
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
|
||||
pub fn is_equivalent(
|
||||
&self,
|
||||
pub fn is_equivalent<'a>(
|
||||
&'a self,
|
||||
other: &[u8],
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<bool> {
|
||||
match self {
|
||||
@@ -407,7 +430,8 @@ impl Calldata {
|
||||
std::borrow::Cow::Borrowed(other)
|
||||
};
|
||||
|
||||
let this = this.resolve(deployed_contracts, chain_state_provider)?;
|
||||
let this =
|
||||
this.resolve(deployed_contracts, variables.clone(), chain_state_provider)?;
|
||||
let other = U256::from_be_slice(&other);
|
||||
if this != other {
|
||||
return Ok(false);
|
||||
@@ -420,16 +444,17 @@ impl Calldata {
|
||||
}
|
||||
|
||||
impl CalldataItem {
|
||||
fn resolve(
|
||||
&self,
|
||||
fn resolve<'a>(
|
||||
&'a self,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<U256> {
|
||||
let mut stack = Vec::<CalldataToken<U256>>::new();
|
||||
|
||||
for token in self
|
||||
.calldata_tokens()
|
||||
.map(|token| token.resolve(deployed_contracts, chain_state_provider))
|
||||
.map(|token| token.resolve(deployed_contracts, variables.clone(), chain_state_provider))
|
||||
{
|
||||
let token = token?;
|
||||
let new_token = match token {
|
||||
@@ -452,8 +477,14 @@ impl CalldataItem {
|
||||
Operation::BitwiseAnd => Some(left_operand & right_operand),
|
||||
Operation::BitwiseOr => 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")?;
|
||||
.context("Invalid calldata arithmetic operation - Invalid operation")?;
|
||||
|
||||
CalldataToken::Item(result)
|
||||
}
|
||||
@@ -464,8 +495,17 @@ impl CalldataItem {
|
||||
match stack.as_slice() {
|
||||
// Empty stack means that we got an empty compound calldata which we resolve to zero.
|
||||
[] => Ok(U256::ZERO),
|
||||
[CalldataToken::Item(item)] => Ok(*item),
|
||||
_ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation")),
|
||||
[CalldataToken::Item(item)] => {
|
||||
tracing::debug!(
|
||||
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"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,6 +518,8 @@ impl CalldataItem {
|
||||
"&" => CalldataToken::Operation(Operation::BitwiseAnd),
|
||||
"|" => CalldataToken::Operation(Operation::BitwiseOr),
|
||||
"^" => CalldataToken::Operation(Operation::BitwiseXor),
|
||||
"<<" => CalldataToken::Operation(Operation::ShiftLeft),
|
||||
">>" => CalldataToken::Operation(Operation::ShiftRight),
|
||||
_ => CalldataToken::Item(item),
|
||||
})
|
||||
}
|
||||
@@ -494,6 +536,7 @@ impl<T> CalldataToken<T> {
|
||||
const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH";
|
||||
const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER";
|
||||
const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP";
|
||||
const VARIABLE_PREFIX: &str = "$VARIABLE:";
|
||||
|
||||
fn into_item(self) -> Option<T> {
|
||||
match self {
|
||||
@@ -512,9 +555,10 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
||||
/// 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:
|
||||
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
|
||||
fn resolve(
|
||||
fn resolve<'a>(
|
||||
self,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<CalldataToken<U256>> {
|
||||
match self {
|
||||
@@ -579,6 +623,16 @@ impl<T: AsRef<str>> CalldataToken<T> {
|
||||
let timestamp =
|
||||
chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?;
|
||||
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 {
|
||||
Ok(U256::from_str_radix(item, 10)
|
||||
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?)
|
||||
@@ -699,7 +753,9 @@ mod tests {
|
||||
(Address::ZERO, parsed_abi),
|
||||
);
|
||||
|
||||
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
|
||||
let encoded = input
|
||||
.encoded_input(&contracts, None, &MockResolver)
|
||||
.unwrap();
|
||||
assert!(encoded.0.starts_with(&selector));
|
||||
|
||||
type T = (u64,);
|
||||
@@ -741,7 +797,9 @@ mod tests {
|
||||
(Address::ZERO, parsed_abi),
|
||||
);
|
||||
|
||||
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
|
||||
let encoded = input
|
||||
.encoded_input(&contracts, None, &MockResolver)
|
||||
.unwrap();
|
||||
assert!(encoded.0.starts_with(&selector));
|
||||
|
||||
type T = (alloy_primitives::Address,);
|
||||
@@ -786,7 +844,9 @@ mod tests {
|
||||
(Address::ZERO, parsed_abi),
|
||||
);
|
||||
|
||||
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
|
||||
let encoded = input
|
||||
.encoded_input(&contracts, None, &MockResolver)
|
||||
.unwrap();
|
||||
assert!(encoded.0.starts_with(&selector));
|
||||
|
||||
type T = (alloy_primitives::Address,);
|
||||
@@ -802,7 +862,7 @@ mod tests {
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
chain_state_provider: &impl ResolverApi,
|
||||
) -> anyhow::Result<U256> {
|
||||
CalldataItem::new(input).resolve(deployed_contracts, chain_state_provider)
|
||||
CalldataItem::new(input).resolve(deployed_contracts, None, chain_state_provider)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1010,4 +1070,53 @@ mod tests {
|
||||
// Assert
|
||||
assert!(resolved.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_json_can_be_deserialized1() {
|
||||
// Arrange
|
||||
let str = r#"
|
||||
{
|
||||
"return_data": [
|
||||
"1"
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"topics": [],
|
||||
"values": []
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
|
||||
// Act
|
||||
let expected = serde_json::from_str::<Expected>(str);
|
||||
|
||||
// Assert
|
||||
expected.expect("Failed to deserialize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_json_can_be_deserialized2() {
|
||||
// Arrange
|
||||
let str = r#"
|
||||
{
|
||||
"return_data": [
|
||||
"1"
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"address": "Main.address",
|
||||
"topics": [],
|
||||
"values": []
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
|
||||
// Act
|
||||
let expected = serde_json::from_str::<Expected>(str);
|
||||
|
||||
// Assert
|
||||
expected.expect("Failed to deserialize");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_common::macros::define_wrapper_type;
|
||||
use revive_dt_common::{iterators::FilesWithExtensionIterator, macros::define_wrapper_type};
|
||||
|
||||
use crate::{
|
||||
case::Case,
|
||||
@@ -193,7 +193,7 @@ impl Metadata {
|
||||
metadata.file_path = Some(path.to_path_buf());
|
||||
metadata.contracts = Some(
|
||||
[(
|
||||
ContractInstance::new("test"),
|
||||
ContractInstance::new("Test"),
|
||||
ContractPathAndIdent {
|
||||
contract_source_path: path.to_path_buf(),
|
||||
contract_ident: ContractIdent::new("Test"),
|
||||
@@ -212,6 +212,29 @@ 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!(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use semver::Version;
|
||||
use serde::de::Deserializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -78,6 +79,15 @@ impl SolcMode {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -152,6 +152,10 @@ impl Instance {
|
||||
.arg("--nodiscover")
|
||||
.arg("--maxpeers")
|
||||
.arg("0")
|
||||
.arg("--txlookuplimit")
|
||||
.arg("0")
|
||||
.arg("--cache.blocklogs")
|
||||
.arg("512")
|
||||
.stderr(stderr_logs_file.try_clone()?)
|
||||
.stdout(stdout_logs_file.try_clone()?)
|
||||
.spawn()?
|
||||
@@ -294,7 +298,10 @@ impl EthereumNode for Instance {
|
||||
}
|
||||
|
||||
match provider.get_transaction_receipt(*transaction_hash).await {
|
||||
Ok(Some(receipt)) => break Ok(receipt),
|
||||
Ok(Some(receipt)) => {
|
||||
tracing::info!(?total_wait_duration, "Found receipt");
|
||||
break Ok(receipt);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
let error_string = error.to_string();
|
||||
|
||||
@@ -10,9 +10,9 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
revive-dt-config = { workspace = true }
|
||||
revive-dt-format = { workspace = true }
|
||||
revive-dt-compiler = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
revive-solc-json-interface = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! The report analyzer enriches the raw report data.
|
||||
|
||||
use revive_dt_compiler::CompilerOutput;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::reporter::CompilationTask;
|
||||
@@ -13,41 +14,27 @@ pub struct CompilerStatistics {
|
||||
pub mean_code_size: usize,
|
||||
/// The mean size of the optimized YUL IR.
|
||||
pub mean_yul_size: usize,
|
||||
/// Is a proxy because the YUL also containes a lot of comments.
|
||||
/// Is a proxy because the YUL also contains a lot of comments.
|
||||
pub yul_to_bytecode_size_ratio: f32,
|
||||
}
|
||||
|
||||
impl CompilerStatistics {
|
||||
/// Cumulatively update the statistics with the next compiler task.
|
||||
pub fn sample(&mut self, compilation_task: &CompilationTask) {
|
||||
let Some(output) = &compilation_task.json_output else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(contracts) = &output.contracts else {
|
||||
let Some(CompilerOutput { contracts }) = &compilation_task.json_output else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (_solidity, contracts) 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;
|
||||
};
|
||||
|
||||
for (_name, (bytecode, _)) in contracts.iter() {
|
||||
// The EVM bytecode can be unlinked and thus is not necessarily a decodable hex
|
||||
// string; for our statistics this is a good enough approximation.
|
||||
let bytecode_size = deploy_code.object.len() / 2;
|
||||
let bytecode_size = bytecode.len() / 2;
|
||||
|
||||
let yul_size = contract
|
||||
.ir_optimized
|
||||
.as_ref()
|
||||
.expect("if the contract has a deploy code it should also have the opimized IR")
|
||||
.len();
|
||||
// TODO: for the time being we set the yul_size to be zero. We need to change this
|
||||
// when we overhaul the reporting.
|
||||
|
||||
self.update_sizes(bytecode_size, yul_size);
|
||||
self.update_sizes(bytecode_size, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use revive_dt_compiler::{CompilerInput, CompilerOutput};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_config::{Arguments, TestingPlatform};
|
||||
use revive_dt_format::{corpus::Corpus, mode::SolcMode};
|
||||
use revive_solc_json_interface::{SolcStandardJsonInput, SolcStandardJsonOutput};
|
||||
|
||||
use crate::analyzer::CompilerStatistics;
|
||||
|
||||
@@ -44,9 +44,9 @@ pub struct Report {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CompilationTask {
|
||||
/// The observed compiler input.
|
||||
pub json_input: SolcStandardJsonInput,
|
||||
pub json_input: CompilerInput,
|
||||
/// The observed compiler output.
|
||||
pub json_output: Option<SolcStandardJsonOutput>,
|
||||
pub json_output: Option<CompilerOutput>,
|
||||
/// The observed compiler mode.
|
||||
pub mode: SolcMode,
|
||||
/// The observed compiler version.
|
||||
@@ -152,15 +152,7 @@ impl Report {
|
||||
for (platform, results) in self.compiler_results.iter() {
|
||||
for result in results {
|
||||
// ignore if there were no errors
|
||||
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)
|
||||
{
|
||||
if result.compilation_task.error.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
revive-dt-common = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::{
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
|
||||
use semver::Version;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
@@ -52,27 +54,50 @@ impl GHDownloader {
|
||||
pub const WINDOWS_NAME: &str = "solc-windows.exe";
|
||||
pub const WASM_NAME: &str = "soljson.js";
|
||||
|
||||
fn new(version: Version, target: &'static str, list: &'static str) -> Self {
|
||||
Self {
|
||||
version,
|
||||
target,
|
||||
list,
|
||||
fn new(
|
||||
version: impl Into<VersionOrRequirement>,
|
||||
target: &'static str,
|
||||
list: &'static str,
|
||||
) -> anyhow::Result<Self> {
|
||||
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: Version) -> Self {
|
||||
pub fn linux(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> {
|
||||
Self::new(version, Self::LINUX_NAME, List::LINUX_URL)
|
||||
}
|
||||
|
||||
pub fn macosx(version: Version) -> Self {
|
||||
pub fn macosx(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> {
|
||||
Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL)
|
||||
}
|
||||
|
||||
pub fn windows(version: Version) -> Self {
|
||||
pub fn windows(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> {
|
||||
Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL)
|
||||
}
|
||||
|
||||
pub fn wasm(version: Version) -> Self {
|
||||
pub fn wasm(version: impl Into<VersionOrRequirement>) -> anyhow::Result<Self> {
|
||||
Self::new(version, Self::WASM_NAME, List::WASM_URL)
|
||||
}
|
||||
|
||||
@@ -111,24 +136,24 @@ mod tests {
|
||||
#[test]
|
||||
fn try_get_windows() {
|
||||
let version = List::download(List::WINDOWS_URL).unwrap().latest_release;
|
||||
GHDownloader::windows(version).download().unwrap();
|
||||
GHDownloader::windows(version).unwrap().download().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_get_macosx() {
|
||||
let version = List::download(List::MACOSX_URL).unwrap().latest_release;
|
||||
GHDownloader::macosx(version).download().unwrap();
|
||||
GHDownloader::macosx(version).unwrap().download().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_get_linux() {
|
||||
let version = List::download(List::LINUX_URL).unwrap().latest_release;
|
||||
GHDownloader::linux(version).download().unwrap();
|
||||
GHDownloader::linux(version).unwrap().download().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_get_wasm() {
|
||||
let version = List::download(List::WASM_URL).unwrap().latest_release;
|
||||
GHDownloader::wasm(version).download().unwrap();
|
||||
GHDownloader::wasm(version).unwrap().download().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use cache::get_or_download;
|
||||
use download::GHDownloader;
|
||||
use semver::Version;
|
||||
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
|
||||
pub mod cache;
|
||||
pub mod download;
|
||||
@@ -20,7 +21,7 @@ pub mod list;
|
||||
/// and not download it again.
|
||||
pub fn download_solc(
|
||||
cache_directory: &Path,
|
||||
version: Version,
|
||||
version: impl Into<VersionOrRequirement>,
|
||||
wasm: bool,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let downloader = if wasm {
|
||||
@@ -33,7 +34,7 @@ pub fn download_solc(
|
||||
GHDownloader::windows(version)
|
||||
} else {
|
||||
unimplemented!()
|
||||
};
|
||||
}?;
|
||||
|
||||
get_or_download(cache_directory, &downloader)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user