Compare commits

..

3 Commits

Author SHA1 Message Date
Marios Christou c3c128c45a fmt 2025-10-09 17:39:41 +03:00
Marios Christou 10472bae22 Merge branch 'main' into refactor_zombinet_substrate 2025-10-09 17:36:43 +03:00
Marios Christou 8bf0d69e54 Refactor: move chainspec patching and address conversion to common module, remove duplicated logic and tests 2025-10-08 16:33:19 +03:00
20 changed files with 1073 additions and 1329 deletions
Generated
+1 -1
View File
@@ -5573,7 +5573,6 @@ dependencies = [
"clap",
"moka",
"once_cell",
"regex",
"schemars 1.0.4",
"semver 1.0.26",
"serde",
@@ -5652,6 +5651,7 @@ dependencies = [
"alloy",
"anyhow",
"futures",
"regex",
"revive-common",
"revive-dt-common",
"schemars 1.0.4",
-1
View File
@@ -14,7 +14,6 @@ anyhow = { workspace = true }
clap = { workspace = true }
moka = { workspace = true, features = ["sync"] }
once_cell = { workspace = true }
regex = { workspace = true }
semver = { workspace = true }
serde = { workspace = true }
schemars = { workspace = true }
-265
View File
@@ -1,11 +1,6 @@
use crate::iterators::EitherIter;
use crate::types::VersionOrRequirement;
use anyhow::{Context as _, bail};
use regex::Regex;
use schemars::JsonSchema;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
@@ -38,19 +33,6 @@ impl Display for Mode {
}
}
impl FromStr for Mode {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parsed_mode = ParsedMode::from_str(s)?;
let mut iter = parsed_mode.to_modes();
let (Some(mode), None) = (iter.next(), iter.next()) else {
bail!("Failed to parse the mode")
};
Ok(mode)
}
}
impl Mode {
/// Return all of the available mode combinations.
pub fn all() -> impl Iterator<Item = &'static Mode> {
@@ -189,250 +171,3 @@ impl ModeOptimizerSetting {
!matches!(self, ModeOptimizerSetting::M0)
}
}
/// This represents a mode that has been parsed from test metadata.
///
/// Mode strings can take the following form (in pseudo-regex):
///
/// ```text
/// [YEILV][+-]? (M[0123sz])? <semver>?
/// ```
///
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
#[serde(try_from = "String", into = "String")]
pub struct ParsedMode {
pub pipeline: Option<ModePipeline>,
pub optimize_flag: Option<bool>,
pub optimize_setting: Option<ModeOptimizerSetting>,
pub version: Option<semver::VersionReq>,
}
impl FromStr for ParsedMode {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?x)
^
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
\s*
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
\s*
(?P<version>[>=<^]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
$
").unwrap()
});
let Some(caps) = REGEX.captures(s) else {
anyhow::bail!("Cannot parse mode '{s}' from string");
};
let pipeline = match caps.name("pipeline") {
Some(m) => Some(
ModePipeline::from_str(m.as_str())
.context("Failed to parse mode pipeline from string")?,
),
None => None,
};
let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+");
let optimize_setting = match caps.name("optimize_setting") {
Some(m) => Some(
ModeOptimizerSetting::from_str(m.as_str())
.context("Failed to parse optimizer setting from string")?,
),
None => None,
};
let version = match caps.name("version") {
Some(m) => Some(
semver::VersionReq::parse(m.as_str())
.map_err(|e| {
anyhow::anyhow!(
"Cannot parse the version requirement '{}': {e}",
m.as_str()
)
})
.context("Failed to parse semver requirement from mode string")?,
),
None => None,
};
Ok(ParsedMode {
pipeline,
optimize_flag,
optimize_setting,
version,
})
}
}
impl Display for ParsedMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut has_written = false;
if let Some(pipeline) = self.pipeline {
pipeline.fmt(f)?;
if let Some(optimize_flag) = self.optimize_flag {
f.write_str(if optimize_flag { "+" } else { "-" })?;
}
has_written = true;
}
if let Some(optimize_setting) = self.optimize_setting {
if has_written {
f.write_str(" ")?;
}
optimize_setting.fmt(f)?;
has_written = true;
}
if let Some(version) = &self.version {
if has_written {
f.write_str(" ")?;
}
version.fmt(f)?;
}
Ok(())
}
}
impl From<ParsedMode> for String {
fn from(parsed_mode: ParsedMode) -> Self {
parsed_mode.to_string()
}
}
impl TryFrom<String> for ParsedMode {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
ParsedMode::from_str(&value)
}
}
impl ParsedMode {
/// This takes a [`ParsedMode`] and expands it into a list of [`Mode`]s that we should try.
pub fn to_modes(&self) -> impl Iterator<Item = Mode> {
let pipeline_iter = self.pipeline.as_ref().map_or_else(
|| EitherIter::A(ModePipeline::test_cases()),
|p| EitherIter::B(std::iter::once(*p)),
);
let optimize_flag_setting = self.optimize_flag.map(|flag| {
if flag {
ModeOptimizerSetting::M3
} else {
ModeOptimizerSetting::M0
}
});
let optimize_flag_iter = match optimize_flag_setting {
Some(setting) => EitherIter::A(std::iter::once(setting)),
None => EitherIter::B(ModeOptimizerSetting::test_cases()),
};
let optimize_settings_iter = self.optimize_setting.as_ref().map_or_else(
|| EitherIter::A(optimize_flag_iter),
|s| EitherIter::B(std::iter::once(*s)),
);
pipeline_iter.flat_map(move |pipeline| {
optimize_settings_iter
.clone()
.map(move |optimize_setting| Mode {
pipeline,
optimize_setting,
version: self.version.clone(),
})
})
}
/// Return a set of [`Mode`]s that correspond to the given [`ParsedMode`]s.
/// This avoids any duplicate entries.
pub fn many_to_modes<'a>(
parsed: impl Iterator<Item = &'a ParsedMode>,
) -> impl Iterator<Item = Mode> {
let modes: HashSet<_> = parsed.flat_map(|p| p.to_modes()).collect();
modes.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parsed_mode_from_str() {
let strings = vec![
("Mz", "Mz"),
("Y", "Y"),
("Y+", "Y+"),
("Y-", "Y-"),
("E", "E"),
("E+", "E+"),
("E-", "E-"),
("Y M0", "Y M0"),
("Y M1", "Y M1"),
("Y M2", "Y M2"),
("Y M3", "Y M3"),
("Y Ms", "Y Ms"),
("Y Mz", "Y Mz"),
("E M0", "E M0"),
("E M1", "E M1"),
("E M2", "E M2"),
("E M3", "E M3"),
("E Ms", "E Ms"),
("E Mz", "E Mz"),
// When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
("Y 0.8.0", "Y ^0.8.0"),
("E+ 0.8.0", "E+ ^0.8.0"),
("Y M3 >=0.8.0", "Y M3 >=0.8.0"),
("E Mz <0.7.0", "E Mz <0.7.0"),
// We can parse +- _and_ M1/M2 but the latter takes priority.
("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
("E- M2 0.7.0", "E- M2 ^0.7.0"),
// We don't see this in the wild but it is parsed.
("<=0.8", "<=0.8"),
];
for (actual, expected) in strings {
let parsed = ParsedMode::from_str(actual)
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
assert_eq!(
expected,
parsed.to_string(),
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
);
}
}
#[test]
fn test_parsed_mode_to_test_modes() {
let strings = vec![
("Mz", vec!["Y Mz", "E Mz"]),
("Y", vec!["Y M0", "Y M3"]),
("E", vec!["E M0", "E M3"]),
("Y+", vec!["Y M3"]),
("Y-", vec!["Y M0"]),
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
(
"<=0.8",
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
),
];
for (actual, expected) in strings {
let parsed = ParsedMode::from_str(actual)
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
let actual_set: HashSet<_> = parsed.to_modes().map(|m| m.to_string()).collect();
assert_eq!(
expected_set, actual_set,
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
);
}
}
}
+39 -68
View File
@@ -12,18 +12,19 @@ use std::{
use alloy::{
genesis::Genesis,
hex::ToHexExt,
network::EthereumWallet,
primitives::{B256, FixedBytes, U256},
primitives::{FixedBytes, U256},
signers::local::PrivateKeySigner,
};
use clap::{Parser, ValueEnum, ValueHint};
use revive_dt_common::types::PlatformIdentifier;
use semver::Version;
use serde::{Deserialize, Serialize, Serializer};
use serde::{Serialize, Serializer};
use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
use temp_dir::TempDir;
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
#[command(name = "retester")]
pub enum Context {
/// Executes tests in the MatterLabs format differentially on multiple targets concurrently.
@@ -199,17 +200,7 @@ impl AsRef<ReportConfiguration> for Context {
}
}
impl AsRef<IgnoreSuccessConfiguration> for Context {
fn as_ref(&self) -> &IgnoreSuccessConfiguration {
match self {
Self::Test(context) => context.as_ref().as_ref(),
Self::Benchmark(..) => unreachable!(),
Self::ExportJsonSchema => unreachable!(),
}
}
}
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct TestExecutionContext {
/// The set of platforms that the differential tests should run on.
#[arg(
@@ -291,13 +282,9 @@ pub struct TestExecutionContext {
/// Configuration parameters for the report.
#[clap(flatten, next_help_heading = "Report Configuration")]
pub report_configuration: ReportConfiguration,
/// Configuration parameters for ignoring certain test cases based on the report
#[clap(flatten, next_help_heading = "Ignore Success Configuration")]
pub ignore_success_configuration: IgnoreSuccessConfiguration,
}
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct BenchmarkingContext {
/// The working directory that the program will use for all of the temporary artifacts needed at
/// runtime.
@@ -474,12 +461,6 @@ impl AsRef<ReportConfiguration> for TestExecutionContext {
}
}
impl AsRef<IgnoreSuccessConfiguration> for TestExecutionContext {
fn as_ref(&self) -> &IgnoreSuccessConfiguration {
&self.ignore_success_configuration
}
}
impl Default for BenchmarkingContext {
fn default() -> Self {
Self::parse_from(["execution-context"])
@@ -571,7 +552,7 @@ impl AsRef<ReportConfiguration> for BenchmarkingContext {
}
/// A set of configuration parameters for the corpus files to use for the execution.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct CorpusConfiguration {
/// A list of test corpus JSON files to be tested.
#[arg(short = 'c', long = "corpus")]
@@ -579,7 +560,7 @@ pub struct CorpusConfiguration {
}
/// A set of configuration parameters for Solc.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct SolcConfiguration {
/// Specifies the default version of the Solc compiler that should be used if there is no
/// override specified by one of the test cases.
@@ -588,7 +569,7 @@ pub struct SolcConfiguration {
}
/// A set of configuration parameters for Resolc.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct ResolcConfiguration {
/// Specifies the path of the resolc compiler to be used by the tool.
///
@@ -599,7 +580,7 @@ pub struct ResolcConfiguration {
}
/// A set of configuration parameters for Polkadot Parachain.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct PolkadotParachainConfiguration {
/// Specifies the path of the polkadot-parachain node to be used by the tool.
///
@@ -623,7 +604,7 @@ pub struct PolkadotParachainConfiguration {
}
/// A set of configuration parameters for Geth.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct GethConfiguration {
/// Specifies the path of the geth node to be used by the tool.
///
@@ -643,7 +624,7 @@ pub struct GethConfiguration {
}
/// A set of configuration parameters for kurtosis.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct KurtosisConfiguration {
/// Specifies the path of the kurtosis node to be used by the tool.
///
@@ -658,7 +639,7 @@ pub struct KurtosisConfiguration {
}
/// A set of configuration parameters for Kitchensink.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct KitchensinkConfiguration {
/// Specifies the path of the kitchensink node to be used by the tool.
///
@@ -682,7 +663,7 @@ pub struct KitchensinkConfiguration {
}
/// A set of configuration parameters for the revive dev node.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct ReviveDevNodeConfiguration {
/// Specifies the path of the revive dev node to be used by the tool.
///
@@ -714,7 +695,7 @@ pub struct ReviveDevNodeConfiguration {
}
/// A set of configuration parameters for the ETH RPC.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct EthRpcConfiguration {
/// Specifies the path of the ETH RPC to be used by the tool.
///
@@ -734,7 +715,7 @@ pub struct EthRpcConfiguration {
}
/// A set of configuration parameters for the genesis.
#[derive(Clone, Debug, Default, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, Parser, Serialize)]
pub struct GenesisConfiguration {
/// Specifies the path of the genesis file to use for the nodes that are started.
///
@@ -772,14 +753,15 @@ impl GenesisConfiguration {
}
/// A set of configuration parameters for the wallet.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct WalletConfiguration {
/// The private key of the default signer.
#[clap(
long = "wallet.default-private-key",
default_value = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"
)]
default_key: B256,
#[serde(serialize_with = "serialize_private_key")]
default_key: PrivateKeySigner,
/// This argument controls which private keys the nodes should have access to and be added to
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
@@ -797,8 +779,7 @@ impl WalletConfiguration {
pub fn wallet(&self) -> Arc<EthereumWallet> {
self.wallet
.get_or_init(|| {
let mut wallet =
EthereumWallet::new(PrivateKeySigner::from_bytes(&self.default_key).unwrap());
let mut wallet = EthereumWallet::new(self.default_key.clone());
for signer in (1..=self.additional_keys)
.map(|id| U256::from(id))
.map(|id| id.to_be_bytes::<32>())
@@ -816,8 +797,15 @@ impl WalletConfiguration {
}
}
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
value.to_bytes().encode_hex().serialize(serializer)
}
/// A set of configuration for concurrency.
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct ConcurrencyConfiguration {
/// Determines the amount of nodes that will be spawned for each chain.
#[clap(long = "concurrency.number-of-nodes", default_value_t = 5)]
@@ -855,14 +843,14 @@ impl ConcurrencyConfiguration {
}
}
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct CompilationConfiguration {
/// Controls if the compilation cache should be invalidated or not.
#[arg(long = "compilation.invalidate-cache")]
pub invalidate_compilation_cache: bool,
}
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[derive(Clone, Debug, Parser, Serialize)]
pub struct ReportConfiguration {
/// Controls if the compiler input is included in the final report.
#[clap(long = "report.include-compiler-input")]
@@ -873,13 +861,6 @@ pub struct ReportConfiguration {
pub include_compiler_output: bool,
}
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
pub struct IgnoreSuccessConfiguration {
/// The path of the report generated by the tool to use to ignore the cases that succeeded.
#[clap(long = "ignore-success.report-path")]
pub path: Option<PathBuf>,
}
/// Represents the working directory that the program uses.
#[derive(Debug, Clone)]
pub enum WorkingDirectoryConfiguration {
@@ -889,24 +870,6 @@ pub enum WorkingDirectoryConfiguration {
Path(PathBuf),
}
impl Serialize for WorkingDirectoryConfiguration {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.as_path().serialize(serializer)
}
}
impl<'a> Deserialize<'a> for WorkingDirectoryConfiguration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
PathBuf::deserialize(deserializer).map(Self::Path)
}
}
impl WorkingDirectoryConfiguration {
pub fn as_path(&self) -> &Path {
self.as_ref()
@@ -956,6 +919,15 @@ impl Display for WorkingDirectoryConfiguration {
}
}
impl Serialize for WorkingDirectoryConfiguration {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_path().serialize(serializer)
}
}
fn parse_duration(s: &str) -> anyhow::Result<Duration> {
u64::from_str(s)
.map(Duration::from_millis)
@@ -1002,7 +974,6 @@ pub enum TestingPlatform {
Ord,
Hash,
Serialize,
Deserialize,
ValueEnum,
EnumString,
Display,
@@ -86,7 +86,6 @@ pub async fn handle_differential_benchmarks(
&full_context,
metadata_files.iter(),
&platforms_and_nodes,
None,
reporter.clone(),
)
.await
@@ -10,7 +10,7 @@ use std::{
use ansi_term::{ANSIStrings, Color};
use anyhow::Context as _;
use futures::{FutureExt, StreamExt};
use revive_dt_common::{cached_fs::read_to_string, types::PrivateKeyAllocator};
use revive_dt_common::types::PrivateKeyAllocator;
use revive_dt_core::Platform;
use tokio::sync::{Mutex, RwLock, Semaphore};
use tracing::{Instrument, error, info, info_span, instrument};
@@ -72,20 +72,11 @@ pub async fn handle_differential_tests(
info!("Spawned the platform nodes");
// Preparing test definitions.
let only_execute_failed_tests = match context.ignore_success_configuration.path.as_ref() {
Some(path) => {
let report = read_to_string(path)
.context("Failed to read the report file to ignore the succeeding test cases")?;
Some(serde_json::from_str(&report).context("Failed to deserialize report")?)
}
None => None,
};
let full_context = Context::Test(Box::new(context.clone()));
let test_definitions = create_test_definitions_stream(
&full_context,
metadata_files.iter(),
&platforms_and_nodes,
only_execute_failed_tests.as_ref(),
reporter.clone(),
)
.await
@@ -318,14 +309,7 @@ async fn start_cli_reporting_task(output_format: OutputFormat, reporter: Reporte
status, success_count, failure_count, ignored_count,
)
.unwrap();
writeln!(buf).unwrap();
buf = tokio::task::spawn_blocking(move || {
buf.flush().unwrap();
buf
})
.await
.unwrap();
writeln!(buf).unwrap()
}
}
}
+5 -39
View File
@@ -5,8 +5,9 @@ use std::{borrow::Cow, path::Path};
use futures::{Stream, StreamExt, stream};
use indexmap::{IndexMap, indexmap};
use revive_dt_common::iterators::EitherIter;
use revive_dt_common::types::{ParsedMode, PlatformIdentifier};
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_config::Context;
use revive_dt_format::mode::ParsedMode;
use serde_json::{Value, json};
use revive_dt_compiler::Mode;
@@ -16,7 +17,7 @@ use revive_dt_format::{
metadata::MetadataFile,
};
use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::{ExecutionSpecificReporter, Report, Reporter, TestCaseStatus};
use revive_dt_report::{ExecutionSpecificReporter, Reporter};
use revive_dt_report::{TestSpecificReporter, TestSpecifier};
use tracing::{debug, error, info};
@@ -29,7 +30,6 @@ pub async fn create_test_definitions_stream<'a>(
context: &Context,
metadata_files: impl IntoIterator<Item = &'a MetadataFile>,
platforms_and_nodes: &'a BTreeMap<PlatformIdentifier, (&dyn Platform, NodePool)>,
only_execute_failed_tests: Option<&Report>,
reporter: Reporter,
) -> impl Stream<Item = TestDefinition<'a>> {
stream::iter(
@@ -140,7 +140,7 @@ pub async fn create_test_definitions_stream<'a>(
)
// Filter out the test cases which are incompatible or that can't run in the current setup.
.filter_map(move |test| async move {
match test.check_compatibility(only_execute_failed_tests) {
match test.check_compatibility() {
Ok(()) => Some(test),
Err((reason, additional_information)) => {
debug!(
@@ -200,16 +200,12 @@ pub struct TestDefinition<'a> {
impl<'a> TestDefinition<'a> {
/// Checks if this test can be ran with the current configuration.
pub fn check_compatibility(
&self,
only_execute_failed_tests: Option<&Report>,
) -> TestCheckFunctionResult {
pub fn check_compatibility(&self) -> TestCheckFunctionResult {
self.check_metadata_file_ignored()?;
self.check_case_file_ignored()?;
self.check_target_compatibility()?;
self.check_evm_version_compatibility()?;
self.check_compiler_compatibility()?;
self.check_ignore_succeeded(only_execute_failed_tests)?;
Ok(())
}
@@ -317,36 +313,6 @@ impl<'a> TestDefinition<'a> {
))
}
}
/// Checks if the test case should be executed or not based on the passed report and whether the
/// user has instructed the tool to ignore the already succeeding test cases.
fn check_ignore_succeeded(
&self,
only_execute_failed_tests: Option<&Report>,
) -> TestCheckFunctionResult {
let Some(report) = only_execute_failed_tests else {
return Ok(());
};
let test_case_status = report
.test_case_information
.get(&(self.metadata_file_path.to_path_buf().into()))
.and_then(|obj| obj.get(&self.mode))
.and_then(|obj| obj.get(&self.case_idx))
.and_then(|obj| obj.status.as_ref());
match test_case_status {
Some(TestCaseStatus::Failed { .. }) => Ok(()),
Some(TestCaseStatus::Ignored { .. }) => Err((
"Ignored since it was ignored in a previous run",
indexmap! {},
)),
Some(TestCaseStatus::Succeeded { .. }) => {
Err(("Ignored since it succeeded in a prior run", indexmap! {}))
}
None => Ok(()),
}
}
}
pub struct TestPlatformInformation<'a> {
+1
View File
@@ -16,6 +16,7 @@ revive-common = { workspace = true }
alloy = { workspace = true }
anyhow = { workspace = true }
futures = { workspace = true }
regex = { workspace = true }
tracing = { workspace = true }
schemars = { workspace = true }
semver = { workspace = true }
+2 -5
View File
@@ -1,12 +1,9 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use revive_dt_common::{
macros::define_wrapper_type,
types::{Mode, ParsedMode},
};
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
use crate::steps::*;
use crate::{mode::ParsedMode, steps::*};
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
pub struct Case {
+1
View File
@@ -3,5 +3,6 @@
pub mod case;
pub mod corpus;
pub mod metadata;
pub mod mode;
pub mod steps;
pub mod traits;
+2 -2
View File
@@ -16,11 +16,11 @@ use revive_dt_common::{
cached_fs::read_to_string,
iterators::FilesWithExtensionIterator,
macros::define_wrapper_type,
types::{Mode, ParsedMode, VmIdentifier},
types::{Mode, VmIdentifier},
};
use tracing::error;
use crate::case::Case;
use crate::{case::Case, mode::ParsedMode};
pub const METADATA_FILE_EXTENSION: &str = "json";
pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol";
+257
View File
@@ -0,0 +1,257 @@
use anyhow::Context as _;
use regex::Regex;
use revive_dt_common::iterators::EitherIter;
use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
/// This represents a mode that has been parsed from test metadata.
///
/// Mode strings can take the following form (in pseudo-regex):
///
/// ```text
/// [YEILV][+-]? (M[0123sz])? <semver>?
/// ```
///
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
#[serde(try_from = "String", into = "String")]
pub struct ParsedMode {
pub pipeline: Option<ModePipeline>,
pub optimize_flag: Option<bool>,
pub optimize_setting: Option<ModeOptimizerSetting>,
pub version: Option<semver::VersionReq>,
}
impl FromStr for ParsedMode {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?x)
^
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
\s*
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
\s*
(?P<version>[>=<]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
$
").unwrap()
});
let Some(caps) = REGEX.captures(s) else {
anyhow::bail!("Cannot parse mode '{s}' from string");
};
let pipeline = match caps.name("pipeline") {
Some(m) => Some(
ModePipeline::from_str(m.as_str())
.context("Failed to parse mode pipeline from string")?,
),
None => None,
};
let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+");
let optimize_setting = match caps.name("optimize_setting") {
Some(m) => Some(
ModeOptimizerSetting::from_str(m.as_str())
.context("Failed to parse optimizer setting from string")?,
),
None => None,
};
let version = match caps.name("version") {
Some(m) => Some(
semver::VersionReq::parse(m.as_str())
.map_err(|e| {
anyhow::anyhow!(
"Cannot parse the version requirement '{}': {e}",
m.as_str()
)
})
.context("Failed to parse semver requirement from mode string")?,
),
None => None,
};
Ok(ParsedMode {
pipeline,
optimize_flag,
optimize_setting,
version,
})
}
}
impl Display for ParsedMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut has_written = false;
if let Some(pipeline) = self.pipeline {
pipeline.fmt(f)?;
if let Some(optimize_flag) = self.optimize_flag {
f.write_str(if optimize_flag { "+" } else { "-" })?;
}
has_written = true;
}
if let Some(optimize_setting) = self.optimize_setting {
if has_written {
f.write_str(" ")?;
}
optimize_setting.fmt(f)?;
has_written = true;
}
if let Some(version) = &self.version {
if has_written {
f.write_str(" ")?;
}
version.fmt(f)?;
}
Ok(())
}
}
impl From<ParsedMode> for String {
fn from(parsed_mode: ParsedMode) -> Self {
parsed_mode.to_string()
}
}
impl TryFrom<String> for ParsedMode {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
ParsedMode::from_str(&value)
}
}
impl ParsedMode {
/// This takes a [`ParsedMode`] and expands it into a list of [`Mode`]s that we should try.
pub fn to_modes(&self) -> impl Iterator<Item = Mode> {
let pipeline_iter = self.pipeline.as_ref().map_or_else(
|| EitherIter::A(ModePipeline::test_cases()),
|p| EitherIter::B(std::iter::once(*p)),
);
let optimize_flag_setting = self.optimize_flag.map(|flag| {
if flag {
ModeOptimizerSetting::M3
} else {
ModeOptimizerSetting::M0
}
});
let optimize_flag_iter = match optimize_flag_setting {
Some(setting) => EitherIter::A(std::iter::once(setting)),
None => EitherIter::B(ModeOptimizerSetting::test_cases()),
};
let optimize_settings_iter = self.optimize_setting.as_ref().map_or_else(
|| EitherIter::A(optimize_flag_iter),
|s| EitherIter::B(std::iter::once(*s)),
);
pipeline_iter.flat_map(move |pipeline| {
optimize_settings_iter
.clone()
.map(move |optimize_setting| Mode {
pipeline,
optimize_setting,
version: self.version.clone(),
})
})
}
/// Return a set of [`Mode`]s that correspond to the given [`ParsedMode`]s.
/// This avoids any duplicate entries.
pub fn many_to_modes<'a>(
parsed: impl Iterator<Item = &'a ParsedMode>,
) -> impl Iterator<Item = Mode> {
let modes: HashSet<_> = parsed.flat_map(|p| p.to_modes()).collect();
modes.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parsed_mode_from_str() {
let strings = vec![
("Mz", "Mz"),
("Y", "Y"),
("Y+", "Y+"),
("Y-", "Y-"),
("E", "E"),
("E+", "E+"),
("E-", "E-"),
("Y M0", "Y M0"),
("Y M1", "Y M1"),
("Y M2", "Y M2"),
("Y M3", "Y M3"),
("Y Ms", "Y Ms"),
("Y Mz", "Y Mz"),
("E M0", "E M0"),
("E M1", "E M1"),
("E M2", "E M2"),
("E M3", "E M3"),
("E Ms", "E Ms"),
("E Mz", "E Mz"),
// When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
("Y 0.8.0", "Y ^0.8.0"),
("E+ 0.8.0", "E+ ^0.8.0"),
("Y M3 >=0.8.0", "Y M3 >=0.8.0"),
("E Mz <0.7.0", "E Mz <0.7.0"),
// We can parse +- _and_ M1/M2 but the latter takes priority.
("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
("E- M2 0.7.0", "E- M2 ^0.7.0"),
// We don't see this in the wild but it is parsed.
("<=0.8", "<=0.8"),
];
for (actual, expected) in strings {
let parsed = ParsedMode::from_str(actual)
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
assert_eq!(
expected,
parsed.to_string(),
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
);
}
}
#[test]
fn test_parsed_mode_to_test_modes() {
let strings = vec![
("Mz", vec!["Y Mz", "E Mz"]),
("Y", vec!["Y M0", "Y M3"]),
("E", vec!["E M0", "E M3"]),
("Y+", vec!["Y M3"]),
("Y-", vec!["Y M0"]),
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
(
"<=0.8",
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
),
];
for (actual, expected) in strings {
let parsed = ParsedMode::from_str(actual)
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
let actual_set: HashSet<_> = parsed.to_modes().map(|m| m.to_string()).collect();
assert_eq!(
expected_set, actual_set,
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
);
}
}
}
@@ -0,0 +1,193 @@
use alloy::{
genesis::{Genesis, GenesisAccount},
network::{Ethereum, NetworkWallet},
primitives::{Address, U256},
};
use anyhow::{Context, Result};
use serde_json::{Value as JsonValue, json};
use sp_core::crypto::Ss58Codec;
use sp_runtime::AccountId32;
use std::{fs::File, path::Path, process::Command};
pub fn export_and_patch_chainspec_json(
binary_path: &Path,
export_command: &str,
chain_arg: &str,
output_path: &Path,
genesis: &mut Genesis,
wallet: &impl NetworkWallet<Ethereum>,
initial_balance: u128,
) -> Result<()> {
// Note: we do not pipe the logs of this process to a separate file since this is just a
// once-off export of the default chain spec and not part of the long-running node process.
let output = Command::new(binary_path)
.arg(export_command)
.arg("--chain")
.arg(chain_arg)
.env_remove("RUST_LOG")
.output()
.context("Failed to export the chain-spec")?;
if !output.status.success() {
anyhow::bail!(
"Export chain-spec failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let content =
String::from_utf8(output.stdout).context("Failed to decode chain-spec output as UTF-8")?;
let mut chainspec_json: JsonValue =
serde_json::from_str(&content).context("Failed to parse chain spec JSON")?;
let existing_chainspec_balances =
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"]
.as_array()
.cloned()
.unwrap_or_default();
let mut merged_balances: Vec<(String, u128)> = existing_chainspec_balances
.into_iter()
.filter_map(|val| {
if let Some(arr) = val.as_array() {
if arr.len() == 2 {
let account = arr[0].as_str()?.to_string();
let balance = arr[1].as_f64()? as u128;
return Some((account, balance));
}
}
None
})
.collect();
for signer_address in wallet.signer_addresses() {
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(initial_balance)));
}
let mut eth_balances =
crate::node_implementations::common::chainspec::extract_balance_from_genesis_file(genesis)
.context("Failed to extract balances from EVM genesis JSON")?;
merged_balances.append(&mut eth_balances);
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] =
json!(merged_balances);
serde_json::to_writer_pretty(
File::create(output_path).context("Failed to create chainspec file")?,
&chainspec_json,
)
.context("Failed to write chainspec JSON")?;
Ok(())
}
pub fn extract_balance_from_genesis_file(genesis: &Genesis) -> anyhow::Result<Vec<(String, u128)>> {
genesis
.alloc
.iter()
.try_fold(Vec::new(), |mut vec, (address, acc)| {
let substrate_address = eth_to_polkadot_address(address);
let balance = acc.balance.try_into()?;
vec.push((substrate_address, balance));
Ok(vec)
})
}
pub fn eth_to_polkadot_address(address: &Address) -> String {
let eth_bytes = address.0.0;
let mut padded = [0xEEu8; 32];
padded[..20].copy_from_slice(&eth_bytes);
let account_id = AccountId32::from(padded);
account_id.to_ss58check()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_genesis_alloc() {
// Create test genesis file
let genesis_json = r#"
{
"alloc": {
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": { "balance": "1000000000000000000" },
"0x0000000000000000000000000000000000000000": { "balance": "0xDE0B6B3A7640000" },
"0xffffffffffffffffffffffffffffffffffffffff": { "balance": "123456789" }
}
}
"#;
let result =
extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
.unwrap();
let result_map: std::collections::HashMap<_, _> = result.into_iter().collect();
assert_eq!(
result_map.get("5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV"),
Some(&1_000_000_000_000_000_000u128)
);
assert_eq!(
result_map.get("5C4hrfjw9DjXZTzV3MwzrrAr9P1MLDHajjSidz9bR544LEq1"),
Some(&1_000_000_000_000_000_000u128)
);
assert_eq!(
result_map.get("5HrN7fHLXWcFiXPwwtq2EkSGns9eMmoUQnbVKweNz3VVr6N4"),
Some(&123_456_789u128)
);
}
#[test]
fn test_eth_to_polkadot_address() {
let cases = vec![
(
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV",
),
(
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV",
),
(
"0x0000000000000000000000000000000000000000",
"5C4hrfjw9DjXZTzV3MwzrrAr9P1MLDHajjSidz9bR544LEq1",
),
(
"0xffffffffffffffffffffffffffffffffffffffff",
"5HrN7fHLXWcFiXPwwtq2EkSGns9eMmoUQnbVKweNz3VVr6N4",
),
];
for (eth_addr, expected_ss58) in cases {
let result = eth_to_polkadot_address(&eth_addr.parse().unwrap());
assert_eq!(
result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}"
);
}
}
#[test]
fn print_eth_to_polkadot_mappings() {
let eth_addresses = vec![
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"0xffffffffffffffffffffffffffffffffffffffff",
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
];
for eth_addr in eth_addresses {
let ss58 = eth_to_polkadot_address(&eth_addr.parse().unwrap());
println!("Ethereum: {eth_addr} -> Polkadot SS58: {ss58}");
}
}
}
@@ -0,0 +1,3 @@
pub(crate) mod chainspec;
pub(crate) mod process;
pub(crate) mod revive;
@@ -0,0 +1,60 @@
use anyhow::Result;
use std::{
process::{Command, Stdio},
{path::Path, time::Duration},
};
use crate::helpers::{Process, ProcessReadinessWaitBehavior};
const PROXY_LOG_ENV: &str = "info,eth-rpc=debug";
pub fn spawn_eth_rpc_process(
logs_directory: &Path,
eth_proxy_binary: &Path,
node_rpc_url: &str,
eth_rpc_port: u16,
extra_args: &[&str],
ready_marker: &str,
) -> anyhow::Result<Process> {
let ready_marker = ready_marker.to_owned();
Process::new(
"proxy",
logs_directory,
eth_proxy_binary,
|command, stdout_file, stderr_file| {
command
.arg("--node-rpc-url")
.arg(node_rpc_url)
.arg("--rpc-cors")
.arg("all")
.arg("--rpc-max-connections")
.arg(u32::MAX.to_string())
.arg("--rpc-port")
.arg(eth_rpc_port.to_string())
.env("RUST_LOG", PROXY_LOG_ENV);
for arg in extra_args {
command.arg(arg);
}
command.stdout(stdout_file).stderr(stderr_file);
},
ProcessReadinessWaitBehavior::TimeBoundedWaitFunction {
max_wait_duration: Duration::from_secs(30),
check_function: Box::new(move |_, stderr_line| match stderr_line {
Some(line) => Ok(line.contains(&ready_marker)),
None => Ok(false),
}),
},
)
}
pub fn command_version(command_binary: &Path) -> Result<String> {
let output = Command::new(command_binary)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?
.wait_with_output()?
.stdout;
Ok(String::from_utf8_lossy(&output).trim().to_string())
}
@@ -0,0 +1,434 @@
use alloy::{
consensus::{BlockHeader, TxEnvelope},
network::{
Ethereum, Network, TransactionBuilder, TransactionBuilderError, UnbuiltTransactionError,
},
primitives::{Address, B64, B256, BlockNumber, Bloom, Bytes, U256},
rpc::types::eth::{Block, Header, Transaction},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReviveNetwork;
impl Network for ReviveNetwork {
type TxType = <Ethereum as Network>::TxType;
type TxEnvelope = <Ethereum as Network>::TxEnvelope;
type UnsignedTx = <Ethereum as Network>::UnsignedTx;
type ReceiptEnvelope = <Ethereum as Network>::ReceiptEnvelope;
type Header = ReviveHeader;
type TransactionRequest = <Ethereum as Network>::TransactionRequest;
type TransactionResponse = <Ethereum as Network>::TransactionResponse;
type ReceiptResponse = <Ethereum as Network>::ReceiptResponse;
type HeaderResponse = Header<ReviveHeader>;
type BlockResponse = Block<Transaction<TxEnvelope>, Header<ReviveHeader>>;
}
impl TransactionBuilder<ReviveNetwork> for <Ethereum as Network>::TransactionRequest {
fn chain_id(&self) -> Option<alloy::primitives::ChainId> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::chain_id(self)
}
fn set_chain_id(&mut self, chain_id: alloy::primitives::ChainId) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_chain_id(
self, chain_id,
)
}
fn nonce(&self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::nonce(self)
}
fn set_nonce(&mut self, nonce: u64) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_nonce(
self, nonce,
)
}
fn take_nonce(&mut self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::take_nonce(
self,
)
}
fn input(&self) -> Option<&alloy::primitives::Bytes> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::input(self)
}
fn set_input<T: Into<alloy::primitives::Bytes>>(&mut self, input: T) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_input(
self, input,
)
}
fn from(&self) -> Option<Address> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::from(self)
}
fn set_from(&mut self, from: Address) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_from(
self, from,
)
}
fn kind(&self) -> Option<alloy::primitives::TxKind> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::kind(self)
}
fn clear_kind(&mut self) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::clear_kind(
self,
)
}
fn set_kind(&mut self, kind: alloy::primitives::TxKind) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_kind(
self, kind,
)
}
fn value(&self) -> Option<alloy::primitives::U256> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::value(self)
}
fn set_value(&mut self, value: alloy::primitives::U256) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_value(
self, value,
)
}
fn gas_price(&self) -> Option<u128> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::gas_price(self)
}
fn set_gas_price(&mut self, gas_price: u128) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_gas_price(
self, gas_price,
)
}
fn max_fee_per_gas(&self) -> Option<u128> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::max_fee_per_gas(
self,
)
}
fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_max_fee_per_gas(
self, max_fee_per_gas
)
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::max_priority_fee_per_gas(
self,
)
}
fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_max_priority_fee_per_gas(
self, max_priority_fee_per_gas
)
}
fn gas_limit(&self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::gas_limit(self)
}
fn set_gas_limit(&mut self, gas_limit: u64) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_gas_limit(
self, gas_limit,
)
}
fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::access_list(
self,
)
}
fn set_access_list(&mut self, access_list: alloy::rpc::types::AccessList) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_access_list(
self,
access_list,
)
}
fn complete_type(
&self,
ty: <ReviveNetwork as Network>::TxType,
) -> Result<(), Vec<&'static str>> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::complete_type(
self, ty,
)
}
fn can_submit(&self) -> bool {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::can_submit(
self,
)
}
fn can_build(&self) -> bool {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::can_build(self)
}
fn output_tx_type(&self) -> <ReviveNetwork as Network>::TxType {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::output_tx_type(
self,
)
}
fn output_tx_type_checked(&self) -> Option<<ReviveNetwork as Network>::TxType> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::output_tx_type_checked(
self,
)
}
fn prep_for_submission(&mut self) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::prep_for_submission(
self,
)
}
fn build_unsigned(
self,
) -> alloy::network::BuildResult<<ReviveNetwork as Network>::UnsignedTx, ReviveNetwork> {
let result = <<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::build_unsigned(
self,
);
match result {
Ok(unsigned_tx) => Ok(unsigned_tx),
Err(UnbuiltTransactionError { request, error }) => {
Err(UnbuiltTransactionError::<ReviveNetwork> {
request,
error: match error {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items) => {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items)
}
TransactionBuilderError::UnsupportedSignatureType => {
TransactionBuilderError::UnsupportedSignatureType
}
TransactionBuilderError::Signer(error) => {
TransactionBuilderError::Signer(error)
}
TransactionBuilderError::Custom(error) => {
TransactionBuilderError::Custom(error)
}
},
})
}
}
}
async fn build<W: alloy::network::NetworkWallet<ReviveNetwork>>(
self,
wallet: &W,
) -> Result<<ReviveNetwork as Network>::TxEnvelope, TransactionBuilderError<ReviveNetwork>>
{
Ok(wallet.sign_request(self).await?)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReviveHeader {
/// The Keccak 256-bit hash of the parent
/// blocks header, in its entirety; formally Hp.
pub parent_hash: B256,
/// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho.
#[serde(rename = "sha3Uncles", alias = "ommersHash")]
pub ommers_hash: B256,
/// The 160-bit address to which all fees collected from the successful mining of this block
/// be transferred; formally Hc.
#[serde(rename = "miner", alias = "beneficiary")]
pub beneficiary: Address,
/// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
/// executed and finalisations applied; formally Hr.
pub state_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// transaction in the transactions list portion of the block; formally Ht.
pub transactions_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
/// of each transaction in the transactions list portion of the block; formally He.
pub receipts_root: B256,
/// The Bloom filter composed from indexable information (logger address and log topics)
/// contained in each log entry from the receipt of each transaction in the transactions list;
/// formally Hb.
pub logs_bloom: Bloom,
/// A scalar value corresponding to the difficulty level of this block. This can be calculated
/// from the previous blocks difficulty level and the timestamp; formally Hd.
pub difficulty: U256,
/// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
/// zero; formally Hi.
#[serde(with = "alloy::serde::quantity")]
pub number: BlockNumber,
/// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
// This is the main difference over the Ethereum network implementation. We use u128 here and
// not u64.
#[serde(with = "alloy::serde::quantity")]
pub gas_limit: u128,
/// A scalar value equal to the total gas used in transactions in this block; formally Hg.
#[serde(with = "alloy::serde::quantity")]
pub gas_used: u64,
/// A scalar value equal to the reasonable output of Unixs time() at this blocks inception;
/// formally Hs.
#[serde(with = "alloy::serde::quantity")]
pub timestamp: u64,
/// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
/// fewer; formally Hx.
pub extra_data: Bytes,
/// A 256-bit hash which, combined with the
/// nonce, proves that a sufficient amount of computation has been carried out on this block;
/// formally Hm.
pub mix_hash: B256,
/// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
/// computation has been carried out on this block; formally Hn.
pub nonce: B64,
/// A scalar representing EIP1559 base fee which can move up or down each block according
/// to a formula which is a function of gas used in parent block and gas target
/// (block gas limit divided by elasticity multiplier) of parent block.
/// The algorithm results in the base fee per gas increasing when blocks are
/// above the gas target, and decreasing when blocks are below the gas target. The base fee per
/// gas is burned.
#[serde(
default,
with = "alloy::serde::quantity::opt",
skip_serializing_if = "Option::is_none"
)]
pub base_fee_per_gas: Option<u64>,
/// The Keccak 256-bit hash of the withdrawals list portion of this block.
/// <https://eips.ethereum.org/EIPS/eip-4895>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawals_root: Option<B256>,
/// The total amount of blob gas consumed by the transactions within the block, added in
/// EIP-4844.
#[serde(
default,
with = "alloy::serde::quantity::opt",
skip_serializing_if = "Option::is_none"
)]
pub blob_gas_used: Option<u64>,
/// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
/// with above-target blob gas consumption increase this value, blocks with below-target blob
/// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
#[serde(
default,
with = "alloy::serde::quantity::opt",
skip_serializing_if = "Option::is_none"
)]
pub excess_blob_gas: Option<u64>,
/// The hash of the parent beacon block's root is included in execution blocks, as proposed by
/// EIP-4788.
///
/// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
/// and more.
///
/// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_beacon_block_root: Option<B256>,
/// The Keccak 256-bit hash of the an RLP encoded list with each
/// [EIP-7685] request in the block body.
///
/// [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685
#[serde(default, skip_serializing_if = "Option::is_none")]
pub requests_hash: Option<B256>,
}
impl BlockHeader for ReviveHeader {
fn parent_hash(&self) -> B256 {
self.parent_hash
}
fn ommers_hash(&self) -> B256 {
self.ommers_hash
}
fn beneficiary(&self) -> Address {
self.beneficiary
}
fn state_root(&self) -> B256 {
self.state_root
}
fn transactions_root(&self) -> B256 {
self.transactions_root
}
fn receipts_root(&self) -> B256 {
self.receipts_root
}
fn withdrawals_root(&self) -> Option<B256> {
self.withdrawals_root
}
fn logs_bloom(&self) -> Bloom {
self.logs_bloom
}
fn difficulty(&self) -> U256 {
self.difficulty
}
fn number(&self) -> BlockNumber {
self.number
}
// There's sadly nothing that we can do about this. We're required to implement this trait on
// any type that represents a header and the gas limit type used here is a u64.
fn gas_limit(&self) -> u64 {
self.gas_limit.try_into().unwrap_or(u64::MAX)
}
fn gas_used(&self) -> u64 {
self.gas_used
}
fn timestamp(&self) -> u64 {
self.timestamp
}
fn mix_hash(&self) -> Option<B256> {
Some(self.mix_hash)
}
fn nonce(&self) -> Option<B64> {
Some(self.nonce)
}
fn base_fee_per_gas(&self) -> Option<u64> {
self.base_fee_per_gas
}
fn blob_gas_used(&self) -> Option<u64> {
self.blob_gas_used
}
fn excess_blob_gas(&self) -> Option<u64> {
self.excess_blob_gas
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.parent_beacon_block_root
}
fn requests_hash(&self) -> Option<B256> {
self.requests_hash
}
fn extra_data(&self) -> &Bytes {
&self.extra_data
}
}
@@ -1,3 +1,5 @@
mod common;
pub mod geth;
pub mod lighthouse_geth;
pub mod substrate;
+31 -674
View File
@@ -2,7 +2,6 @@ use std::{
fs::{create_dir_all, remove_dir_all},
path::PathBuf,
pin::Pin,
process::{Command, Stdio},
sync::{
Arc,
atomic::{AtomicU32, Ordering},
@@ -11,17 +10,10 @@ use std::{
};
use alloy::{
consensus::{BlockHeader, TxEnvelope},
eips::BlockNumberOrTag,
genesis::{Genesis, GenesisAccount},
network::{
Ethereum, EthereumWallet, Network, NetworkWallet, TransactionBuilder,
TransactionBuilderError, UnbuiltTransactionError,
},
primitives::{
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, StorageKey,
TxHash, U256,
},
genesis::Genesis,
network::EthereumWallet,
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256},
providers::{
Provider,
ext::DebugApi,
@@ -29,7 +21,6 @@ use alloy::{
},
rpc::types::{
EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest,
eth::{Block, Header, Transaction},
trace::geth::{
DiffMode, GethDebugTracingOptions, GethTrace, PreStateConfig, PreStateFrame,
},
@@ -40,13 +31,8 @@ use async_stream::stream;
use futures::Stream;
use revive_common::EVMVersion;
use revive_dt_common::fs::clear_directory;
use revive_dt_format::traits::ResolverApi;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use sp_core::crypto::Ss58Codec;
use sp_runtime::AccountId32;
use revive_dt_config::*;
use revive_dt_format::traits::ResolverApi;
use revive_dt_node_interaction::{EthereumNode, MinedBlockInformation};
use tokio::sync::OnceCell;
use tracing::instrument;
@@ -55,6 +41,11 @@ use crate::{
Node,
constants::{CHAIN_ID, INITIAL_BALANCE},
helpers::{Process, ProcessReadinessWaitBehavior},
node_implementations::common::{
chainspec::export_and_patch_chainspec_json,
process::{command_version, spawn_eth_rpc_process},
revive::ReviveNetwork,
},
provider_utils::{
ConcreteProvider, FallbackGasFiller, construct_concurrency_limited_provider,
execute_transaction,
@@ -96,7 +87,6 @@ impl SubstrateNode {
const BASE_PROXY_RPC_PORT: u16 = 8545;
const SUBSTRATE_LOG_ENV: &str = "error,evm=debug,sc_rpc_server=info,runtime::revive=debug";
const PROXY_LOG_ENV: &str = "info,eth-rpc=debug";
pub const KITCHENSINK_EXPORT_CHAINSPEC_COMMAND: &str = "export-chain-spec";
pub const REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND: &str = "build-spec";
@@ -150,72 +140,16 @@ impl SubstrateNode {
let template_chainspec_path = self.base_directory.join(Self::CHAIN_SPEC_JSON_FILE);
// Note: we do not pipe the logs of this process to a separate file since this is just a
// once-off export of the default chain spec and not part of the long-running node process.
let output = Command::new(&self.node_binary)
.arg(self.export_chainspec_command.as_str())
.arg("--chain")
.arg("dev")
.env_remove("RUST_LOG")
.output()
.context("Failed to export the chain-spec")?;
export_and_patch_chainspec_json(
&self.node_binary,
self.export_chainspec_command.as_str(),
"dev",
&template_chainspec_path,
&mut genesis,
&self.wallet,
INITIAL_BALANCE,
)?;
if !output.status.success() {
anyhow::bail!(
"Substrate-node export-chain-spec failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let content = String::from_utf8(output.stdout)
.context("Failed to decode Substrate export-chain-spec output as UTF-8")?;
let mut chainspec_json: JsonValue =
serde_json::from_str(&content).context("Failed to parse Substrate chain spec JSON")?;
let existing_chainspec_balances =
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"]
.as_array()
.cloned()
.unwrap_or_default();
let mut merged_balances: Vec<(String, u128)> = existing_chainspec_balances
.into_iter()
.filter_map(|val| {
if let Some(arr) = val.as_array() {
if arr.len() == 2 {
let account = arr[0].as_str()?.to_string();
let balance = arr[1].as_f64()? as u128;
return Some((account, balance));
}
}
None
})
.collect();
let mut eth_balances = {
for signer_address in
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{
// Note, the use of the entry API here means that we only modify the entries for any
// account that is not in the `alloc` field of the genesis state.
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
}
self.extract_balance_from_genesis_file(&genesis)
.context("Failed to extract balances from EVM genesis JSON")?
};
merged_balances.append(&mut eth_balances);
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] =
json!(merged_balances);
serde_json::to_writer_pretty(
std::fs::File::create(&template_chainspec_path)
.context("Failed to create substrate template chainspec file")?,
&chainspec_json,
)
.context("Failed to write substrate template chainspec JSON")?;
Ok(self)
}
@@ -277,32 +211,15 @@ impl SubstrateNode {
return Err(err);
}
}
let eth_proxy_process = Process::new(
"proxy",
let eth_proxy_process = spawn_eth_rpc_process(
self.logs_directory.as_path(),
self.eth_proxy_binary.as_path(),
|command, stdout_file, stderr_file| {
command
.arg("--dev")
.arg("--rpc-port")
.arg(proxy_rpc_port.to_string())
.arg("--node-rpc-url")
.arg(format!("ws://127.0.0.1:{substrate_rpc_port}"))
.arg("--rpc-max-connections")
.arg(u32::MAX.to_string())
.env("RUST_LOG", Self::PROXY_LOG_ENV)
.stdout(stdout_file)
.stderr(stderr_file);
},
ProcessReadinessWaitBehavior::TimeBoundedWaitFunction {
max_wait_duration: Duration::from_secs(30),
check_function: Box::new(|_, stderr_line| match stderr_line {
Some(line) => Ok(line.contains(Self::ETH_PROXY_READY_MARKER)),
None => Ok(false),
}),
},
&format!("ws://127.0.0.1:{substrate_rpc_port}"),
proxy_rpc_port,
&["--dev"], // extra args
Self::ETH_PROXY_READY_MARKER,
);
match eth_proxy_process {
Ok(process) => self.eth_proxy_process = Some(process),
Err(err) => {
@@ -316,41 +233,8 @@ impl SubstrateNode {
Ok(())
}
fn extract_balance_from_genesis_file(
&self,
genesis: &Genesis,
) -> anyhow::Result<Vec<(String, u128)>> {
genesis
.alloc
.iter()
.try_fold(Vec::new(), |mut vec, (address, acc)| {
let substrate_address = Self::eth_to_substrate_address(address);
let balance = acc.balance.try_into()?;
vec.push((substrate_address, balance));
Ok(vec)
})
}
fn eth_to_substrate_address(address: &Address) -> String {
let eth_bytes = address.0.0;
let mut padded = [0xEEu8; 32];
padded[..20].copy_from_slice(&eth_bytes);
let account_id = AccountId32::from(padded);
account_id.to_ss58check()
}
pub fn eth_rpc_version(&self) -> anyhow::Result<String> {
let output = Command::new(&self.eth_proxy_binary)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?
.wait_with_output()?
.stdout;
Ok(String::from_utf8_lossy(&output).trim().to_string())
command_version(&self.eth_proxy_binary)
}
async fn provider(
@@ -709,17 +593,7 @@ impl Node for SubstrateNode {
}
fn version(&self) -> anyhow::Result<String> {
let output = Command::new(&self.node_binary)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.context("Failed to spawn substrate --version")?
.wait_with_output()
.context("Failed to wait for substrate --version")?
.stdout;
Ok(String::from_utf8_lossy(&output).into())
command_version(&self.node_binary)
}
}
@@ -729,430 +603,6 @@ impl Drop for SubstrateNode {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReviveNetwork;
impl Network for ReviveNetwork {
type TxType = <Ethereum as Network>::TxType;
type TxEnvelope = <Ethereum as Network>::TxEnvelope;
type UnsignedTx = <Ethereum as Network>::UnsignedTx;
type ReceiptEnvelope = <Ethereum as Network>::ReceiptEnvelope;
type Header = ReviveHeader;
type TransactionRequest = <Ethereum as Network>::TransactionRequest;
type TransactionResponse = <Ethereum as Network>::TransactionResponse;
type ReceiptResponse = <Ethereum as Network>::ReceiptResponse;
type HeaderResponse = Header<ReviveHeader>;
type BlockResponse = Block<Transaction<TxEnvelope>, Header<ReviveHeader>>;
}
impl TransactionBuilder<ReviveNetwork> for <Ethereum as Network>::TransactionRequest {
fn chain_id(&self) -> Option<alloy::primitives::ChainId> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::chain_id(self)
}
fn set_chain_id(&mut self, chain_id: alloy::primitives::ChainId) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_chain_id(
self, chain_id,
)
}
fn nonce(&self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::nonce(self)
}
fn set_nonce(&mut self, nonce: u64) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_nonce(
self, nonce,
)
}
fn take_nonce(&mut self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::take_nonce(
self,
)
}
fn input(&self) -> Option<&alloy::primitives::Bytes> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::input(self)
}
fn set_input<T: Into<alloy::primitives::Bytes>>(&mut self, input: T) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_input(
self, input,
)
}
fn from(&self) -> Option<Address> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::from(self)
}
fn set_from(&mut self, from: Address) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_from(
self, from,
)
}
fn kind(&self) -> Option<alloy::primitives::TxKind> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::kind(self)
}
fn clear_kind(&mut self) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::clear_kind(
self,
)
}
fn set_kind(&mut self, kind: alloy::primitives::TxKind) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_kind(
self, kind,
)
}
fn value(&self) -> Option<alloy::primitives::U256> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::value(self)
}
fn set_value(&mut self, value: alloy::primitives::U256) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_value(
self, value,
)
}
fn gas_price(&self) -> Option<u128> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::gas_price(self)
}
fn set_gas_price(&mut self, gas_price: u128) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_gas_price(
self, gas_price,
)
}
fn max_fee_per_gas(&self) -> Option<u128> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::max_fee_per_gas(
self,
)
}
fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_max_fee_per_gas(
self, max_fee_per_gas
)
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::max_priority_fee_per_gas(
self,
)
}
fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_max_priority_fee_per_gas(
self, max_priority_fee_per_gas
)
}
fn gas_limit(&self) -> Option<u64> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::gas_limit(self)
}
fn set_gas_limit(&mut self, gas_limit: u64) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_gas_limit(
self, gas_limit,
)
}
fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::access_list(
self,
)
}
fn set_access_list(&mut self, access_list: alloy::rpc::types::AccessList) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_access_list(
self,
access_list,
)
}
fn complete_type(
&self,
ty: <ReviveNetwork as Network>::TxType,
) -> Result<(), Vec<&'static str>> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::complete_type(
self, ty,
)
}
fn can_submit(&self) -> bool {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::can_submit(
self,
)
}
fn can_build(&self) -> bool {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::can_build(self)
}
fn output_tx_type(&self) -> <ReviveNetwork as Network>::TxType {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::output_tx_type(
self,
)
}
fn output_tx_type_checked(&self) -> Option<<ReviveNetwork as Network>::TxType> {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::output_tx_type_checked(
self,
)
}
fn prep_for_submission(&mut self) {
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::prep_for_submission(
self,
)
}
fn build_unsigned(
self,
) -> alloy::network::BuildResult<<ReviveNetwork as Network>::UnsignedTx, ReviveNetwork> {
let result = <<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::build_unsigned(
self,
);
match result {
Ok(unsigned_tx) => Ok(unsigned_tx),
Err(UnbuiltTransactionError { request, error }) => {
Err(UnbuiltTransactionError::<ReviveNetwork> {
request,
error: match error {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items) => {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items)
}
TransactionBuilderError::UnsupportedSignatureType => {
TransactionBuilderError::UnsupportedSignatureType
}
TransactionBuilderError::Signer(error) => {
TransactionBuilderError::Signer(error)
}
TransactionBuilderError::Custom(error) => {
TransactionBuilderError::Custom(error)
}
},
})
}
}
}
async fn build<W: alloy::network::NetworkWallet<ReviveNetwork>>(
self,
wallet: &W,
) -> Result<<ReviveNetwork as Network>::TxEnvelope, TransactionBuilderError<ReviveNetwork>>
{
Ok(wallet.sign_request(self).await?)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReviveHeader {
/// The Keccak 256-bit hash of the parent
/// blocks header, in its entirety; formally Hp.
pub parent_hash: B256,
/// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho.
#[serde(rename = "sha3Uncles", alias = "ommersHash")]
pub ommers_hash: B256,
/// The 160-bit address to which all fees collected from the successful mining of this block
/// be transferred; formally Hc.
#[serde(rename = "miner", alias = "beneficiary")]
pub beneficiary: Address,
/// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
/// executed and finalisations applied; formally Hr.
pub state_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// transaction in the transactions list portion of the block; formally Ht.
pub transactions_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
/// of each transaction in the transactions list portion of the block; formally He.
pub receipts_root: B256,
/// The Bloom filter composed from indexable information (logger address and log topics)
/// contained in each log entry from the receipt of each transaction in the transactions list;
/// formally Hb.
pub logs_bloom: Bloom,
/// A scalar value corresponding to the difficulty level of this block. This can be calculated
/// from the previous blocks difficulty level and the timestamp; formally Hd.
pub difficulty: U256,
/// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
/// zero; formally Hi.
#[serde(with = "alloy::serde::quantity")]
pub number: BlockNumber,
/// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
// This is the main difference over the Ethereum network implementation. We use u128 here and
// not u64.
#[serde(with = "alloy::serde::quantity")]
pub gas_limit: u128,
/// A scalar value equal to the total gas used in transactions in this block; formally Hg.
#[serde(with = "alloy::serde::quantity")]
pub gas_used: u64,
/// A scalar value equal to the reasonable output of Unixs time() at this blocks inception;
/// formally Hs.
#[serde(with = "alloy::serde::quantity")]
pub timestamp: u64,
/// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
/// fewer; formally Hx.
pub extra_data: Bytes,
/// A 256-bit hash which, combined with the
/// nonce, proves that a sufficient amount of computation has been carried out on this block;
/// formally Hm.
pub mix_hash: B256,
/// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
/// computation has been carried out on this block; formally Hn.
pub nonce: B64,
/// A scalar representing EIP1559 base fee which can move up or down each block according
/// to a formula which is a function of gas used in parent block and gas target
/// (block gas limit divided by elasticity multiplier) of parent block.
/// The algorithm results in the base fee per gas increasing when blocks are
/// above the gas target, and decreasing when blocks are below the gas target. The base fee per
/// gas is burned.
#[serde(
default,
with = "alloy::serde::quantity::opt",
skip_serializing_if = "Option::is_none"
)]
pub base_fee_per_gas: Option<u64>,
/// The Keccak 256-bit hash of the withdrawals list portion of this block.
/// <https://eips.ethereum.org/EIPS/eip-4895>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub withdrawals_root: Option<B256>,
/// The total amount of blob gas consumed by the transactions within the block, added in
/// EIP-4844.
#[serde(
default,
with = "alloy::serde::quantity::opt",
skip_serializing_if = "Option::is_none"
)]
pub blob_gas_used: Option<u64>,
/// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
/// with above-target blob gas consumption increase this value, blocks with below-target blob
/// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
#[serde(
default,
with = "alloy::serde::quantity::opt",
skip_serializing_if = "Option::is_none"
)]
pub excess_blob_gas: Option<u64>,
/// The hash of the parent beacon block's root is included in execution blocks, as proposed by
/// EIP-4788.
///
/// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
/// and more.
///
/// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_beacon_block_root: Option<B256>,
/// The Keccak 256-bit hash of the an RLP encoded list with each
/// [EIP-7685] request in the block body.
///
/// [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685
#[serde(default, skip_serializing_if = "Option::is_none")]
pub requests_hash: Option<B256>,
}
impl BlockHeader for ReviveHeader {
fn parent_hash(&self) -> B256 {
self.parent_hash
}
fn ommers_hash(&self) -> B256 {
self.ommers_hash
}
fn beneficiary(&self) -> Address {
self.beneficiary
}
fn state_root(&self) -> B256 {
self.state_root
}
fn transactions_root(&self) -> B256 {
self.transactions_root
}
fn receipts_root(&self) -> B256 {
self.receipts_root
}
fn withdrawals_root(&self) -> Option<B256> {
self.withdrawals_root
}
fn logs_bloom(&self) -> Bloom {
self.logs_bloom
}
fn difficulty(&self) -> U256 {
self.difficulty
}
fn number(&self) -> BlockNumber {
self.number
}
// There's sadly nothing that we can do about this. We're required to implement this trait on
// any type that represents a header and the gas limit type used here is a u64.
fn gas_limit(&self) -> u64 {
self.gas_limit.try_into().unwrap_or(u64::MAX)
}
fn gas_used(&self) -> u64 {
self.gas_used
}
fn timestamp(&self) -> u64 {
self.timestamp
}
fn mix_hash(&self) -> Option<B256> {
Some(self.mix_hash)
}
fn nonce(&self) -> Option<B64> {
Some(self.nonce)
}
fn base_fee_per_gas(&self) -> Option<u64> {
self.base_fee_per_gas
}
fn blob_gas_used(&self) -> Option<u64> {
self.blob_gas_used
}
fn excess_blob_gas(&self) -> Option<u64> {
self.excess_blob_gas
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.parent_beacon_block_root
}
fn requests_hash(&self) -> Option<B256> {
self.requests_hash
}
fn extra_data(&self) -> &Bytes {
&self.extra_data
}
}
#[cfg(test)]
mod tests {
use alloy::rpc::types::TransactionRequest;
@@ -1161,7 +611,7 @@ mod tests {
use std::fs;
use super::*;
use crate::Node;
use crate::node_implementations::common::chainspec::eth_to_polkadot_address;
fn test_config() -> TestExecutionContext {
TestExecutionContext::default()
@@ -1274,12 +724,10 @@ mod tests {
let contents = fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec");
// Validate that the Substrate addresses derived from the Ethereum addresses are in the file
let first_eth_addr = SubstrateNode::eth_to_substrate_address(
&"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap(),
);
let second_eth_addr = SubstrateNode::eth_to_substrate_address(
&"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap(),
);
let first_eth_addr =
eth_to_polkadot_address(&"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap());
let second_eth_addr =
eth_to_polkadot_address(&"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap());
assert!(
contents.contains(&first_eth_addr),
@@ -1291,97 +739,6 @@ mod tests {
);
}
#[test]
#[ignore = "Ignored since they take a long time to run"]
fn test_parse_genesis_alloc() {
// Create test genesis file
let genesis_json = r#"
{
"alloc": {
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": { "balance": "1000000000000000000" },
"0x0000000000000000000000000000000000000000": { "balance": "0xDE0B6B3A7640000" },
"0xffffffffffffffffffffffffffffffffffffffff": { "balance": "123456789" }
}
}
"#;
let context = test_config();
let node = SubstrateNode::new(
context.kitchensink_configuration.path.clone(),
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
None,
&context,
);
let result = node
.extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
.unwrap();
let result_map: std::collections::HashMap<_, _> = result.into_iter().collect();
assert_eq!(
result_map.get("5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV"),
Some(&1_000_000_000_000_000_000u128)
);
assert_eq!(
result_map.get("5C4hrfjw9DjXZTzV3MwzrrAr9P1MLDHajjSidz9bR544LEq1"),
Some(&1_000_000_000_000_000_000u128)
);
assert_eq!(
result_map.get("5HrN7fHLXWcFiXPwwtq2EkSGns9eMmoUQnbVKweNz3VVr6N4"),
Some(&123_456_789u128)
);
}
#[test]
#[ignore = "Ignored since they take a long time to run"]
fn print_eth_to_substrate_mappings() {
let eth_addresses = vec![
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"0xffffffffffffffffffffffffffffffffffffffff",
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
];
for eth_addr in eth_addresses {
let ss58 = SubstrateNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
println!("Ethereum: {eth_addr} -> Substrate SS58: {ss58}");
}
}
#[test]
#[ignore = "Ignored since they take a long time to run"]
fn test_eth_to_substrate_address() {
let cases = vec![
(
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV",
),
(
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV",
),
(
"0x0000000000000000000000000000000000000000",
"5C4hrfjw9DjXZTzV3MwzrrAr9P1MLDHajjSidz9bR544LEq1",
),
(
"0xffffffffffffffffffffffffffffffffffffffff",
"5HrN7fHLXWcFiXPwwtq2EkSGns9eMmoUQnbVKweNz3VVr6N4",
),
];
for (eth_addr, expected_ss58) in cases {
let result = SubstrateNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
assert_eq!(
result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}"
);
}
}
#[test]
#[ignore = "Ignored since they take a long time to run"]
fn version_works() {
+33 -248
View File
@@ -30,7 +30,6 @@ use std::{
fs::{create_dir_all, remove_dir_all},
path::PathBuf,
pin::Pin,
process::{Command, Stdio},
sync::{
Arc,
atomic::{AtomicU32, Ordering},
@@ -40,8 +39,8 @@ use std::{
use alloy::{
eips::BlockNumberOrTag,
genesis::{Genesis, GenesisAccount},
network::{Ethereum, EthereumWallet, NetworkWallet},
genesis::Genesis,
network::EthereumWallet,
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256},
providers::{
Provider,
@@ -62,9 +61,6 @@ use revive_dt_common::fs::clear_directory;
use revive_dt_config::*;
use revive_dt_format::traits::ResolverApi;
use revive_dt_node_interaction::{EthereumNode, MinedBlockInformation};
use serde_json::{Value as JsonValue, json};
use sp_core::crypto::Ss58Codec;
use sp_runtime::AccountId32;
use tokio::sync::OnceCell;
use tracing::instrument;
use zombienet_sdk::{LocalFileSystem, NetworkConfigBuilder, NetworkConfigExt};
@@ -72,8 +68,12 @@ use zombienet_sdk::{LocalFileSystem, NetworkConfigBuilder, NetworkConfigExt};
use crate::{
Node,
constants::INITIAL_BALANCE,
helpers::{Process, ProcessReadinessWaitBehavior},
node_implementations::substrate::ReviveNetwork,
helpers::Process,
node_implementations::common::{
chainspec::export_and_patch_chainspec_json,
process::{command_version, spawn_eth_rpc_process},
revive::ReviveNetwork,
},
provider_utils::{
ConcreteProvider, FallbackGasFiller, construct_concurrency_limited_provider,
execute_transaction,
@@ -123,8 +123,6 @@ impl ZombienetNode {
const PARACHAIN_ID: u32 = 100;
const ETH_RPC_BASE_PORT: u16 = 8545;
const PROXY_LOG_ENV: &str = "info,eth-rpc=debug";
const ETH_RPC_READY_MARKER: &str = "Running JSON-RPC server";
const EXPORT_CHAINSPEC_COMMAND: &str = "build-spec";
@@ -165,7 +163,7 @@ impl ZombienetNode {
}
}
fn init(&mut self, genesis: Genesis) -> anyhow::Result<&mut Self> {
fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> {
let _ = clear_directory(&self.base_directory);
let _ = clear_directory(&self.logs_directory);
@@ -175,7 +173,16 @@ impl ZombienetNode {
.context("Failed to create logs directory for zombie node")?;
let template_chainspec_path = self.base_directory.join(Self::CHAIN_SPEC_JSON_FILE);
self.prepare_chainspec(template_chainspec_path.clone(), genesis)?;
export_and_patch_chainspec_json(
&self.polkadot_parachain_path,
Self::EXPORT_CHAINSPEC_COMMAND,
"asset-hub-westend-local",
&template_chainspec_path,
&mut genesis,
&self.wallet,
INITIAL_BALANCE,
)?;
let polkadot_parachain_path = self
.polkadot_parachain_path
.to_str()
@@ -242,31 +249,13 @@ impl ZombienetNode {
let node_url = format!("ws://localhost:{}", self.node_rpc_port.unwrap());
let eth_rpc_port = Self::ETH_RPC_BASE_PORT + self.id as u16;
let eth_rpc_process = Process::new(
"proxy",
let eth_rpc_process = spawn_eth_rpc_process(
self.logs_directory.as_path(),
self.eth_proxy_binary.as_path(),
|command, stdout_file, stderr_file| {
command
.arg("--node-rpc-url")
.arg(node_url)
.arg("--rpc-cors")
.arg("all")
.arg("--rpc-max-connections")
.arg(u32::MAX.to_string())
.arg("--rpc-port")
.arg(eth_rpc_port.to_string())
.env("RUST_LOG", Self::PROXY_LOG_ENV)
.stdout(stdout_file)
.stderr(stderr_file);
},
ProcessReadinessWaitBehavior::TimeBoundedWaitFunction {
max_wait_duration: Duration::from_secs(30),
check_function: Box::new(|_, stderr_line| match stderr_line {
Some(line) => Ok(line.contains(Self::ETH_RPC_READY_MARKER)),
None => Ok(false),
}),
},
&node_url,
eth_rpc_port,
&[], // extra args
Self::ETH_RPC_READY_MARKER,
);
match eth_rpc_process {
@@ -287,116 +276,8 @@ impl ZombienetNode {
Ok(())
}
fn prepare_chainspec(
&mut self,
template_chainspec_path: PathBuf,
mut genesis: Genesis,
) -> anyhow::Result<()> {
let output = Command::new(self.polkadot_parachain_path.as_path())
.arg(Self::EXPORT_CHAINSPEC_COMMAND)
.arg("--chain")
.arg("asset-hub-westend-local")
.env_remove("RUST_LOG")
.output()
.context("Failed to export the chainspec of the chain")?;
if !output.status.success() {
anyhow::bail!(
"Build chain-spec failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let content = String::from_utf8(output.stdout)
.context("Failed to decode collators chain-spec output as UTF-8")?;
let mut chainspec_json: JsonValue =
serde_json::from_str(&content).context("Failed to parse collators chain spec JSON")?;
let existing_chainspec_balances =
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"]
.as_array()
.cloned()
.unwrap_or_default();
let mut merged_balances: Vec<(String, u128)> = existing_chainspec_balances
.into_iter()
.filter_map(|val| {
if let Some(arr) = val.as_array() {
if arr.len() == 2 {
let account = arr[0].as_str()?.to_string();
let balance = arr[1].as_f64()? as u128;
return Some((account, balance));
}
}
None
})
.collect();
let mut eth_balances = {
for signer_address in
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{
// Note, the use of the entry API here means that we only modify the entries for any
// account that is not in the `alloc` field of the genesis state.
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
}
self.extract_balance_from_genesis_file(&genesis)
.context("Failed to extract balances from EVM genesis JSON")?
};
merged_balances.append(&mut eth_balances);
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] =
json!(merged_balances);
let writer = std::fs::File::create(&template_chainspec_path)
.context("Failed to create template chainspec file")?;
serde_json::to_writer_pretty(writer, &chainspec_json)
.context("Failed to write template chainspec JSON")?;
Ok(())
}
fn extract_balance_from_genesis_file(
&self,
genesis: &Genesis,
) -> anyhow::Result<Vec<(String, u128)>> {
genesis
.alloc
.iter()
.try_fold(Vec::new(), |mut vec, (address, acc)| {
let polkadot_address = Self::eth_to_polkadot_address(address);
let balance = acc.balance.try_into()?;
vec.push((polkadot_address, balance));
Ok(vec)
})
}
fn eth_to_polkadot_address(address: &Address) -> String {
let eth_bytes = address.0.0;
let mut padded = [0xEEu8; 32];
padded[..20].copy_from_slice(&eth_bytes);
let account_id = AccountId32::from(padded);
account_id.to_ss58check()
}
pub fn eth_rpc_version(&self) -> anyhow::Result<String> {
let output = Command::new(&self.eth_proxy_binary)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?
.wait_with_output()?
.stdout;
Ok(String::from_utf8_lossy(&output).trim().to_string())
command_version(&self.eth_proxy_binary)
}
async fn provider(
@@ -772,17 +653,7 @@ impl Node for ZombienetNode {
}
fn version(&self) -> anyhow::Result<String> {
let output = Command::new(&self.polkadot_parachain_path)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.context("Failed execute --version")?
.wait_with_output()
.context("Failed to wait --version")?
.stdout;
Ok(String::from_utf8_lossy(&output).into())
command_version(&self.polkadot_parachain_path)
}
}
@@ -796,7 +667,9 @@ impl Drop for ZombienetNode {
mod tests {
use alloy::rpc::types::TransactionRequest;
use crate::node_implementations::zombienet::tests::utils::shared_node;
use crate::node_implementations::{
common::chainspec::eth_to_polkadot_address, zombienet::tests::utils::shared_node,
};
use super::*;
@@ -901,12 +774,10 @@ mod tests {
std::fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec");
// Validate that the Polkadot addresses derived from the Ethereum addresses are in the file
let first_eth_addr = ZombienetNode::eth_to_polkadot_address(
&"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap(),
);
let second_eth_addr = ZombienetNode::eth_to_polkadot_address(
&"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap(),
);
let first_eth_addr =
eth_to_polkadot_address(&"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap());
let second_eth_addr =
eth_to_polkadot_address(&"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap());
assert!(
contents.contains(&first_eth_addr),
@@ -918,92 +789,6 @@ mod tests {
);
}
#[tokio::test]
async fn test_parse_genesis_alloc() {
// Create test genesis file
let genesis_json = r#"
{
"alloc": {
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": { "balance": "1000000000000000000" },
"0x0000000000000000000000000000000000000000": { "balance": "0xDE0B6B3A7640000" },
"0xffffffffffffffffffffffffffffffffffffffff": { "balance": "123456789" }
}
}
"#;
let context = test_config();
let node = ZombienetNode::new(
context.polkadot_parachain_configuration.path.clone(),
&context,
);
let result = node
.extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
.unwrap();
let result_map: std::collections::HashMap<_, _> = result.into_iter().collect();
assert_eq!(
result_map.get("5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV"),
Some(&1_000_000_000_000_000_000u128)
);
assert_eq!(
result_map.get("5C4hrfjw9DjXZTzV3MwzrrAr9P1MLDHajjSidz9bR544LEq1"),
Some(&1_000_000_000_000_000_000u128)
);
assert_eq!(
result_map.get("5HrN7fHLXWcFiXPwwtq2EkSGns9eMmoUQnbVKweNz3VVr6N4"),
Some(&123_456_789u128)
);
}
#[test]
fn print_eth_to_polkadot_mappings() {
let eth_addresses = vec![
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"0xffffffffffffffffffffffffffffffffffffffff",
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
];
for eth_addr in eth_addresses {
let ss58 = ZombienetNode::eth_to_polkadot_address(&eth_addr.parse().unwrap());
println!("Ethereum: {eth_addr} -> Polkadot SS58: {ss58}");
}
}
#[test]
fn test_eth_to_polkadot_address() {
let cases = vec![
(
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV",
),
(
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1",
"5FLneRcWAfk3X3tg6PuGyLNGAquPAZez5gpqvyuf3yUK8VaV",
),
(
"0x0000000000000000000000000000000000000000",
"5C4hrfjw9DjXZTzV3MwzrrAr9P1MLDHajjSidz9bR544LEq1",
),
(
"0xffffffffffffffffffffffffffffffffffffffff",
"5HrN7fHLXWcFiXPwwtq2EkSGns9eMmoUQnbVKweNz3VVr6N4",
),
];
for (eth_addr, expected_ss58) in cases {
let result = ZombienetNode::eth_to_polkadot_address(&eth_addr.parse().unwrap());
assert_eq!(
result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}"
);
}
}
#[test]
fn eth_rpc_version_works() {
// Arrange
+7 -7
View File
@@ -16,7 +16,7 @@ use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode};
use revive_dt_config::Context;
use revive_dt_format::{case::CaseIdx, corpus::Corpus, metadata::ContractInstance};
use semver::Version;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use serde_with::{DisplayFromStr, serde_as};
use tokio::sync::{
broadcast::{Sender, channel},
@@ -415,7 +415,7 @@ impl ReportAggregator {
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize)]
pub struct Report {
/// The context that the tool was started up with.
pub context: Context,
@@ -440,7 +440,7 @@ impl Report {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[derive(Clone, Debug, Serialize, Default)]
pub struct TestCaseReport {
/// Information on the status of the test case and whether it succeeded, failed, or was ignored.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -451,7 +451,7 @@ pub struct TestCaseReport {
/// Information related to the status of the test. Could be that the test succeeded, failed, or that
/// it was ignored.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "status")]
pub enum TestCaseStatus {
/// The test case succeeded.
@@ -475,7 +475,7 @@ pub enum TestCaseStatus {
}
/// Information related to the platform node that's being used to execute the step.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize)]
pub struct TestCaseNodeInformation {
/// The ID of the node that this case is being executed on.
pub id: usize,
@@ -486,7 +486,7 @@ pub struct TestCaseNodeInformation {
}
/// Execution information tied to the platform.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, Serialize)]
pub struct ExecutionInformation {
/// Information related to the node assigned to this test case.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -506,7 +506,7 @@ pub struct ExecutionInformation {
}
/// Information related to compilation
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "status")]
pub enum CompilationStatus {
/// The compilation was successful.