Compare commits

..

2 Commits

Author SHA1 Message Date
Omar Abdulla 983ee7f355 Merge remote-tracking branch 'origin/main' into refactor/update-compiler-semaphore 2025-10-06 01:27:35 +03:00
Omar Abdulla bd983a0919 Update compiler semaphore 2025-10-06 01:26:39 +03:00
42 changed files with 323 additions and 4759 deletions
-81
View File
@@ -15,31 +15,11 @@ concurrency:
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
POLKADOT_VERSION: polkadot-stable2506-2
jobs: jobs:
fmt:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install nightly toolchain
run: rustup toolchain install nightly
- name: Install rustfmt for nightly
run: rustup component add --toolchain nightly rustfmt
- name: Cargo fmt
run: cargo +nightly fmt --all -- --check
cache-polkadot: cache-polkadot:
name: Build and cache Polkadot binaries on ${{ matrix.os }} name: Build and cache Polkadot binaries on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: [fmt]
strategy: strategy:
matrix: matrix:
os: [ubuntu-24.04, macos-14] os: [ubuntu-24.04, macos-14]
@@ -86,37 +66,6 @@ jobs:
cd polkadot-sdk cd polkadot-sdk
cargo install --path substrate/frame/revive/rpc --bin eth-rpc cargo install --path substrate/frame/revive/rpc --bin eth-rpc
- name: Cache downloaded Polkadot binaries
id: cache-polkadot
uses: actions/cache@v3
with:
path: |
~/polkadot-cache/polkadot
~/polkadot-cache/polkadot-execute-worker
~/polkadot-cache/polkadot-prepare-worker
~/polkadot-cache/polkadot-parachain
key: polkadot-downloaded-${{ matrix.os }}-${{ env.POLKADOT_VERSION }}
- name: Download Polkadot binaries on macOS
if: matrix.os == 'macos-14' && steps.cache-polkadot.outputs.cache-hit != 'true'
run: |
mkdir -p ~/polkadot-cache
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-aarch64-apple-darwin -o ~/polkadot-cache/polkadot
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-execute-worker-aarch64-apple-darwin -o ~/polkadot-cache/polkadot-execute-worker
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-prepare-worker-aarch64-apple-darwin -o ~/polkadot-cache/polkadot-prepare-worker
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-parachain-aarch64-apple-darwin -o ~/polkadot-cache/polkadot-parachain
chmod +x ~/polkadot-cache/*
- name: Download Polkadot binaries on Ubuntu
if: matrix.os == 'ubuntu-24.04' && steps.cache-polkadot.outputs.cache-hit != 'true'
run: |
mkdir -p ~/polkadot-cache
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot -o ~/polkadot-cache/polkadot
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-execute-worker -o ~/polkadot-cache/polkadot-execute-worker
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-prepare-worker -o ~/polkadot-cache/polkadot-prepare-worker
curl -sL https://github.com/paritytech/polkadot-sdk/releases/download/${{ env.POLKADOT_VERSION }}/polkadot-parachain -o ~/polkadot-cache/polkadot-parachain
chmod +x ~/polkadot-cache/*
ci: ci:
name: CI on ${{ matrix.os }} name: CI on ${{ matrix.os }}
needs: cache-polkadot needs: cache-polkadot
@@ -137,24 +86,6 @@ jobs:
~/.cargo/bin/eth-rpc ~/.cargo/bin/eth-rpc
key: polkadot-binaries-${{ matrix.os }}-${{ hashFiles('polkadot-sdk/.git') }} key: polkadot-binaries-${{ matrix.os }}-${{ hashFiles('polkadot-sdk/.git') }}
- name: Restore downloaded Polkadot binaries from cache
uses: actions/cache@v3
with:
path: |
~/polkadot-cache/polkadot
~/polkadot-cache/polkadot-execute-worker
~/polkadot-cache/polkadot-prepare-worker
~/polkadot-cache/polkadot-parachain
key: polkadot-downloaded-${{ matrix.os }}-${{ env.POLKADOT_VERSION }}
- name: Install Polkadot binaries
run: |
sudo cp ~/polkadot-cache/polkadot /usr/local/bin/
sudo cp ~/polkadot-cache/polkadot-execute-worker /usr/local/bin/
sudo cp ~/polkadot-cache/polkadot-prepare-worker /usr/local/bin/
sudo cp ~/polkadot-cache/polkadot-parachain /usr/local/bin/
sudo chmod +x /usr/local/bin/polkadot*
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
@@ -239,18 +170,6 @@ jobs:
- name: Check resolc version - name: Check resolc version
run: resolc --version run: resolc --version
- name: Check polkadot version
run: polkadot --version
- name: Check polkadot-parachain version
run: polkadot-parachain --version
- name: Check polkadot-execute-worker version
run: polkadot-execute-worker --version
- name: Check polkadot-prepare-worker version
run: polkadot-prepare-worker --version
- name: Test Formatting - name: Test Formatting
run: make format run: make format
+1
View File
@@ -13,3 +13,4 @@ resolc-compiler-tests
workdir workdir
!/schema.json !/schema.json
!/dev-genesis.json
Generated
+57 -2147
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -73,8 +73,6 @@ revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev
revive-common = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" } revive-common = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" } revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
zombienet-sdk = { git = "https://github.com/paritytech/zombienet-sdk.git", rev ="891f6554354ce466abd496366dbf8b4f82141241" }
[workspace.dependencies.alloy] [workspace.dependencies.alloy]
version = "1.0.37" version = "1.0.37"
default-features = false default-features = false
+3 -5
View File
@@ -1,11 +1,9 @@
//! This module implements a cached file system allowing for results to be stored in-memory rather //! This module implements a cached file system allowing for results to be stored in-memory rather
//! rather being queried from the file system again. //! rather being queried from the file system again.
use std::{ use std::fs;
fs, use std::io::{Error, Result};
io::{Error, Result}, use std::path::{Path, PathBuf};
path::{Path, PathBuf},
};
use moka::sync::Cache; use moka::sync::Cache;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
+2 -1
View File
@@ -1,4 +1,5 @@
use std::{ops::ControlFlow, time::Duration}; use std::ops::ControlFlow;
use std::time::Duration;
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
@@ -135,6 +135,6 @@ macro_rules! define_wrapper_type {
}; };
} }
/// Technically not needed but this allows for the macro to be found in the `macros` module of /// Technically not needed but this allows for the macro to be found in the `macros` module of the
/// the crate in addition to being found in the root of the crate. /// crate in addition to being found in the root of the crate.
pub use {define_wrapper_type, impl_for_wrapper}; pub use {define_wrapper_type, impl_for_wrapper};
-6
View File
@@ -39,10 +39,6 @@ pub enum PlatformIdentifier {
ReviveDevNodePolkavmResolc, ReviveDevNodePolkavmResolc,
/// The revive dev node with the REVM backend with the solc compiler. /// The revive dev node with the REVM backend with the solc compiler.
ReviveDevNodeRevmSolc, ReviveDevNodeRevmSolc,
/// A zombienet based Substrate/Polkadot node with the PolkaVM backend with the resolc compiler.
ZombienetPolkavmResolc,
/// A zombienet based Substrate/Polkadot node with the REVM backend with the solc compiler.
ZombienetRevmSolc,
} }
/// An enum of the platform identifiers of all of the platforms supported by this framework. /// An enum of the platform identifiers of all of the platforms supported by this framework.
@@ -99,8 +95,6 @@ pub enum NodeIdentifier {
Kitchensink, Kitchensink,
/// The revive dev node implementation. /// The revive dev node implementation.
ReviveDevNode, ReviveDevNode,
/// A zombienet spawned nodes
Zombienet,
} }
/// An enum representing the identifiers of the supported VMs. /// An enum representing the identifiers of the supported VMs.
+3 -1
View File
@@ -1,7 +1,9 @@
use crate::types::VersionOrRequirement; use crate::types::VersionOrRequirement;
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Display, str::FromStr, sync::LazyLock}; use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
/// This represents a mode that a given test should be run with, if possible. /// This represents a mode that a given test should be run with, if possible.
/// ///
@@ -1,4 +1,5 @@
use alloy::{primitives::U256, signers::local::PrivateKeySigner}; use alloy::primitives::U256;
use alloy::signers::local::PrivateKeySigner;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
/// This is a sequential private key allocator. When instantiated, it allocated private keys in /// This is a sequential private key allocator. When instantiated, it allocated private keys in
+2 -1
View File
@@ -10,7 +10,8 @@ use std::{
pin::Pin, pin::Pin,
}; };
use alloy::{json_abi::JsonAbi, primitives::Address}; use alloy::json_abi::JsonAbi;
use alloy::primitives::Address;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
+38 -37
View File
@@ -253,44 +253,45 @@ impl SolidityCompiler for Resolc {
.evm .evm
.and_then(|evm| evm.bytecode.clone()) .and_then(|evm| evm.bytecode.clone())
.context("Unexpected - Contract compiled with resolc has no bytecode")?; .context("Unexpected - Contract compiled with resolc has no bytecode")?;
let abi = let abi = {
{ let metadata = contract_information
let metadata = contract_information .metadata
.metadata .as_ref()
.as_ref() .context("No metadata found for the contract")?;
.context("No metadata found for the contract")?; let solc_metadata_str = match metadata {
let solc_metadata_str = match metadata { serde_json::Value::String(solc_metadata_str) => {
serde_json::Value::String(solc_metadata_str) => { solc_metadata_str.as_str()
solc_metadata_str.as_str() }
} serde_json::Value::Object(metadata_object) => {
serde_json::Value::Object(metadata_object) => { let solc_metadata_value = metadata_object
let solc_metadata_value = metadata_object .get("solc_metadata")
.get("solc_metadata") .context("Contract doesn't have a 'solc_metadata' field")?;
.context("Contract doesn't have a 'solc_metadata' field")?; solc_metadata_value
solc_metadata_value .as_str()
.as_str() .context("The 'solc_metadata' field is not a string")?
.context("The 'solc_metadata' field is not a string")? }
} serde_json::Value::Null
serde_json::Value::Null | serde_json::Value::Bool(_)
| serde_json::Value::Bool(_) | serde_json::Value::Number(_)
| serde_json::Value::Number(_) | serde_json::Value::Array(_) => {
| serde_json::Value::Array(_) => { anyhow::bail!("Unsupported type of metadata {metadata:?}")
anyhow::bail!("Unsupported type of metadata {metadata:?}") }
}
};
let solc_metadata =
serde_json::from_str::<serde_json::Value>(solc_metadata_str).context(
"Failed to deserialize the solc_metadata as a serde_json generic value",
)?;
let output_value = solc_metadata
.get("output")
.context("solc_metadata doesn't have an output field")?;
let abi_value = output_value
.get("abi")
.context("solc_metadata output doesn't contain an abi field")?;
serde_json::from_value::<JsonAbi>(abi_value.clone())
.context("ABI found in solc_metadata output is not valid ABI")?
}; };
let solc_metadata = serde_json::from_str::<serde_json::Value>(
solc_metadata_str,
)
.context(
"Failed to deserialize the solc_metadata as a serde_json generic value",
)?;
let output_value = solc_metadata
.get("output")
.context("solc_metadata doesn't have an output field")?;
let abi_value = output_value
.get("abi")
.context("solc_metadata output doesn't contain an abi field")?;
serde_json::from_value::<JsonAbi>(abi_value.clone())
.context("ABI found in solc_metadata output is not valid ABI")?
};
map.insert(contract_name, (bytecode.object, abi)); map.insert(contract_name, (bytecode.object, abi));
} }
} }
+4 -4
View File
@@ -21,7 +21,8 @@ use foundry_compilers_artifacts::{
output_selection::{ output_selection::{
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection, BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection,
}, },
solc::{CompilerOutput as SolcOutput, *}, solc::CompilerOutput as SolcOutput,
solc::*,
}; };
use semver::Version; use semver::Version;
use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand}; use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand};
@@ -283,9 +284,8 @@ impl SolidityCompiler for Solc {
_optimize_setting: ModeOptimizerSetting, _optimize_setting: ModeOptimizerSetting,
pipeline: ModePipeline, pipeline: ModePipeline,
) -> bool { ) -> bool {
// solc 0.8.13 and above supports --via-ir, and less than that does not. Thus, we support // solc 0.8.13 and above supports --via-ir, and less than that does not. Thus, we support mode E
// mode E (ie no Yul IR) in either case, but only support Y (via Yul IR) if the compiler // (ie no Yul IR) in either case, but only support Y (via Yul IR) if the compiler is new enough.
// is new enough.
pipeline == ModePipeline::ViaEVMAssembly pipeline == ModePipeline::ViaEVMAssembly
|| (pipeline == ModePipeline::ViaYulIR && self.compiler_supports_yul()) || (pipeline == ModePipeline::ViaYulIR && self.compiler_supports_yul())
} }
+12 -68
View File
@@ -107,16 +107,6 @@ impl AsRef<KurtosisConfiguration> for Context {
} }
} }
impl AsRef<PolkadotParachainConfiguration> for Context {
fn as_ref(&self) -> &PolkadotParachainConfiguration {
match self {
Self::Test(context) => context.as_ref().as_ref(),
Self::Benchmark(context) => context.as_ref().as_ref(),
Self::ExportJsonSchema => unreachable!(),
}
}
}
impl AsRef<KitchensinkConfiguration> for Context { impl AsRef<KitchensinkConfiguration> for Context {
fn as_ref(&self) -> &KitchensinkConfiguration { fn as_ref(&self) -> &KitchensinkConfiguration {
match self { match self {
@@ -202,11 +192,11 @@ impl AsRef<ReportConfiguration> for Context {
#[derive(Clone, Debug, Parser, Serialize)] #[derive(Clone, Debug, Parser, Serialize)]
pub struct TestExecutionContext { pub struct TestExecutionContext {
/// The working directory that the program will use for all of the temporary artifacts needed /// The working directory that the program will use for all of the temporary artifacts needed at
/// at runtime. /// runtime.
/// ///
/// If not specified, then a temporary directory will be created and used by the program for /// If not specified, then a temporary directory will be created and used by the program for all
/// all temporary artifacts. /// temporary artifacts.
#[clap( #[clap(
short, short,
long, long,
@@ -235,10 +225,6 @@ pub struct TestExecutionContext {
#[clap(flatten, next_help_heading = "Resolc Configuration")] #[clap(flatten, next_help_heading = "Resolc Configuration")]
pub resolc_configuration: ResolcConfiguration, pub resolc_configuration: ResolcConfiguration,
/// Configuration parameters for the Polkadot Parachain.
#[clap(flatten, next_help_heading = "Polkadot Parachain Configuration")]
pub polkadot_parachain_configuration: PolkadotParachainConfiguration,
/// Configuration parameters for the geth node. /// Configuration parameters for the geth node.
#[clap(flatten, next_help_heading = "Geth Configuration")] #[clap(flatten, next_help_heading = "Geth Configuration")]
pub geth_configuration: GethConfiguration, pub geth_configuration: GethConfiguration,
@@ -282,11 +268,11 @@ pub struct TestExecutionContext {
#[derive(Clone, Debug, Parser, Serialize)] #[derive(Clone, Debug, Parser, Serialize)]
pub struct BenchmarkingContext { pub struct BenchmarkingContext {
/// The working directory that the program will use for all of the temporary artifacts needed /// The working directory that the program will use for all of the temporary artifacts needed at
/// at runtime. /// runtime.
/// ///
/// If not specified, then a temporary directory will be created and used by the program for /// If not specified, then a temporary directory will be created and used by the program for all
/// all temporary artifacts. /// temporary artifacts.
#[clap( #[clap(
short, short,
long, long,
@@ -332,10 +318,6 @@ pub struct BenchmarkingContext {
#[clap(flatten, next_help_heading = "Kitchensink Configuration")] #[clap(flatten, next_help_heading = "Kitchensink Configuration")]
pub kitchensink_configuration: KitchensinkConfiguration, pub kitchensink_configuration: KitchensinkConfiguration,
/// Configuration parameters for the Polkadot Parachain.
#[clap(flatten, next_help_heading = "Polkadot Parachain Configuration")]
pub polkadot_parachain_configuration: PolkadotParachainConfiguration,
/// Configuration parameters for the Revive Dev Node. /// Configuration parameters for the Revive Dev Node.
#[clap(flatten, next_help_heading = "Revive Dev Node Configuration")] #[clap(flatten, next_help_heading = "Revive Dev Node Configuration")]
pub revive_dev_node_configuration: ReviveDevNodeConfiguration, pub revive_dev_node_configuration: ReviveDevNodeConfiguration,
@@ -397,12 +379,6 @@ impl AsRef<GethConfiguration> for TestExecutionContext {
} }
} }
impl AsRef<PolkadotParachainConfiguration> for TestExecutionContext {
fn as_ref(&self) -> &PolkadotParachainConfiguration {
&self.polkadot_parachain_configuration
}
}
impl AsRef<KurtosisConfiguration> for TestExecutionContext { impl AsRef<KurtosisConfiguration> for TestExecutionContext {
fn as_ref(&self) -> &KurtosisConfiguration { fn as_ref(&self) -> &KurtosisConfiguration {
&self.lighthouse_configuration &self.lighthouse_configuration
@@ -499,12 +475,6 @@ impl AsRef<KurtosisConfiguration> for BenchmarkingContext {
} }
} }
impl AsRef<PolkadotParachainConfiguration> for BenchmarkingContext {
fn as_ref(&self) -> &PolkadotParachainConfiguration {
&self.polkadot_parachain_configuration
}
}
impl AsRef<KitchensinkConfiguration> for BenchmarkingContext { impl AsRef<KitchensinkConfiguration> for BenchmarkingContext {
fn as_ref(&self) -> &KitchensinkConfiguration { fn as_ref(&self) -> &KitchensinkConfiguration {
&self.kitchensink_configuration &self.kitchensink_configuration
@@ -575,30 +545,6 @@ pub struct ResolcConfiguration {
pub path: PathBuf, pub path: PathBuf,
} }
/// A set of configuration parameters for Polkadot Parachain.
#[derive(Clone, Debug, Parser, Serialize)]
pub struct PolkadotParachainConfiguration {
/// Specifies the path of the polkadot-parachain node to be used by the tool.
///
/// If this is not specified, then the tool assumes that it should use the polkadot-parachain
/// binary that's provided in the user's $PATH.
#[clap(
id = "polkadot-parachain.path",
long = "polkadot-parachain.path",
default_value = "polkadot-parachain"
)]
pub path: PathBuf,
/// The amount of time to wait upon startup before considering that the node timed out.
#[clap(
id = "polkadot-parachain.start-timeout-ms",
long = "polkadot-parachain.start-timeout-ms",
default_value = "5000",
value_parser = parse_duration
)]
pub start_timeout_ms: Duration,
}
/// A set of configuration parameters for Geth. /// A set of configuration parameters for Geth.
#[derive(Clone, Debug, Parser, Serialize)] #[derive(Clone, Debug, Parser, Serialize)]
pub struct GethConfiguration { pub struct GethConfiguration {
@@ -624,8 +570,8 @@ pub struct GethConfiguration {
pub struct KurtosisConfiguration { pub struct KurtosisConfiguration {
/// Specifies the path of the kurtosis node to be used by the tool. /// Specifies the path of the kurtosis node to be used by the tool.
/// ///
/// If this is not specified, then the tool assumes that it should use the kurtosis binary /// If this is not specified, then the tool assumes that it should use the kurtosis binary that's
/// that's provided in the user's $PATH. /// provided in the user's $PATH.
#[clap( #[clap(
id = "kurtosis.path", id = "kurtosis.path",
long = "kurtosis.path", long = "kurtosis.path",
@@ -663,8 +609,8 @@ pub struct KitchensinkConfiguration {
pub struct ReviveDevNodeConfiguration { pub struct ReviveDevNodeConfiguration {
/// Specifies the path of the revive dev node to be used by the tool. /// Specifies the path of the revive dev node to be used by the tool.
/// ///
/// If this is not specified, then the tool assumes that it should use the revive dev node /// If this is not specified, then the tool assumes that it should use the revive dev node binary
/// binary that's provided in the user's $PATH. /// that's provided in the user's $PATH.
#[clap( #[clap(
id = "revive-dev-node.path", id = "revive-dev-node.path",
long = "revive-dev-node.path", long = "revive-dev-node.path",
@@ -947,6 +893,4 @@ pub enum TestingPlatform {
Geth, Geth,
/// The kitchensink runtime provides the PolkaVM (PVM) based node implementation. /// The kitchensink runtime provides the PolkaVM (PVM) based node implementation.
Kitchensink, Kitchensink,
/// A polkadot/Substrate based network
Zombienet,
} }
@@ -59,8 +59,8 @@ pub struct Driver<'a, I> {
/// The definition of the test that the driver is instructed to execute. /// The definition of the test that the driver is instructed to execute.
test_definition: &'a TestDefinition<'a>, test_definition: &'a TestDefinition<'a>,
/// The private key allocator used by this driver and other drivers when account allocations /// The private key allocator used by this driver and other drivers when account allocations are
/// are needed. /// needed.
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>, private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
/// The execution state associated with the platform. /// The execution state associated with the platform.
@@ -10,15 +10,14 @@ use revive_dt_format::metadata::{ContractIdent, ContractInstance};
#[derive(Clone)] #[derive(Clone)]
/// The state associated with the test execution of one of the workloads. /// The state associated with the test execution of one of the workloads.
pub struct ExecutionState { pub struct ExecutionState {
/// The compiled contracts, these contracts have been compiled and have had the libraries /// The compiled contracts, these contracts have been compiled and have had the libraries linked
/// linked against them and therefore they're ready to be deployed on-demand. /// against them and therefore they're ready to be deployed on-demand.
pub compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>, pub compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
/// A map of all of the deployed contracts and information about them. /// A map of all of the deployed contracts and information about them.
pub deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>, pub deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
/// This map stores the variables used for each one of the cases contained in the metadata /// This map stores the variables used for each one of the cases contained in the metadata file.
/// file.
pub variables: HashMap<String, U256>, pub variables: HashMap<String, U256>,
} }
+29 -30
View File
@@ -31,7 +31,7 @@ use revive_dt_format::{
traits::ResolutionContext, traits::ResolutionContext,
}; };
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::{error, info, instrument}; use tracing::{debug, error, info, instrument};
use crate::{ use crate::{
differential_tests::ExecutionState, differential_tests::ExecutionState,
@@ -109,6 +109,7 @@ impl<'a> Driver<'a, StepsIterator> {
// endregion:Constructors // endregion:Constructors
// region:Execution // region:Execution
#[instrument(level = "info", skip_all)]
pub async fn execute_all(mut self) -> Result<usize> { pub async fn execute_all(mut self) -> Result<usize> {
let platform_drivers = std::mem::take(&mut self.platform_drivers); let platform_drivers = std::mem::take(&mut self.platform_drivers);
let results = futures::future::try_join_all( let results = futures::future::try_join_all(
@@ -131,8 +132,8 @@ pub struct PlatformDriver<'a, I> {
/// The definition of the test that the driver is instructed to execute. /// The definition of the test that the driver is instructed to execute.
test_definition: &'a TestDefinition<'a>, test_definition: &'a TestDefinition<'a>,
/// The private key allocator used by this driver and other drivers when account allocations /// The private key allocator used by this driver and other drivers when account allocations are
/// are needed. /// needed.
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>, private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
/// The execution state associated with the platform. /// The execution state associated with the platform.
@@ -217,6 +218,8 @@ where
.flatten() .flatten()
.flat_map(|(_, map)| map.values()) .flat_map(|(_, map)| map.values())
{ {
debug!(%library_instance, "Deploying Library Instance");
let ContractPathAndIdent { let ContractPathAndIdent {
contract_source_path: library_source_path, contract_source_path: library_source_path,
contract_ident: library_ident, contract_ident: library_ident,
@@ -265,6 +268,12 @@ where
) )
})?; })?;
debug!(
?library_instance,
platform_identifier = %platform_information.platform.platform_identifier(),
"Deployed library"
);
let library_address = receipt let library_address = receipt
.contract_address .contract_address
.expect("Failed to deploy the library"); .expect("Failed to deploy the library");
@@ -303,6 +312,7 @@ where
// endregion:Constructors & Initialization // endregion:Constructors & Initialization
// region:Step Handling // region:Step Handling
#[instrument(level = "info", skip_all)]
pub async fn execute_all(mut self) -> Result<usize> { pub async fn execute_all(mut self) -> Result<usize> {
while let Some(result) = self.execute_next_step().await { while let Some(result) = self.execute_next_step().await {
result? result?
@@ -310,6 +320,14 @@ where
Ok(self.steps_executed) Ok(self.steps_executed)
} }
#[instrument(
level = "info",
skip_all,
fields(
platform_identifier = %self.platform_information.platform.platform_identifier(),
node_id = self.platform_information.node.id(),
),
)]
pub async fn execute_next_step(&mut self) -> Option<Result<()>> { pub async fn execute_next_step(&mut self) -> Option<Result<()>> {
let (step_path, step) = self.steps_iterator.next()?; let (step_path, step) = self.steps_iterator.next()?;
info!(%step_path, "Executing Step"); info!(%step_path, "Executing Step");
@@ -326,7 +344,6 @@ where
skip_all, skip_all,
fields( fields(
platform_identifier = %self.platform_information.platform.platform_identifier(), platform_identifier = %self.platform_information.platform.platform_identifier(),
node_id = self.platform_information.node.id(),
%step_path, %step_path,
), ),
err(Debug), err(Debug),
@@ -385,7 +402,6 @@ where
Ok(1) Ok(1)
} }
#[instrument(level = "debug", skip_all)]
async fn handle_function_call_contract_deployment( async fn handle_function_call_contract_deployment(
&mut self, &mut self,
step: &FunctionCallStep, step: &FunctionCallStep,
@@ -415,13 +431,9 @@ where
let caller = { let caller = {
let context = self.default_resolution_context(); let context = self.default_resolution_context();
let resolver = self.platform_information.node.resolver().await?; let resolver = self.platform_information.node.resolver().await?;
let resolved = step step.caller
.caller
.resolve_address(resolver.as_ref(), context) .resolve_address(resolver.as_ref(), context)
.await?; .await?
self.platform_information
.node
.resolve_signer_or_default(resolved)
}; };
if let (_, _, Some(receipt)) = self if let (_, _, Some(receipt)) = self
.get_or_deploy_contract_instance(&instance, caller, calldata, value) .get_or_deploy_contract_instance(&instance, caller, calldata, value)
@@ -435,7 +447,6 @@ where
Ok(receipts) Ok(receipts)
} }
#[instrument(level = "debug", skip_all)]
async fn handle_function_call_execution( async fn handle_function_call_execution(
&mut self, &mut self,
step: &FunctionCallStep, step: &FunctionCallStep,
@@ -449,7 +460,7 @@ where
.context("Failed to find deployment receipt for constructor call"), .context("Failed to find deployment receipt for constructor call"),
Method::Fallback | Method::FunctionName(_) => { Method::Fallback | Method::FunctionName(_) => {
let resolver = self.platform_information.node.resolver().await?; let resolver = self.platform_information.node.resolver().await?;
let mut tx = match step let tx = match step
.as_transaction(resolver.as_ref(), self.default_resolution_context()) .as_transaction(resolver.as_ref(), self.default_resolution_context())
.await .await
{ {
@@ -459,21 +470,14 @@ where
} }
}; };
// Resolve the signer to ensure we use an address that has keys match self.platform_information.node.execute_transaction(tx).await {
if let Some(from) = tx.from { Ok(receipt) => Ok(receipt),
tx.from = Some( Err(err) => Err(err),
self.platform_information
.node
.resolve_signer_or_default(from),
);
} }
self.platform_information.node.execute_transaction(tx).await
} }
} }
} }
#[instrument(level = "debug", skip_all)]
async fn handle_function_call_call_frame_tracing( async fn handle_function_call_call_frame_tracing(
&mut self, &mut self,
tx_hash: TxHash, tx_hash: TxHash,
@@ -505,7 +509,6 @@ where
}) })
} }
#[instrument(level = "debug", skip_all)]
async fn handle_function_call_variable_assignment( async fn handle_function_call_variable_assignment(
&mut self, &mut self,
step: &FunctionCallStep, step: &FunctionCallStep,
@@ -538,7 +541,6 @@ where
Ok(()) Ok(())
} }
#[instrument(level = "debug", skip_all)]
async fn handle_function_call_assertions( async fn handle_function_call_assertions(
&mut self, &mut self,
step: &FunctionCallStep, step: &FunctionCallStep,
@@ -581,7 +583,6 @@ where
.await .await
} }
#[instrument(level = "debug", skip_all)]
async fn handle_function_call_assertion_item( async fn handle_function_call_assertion_item(
&self, &self,
receipt: &TransactionReceipt, receipt: &TransactionReceipt,
@@ -864,6 +865,7 @@ where
level = "info", level = "info",
skip_all, skip_all,
fields( fields(
platform_identifier = %self.platform_information.platform.platform_identifier(),
%contract_instance, %contract_instance,
%deployer %deployer
), ),
@@ -905,6 +907,7 @@ where
level = "info", level = "info",
skip_all, skip_all,
fields( fields(
platform_identifier = %self.platform_information.platform.platform_identifier(),
%contract_instance, %contract_instance,
%deployer %deployer
), ),
@@ -967,10 +970,6 @@ where
} }
let tx = { let tx = {
let deployer = self
.platform_information
.node
.resolve_signer_or_default(deployer);
let tx = TransactionRequest::default().from(deployer); let tx = TransactionRequest::default().from(deployer);
let tx = match value { let tx = match value {
Some(ref value) => tx.value(value.into_inner()), Some(ref value) => tx.value(value.into_inner()),
@@ -1,17 +1,17 @@
//! The main entry point into differential testing. //! The main entry point into differential testing.
use std::{ use std::{
collections::{BTreeMap, BTreeSet}, collections::BTreeMap,
io::{BufWriter, Write, stderr}, io::{BufWriter, Write, stderr},
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::Instant,
}; };
use crate::Platform;
use anyhow::Context as _; use anyhow::Context as _;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use revive_dt_common::types::PrivateKeyAllocator; use revive_dt_common::types::PrivateKeyAllocator;
use tokio::sync::{Mutex, RwLock, Semaphore}; use revive_dt_core::Platform;
use tokio::sync::Mutex;
use tracing::{Instrument, error, info, info_span, instrument}; use tracing::{Instrument, error, info, info_span, instrument};
use revive_dt_config::{Context, TestExecutionContext}; use revive_dt_config::{Context, TestExecutionContext};
@@ -101,40 +101,20 @@ pub async fn handle_differential_tests(
))); )));
// Creating the driver and executing all of the steps. // Creating the driver and executing all of the steps.
let semaphore = context let driver_task = futures::future::join_all(test_definitions.iter().map(|test_definition| {
.concurrency_configuration let private_key_allocator = private_key_allocator.clone();
.concurrency_limit() let cached_compiler = cached_compiler.clone();
.map(Semaphore::new) let mode = test_definition.mode.clone();
.map(Arc::new); let span = info_span!(
let running_task_list = Arc::new(RwLock::new(BTreeSet::<usize>::new())); "Executing Test Case",
let driver_task = futures::future::join_all(test_definitions.iter().enumerate().map( metadata_file_path = %test_definition.metadata_file_path.display(),
|(test_id, test_definition)| { case_idx = %test_definition.case_idx,
let running_task_list = running_task_list.clone(); mode = %mode
let semaphore = semaphore.clone(); );
async move {
let private_key_allocator = private_key_allocator.clone(); let driver =
let cached_compiler = cached_compiler.clone(); match Driver::new_root(test_definition, private_key_allocator, &cached_compiler)
let mode = test_definition.mode.clone(); .await
let span = info_span!(
"Executing Test Case",
test_id,
metadata_file_path = %test_definition.metadata_file_path.display(),
case_idx = %test_definition.case_idx,
mode = %mode,
);
async move {
let permit = match semaphore.as_ref() {
Some(semaphore) => Some(semaphore.acquire().await.expect("Can't fail")),
None => None,
};
running_task_list.write().await.insert(test_id);
let driver = match Driver::new_root(
test_definition,
private_key_allocator,
&cached_compiler,
)
.await
{ {
Ok(driver) => driver, Ok(driver) => driver,
Err(error) => { Err(error) => {
@@ -143,33 +123,28 @@ pub async fn handle_differential_tests(
.report_test_failed_event(format!("{error:#}")) .report_test_failed_event(format!("{error:#}"))
.expect("Can't fail"); .expect("Can't fail");
error!("Test Case Failed"); error!("Test Case Failed");
drop(permit);
running_task_list.write().await.remove(&test_id);
return; return;
} }
}; };
info!("Created the driver for the test case"); info!("Created the driver for the test case");
match driver.execute_all().await { match driver.execute_all().await {
Ok(steps_executed) => test_definition Ok(steps_executed) => test_definition
.reporter
.report_test_succeeded_event(steps_executed)
.expect("Can't fail"),
Err(error) => {
test_definition
.reporter .reporter
.report_test_succeeded_event(steps_executed) .report_test_failed_event(format!("{error:#}"))
.expect("Can't fail"), .expect("Can't fail");
Err(error) => { error!("Test Case Failed");
test_definition }
.reporter };
.report_test_failed_event(format!("{error:#}")) info!("Finished the execution of the test case")
.expect("Can't fail"); }
error!("Test Case Failed"); .instrument(span)
} }))
};
info!("Finished the execution of the test case");
drop(permit);
running_task_list.write().await.remove(&test_id);
}
.instrument(span)
},
))
.inspect(|_| { .inspect(|_| {
info!("Finished executing all test cases"); info!("Finished executing all test cases");
reporter_clone reporter_clone
@@ -178,18 +153,6 @@ pub async fn handle_differential_tests(
}); });
let cli_reporting_task = start_cli_reporting_task(reporter); let cli_reporting_task = start_cli_reporting_task(reporter);
tokio::task::spawn(async move {
loop {
let remaining_tasks = running_task_list.read().await;
info!(
count = remaining_tasks.len(),
?remaining_tasks,
"Remaining Tests"
);
tokio::time::sleep(Duration::from_secs(10)).await
}
});
futures::future::join(driver_task, cli_reporting_task).await; futures::future::join(driver_task, cli_reporting_task).await;
Ok(()) Ok(())
@@ -10,15 +10,14 @@ use revive_dt_format::metadata::{ContractIdent, ContractInstance};
#[derive(Clone)] #[derive(Clone)]
/// The state associated with the test execution of one of the tests. /// The state associated with the test execution of one of the tests.
pub struct ExecutionState { pub struct ExecutionState {
/// The compiled contracts, these contracts have been compiled and have had the libraries /// The compiled contracts, these contracts have been compiled and have had the libraries linked
/// linked against them and therefore they're ready to be deployed on-demand. /// against them and therefore they're ready to be deployed on-demand.
pub compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>, pub compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
/// A map of all of the deployed contracts and information about them. /// A map of all of the deployed contracts and information about them.
pub deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>, pub deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
/// This map stores the variables used for each one of the cases contained in the metadata /// This map stores the variables used for each one of the cases contained in the metadata file.
/// file.
pub variables: HashMap<String, U256>, pub variables: HashMap<String, U256>,
} }
+1 -1
View File
@@ -8,10 +8,10 @@ use std::{
sync::{Arc, LazyLock}, sync::{Arc, LazyLock},
}; };
use crate::Platform;
use futures::FutureExt; use futures::FutureExt;
use revive_dt_common::{iterators::FilesWithExtensionIterator, types::CompilerIdentifier}; use revive_dt_common::{iterators::FilesWithExtensionIterator, types::CompilerIdentifier};
use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler}; use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler};
use revive_dt_core::Platform;
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata}; use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address}; use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
+1 -1
View File
@@ -2,9 +2,9 @@
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use crate::Platform;
use anyhow::Context as _; use anyhow::Context as _;
use revive_dt_config::*; use revive_dt_config::*;
use revive_dt_core::Platform;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
/// The node pool starts one or more [Node] which then can be accessed /// The node pool starts one or more [Node] which then can be accessed
+13 -7
View File
@@ -1,22 +1,28 @@
use std::{borrow::Cow, collections::BTreeMap, path::Path, sync::Arc}; use std::collections::BTreeMap;
use std::sync::Arc;
use std::{borrow::Cow, path::Path};
use futures::{Stream, StreamExt, stream}; use futures::{Stream, StreamExt, stream};
use indexmap::{IndexMap, indexmap}; use indexmap::{IndexMap, indexmap};
use revive_dt_common::{iterators::EitherIter, types::PlatformIdentifier}; use revive_dt_common::iterators::EitherIter;
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_config::Context; use revive_dt_config::Context;
use revive_dt_format::mode::ParsedMode; use revive_dt_format::mode::ParsedMode;
use serde_json::{Value, json}; use serde_json::{Value, json};
use revive_dt_compiler::{Mode, SolidityCompiler}; use revive_dt_compiler::Mode;
use revive_dt_compiler::SolidityCompiler;
use revive_dt_format::{ use revive_dt_format::{
case::{Case, CaseIdx}, case::{Case, CaseIdx},
metadata::MetadataFile, metadata::MetadataFile,
}; };
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::{ExecutionSpecificReporter, Reporter, TestSpecificReporter, TestSpecifier}; use revive_dt_report::{ExecutionSpecificReporter, Reporter};
use revive_dt_report::{TestSpecificReporter, TestSpecifier};
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use crate::{Platform, helpers::NodePool}; use crate::Platform;
use crate::helpers::NodePool;
pub async fn create_test_definitions_stream<'a>( pub async fn create_test_definitions_stream<'a>(
// This is only required for creating the compiler objects and is not used anywhere else in the // This is only required for creating the compiler objects and is not used anywhere else in the
@@ -63,8 +69,8 @@ pub async fn create_test_definitions_stream<'a>(
) )
}) })
}) })
// Inform the reporter of each one of the test cases that were discovered which we // Inform the reporter of each one of the test cases that were discovered which we expect to
// expect to run. // run.
.inspect(|(_, _, _, _, reporter)| { .inspect(|(_, _, _, _, reporter)| {
reporter reporter
.report_test_case_discovery_event() .report_test_case_discovery_event()
+3 -114
View File
@@ -3,9 +3,6 @@
//! This crate defines the testing configuration and //! This crate defines the testing configuration and
//! provides a helper utility to execute tests. //! provides a helper utility to execute tests.
pub mod differential_tests;
pub mod helpers;
use std::{ use std::{
pin::Pin, pin::Pin,
thread::{self, JoinHandle}, thread::{self, JoinHandle},
@@ -17,17 +14,13 @@ use revive_dt_common::types::*;
use revive_dt_compiler::{SolidityCompiler, revive_resolc::Resolc, solc::Solc}; use revive_dt_compiler::{SolidityCompiler, revive_resolc::Resolc, solc::Solc};
use revive_dt_config::*; use revive_dt_config::*;
use revive_dt_node::{ use revive_dt_node::{
Node, Node, node_implementations::geth::GethNode,
node_implementations::{ node_implementations::lighthouse_geth::LighthouseGethNode,
geth::GethNode, lighthouse_geth::LighthouseGethNode, substrate::SubstrateNode, node_implementations::substrate::SubstrateNode,
zombienet::ZombieNode,
},
}; };
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use tracing::info; use tracing::info;
pub use helpers::CachedCompiler;
/// A trait that describes the interface for the platforms that are supported by the tool. /// A trait that describes the interface for the platforms that are supported by the tool.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub trait Platform { pub trait Platform {
@@ -366,102 +359,6 @@ impl Platform for ReviveDevNodeRevmSolcPlatform {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct ZombienetPolkavmResolcPlatform;
impl Platform for ZombienetPolkavmResolcPlatform {
fn platform_identifier(&self) -> PlatformIdentifier {
PlatformIdentifier::ZombienetPolkavmResolc
}
fn node_identifier(&self) -> NodeIdentifier {
NodeIdentifier::Zombienet
}
fn vm_identifier(&self) -> VmIdentifier {
VmIdentifier::PolkaVM
}
fn compiler_identifier(&self) -> CompilerIdentifier {
CompilerIdentifier::Resolc
}
fn new_node(
&self,
context: Context,
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let polkadot_parachain_path = AsRef::<PolkadotParachainConfiguration>::as_ref(&context)
.path
.clone();
let genesis = genesis_configuration.genesis()?.clone();
Ok(thread::spawn(move || {
let node = ZombieNode::new(polkadot_parachain_path, context);
let node = spawn_node(node, genesis)?;
Ok(Box::new(node) as Box<_>)
}))
}
fn new_compiler(
&self,
context: Context,
version: Option<VersionOrRequirement>,
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
Box::pin(async move {
let compiler = Solc::new(context, version).await;
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct ZombienetRevmSolcPlatform;
impl Platform for ZombienetRevmSolcPlatform {
fn platform_identifier(&self) -> PlatformIdentifier {
PlatformIdentifier::ZombienetRevmSolc
}
fn node_identifier(&self) -> NodeIdentifier {
NodeIdentifier::Zombienet
}
fn vm_identifier(&self) -> VmIdentifier {
VmIdentifier::Evm
}
fn compiler_identifier(&self) -> CompilerIdentifier {
CompilerIdentifier::Solc
}
fn new_node(
&self,
context: Context,
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let polkadot_parachain_path = AsRef::<PolkadotParachainConfiguration>::as_ref(&context)
.path
.clone();
let genesis = genesis_configuration.genesis()?.clone();
Ok(thread::spawn(move || {
let node = ZombieNode::new(polkadot_parachain_path, context);
let node = spawn_node(node, genesis)?;
Ok(Box::new(node) as Box<_>)
}))
}
fn new_compiler(
&self,
context: Context,
version: Option<VersionOrRequirement>,
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
Box::pin(async move {
let compiler = Solc::new(context, version).await;
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
})
}
}
impl From<PlatformIdentifier> for Box<dyn Platform> { impl From<PlatformIdentifier> for Box<dyn Platform> {
fn from(value: PlatformIdentifier) -> Self { fn from(value: PlatformIdentifier) -> Self {
match value { match value {
@@ -481,10 +378,6 @@ impl From<PlatformIdentifier> for Box<dyn Platform> {
PlatformIdentifier::ReviveDevNodeRevmSolc => { PlatformIdentifier::ReviveDevNodeRevmSolc => {
Box::new(ReviveDevNodeRevmSolcPlatform) as Box<_> Box::new(ReviveDevNodeRevmSolcPlatform) as Box<_>
} }
PlatformIdentifier::ZombienetPolkavmResolc => {
Box::new(ZombienetPolkavmResolcPlatform) as Box<_>
}
PlatformIdentifier::ZombienetRevmSolc => Box::new(ZombienetRevmSolcPlatform) as Box<_>,
} }
} }
} }
@@ -508,10 +401,6 @@ impl From<PlatformIdentifier> for &dyn Platform {
PlatformIdentifier::ReviveDevNodeRevmSolc => { PlatformIdentifier::ReviveDevNodeRevmSolc => {
&ReviveDevNodeRevmSolcPlatform as &dyn Platform &ReviveDevNodeRevmSolcPlatform as &dyn Platform
} }
PlatformIdentifier::ZombienetPolkavmResolc => {
&ZombienetPolkavmResolcPlatform as &dyn Platform
}
PlatformIdentifier::ZombienetRevmSolc => &ZombienetRevmSolcPlatform as &dyn Platform,
} }
} }
} }
+2 -2
View File
@@ -45,8 +45,8 @@ pub struct Case {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub expected: Option<Expected>, pub expected: Option<Expected>,
/// An optional boolean which defines if the case as a whole should be ignored. If null then /// An optional boolean which defines if the case as a whole should be ignored. If null then the
/// the case will not be ignored. /// case will not be ignored.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub ignore: Option<bool>, pub ignore: Option<bool>,
} }
+11 -13
View File
@@ -31,8 +31,8 @@ pub struct MetadataFile {
/// The path of the metadata file. This will either be a JSON or solidity file. /// The path of the metadata file. This will either be a JSON or solidity file.
pub metadata_file_path: PathBuf, pub metadata_file_path: PathBuf,
/// This is the path contained within the corpus file. This could either be the path of some /// This is the path contained within the corpus file. This could either be the path of some dir
/// dir or could be the actual metadata file path. /// or could be the actual metadata file path.
pub corpus_file_path: PathBuf, pub corpus_file_path: PathBuf,
/// The metadata contained within the file. /// The metadata contained within the file.
@@ -69,13 +69,13 @@ impl Deref for MetadataFile {
/// of steps and assertions that should be performed as part of the test case. /// of steps and assertions that should be performed as part of the test case.
#[derive(Debug, Default, Serialize, Deserialize, JsonSchema, Clone, Eq, PartialEq)] #[derive(Debug, Default, Serialize, Deserialize, JsonSchema, Clone, Eq, PartialEq)]
pub struct Metadata { pub struct Metadata {
/// This is an optional comment on the metadata file which has no impact on the execution in /// This is an optional comment on the metadata file which has no impact on the execution in any
/// any way. /// way.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>, pub comment: Option<String>,
/// An optional boolean which defines if the metadata file as a whole should be ignored. If /// An optional boolean which defines if the metadata file as a whole should be ignored. If null
/// null then the metadata file will not be ignored. /// then the metadata file will not be ignored.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub ignore: Option<bool>, pub ignore: Option<bool>,
@@ -94,8 +94,8 @@ pub struct Metadata {
/// This is a map where the key is the name of the contract instance and the value is the /// This is a map where the key is the name of the contract instance and the value is the
/// contract's path and ident in the file. /// contract's path and ident in the file.
/// ///
/// If any contract is to be used by the test then it must be included in here first so that /// If any contract is to be used by the test then it must be included in here first so that the
/// the framework is aware of its path, compiles it, and prepares it. /// framework is aware of its path, compiles it, and prepares it.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdent>>, pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdent>>,
@@ -123,9 +123,8 @@ pub struct Metadata {
pub required_evm_version: Option<EvmVersionRequirement>, pub required_evm_version: Option<EvmVersionRequirement>,
/// A set of compilation directives that will be passed to the compiler whenever the contracts /// A set of compilation directives that will be passed to the compiler whenever the contracts
/// for the test are being compiled. Note that this differs from the [`Mode`]s in that a /// for the test are being compiled. Note that this differs from the [`Mode`]s in that a [`Mode`]
/// [`Mode`] is just a filter for when a test can run whereas this is an instruction to the /// is just a filter for when a test can run whereas this is an instruction to the compiler.
/// compiler.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub compiler_directives: Option<CompilationDirectives>, pub compiler_directives: Option<CompilationDirectives>,
} }
@@ -327,8 +326,7 @@ define_wrapper_type!(
)] )]
#[serde(try_from = "String", into = "String")] #[serde(try_from = "String", into = "String")]
pub struct ContractPathAndIdent { pub struct ContractPathAndIdent {
/// The path of the contract source code relative to the directory containing the metadata /// The path of the contract source code relative to the directory containing the metadata file.
/// file.
pub contract_source_path: PathBuf, pub contract_source_path: PathBuf,
/// The identifier of the contract. /// The identifier of the contract.
+6 -5
View File
@@ -1,12 +1,13 @@
use anyhow::Context as _; use anyhow::Context as _;
use regex::Regex; use regex::Regex;
use revive_dt_common::{ use revive_dt_common::iterators::EitherIter;
iterators::EitherIter, use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
types::{Mode, ModeOptimizerSetting, ModePipeline},
};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashSet, fmt::Display, str::FromStr, sync::LazyLock}; 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. /// This represents a mode that has been parsed from test metadata.
/// ///
+26 -31
View File
@@ -1,10 +1,11 @@
use std::{collections::HashMap, fmt::Display, str::FromStr}; use std::{collections::HashMap, fmt::Display, str::FromStr};
use alloy::primitives::{FixedBytes, utils::parse_units};
use alloy::{ use alloy::{
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
json_abi::Function, json_abi::Function,
network::TransactionBuilder, network::TransactionBuilder,
primitives::{Address, Bytes, FixedBytes, U256, utils::parse_units}, primitives::{Address, Bytes, U256},
rpc::types::TransactionRequest, rpc::types::TransactionRequest,
}; };
use anyhow::Context as _; use anyhow::Context as _;
@@ -16,10 +17,8 @@ use serde::{Deserialize, Serialize};
use revive_dt_common::macros::define_wrapper_type; use revive_dt_common::macros::define_wrapper_type;
use tracing::{Instrument, info_span, instrument}; use tracing::{Instrument, info_span, instrument};
use crate::{ use crate::traits::ResolverApi;
metadata::ContractInstance, use crate::{metadata::ContractInstance, traits::ResolutionContext};
traits::{ResolutionContext, ResolverApi},
};
/// A test step. /// A test step.
/// ///
@@ -148,8 +147,8 @@ pub struct FunctionCallStep {
#[schemars(skip)] #[schemars(skip)]
pub storage: Option<HashMap<String, Calldata>>, pub storage: Option<HashMap<String, Calldata>>,
/// Variable assignment to perform in the framework allowing us to reference them again later /// Variable assignment to perform in the framework allowing us to reference them again later on
/// on during the execution. /// during the execution.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub variable_assignments: Option<VariableAssignments>, pub variable_assignments: Option<VariableAssignments>,
} }
@@ -489,20 +488,21 @@ impl FunctionCallStep {
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref()); anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
}; };
// We follow the same logic that's implemented in the matter-labs-tester where they // We follow the same logic that's implemented in the matter-labs-tester where they resolve
// resolve the function name into a function selector and they assume that he // the function name into a function selector and they assume that he function doesn't have
// function doesn't have any existing overloads. // any existing overloads.
// Overloads are handled by providing the full function signature in the "function // Overloads are handled by providing the full function signature in the "function
// name". // name".
// https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190 // https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190
let selector = if function_name.contains('(') && function_name.contains(')') { let selector =
Function::parse(function_name) if function_name.contains('(') && function_name.contains(')') {
Function::parse(function_name)
.context( .context(
"Failed to parse the provided function name into a function signature", "Failed to parse the provided function name into a function signature",
)? )?
.selector() .selector()
} else { } else {
abi.functions() abi.functions()
.find(|function| function.signature().starts_with(function_name)) .find(|function| function.signature().starts_with(function_name))
.ok_or_else(|| { .ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
@@ -511,21 +511,19 @@ impl FunctionCallStep {
&self.instance &self.instance
) )
}) })
.with_context(|| { .with_context(|| format!(
format!( "Failed to resolve function selector for {:?} on instance {:?}",
"Failed to resolve function selector for {:?} on instance {:?}", function_name, &self.instance
function_name, &self.instance ))?
)
})?
.selector() .selector()
}; };
// Allocating a vector that we will be using for the calldata. The vector size will // Allocating a vector that we will be using for the calldata. The vector size will be:
// be: 4 bytes for the function selector. // 4 bytes for the function selector.
// function.inputs.len() * 32 bytes for the arguments (each argument is a U256). // function.inputs.len() * 32 bytes for the arguments (each argument is a U256).
// //
// We're using indices in the following code in order to avoid the need for us to // We're using indices in the following code in order to avoid the need for us to allocate
// allocate a new buffer for each one of the resolved arguments. // a new buffer for each one of the resolved arguments.
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement()); let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
calldata.extend(selector.0); calldata.extend(selector.0);
self.calldata self.calldata
@@ -961,12 +959,9 @@ impl<'de> Deserialize<'de> for EtherValue {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloy::{ use alloy::primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address};
eips::BlockNumberOrTag, use alloy::sol_types::SolValue;
json_abi::JsonAbi, use alloy::{eips::BlockNumberOrTag, json_abi::JsonAbi};
primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address},
sol_types::SolValue,
};
use std::{collections::HashMap, pin::Pin}; use std::{collections::HashMap, pin::Pin};
use super::*; use super::*;
+6 -6
View File
@@ -1,10 +1,10 @@
use std::{collections::HashMap, pin::Pin}; use std::collections::HashMap;
use std::pin::Pin;
use alloy::{ use alloy::eips::BlockNumberOrTag;
eips::BlockNumberOrTag, use alloy::json_abi::JsonAbi;
json_abi::JsonAbi, use alloy::primitives::TxHash;
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, U256}, use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
};
use anyhow::Result; use anyhow::Result;
use crate::metadata::{ContractIdent, ContractInstance}; use crate::metadata::{ContractIdent, ContractInstance};
-34
View File
@@ -1,34 +0,0 @@
[package]
name = "ml-test-runner"
description = "ML-based test runner for executing differential tests file by file"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
rust-version.workspace = true
[[bin]]
name = "ml-test-runner"
path = "src/main.rs"
[dependencies]
revive-dt-common = { workspace = true }
revive-dt-compiler = { workspace = true }
revive-dt-config = { workspace = true }
revive-dt-core = { workspace = true }
revive-dt-format = { workspace = true }
revive-dt-node = { workspace = true }
revive-dt-node-interaction = { workspace = true }
revive-dt-report = { workspace = true }
alloy = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
tokio = { workspace = true }
temp-dir = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
[lints]
workspace = true
-74
View File
@@ -1,74 +0,0 @@
# ML Test Runner
A test runner for executing Revive differential tests file-by-file with cargo-test-style output.
This is similar to the `retester` binary but designed for ML-based test execution with a focus on:
- Running tests file-by-file (rather than in bulk)
- Caching passed tests to skip them in future runs
- Providing cargo-test-style output for easy integration with ML pipelines
- Single platform testing (rather than differential testing)
## Features
- **File-by-file execution**: Run tests on individual `.sol` files, corpus files (`.json`), or recursively walk directories
- **Cached results**: Skip tests that have already passed using `--cached-passed`
- **Fail fast**: Stop on first failure with `--bail`
- **Cargo-like output**: Familiar test output format with colored pass/fail indicators
- **Platform support**: Test against `geth` or `kitchensink` platforms
## Usage
```bash
# Run a single .sol file (compile-only mode, default)
./ml-test-runner path/to/test.sol --platform geth
# Run all tests in a corpus file
./ml-test-runner path/to/corpus.json --platform kitchensink
# Walk a directory recursively for .sol files
./ml-test-runner path/to/tests/ --platform geth
# Use cached results and bail on first failure
./ml-test-runner path/to/tests/ --cached-passed ./cache.txt --bail
# Start the platform and execute tests (full mode)
./ml-test-runner path/to/tests/ --platform geth --start-platform
# Enable verbose logging (info, debug, or trace level)
RUST_LOG=info ./ml-test-runner path/to/tests/
RUST_LOG=debug ./ml-test-runner path/to/tests/ --start-platform
RUST_LOG=trace ./ml-test-runner path/to/tests/ --start-platform
```
## Arguments
- `<PATH>` - Path to test file (`.sol`), corpus file (`.json`), or folder of `.sol` files
- `--cached-passed <FILE>` - File to track tests that have already passed
- `--bail` - Stop after the first file failure
- `--platform <PLATFORM>` - Platform to test against (`geth`, `kitchensink`, or `zombienet`, default: `geth`)
- `--start-platform` - Start the platform and execute tests (default: `false`, compile-only mode)
## Output Format
The runner produces cargo-test-style output:
```
test path/to/test1.sol ... ok
test path/to/test2.sol ... FAILED
test path/to/test3.sol ... cached
failures:
---- path/to/test2.sol ----
Error: ...
test result: FAILED. 1 passed; 1 failed; 1 cached; finished in 2.34s
```
## Building
```bash
cargo build --release -p ml-test-runner
```
The binary will be available at `target/release/ml-test-runner`.
-541
View File
@@ -1,541 +0,0 @@
use anyhow::Context;
use clap::Parser;
use revive_dt_common::{
iterators::FilesWithExtensionIterator,
types::{PlatformIdentifier, PrivateKeyAllocator},
};
use revive_dt_config::TestExecutionContext;
use revive_dt_core::{
CachedCompiler, Platform,
helpers::{TestDefinition, TestPlatformInformation},
};
use revive_dt_format::{
case::CaseIdx,
corpus::Corpus,
metadata::{Metadata, MetadataFile},
};
use std::{
borrow::Cow,
collections::{BTreeMap, HashSet},
fs::File,
io::{BufRead, BufReader, BufWriter, Write},
path::{Path, PathBuf},
sync::Arc,
time::Instant,
};
use temp_dir::TempDir;
use tokio::sync::Mutex;
use tracing::info;
use tracing_subscriber::{EnvFilter, FmtSubscriber};
/// ML-based test runner for executing differential tests file by file
#[derive(Debug, Parser)]
#[command(name = "ml-test-runner")]
struct MlTestRunnerArgs {
/// Path to test file (.sol), corpus file (.json), or folder containing .sol files
#[arg(value_name = "PATH")]
path: PathBuf,
/// File to cache tests that have already passed
#[arg(long = "cached-passed")]
cached_passed: Option<PathBuf>,
/// Stop after the first file failure
#[arg(long = "bail")]
bail: bool,
/// Platform to test against (e.g., geth-evm-solc, kitchensink-polkavm-resolc)
#[arg(long = "platform", default_value = "geth-evm-solc")]
platform: PlatformIdentifier,
/// Start the platform and wait for RPC readiness
#[arg(long = "start-platform", default_value = "false")]
start_platform: bool,
/// Private key to use for wallet initialization (hex string with or without 0x prefix)
#[arg(
long = "private-key",
default_value = "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"
)]
private_key: String,
/// RPC port to connect to when using existing node
#[arg(long = "rpc-port", default_value = "8545")]
rpc_port: u16,
}
fn main() -> anyhow::Result<()> {
let subscriber = FmtSubscriber::builder()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("Failed to set tracing subscriber");
let args = MlTestRunnerArgs::parse();
info!("ML test runner starting");
info!("Platform: {:?}", args.platform);
info!("Start platform: {}", args.start_platform);
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(run(args))
}
async fn run(args: MlTestRunnerArgs) -> anyhow::Result<()> {
let start_time = Instant::now();
info!("Discovering test files from: {}", args.path.display());
let test_files = discover_test_files(&args.path)?;
info!("Found {} test file(s)", test_files.len());
let cached_passed = if let Some(cache_file) = &args.cached_passed {
let cached = load_cached_passed(cache_file)?;
info!("Loaded {} cached passed test(s)", cached.len());
cached
} else {
HashSet::new()
};
let cached_passed = Arc::new(Mutex::new(cached_passed));
let mut passed_files = 0;
let mut failed_files = 0;
let mut skipped_files = 0;
let mut failures = Vec::new();
const GREEN: &str = "\x1B[32m";
const RED: &str = "\x1B[31m";
const YELLOW: &str = "\x1B[33m";
const COLOUR_RESET: &str = "\x1B[0m";
const BOLD: &str = "\x1B[1m";
const BOLD_RESET: &str = "\x1B[22m";
for test_file in test_files {
let file_display = test_file.display().to_string();
// Check if already passed
{
let cache = cached_passed.lock().await;
if cache.contains(&file_display) {
println!("test {} ... {YELLOW}cached{COLOUR_RESET}", file_display);
skipped_files += 1;
continue;
}
}
info!("Loading metadata from: {}", test_file.display());
let metadata_file = match load_metadata_file(&test_file) {
Ok(mf) => {
info!("Loaded metadata with {} case(s)", mf.cases.len());
mf
}
Err(e) => {
println!("test {} ... {RED}FAILED{COLOUR_RESET}", file_display);
println!(" Error loading metadata: {}", e);
failed_files += 1;
failures.push((
file_display.clone(),
format!("Error loading metadata: {}", e),
));
if args.bail {
break;
}
continue;
}
};
info!("Executing test file: {}", file_display);
match execute_test_file(&args, &metadata_file).await {
Ok(_) => {
println!("test {} ... {GREEN}ok{COLOUR_RESET}", file_display);
info!("Test file passed: {}", file_display);
passed_files += 1;
{
let mut cache = cached_passed.lock().await;
cache.insert(file_display);
}
}
Err(e) => {
println!("test {} ... {RED}FAILED{COLOUR_RESET}", file_display);
failed_files += 1;
failures.push((file_display, format!("{:?}", e)));
if args.bail {
info!("Bailing after first failure");
break;
}
}
}
}
if let Some(cache_file) = &args.cached_passed {
let cache = cached_passed.lock().await;
info!("Saving {} cached passed test(s)", cache.len());
save_cached_passed(cache_file, &cache)?;
}
// Print summary
println!();
if !failures.is_empty() {
println!("{BOLD}failures:{BOLD_RESET}");
println!();
for (file, error) in &failures {
println!("---- {} ----", file);
println!("{}", error);
println!();
}
}
let elapsed = start_time.elapsed();
println!(
"test result: {}. {} passed; {} failed; {} cached; finished in {:.2}s",
if failed_files == 0 {
format!("{GREEN}ok{COLOUR_RESET}")
} else {
format!("{RED}FAILED{COLOUR_RESET}")
},
passed_files,
failed_files,
skipped_files,
elapsed.as_secs_f64()
);
if failed_files > 0 {
std::process::exit(1);
}
Ok(())
}
/// Discover test files from the given path
fn discover_test_files(path: &Path) -> anyhow::Result<Vec<PathBuf>> {
if !path.exists() {
anyhow::bail!("Path does not exist: {}", path.display());
}
let mut files = Vec::new();
if path.is_file() {
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");
match extension {
"sol" => {
// Single .sol file
files.push(path.to_path_buf());
}
"json" => {
// Corpus file - enumerate its tests
let corpus = Corpus::try_from_path(path)?;
let metadata_files = corpus.enumerate_tests();
for metadata in metadata_files {
files.push(metadata.metadata_file_path);
}
}
_ => anyhow::bail!(
"Unsupported file extension: {}. Expected .sol or .json",
extension
),
}
} else if path.is_dir() {
// Walk directory recursively for .sol files
for entry in FilesWithExtensionIterator::new(path)
.with_allowed_extension("sol")
.with_use_cached_fs(true)
{
files.push(entry);
}
} else {
anyhow::bail!("Path is neither a file nor a directory: {}", path.display());
}
Ok(files)
}
/// Load metadata from a test file
fn load_metadata_file(path: &Path) -> anyhow::Result<MetadataFile> {
let metadata = Metadata::try_from_file(path)
.ok_or_else(|| anyhow::anyhow!("Failed to load metadata from {}", path.display()))?;
Ok(MetadataFile {
metadata_file_path: path.to_path_buf(),
corpus_file_path: path.to_path_buf(),
content: metadata,
})
}
/// Execute all test cases in a metadata file
async fn execute_test_file(
args: &MlTestRunnerArgs,
metadata_file: &MetadataFile,
) -> anyhow::Result<()> {
if metadata_file.cases.is_empty() {
anyhow::bail!("No test cases found in file");
}
info!("Processing {} test case(s)", metadata_file.cases.len());
// Get the platform based on CLI args
let platform: &dyn Platform = match args.platform {
PlatformIdentifier::GethEvmSolc => &revive_dt_core::GethEvmSolcPlatform,
PlatformIdentifier::LighthouseGethEvmSolc => &revive_dt_core::LighthouseGethEvmSolcPlatform,
PlatformIdentifier::KitchensinkPolkavmResolc => {
&revive_dt_core::KitchensinkPolkavmResolcPlatform
}
PlatformIdentifier::KitchensinkRevmSolc => &revive_dt_core::KitchensinkRevmSolcPlatform,
PlatformIdentifier::ReviveDevNodePolkavmResolc => {
&revive_dt_core::ReviveDevNodePolkavmResolcPlatform
}
PlatformIdentifier::ReviveDevNodeRevmSolc => &revive_dt_core::ReviveDevNodeRevmSolcPlatform,
PlatformIdentifier::ZombienetPolkavmResolc => {
&revive_dt_core::ZombienetPolkavmResolcPlatform
}
PlatformIdentifier::ZombienetRevmSolc => &revive_dt_core::ZombienetRevmSolcPlatform,
};
let temp_dir = TempDir::new()?;
info!("Created temporary directory: {}", temp_dir.path().display());
let test_context = TestExecutionContext::default();
let context = revive_dt_config::Context::Test(Box::new(test_context));
let node: &'static dyn revive_dt_node_interaction::EthereumNode = if args.start_platform {
info!("Starting blockchain node...");
let node_handle = platform
.new_node(context.clone())
.context("Failed to spawn node thread")?;
info!("Waiting for node to start...");
let node = node_handle
.join()
.map_err(|e| anyhow::anyhow!("Node thread panicked: {:?}", e))?
.context("Failed to start node")?;
info!(
"Node started with ID: {}, connection: {}",
node.id(),
node.connection_string()
);
let node = Box::leak(node);
info!("Running pre-transactions...");
node.pre_transactions()
.await
.context("Failed to run pre-transactions")?;
info!("Pre-transactions completed");
node
} else {
info!("Using existing node");
let existing_node: Box<dyn revive_dt_node_interaction::EthereumNode> = match args.platform {
PlatformIdentifier::GethEvmSolc | PlatformIdentifier::LighthouseGethEvmSolc => {
Box::new(
revive_dt_node::node_implementations::geth::GethNode::new_existing(
&args.private_key,
args.rpc_port,
)
.await?,
)
}
PlatformIdentifier::KitchensinkPolkavmResolc
| PlatformIdentifier::KitchensinkRevmSolc
| PlatformIdentifier::ReviveDevNodePolkavmResolc
| PlatformIdentifier::ReviveDevNodeRevmSolc
| PlatformIdentifier::ZombienetPolkavmResolc
| PlatformIdentifier::ZombienetRevmSolc => Box::new(
revive_dt_node::node_implementations::substrate::SubstrateNode::new_existing(
&args.private_key,
args.rpc_port,
)
.await?,
),
};
Box::leak(existing_node)
};
info!("Initializing cached compiler");
let cached_compiler = CachedCompiler::new(temp_dir.path().join("compilation_cache"), false)
.await
.map(Arc::new)
.context("Failed to create cached compiler")?;
let private_key_allocator = Arc::new(Mutex::new(PrivateKeyAllocator::new(
alloy::primitives::U256::from(100),
)));
let (reporter, report_task) =
revive_dt_report::ReportAggregator::new(context.clone()).into_task();
tokio::spawn(report_task);
info!(
"Building test definitions for {} case(s)",
metadata_file.cases.len()
);
let mut test_definitions = Vec::new();
for (case_idx, case) in metadata_file.cases.iter().enumerate() {
info!("Building test definition for case {}", case_idx);
let test_def = build_test_definition(
metadata_file,
case,
case_idx,
platform,
node,
&context,
&reporter,
)
.await?;
if let Some(test_def) = test_def {
info!("Test definition for case {} created successfully", case_idx);
test_definitions.push(test_def);
}
}
info!("Executing {} test definition(s)", test_definitions.len());
for (idx, test_definition) in test_definitions.iter().enumerate() {
info!("─────────────────────────────────────────────────────────────────");
info!(
"Executing case {}/{}: case_idx={}, mode={}, steps={}",
idx + 1,
test_definitions.len(),
test_definition.case_idx,
test_definition.mode,
test_definition.case.steps.len()
);
info!("Creating driver for case {}", test_definition.case_idx);
let driver = revive_dt_core::differential_tests::Driver::new_root(
test_definition,
private_key_allocator.clone(),
&cached_compiler,
)
.await
.context("Failed to create driver")?;
info!(
"Running {} step(s) for case {}",
test_definition.case.steps.len(),
test_definition.case_idx
);
let steps_executed = driver.execute_all().await.context(format!(
"Failed to execute case {}",
test_definition.case_idx
))?;
info!(
"✓ Case {} completed successfully, executed {} step(s)",
test_definition.case_idx, steps_executed
);
}
info!("─────────────────────────────────────────────────────────────────");
info!(
"All {} test case(s) executed successfully",
test_definitions.len()
);
Ok(())
}
/// Build a test definition for a single test case
async fn build_test_definition<'a>(
metadata_file: &'a MetadataFile,
case: &'a revive_dt_format::case::Case,
case_idx: usize,
platform: &'a dyn Platform,
node: &'a dyn revive_dt_node_interaction::EthereumNode,
context: &revive_dt_config::Context,
reporter: &revive_dt_report::Reporter,
) -> anyhow::Result<Option<TestDefinition<'a>>> {
let mode = case
.modes
.as_ref()
.or(metadata_file.modes.as_ref())
.and_then(|modes| modes.first())
.and_then(|parsed_mode| parsed_mode.to_modes().next())
.map(Cow::Owned)
.or_else(|| revive_dt_compiler::Mode::all().next().map(Cow::Borrowed))
.unwrap();
let compiler = platform
.new_compiler(context.clone(), mode.version.clone().map(Into::into))
.await
.context("Failed to create compiler")?;
let test_reporter =
reporter.test_specific_reporter(Arc::new(revive_dt_report::TestSpecifier {
solc_mode: mode.as_ref().clone(),
metadata_file_path: metadata_file.metadata_file_path.clone(),
case_idx: CaseIdx::new(case_idx),
}));
let execution_reporter =
test_reporter.execution_specific_reporter(node.id(), platform.platform_identifier());
let mut platforms = BTreeMap::new();
platforms.insert(
platform.platform_identifier(),
TestPlatformInformation {
platform,
node,
compiler,
reporter: execution_reporter,
},
);
let test_definition = TestDefinition {
metadata: metadata_file,
metadata_file_path: &metadata_file.metadata_file_path,
mode,
case_idx: CaseIdx::new(case_idx),
case,
platforms,
reporter: test_reporter,
};
if let Err((reason, _)) = test_definition.check_compatibility() {
println!(" Skipping case {}: {}", case_idx, reason);
return Ok(None);
}
Ok(Some(test_definition))
}
/// Load cached passed tests from file
fn load_cached_passed(path: &Path) -> anyhow::Result<HashSet<String>> {
if !path.exists() {
return Ok(HashSet::new());
}
let file = File::open(path).context("Failed to open cached-passed file")?;
let reader = BufReader::new(file);
let mut cache = HashSet::new();
for line in reader.lines() {
let line = line?;
let trimmed = line.trim();
if !trimmed.is_empty() {
cache.insert(trimmed.to_string());
}
}
Ok(cache)
}
/// Save cached passed tests to file
fn save_cached_passed(path: &Path, cache: &HashSet<String>) -> anyhow::Result<()> {
let file = File::create(path).context("Failed to create cached-passed file")?;
let mut writer = BufWriter::new(file);
let mut entries: Vec<_> = cache.iter().collect();
entries.sort();
for entry in entries {
writeln!(writer, "{}", entry)?;
}
writer.flush()?;
Ok(())
}
+5 -12
View File
@@ -1,14 +1,11 @@
//! This crate implements all node interactions. //! This crate implements all node interactions.
use std::{pin::Pin, sync::Arc}; use std::pin::Pin;
use std::sync::Arc;
use alloy::{ use alloy::primitives::{Address, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256};
primitives::{Address, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256}, use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
rpc::types::{ use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest,
trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace},
},
};
use anyhow::Result; use anyhow::Result;
use futures::Stream; use futures::Stream;
@@ -77,10 +74,6 @@ pub trait EthereumNode {
+ '_, + '_,
>, >,
>; >;
/// Checks if the provided address is in the wallet. If it is, returns the address.
/// Otherwise, returns the default signer's address.
fn resolve_signer_or_default(&self, address: Address) -> Address;
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-1
View File
@@ -29,7 +29,6 @@ serde_yaml_ng = { workspace = true }
sp-core = { workspace = true } sp-core = { workspace = true }
sp-runtime = { workspace = true } sp-runtime = { workspace = true }
zombienet-sdk = { workspace = true }
[dev-dependencies] [dev-dependencies]
temp-dir = { workspace = true } temp-dir = { workspace = true }
+17 -143
View File
@@ -18,9 +18,7 @@ use alloy::{
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
genesis::{Genesis, GenesisAccount}, genesis::{Genesis, GenesisAccount},
network::{Ethereum, EthereumWallet, NetworkWallet}, network::{Ethereum, EthereumWallet, NetworkWallet},
primitives::{ primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256},
Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, StorageKey, TxHash, U256,
},
providers::{ providers::{
Provider, Provider,
ext::DebugApi, ext::DebugApi,
@@ -77,7 +75,6 @@ pub struct GethNode {
wallet: Arc<EthereumWallet>, wallet: Arc<EthereumWallet>,
nonce_manager: CachedNonceManager, nonce_manager: CachedNonceManager,
provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>, provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>,
chain_id: ChainId,
} }
impl GethNode { impl GethNode {
@@ -128,120 +125,9 @@ impl GethNode {
wallet: wallet.clone(), wallet: wallet.clone(),
nonce_manager: Default::default(), nonce_manager: Default::default(),
provider: Default::default(), provider: Default::default(),
chain_id: CHAIN_ID,
} }
} }
pub async fn new_existing(private_key: &str, rpc_port: u16) -> anyhow::Result<Self> {
use alloy::{
primitives::FixedBytes,
providers::{Provider, ProviderBuilder},
signers::local::PrivateKeySigner,
};
let key_str = private_key
.trim()
.strip_prefix("0x")
.unwrap_or(private_key.trim());
let key_bytes = alloy::hex::decode(key_str)
.map_err(|e| anyhow::anyhow!("Failed to decode private key hex: {}", e))?;
if key_bytes.len() != 32 {
anyhow::bail!(
"Private key must be 32 bytes (64 hex characters), got {}",
key_bytes.len()
);
}
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&key_bytes);
let signer = PrivateKeySigner::from_bytes(&FixedBytes(bytes))
.map_err(|e| anyhow::anyhow!("Failed to create signer from private key: {}", e))?;
let address = signer.address();
let wallet = Arc::new(EthereumWallet::new(signer));
let connection_string = format!("http://localhost:{}", rpc_port);
let chain_id = ProviderBuilder::new()
.connect_http(connection_string.parse()?)
.get_chain_id()
.await
.context("Failed to query chain ID from RPC")?;
let node = Self {
connection_string: format!("http://localhost:{}", rpc_port),
base_directory: PathBuf::new(),
data_directory: PathBuf::new(),
logs_directory: PathBuf::new(),
geth: PathBuf::new(),
id: 0,
chain_id,
handle: None,
start_timeout: Duration::from_secs(0),
wallet,
nonce_manager: Default::default(),
provider: Default::default(),
};
// Check balance and fund if needed
node.ensure_funded(address).await?;
Ok(node)
}
/// Ensure that the given address has at least 1000 ETH, funding it from the node's managed
/// account if necessary.
async fn ensure_funded(&self, address: Address) -> anyhow::Result<()> {
use alloy::{
primitives::utils::{format_ether, parse_ether},
providers::{Provider, ProviderBuilder},
};
let provider = ProviderBuilder::new().connect_http(self.connection_string.parse()?);
let balance = provider.get_balance(address).await?;
let min_balance = parse_ether("1000")?;
if balance >= min_balance {
tracing::info!(
"Wallet {} already has sufficient balance: {} ETH",
address,
format_ether(balance)
);
return Ok(());
}
tracing::info!(
"Funding wallet {} (current: {} ETH, target: 1000 ETH)",
address,
format_ether(balance)
);
// Get the node's managed account
let accounts = provider.get_accounts().await?;
if accounts.is_empty() {
anyhow::bail!("No managed accounts available on the node to fund wallet");
}
let from_account = accounts[0];
let funding_amount = min_balance - balance;
let tx = TransactionRequest::default()
.from(from_account)
.to(address)
.value(funding_amount);
provider
.send_transaction(tx)
.await?
.get_receipt()
.await
.context("Failed to get receipt for funding transaction")?;
tracing::info!("Successfully funded wallet {}", address);
Ok(())
}
/// Create the node directory and call `geth init` to configure the genesis. /// Create the node directory and call `geth init` to configure the genesis.
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> { fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> {
@@ -369,7 +255,7 @@ impl GethNode {
construct_concurrency_limited_provider::<Ethereum, _>( construct_concurrency_limited_provider::<Ethereum, _>(
self.connection_string.as_str(), self.connection_string.as_str(),
FallbackGasFiller::default(), FallbackGasFiller::default(),
ChainIdFiller::new(Some(self.chain_id)), ChainIdFiller::new(Some(CHAIN_ID)),
NonceFiller::new(self.nonce_manager.clone()), NonceFiller::new(self.nonce_manager.clone()),
self.wallet.clone(), self.wallet.clone(),
) )
@@ -463,25 +349,23 @@ impl EthereumNode for GethNode {
.context("Failed to submit transaction to geth node")?; .context("Failed to submit transaction to geth node")?;
let transaction_hash = *pending_transaction.tx_hash(); let transaction_hash = *pending_transaction.tx_hash();
// The following is a fix for the "transaction indexing is in progress" error that we // The following is a fix for the "transaction indexing is in progress" error that we used
// used to get. You can find more information on this in the following GH issue in // to get. You can find more information on this in the following GH issue in geth
// geth https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on, // https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on,
// before we can get the receipt of the transaction it needs to have been indexed by the // before we can get the receipt of the transaction it needs to have been indexed by the
// node's indexer. Just because the transaction has been confirmed it doesn't mean that // node's indexer. Just because the transaction has been confirmed it doesn't mean that it
// it has been indexed. When we call alloy's `get_receipt` it checks if the // has been indexed. When we call alloy's `get_receipt` it checks if the transaction was
// transaction was confirmed. If it has been, then it will call // confirmed. If it has been, then it will call `eth_getTransactionReceipt` method which
// `eth_getTransactionReceipt` method which _might_ return the above error if the tx // _might_ return the above error if the tx has not yet been indexed yet. So, we need to
// has not yet been indexed yet. So, we need to implement a retry mechanism for the // implement a retry mechanism for the receipt to keep retrying to get it until it
// receipt to keep retrying to get it until it eventually works, but we only do that // eventually works, but we only do that if the error we get back is the "transaction
// if the error we get back is the "transaction indexing is in progress" error or if // indexing is in progress" error or if the receipt is None.
// the receipt is None.
// //
// Getting the transaction indexed and taking a receipt can take a long time especially // Getting the transaction indexed and taking a receipt can take a long time especially when
// when a lot of transactions are being submitted to the node. Thus, while initially // a lot of transactions are being submitted to the node. Thus, while initially we only
// we only allowed for 60 seconds of waiting with a 1 second delay in polling, we // allowed for 60 seconds of waiting with a 1 second delay in polling, we need to allow for
// need to allow for a larger wait time. Therefore, in here we allow for 5 minutes of // a larger wait time. Therefore, in here we allow for 5 minutes of waiting with exponential
// waiting with exponential backoff each time we attempt to get the receipt and find // backoff each time we attempt to get the receipt and find that it's not available.
// that it's not available.
poll( poll(
Self::RECEIPT_POLLING_DURATION, Self::RECEIPT_POLLING_DURATION,
PollingWaitBehavior::Constant(Duration::from_millis(200)), PollingWaitBehavior::Constant(Duration::from_millis(200)),
@@ -658,16 +542,6 @@ impl EthereumNode for GethNode {
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>) as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
}) })
} }
fn resolve_signer_or_default(&self, address: Address) -> Address {
let signer_addresses: Vec<_> =
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet).collect();
if signer_addresses.contains(&address) {
address
} else {
self.wallet.default_signer().address()
}
}
} }
pub struct GethNodeResolver { pub struct GethNodeResolver {
@@ -17,7 +17,7 @@ use std::{
pin::Pin, pin::Pin,
process::{Command, Stdio}, process::{Command, Stdio},
sync::{ sync::{
Arc, Arc, LazyLock,
atomic::{AtomicU32, Ordering}, atomic::{AtomicU32, Ordering},
}, },
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
@@ -47,7 +47,7 @@ use futures::{Stream, StreamExt};
use revive_common::EVMVersion; use revive_common::EVMVersion;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::serde_as; use serde_with::serde_as;
use tokio::sync::OnceCell; use tokio::sync::{OnceCell, Semaphore};
use tracing::{Instrument, info, instrument}; use tracing::{Instrument, info, instrument};
use revive_dt_common::{ use revive_dt_common::{
@@ -105,6 +105,7 @@ pub struct LighthouseGethNode {
persistent_http_provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>, persistent_http_provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>,
persistent_ws_provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>, persistent_ws_provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>,
http_provider_requests_semaphore: LazyLock<Semaphore>,
} }
impl LighthouseGethNode { impl LighthouseGethNode {
@@ -175,6 +176,7 @@ impl LighthouseGethNode {
nonce_manager: Default::default(), nonce_manager: Default::default(),
persistent_http_provider: OnceCell::const_new(), persistent_http_provider: OnceCell::const_new(),
persistent_ws_provider: OnceCell::const_new(), persistent_ws_provider: OnceCell::const_new(),
http_provider_requests_semaphore: LazyLock::new(|| Semaphore::const_new(500)),
} }
} }
@@ -564,6 +566,8 @@ impl EthereumNode for LighthouseGethNode {
transaction: TransactionRequest, transaction: TransactionRequest,
) -> Pin<Box<dyn Future<Output = anyhow::Result<TxHash>> + '_>> { ) -> Pin<Box<dyn Future<Output = anyhow::Result<TxHash>> + '_>> {
Box::pin(async move { Box::pin(async move {
let _permit = self.http_provider_requests_semaphore.acquire().await;
let provider = self let provider = self
.http_provider() .http_provider()
.await .await
@@ -761,16 +765,6 @@ impl EthereumNode for LighthouseGethNode {
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>) as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
}) })
} }
fn resolve_signer_or_default(&self, address: Address) -> Address {
let signer_addresses: Vec<_> =
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet).collect();
if signer_addresses.contains(&address) {
address
} else {
self.wallet.default_signer().address()
}
}
} }
pub struct LighthouseGethNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> { pub struct LighthouseGethNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> {
@@ -1,4 +1,3 @@
pub mod geth; pub mod geth;
pub mod lighthouse_geth; pub mod lighthouse_geth;
pub mod substrate; pub mod substrate;
pub mod zombienet;
@@ -54,10 +54,7 @@ use crate::{
Node, Node,
constants::{CHAIN_ID, INITIAL_BALANCE}, constants::{CHAIN_ID, INITIAL_BALANCE},
helpers::{Process, ProcessReadinessWaitBehavior}, helpers::{Process, ProcessReadinessWaitBehavior},
provider_utils::{ provider_utils::{ConcreteProvider, FallbackGasFiller, construct_concurrency_limited_provider},
ConcreteProvider, FallbackGasFiller, construct_concurrency_limited_provider,
execute_transaction,
},
}; };
static NODE_COUNT: AtomicU32 = AtomicU32::new(0); static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -80,11 +77,10 @@ pub struct SubstrateNode {
wallet: Arc<EthereumWallet>, wallet: Arc<EthereumWallet>,
nonce_manager: CachedNonceManager, nonce_manager: CachedNonceManager,
provider: OnceCell<ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>>, provider: OnceCell<ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>>,
chain_id: alloy::primitives::ChainId,
} }
impl SubstrateNode { impl SubstrateNode {
const BASE_DIRECTORY: &str = "substrate"; const BASE_DIRECTORY: &str = "Substrate";
const LOGS_DIRECTORY: &str = "logs"; const LOGS_DIRECTORY: &str = "logs";
const DATA_DIRECTORY: &str = "chains"; const DATA_DIRECTORY: &str = "chains";
@@ -132,64 +128,9 @@ impl SubstrateNode {
wallet: wallet.clone(), wallet: wallet.clone(),
nonce_manager: Default::default(), nonce_manager: Default::default(),
provider: Default::default(), provider: Default::default(),
chain_id: CHAIN_ID,
} }
} }
pub async fn new_existing(private_key: &str, rpc_port: u16) -> anyhow::Result<Self> {
use alloy::{
primitives::FixedBytes,
providers::{Provider, ProviderBuilder},
signers::local::PrivateKeySigner,
};
let key_str = private_key
.trim()
.strip_prefix("0x")
.unwrap_or(private_key.trim());
let key_bytes = alloy::hex::decode(key_str)
.map_err(|e| anyhow::anyhow!("Failed to decode private key hex: {}", e))?;
if key_bytes.len() != 32 {
anyhow::bail!(
"Private key must be 32 bytes (64 hex characters), got {}",
key_bytes.len()
);
}
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&key_bytes);
let signer = PrivateKeySigner::from_bytes(&FixedBytes(bytes))
.map_err(|e| anyhow::anyhow!("Failed to create signer from private key: {}", e))?;
let wallet = Arc::new(EthereumWallet::new(signer));
let rpc_url = format!("http://localhost:{}", rpc_port);
// Query the chain ID from the RPC
let chain_id = ProviderBuilder::new()
.connect_http(rpc_url.parse()?)
.get_chain_id()
.await
.context("Failed to query chain ID from RPC")?;
Ok(Self {
id: 0,
node_binary: PathBuf::new(),
eth_proxy_binary: PathBuf::new(),
export_chainspec_command: String::new(),
rpc_url,
base_directory: PathBuf::new(),
logs_directory: PathBuf::new(),
substrate_process: None,
eth_proxy_process: None,
wallet,
nonce_manager: Default::default(),
provider: Default::default(),
chain_id,
})
}
fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> { fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> {
let _ = remove_dir_all(self.base_directory.as_path()); let _ = remove_dir_all(self.base_directory.as_path());
let _ = clear_directory(&self.base_directory); let _ = clear_directory(&self.base_directory);
@@ -405,8 +346,8 @@ impl SubstrateNode {
.get_or_try_init(|| async move { .get_or_try_init(|| async move {
construct_concurrency_limited_provider::<ReviveNetwork, _>( construct_concurrency_limited_provider::<ReviveNetwork, _>(
self.rpc_url.as_str(), self.rpc_url.as_str(),
FallbackGasFiller::new(u64::MAX, 5_000_000_000, 1_000_000_000), FallbackGasFiller::new(250_000_000, 5_000_000_000, 1_000_000_000),
ChainIdFiller::new(Some(self.chain_id)), ChainIdFiller::new(Some(CHAIN_ID)),
NonceFiller::new(self.nonce_manager.clone()), NonceFiller::new(self.nonce_manager.clone()),
self.wallet.clone(), self.wallet.clone(),
) )
@@ -467,12 +408,23 @@ impl EthereumNode for SubstrateNode {
&self, &self,
transaction: TransactionRequest, transaction: TransactionRequest,
) -> Pin<Box<dyn Future<Output = anyhow::Result<TransactionReceipt>> + '_>> { ) -> Pin<Box<dyn Future<Output = anyhow::Result<TransactionReceipt>> + '_>> {
static SEMAPHORE: std::sync::LazyLock<tokio::sync::Semaphore> =
std::sync::LazyLock::new(|| tokio::sync::Semaphore::new(500));
Box::pin(async move { Box::pin(async move {
let provider = self let _permit = SEMAPHORE.acquire().await?;
let receipt = self
.provider() .provider()
.await .await
.context("Failed to create the provider")?; .context("Failed to create provider for transaction submission")?
execute_transaction(provider, transaction).await .send_transaction(transaction)
.await
.context("Failed to submit transaction to substrate proxy")?
.get_receipt()
.await
.context("Failed to fetch transaction receipt from substrate proxy")?;
Ok(receipt)
}) })
} }
@@ -597,16 +549,6 @@ impl EthereumNode for SubstrateNode {
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>) as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
}) })
} }
fn resolve_signer_or_default(&self, address: Address) -> Address {
let signer_addresses: Vec<_> =
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet).collect();
if signer_addresses.contains(&address) {
address
} else {
self.wallet.default_signer().address()
}
}
} }
pub struct SubstrateNodeResolver { pub struct SubstrateNodeResolver {
File diff suppressed because it is too large Load Diff
+3 -68
View File
@@ -1,16 +1,14 @@
use std::{ops::ControlFlow, sync::LazyLock, time::Duration}; use std::sync::LazyLock;
use alloy::{ use alloy::{
network::{Ethereum, Network, NetworkWallet, TransactionBuilder4844}, network::{Network, NetworkWallet, TransactionBuilder4844},
providers::{ providers::{
Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, Identity, ProviderBuilder, RootProvider,
fillers::{ChainIdFiller, FillProvider, JoinFill, NonceFiller, TxFiller, WalletFiller}, fillers::{ChainIdFiller, FillProvider, JoinFill, NonceFiller, TxFiller, WalletFiller},
}, },
rpc::client::ClientBuilder, rpc::client::ClientBuilder,
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use revive_dt_common::futures::{PollingWaitBehavior, poll};
use tracing::debug;
use crate::provider_utils::{ConcurrencyLimiterLayer, FallbackGasFiller}; use crate::provider_utils::{ConcurrencyLimiterLayer, FallbackGasFiller};
@@ -63,66 +61,3 @@ where
Ok(provider) Ok(provider)
} }
pub async fn execute_transaction<N, W>(
provider: ConcreteProvider<N, W>,
transaction: N::TransactionRequest,
) -> Result<N::ReceiptResponse>
where
N: Network<
TransactionRequest: TransactionBuilder4844,
TxEnvelope = <Ethereum as Network>::TxEnvelope,
>,
W: NetworkWallet<N>,
Identity: TxFiller<N>,
FallbackGasFiller: TxFiller<N>,
ChainIdFiller: TxFiller<N>,
NonceFiller: TxFiller<N>,
WalletFiller<W>: TxFiller<N>,
{
let sendable_transaction = provider
.fill(transaction)
.await
.context("Failed to fill transaction")?;
let transaction_envelope = sendable_transaction
.try_into_envelope()
.context("Failed to convert transaction into an envelope")?;
let tx_hash = *transaction_envelope.tx_hash();
let mut pending_transaction = match provider.send_tx_envelope(transaction_envelope).await {
Ok(pending_transaction) => pending_transaction,
Err(error) => {
let error_string = error.to_string();
if error_string.contains("Transaction Already Imported") {
PendingTransactionBuilder::<N>::new(provider.root().clone(), tx_hash)
} else {
return Err(error).context(format!("Failed to submit transaction {tx_hash}"));
}
}
};
debug!(%tx_hash, "Submitted Transaction");
pending_transaction.set_timeout(Some(Duration::from_secs(120)));
let tx_hash = pending_transaction.watch().await.context(format!(
"Transaction inclusion watching timeout for {tx_hash}"
))?;
poll(
Duration::from_secs(60),
PollingWaitBehavior::Constant(Duration::from_secs(3)),
|| {
let provider = provider.clone();
async move {
match provider.get_transaction_receipt(tx_hash).await {
Ok(Some(receipt)) => Ok(ControlFlow::Break(receipt)),
_ => Ok(ControlFlow::Continue(())),
}
}
},
)
.await
.context(format!("Polling for receipt failed for {tx_hash}"))
}
+1 -2
View File
@@ -442,8 +442,7 @@ impl Report {
#[derive(Clone, Debug, Serialize, Default)] #[derive(Clone, Debug, Serialize, Default)]
pub struct TestCaseReport { pub struct TestCaseReport {
/// Information on the status of the test case and whether it succeeded, failed, or was /// Information on the status of the test case and whether it succeeded, failed, or was ignored.
/// ignored.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<TestCaseStatus>, pub status: Option<TestCaseStatus>,
/// Information related to the execution on one of the platforms. /// Information related to the execution on one of the platforms.
+2 -4
View File
@@ -8,10 +8,8 @@ use anyhow::Context as _;
use indexmap::IndexMap; use indexmap::IndexMap;
use revive_dt_common::types::PlatformIdentifier; use revive_dt_common::types::PlatformIdentifier;
use revive_dt_compiler::{CompilerInput, CompilerOutput}; use revive_dt_compiler::{CompilerInput, CompilerOutput};
use revive_dt_format::{ use revive_dt_format::metadata::Metadata;
corpus::Corpus, use revive_dt_format::{corpus::Corpus, metadata::ContractInstance};
metadata::{ContractInstance, Metadata},
};
use semver::Version; use semver::Version;
use tokio::sync::{broadcast, oneshot}; use tokio::sync::{broadcast, oneshot};
+4 -3
View File
@@ -76,6 +76,8 @@ cat > "$CORPUS_FILE" << EOF
{ {
"name": "MatterLabs Solidity Simple, Complex, and Semantic Tests", "name": "MatterLabs Solidity Simple, Complex, and Semantic Tests",
"paths": [ "paths": [
"$(realpath "$TEST_REPO_DIR/fixtures/solidity/translated_semantic_tests")",
"$(realpath "$TEST_REPO_DIR/fixtures/solidity/complex")",
"$(realpath "$TEST_REPO_DIR/fixtures/solidity/simple")" "$(realpath "$TEST_REPO_DIR/fixtures/solidity/simple")"
] ]
} }
@@ -93,12 +95,11 @@ echo ""
# Run the tool # Run the tool
cargo build --release; cargo build --release;
RUST_LOG="info,alloy_pubsub::service=error" ./target/release/retester test \ RUST_LOG="info,alloy_pubsub::service=error" ./target/release/retester test \
--platform geth-evm-solc \
--platform revive-dev-node-revm-solc \ --platform revive-dev-node-revm-solc \
--corpus "$CORPUS_FILE" \ --corpus "$CORPUS_FILE" \
--working-directory "$WORKDIR" \ --working-directory "$WORKDIR" \
--concurrency.number-of-nodes 10 \ --concurrency.number-of-nodes 5 \
--concurrency.number-of-threads 5 \
--concurrency.number-of-concurrent-tasks 1000 \
--wallet.additional-keys 100000 \ --wallet.additional-keys 100000 \
--kitchensink.path "$SUBSTRATE_NODE_BIN" \ --kitchensink.path "$SUBSTRATE_NODE_BIN" \
--revive-dev-node.path "$REVIVE_DEV_NODE_BIN" \ --revive-dev-node.path "$REVIVE_DEV_NODE_BIN" \