This commit is contained in:
pgherveou
2025-10-08 08:22:26 +02:00
parent 6e64f678ee
commit 765569a8b6
39 changed files with 662 additions and 1178 deletions
+2 -1
View File
@@ -13,4 +13,5 @@ resolc-compiler-tests
workdir
!/schema.json
!/dev-genesis.json
!/dev-genesis.json
geth-dev/
+6 -7
View File
@@ -1,9 +1,11 @@
//! This module implements a cached file system allowing for results to be stored in-memory rather
//! rather being queried from the file system again.
use std::fs;
use std::io::{Error, Result};
use std::path::{Path, PathBuf};
use std::{
fs,
io::{Error, Result},
path::{Path, PathBuf},
};
use moka::sync::Cache;
use once_cell::sync::Lazy;
@@ -25,10 +27,7 @@ pub fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
let content = read(path)?;
String::from_utf8(content).map_err(|_| {
Error::new(
std::io::ErrorKind::InvalidData,
"The contents of the file are not valid UTF8",
)
Error::new(std::io::ErrorKind::InvalidData, "The contents of the file are not valid UTF8")
})
}
+1 -4
View File
@@ -12,10 +12,7 @@ pub fn clear_directory(path: impl AsRef<Path>) -> Result<()> {
.with_context(|| format!("Failed to read directory: {}", path.as_ref().display()))?
{
let entry = entry.with_context(|| {
format!(
"Failed to read an entry in directory: {}",
path.as_ref().display()
)
format!("Failed to read an entry in directory: {}", path.as_ref().display())
})?;
let entry_path = entry.path();
+2 -6
View File
@@ -1,5 +1,4 @@
use std::ops::ControlFlow;
use std::time::Duration;
use std::{ops::ControlFlow, time::Duration};
use anyhow::{Context as _, Result, anyhow};
@@ -38,10 +37,7 @@ where
));
}
match future()
.await
.context("Polled future returned an error during polling loop")?
{
match future().await.context("Polled future returned an error during polling loop")? {
ControlFlow::Continue(()) => {
let next_wait_duration = match polling_wait_behavior {
PollingWaitBehavior::Constant(duration) => duration,
+2 -6
View File
@@ -1,9 +1,7 @@
use crate::types::VersionOrRequirement;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
use std::{fmt::Display, str::FromStr, sync::LazyLock};
/// This represents a mode that a given test should be run with, if possible.
///
@@ -78,9 +76,7 @@ impl FromStr for ModePipeline {
// Don't go via Yul IR
"E" => Ok(ModePipeline::ViaEVMAssembly),
// Anything else that we see isn't a mode at all
_ => Err(anyhow::anyhow!(
"Unsupported pipeline '{s}': expected 'Y' or 'E'"
)),
_ => Err(anyhow::anyhow!("Unsupported pipeline '{s}': expected 'Y' or 'E'")),
}
}
}
@@ -1,5 +1,4 @@
use alloy::primitives::U256;
use alloy::signers::local::PrivateKeySigner;
use alloy::{primitives::U256, signers::local::PrivateKeySigner};
use anyhow::{Context, Result, bail};
/// This is a sequential private key allocator. When instantiated, it allocated private keys in
+1 -2
View File
@@ -10,8 +10,7 @@ use std::{
pin::Pin,
};
use alloy::json_abi::JsonAbi;
use alloy::primitives::Address;
use alloy::{json_abi::JsonAbi, primitives::Address};
use anyhow::{Context as _, Result};
use semver::Version;
use serde::{Deserialize, Serialize};
+1 -3
View File
@@ -141,9 +141,7 @@ impl SolidityCompiler for Resolc {
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
via_ir: Some(true),
optimizer: SolcStandardJsonInputSettingsOptimizer::new(
optimization
.unwrap_or(ModeOptimizerSetting::M0)
.optimizations_enabled(),
optimization.unwrap_or(ModeOptimizerSetting::M0).optimizations_enabled(),
None,
&Version::new(0, 0, 0),
false,
+3 -9
View File
@@ -21,8 +21,7 @@ use foundry_compilers_artifacts::{
output_selection::{
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection,
},
solc::CompilerOutput as SolcOutput,
solc::*,
solc::{CompilerOutput as SolcOutput, *},
};
use semver::Version;
use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand};
@@ -57,9 +56,7 @@ impl Solc {
// resolution for us. Therefore, even if the download didn't proceed, this function will
// resolve the version requirement into a canonical version of the compiler. It's then up
// to us to either use the provided path or not.
let version = version
.into()
.unwrap_or_else(|| solc_configuration.version.clone().into());
let version = version.into().unwrap_or_else(|| solc_configuration.version.clone().into());
let (version, path) =
download_solc(working_directory_configuration.as_path(), version, false)
.await
@@ -253,10 +250,7 @@ impl SolidityCompiler for Solc {
let map = compiler_output
.contracts
.entry(contract_path.canonicalize().with_context(|| {
format!(
"Failed to canonicalize contract path {}",
contract_path.display()
)
format!("Failed to canonicalize contract path {}", contract_path.display())
})?)
.or_default();
for (contract_name, contract_info) in contracts.into_iter() {
+8 -22
View File
@@ -9,9 +9,8 @@ use semver::Version;
async fn contracts_can_be_compiled_with_solc() {
// Arrange
let args = TestExecutionContext::default();
let solc = Solc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
.await
.unwrap();
let solc =
Solc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30))).await.unwrap();
// Act
let output = Compiler::new()
@@ -28,18 +27,12 @@ async fn contracts_can_be_compiled_with_solc() {
let main_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/main.sol")
.canonicalize()
.unwrap(),
)
.get(&PathBuf::from("./tests/assets/array_one_element/main.sol").canonicalize().unwrap())
.unwrap();
let callable_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
.canonicalize()
.unwrap(),
&PathBuf::from("./tests/assets/array_one_element/callable.sol").canonicalize().unwrap(),
)
.unwrap();
assert!(main_file_contracts.contains_key("Main"));
@@ -50,9 +43,8 @@ async fn contracts_can_be_compiled_with_solc() {
async fn contracts_can_be_compiled_with_resolc() {
// Arrange
let args = TestExecutionContext::default();
let resolc = Resolc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
.await
.unwrap();
let resolc =
Resolc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30))).await.unwrap();
// Act
let output = Compiler::new()
@@ -69,18 +61,12 @@ async fn contracts_can_be_compiled_with_resolc() {
let main_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/main.sol")
.canonicalize()
.unwrap(),
)
.get(&PathBuf::from("./tests/assets/array_one_element/main.sol").canonicalize().unwrap())
.unwrap();
let callable_file_contracts = output
.contracts
.get(
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
.canonicalize()
.unwrap(),
&PathBuf::from("./tests/assets/array_one_element/callable.sol").canonicalize().unwrap(),
)
.unwrap();
assert!(main_file_contracts.contains_key("Main"));
+18 -17
View File
@@ -626,11 +626,7 @@ pub struct KurtosisConfiguration {
///
/// If this is not specified, then the tool assumes that it should use the kurtosis binary that's
/// provided in the user's $PATH.
#[clap(
id = "kurtosis.path",
long = "kurtosis.path",
default_value = "kurtosis"
)]
#[clap(id = "kurtosis.path", long = "kurtosis.path", default_value = "kurtosis")]
pub path: PathBuf,
}
@@ -641,11 +637,7 @@ pub struct KitchensinkConfiguration {
///
/// If this is not specified, then the tool assumes that it should use the kitchensink binary
/// that's provided in the user's $PATH.
#[clap(
id = "kitchensink.path",
long = "kitchensink.path",
default_value = "substrate-node"
)]
#[clap(id = "kitchensink.path", long = "kitchensink.path", default_value = "substrate-node")]
pub path: PathBuf,
/// The amount of time to wait upon startup before considering that the node timed out.
@@ -785,6 +777,20 @@ impl WalletConfiguration {
}
}
impl Default for WalletConfiguration {
fn default() -> Self {
let mut config = Self::parse_from::<[&str; 0], &str>([]);
config.additional_keys = 0;
config
// config.default_key = PrivateKeySigner::from_bytes(
// &FixedBytes::from_hex_str(
// "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
// )
// .unwrap(),
// )
}
}
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -823,10 +829,7 @@ impl ConcurrencyConfiguration {
pub fn concurrency_limit(&self) -> Option<usize> {
match self.ignore_concurrency_limit {
true => None,
false => Some(
self.number_concurrent_tasks
.unwrap_or(20 * self.number_of_nodes),
),
false => Some(self.number_concurrent_tasks.unwrap_or(20 * self.number_of_nodes)),
}
}
}
@@ -917,9 +920,7 @@ impl Serialize for WorkingDirectoryConfiguration {
}
fn parse_duration(s: &str) -> anyhow::Result<Duration> {
u64::from_str(s)
.map(Duration::from_millis)
.map_err(Into::into)
u64::from_str(s).map(Duration::from_millis).map_err(Into::into)
}
/// The Solidity compatible node implementation.
@@ -206,9 +206,7 @@ where
"Deployed library"
);
let library_address = receipt
.contract_address
.expect("Failed to deploy the library");
let library_address = receipt.contract_address.expect("Failed to deploy the library");
deployed_libraries.get_or_insert_default().insert(
library_instance.clone(),
@@ -236,10 +234,8 @@ where
})
.context("Failed to compile the post-link contracts")?;
self.execution_state = ExecutionState::new(
compiler_output.contracts,
deployed_libraries.unwrap_or_default(),
);
self.execution_state =
ExecutionState::new(compiler_output.contracts, deployed_libraries.unwrap_or_default());
Ok(())
}
@@ -325,11 +321,7 @@ where
) -> Result<HashMap<ContractInstance, TransactionReceipt>> {
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
for instance in step.find_all_contract_instances().into_iter() {
if !self
.execution_state
.deployed_contracts
.contains_key(&instance)
{
if !self.execution_state.deployed_contracts.contains_key(&instance) {
instances_we_must_deploy.entry(instance).or_insert(false);
}
}
@@ -341,15 +333,11 @@ where
let mut receipts = HashMap::new();
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
let calldata = deploy_with_constructor_arguments.then_some(&step.calldata);
let value = deploy_with_constructor_arguments
.then_some(step.value)
.flatten();
let value = deploy_with_constructor_arguments.then_some(step.value).flatten();
let caller = {
let context = self.default_resolution_context();
step.caller
.resolve_address(self.resolver.as_ref(), context)
.await?
step.caller.resolve_address(self.resolver.as_ref(), context).await?
};
if let (_, _, Some(receipt)) = self
.get_or_deploy_contract_instance(&instance, caller, calldata, value)
@@ -424,18 +412,13 @@ where
};
// Handling the return data variable assignments.
for (variable_name, output_word) in assignments.return_data.iter().zip(
tracing_result
.output
.as_ref()
.unwrap_or_default()
.to_vec()
.chunks(32),
) {
for (variable_name, output_word) in assignments
.return_data
.iter()
.zip(tracing_result.output.as_ref().unwrap_or_default().to_vec().chunks(32))
{
let value = U256::from_be_slice(output_word);
self.execution_state
.variables
.insert(variable_name.clone(), value);
self.execution_state.variables.insert(variable_name.clone(), value);
tracing::info!(
variable_name,
variable_value = hex::encode(value.to_be_bytes::<32>()),
@@ -508,9 +491,8 @@ where
})
.context("Failed to send message on the watcher's tx")?;
let res = futures::future::try_join_all(tasks)
.await
.context("Repetition execution failed")?;
let res =
futures::future::try_join_all(tasks).await.context("Repetition execution failed")?;
Ok(res.into_iter().sum())
}
@@ -533,9 +515,7 @@ where
let account = private_key.address();
let variable = U256::from_be_slice(account.0.as_slice());
self.execution_state
.variables
.insert(variable_name.to_string(), variable);
self.execution_state.variables.insert(variable_name.to_string(), variable);
Ok(1)
}
@@ -560,10 +540,8 @@ where
calldata: Option<&Calldata>,
value: Option<EtherValue>,
) -> Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
if let Some((_, address, abi)) = self
.execution_state
.deployed_contracts
.get(contract_instance)
if let Some((_, address, abi)) =
self.execution_state.deployed_contracts.get(contract_instance)
{
info!(
@@ -606,16 +584,9 @@ where
let Some(ContractPathAndIdent {
contract_source_path,
contract_ident,
}) = self
.test_definition
.metadata
.contract_sources()?
.remove(contract_instance)
}) = self.test_definition.metadata.contract_sources()?.remove(contract_instance)
else {
anyhow::bail!(
"Contract source not found for instance {:?}",
contract_instance
)
anyhow::bail!("Contract source not found for instance {:?}", contract_instance)
};
let Some((code, abi)) = self
@@ -625,10 +596,7 @@ where
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
.cloned()
else {
anyhow::bail!(
"Failed to find information for contract {:?}",
contract_instance
)
anyhow::bail!("Failed to find information for contract {:?}", contract_instance)
};
let mut code = match alloy::hex::decode(&code) {
@@ -680,10 +648,9 @@ where
.reporter
.report_contract_deployed_event(contract_instance.clone(), address)?;
self.execution_state.deployed_contracts.insert(
contract_instance.clone(),
(contract_ident, address, abi.clone()),
);
self.execution_state
.deployed_contracts
.insert(contract_instance.clone(), (contract_ident, address, abi.clone()));
Ok((address, abi, receipt))
}
@@ -696,9 +663,7 @@ where
match step_address {
StepAddress::Address(address) => Ok(*address),
StepAddress::ResolvableAddress(resolvable) => {
let Some(instance) = resolvable
.strip_suffix(".address")
.map(ContractInstance::new)
let Some(instance) = resolvable.strip_suffix(".address").map(ContractInstance::new)
else {
bail!("Not an address variable");
};
@@ -736,15 +701,15 @@ where
transaction: TransactionRequest,
) -> anyhow::Result<TransactionReceipt> {
let node = self.platform_information.node;
let transaction_hash = node
.submit_transaction(transaction)
.await
.context("Failed to submit transaction")?;
let transaction_hash =
node.submit_transaction(transaction).await.context("Failed to submit transaction")?;
Span::current().record("transaction_hash", display(transaction_hash));
info!("Submitted transaction");
self.watcher_tx
.send(WatcherEvent::SubmittedTransaction { transaction_hash })
.send(WatcherEvent::SubmittedTransaction {
transaction_hash,
})
.context("Failed to send the transaction hash to the watcher")?;
info!("Starting to poll for transaction receipt");
@@ -44,12 +44,8 @@ pub async fn handle_differential_benchmarks(
info!(len = metadata_files.len(), "Discovered metadata files");
// Discover the list of platforms that the tests should run on based on the context.
let platforms = context
.platforms
.iter()
.copied()
.map(Into::<&dyn Platform>::into)
.collect::<Vec<_>>();
let platforms =
context.platforms.iter().copied().map(Into::<&dyn Platform>::into).collect::<Vec<_>>();
// Starting the nodes of the various platforms specified in the context. Note that we use the
// node pool since it contains all of the code needed to spawn nodes from A to Z and therefore
@@ -96,13 +92,8 @@ pub async fn handle_differential_benchmarks(
// Creating the objects that will be shared between the various runs. The cached compiler is the
// only one at the current moment of time that's safe to share between runs.
let cached_compiler = CachedCompiler::new(
context
.working_directory
.as_path()
.join("compilation_cache"),
context
.compilation_configuration
.invalidate_compilation_cache,
context.working_directory.as_path().join("compilation_cache"),
context.compilation_configuration.invalidate_compilation_cache,
)
.await
.map(Arc::new)
@@ -161,9 +152,7 @@ pub async fn handle_differential_benchmarks(
watcher.run(),
driver.execute_all().inspect(|_| {
info!("All transactions submitted - driver completed execution");
watcher_tx
.send(WatcherEvent::AllTransactionsSubmitted)
.unwrap()
watcher_tx.send(WatcherEvent::AllTransactionsSubmitted).unwrap()
}),
)
.await
@@ -80,12 +80,13 @@ impl Watcher {
// Subsequent repetition starts are ignored since certain workloads can
// contain nested repetitions and therefore there's no use in doing any
// action if the repetitions are nested.
WatcherEvent::RepetitionStartEvent { .. } => {}
WatcherEvent::SubmittedTransaction { transaction_hash } => {
watch_for_transaction_hashes
.write()
.await
.insert(transaction_hash);
WatcherEvent::RepetitionStartEvent {
..
} => {}
WatcherEvent::SubmittedTransaction {
transaction_hash,
} => {
watch_for_transaction_hashes.write().await.insert(transaction_hash);
}
WatcherEvent::AllTransactionsSubmitted => {
*all_transactions_submitted.write().await = true;
@@ -151,15 +152,8 @@ impl Watcher {
use std::io::Write;
let mut stderr = std::io::stderr().lock();
writeln!(
stderr,
"Watcher information for {}",
self.platform_identifier
)?;
writeln!(
stderr,
"block_number,block_timestamp,mined_gas,block_gas_limit,tx_count"
)?;
writeln!(stderr, "Watcher information for {}", self.platform_identifier)?;
writeln!(stderr, "block_number,block_timestamp,mined_gas,block_gas_limit,tx_count")?;
for block in mined_blocks_information {
writeln!(
stderr,
+51 -91
View File
@@ -76,7 +76,9 @@ impl<'a> Driver<'a, StepsIterator> {
.into_iter()
.collect::<BTreeMap<_, _>>();
Ok(Self { platform_drivers })
Ok(Self {
platform_drivers,
})
}
async fn create_platform_driver(
@@ -112,9 +114,7 @@ impl<'a> Driver<'a, StepsIterator> {
pub async fn execute_all(mut self) -> Result<usize> {
let platform_drivers = std::mem::take(&mut self.platform_drivers);
let results = futures::future::try_join_all(
platform_drivers
.into_values()
.map(|driver| driver.execute_all()),
platform_drivers.into_values().map(|driver| driver.execute_all()),
)
.await
.context("Failed to execute all of the steps on the driver")?;
@@ -210,12 +210,8 @@ where
)
})
.context("Failed to get the contract instances from the metadata file")?;
for library_instance in test_definition
.metadata
.libraries
.iter()
.flatten()
.flat_map(|(_, map)| map.values())
for library_instance in
test_definition.metadata.libraries.iter().flatten().flat_map(|(_, map)| map.values())
{
let ContractPathAndIdent {
contract_source_path: library_source_path,
@@ -252,11 +248,8 @@ where
TransactionRequest::default().from(deployer_address),
code,
);
let receipt = platform_information
.node
.execute_transaction(tx)
.await
.inspect_err(|err| {
let receipt =
platform_information.node.execute_transaction(tx).await.inspect_err(|err| {
error!(
?err,
%library_instance,
@@ -265,9 +258,7 @@ where
)
})?;
let library_address = receipt
.contract_address
.expect("Failed to deploy the library");
let library_address = receipt.contract_address.expect("Failed to deploy the library");
deployed_libraries.get_or_insert_default().insert(
library_instance.clone(),
@@ -295,10 +286,7 @@ where
})
.context("Failed to compile the post-link contracts")?;
Ok(ExecutionState::new(
compiler_output.contracts,
deployed_libraries.unwrap_or_default(),
))
Ok(ExecutionState::new(compiler_output.contracts, deployed_libraries.unwrap_or_default()))
}
// endregion:Constructors & Initialization
@@ -392,11 +380,7 @@ where
) -> Result<HashMap<ContractInstance, TransactionReceipt>> {
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
for instance in step.find_all_contract_instances().into_iter() {
if !self
.execution_state
.deployed_contracts
.contains_key(&instance)
{
if !self.execution_state.deployed_contracts.contains_key(&instance) {
instances_we_must_deploy.entry(instance).or_insert(false);
}
}
@@ -408,16 +392,13 @@ where
let mut receipts = HashMap::new();
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
let calldata = deploy_with_constructor_arguments.then_some(&step.calldata);
let value = deploy_with_constructor_arguments
.then_some(step.value)
.flatten();
let value = deploy_with_constructor_arguments.then_some(step.value).flatten();
let caller = {
let context = self.default_resolution_context();
let resolver = self.platform_information.node.resolver().await?;
step.caller
.resolve_address(resolver.as_ref(), context)
.await?
let resolved = step.caller.resolve_address(resolver.as_ref(), context).await?;
self.platform_information.node.resolve_signer_or_default(resolved)
};
if let (_, _, Some(receipt)) = self
.get_or_deploy_contract_instance(&instance, caller, calldata, value)
@@ -445,7 +426,7 @@ where
.context("Failed to find deployment receipt for constructor call"),
Method::Fallback | Method::FunctionName(_) => {
let resolver = self.platform_information.node.resolver().await?;
let tx = match step
let mut tx = match step
.as_transaction(resolver.as_ref(), self.default_resolution_context())
.await
{
@@ -455,6 +436,11 @@ where
}
};
// Resolve the signer to ensure we use an address that has keys
if let Some(from) = tx.from {
tx.from = Some(self.platform_information.node.resolve_signer_or_default(from));
}
self.platform_information.node.execute_transaction(tx).await
}
}
@@ -503,18 +489,13 @@ where
};
// Handling the return data variable assignments.
for (variable_name, output_word) in assignments.return_data.iter().zip(
tracing_result
.output
.as_ref()
.unwrap_or_default()
.to_vec()
.chunks(32),
) {
for (variable_name, output_word) in assignments
.return_data
.iter()
.zip(tracing_result.output.as_ref().unwrap_or_default().to_vec().chunks(32))
{
let value = U256::from_be_slice(output_word);
self.execution_state
.variables
.insert(variable_name.clone(), value);
self.execution_state.variables.insert(variable_name.clone(), value);
tracing::info!(
variable_name,
variable_value = hex::encode(value.to_be_bytes::<32>()),
@@ -546,7 +527,10 @@ where
expected: Some(Expected::ExpectedMany(expected)),
..
} => expected.clone(),
FunctionCallStep { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
FunctionCallStep {
expected: None,
..
} => vec![ExpectedOutput::new().with_success()],
};
// This is a bit of a special case and we have to support it separately on it's own. If it's
@@ -562,8 +546,7 @@ where
futures::stream::iter(expectations.into_iter().map(Ok))
.try_for_each_concurrent(None, |expectation| async {
self.handle_function_call_assertion_item(receipt, tracing_result, expectation)
.await
self.handle_function_call_assertion_item(receipt, tracing_result, expectation).await
})
.await
}
@@ -664,11 +647,8 @@ where
}
// Handling the topics assertion.
for (expected, actual) in expected_event
.topics
.as_slice()
.iter()
.zip(actual_event.topics())
for (expected, actual) in
expected_event.topics.as_slice().iter().zip(actual_event.topics())
{
let expected = Calldata::new_compound([expected]);
if !expected
@@ -764,11 +744,8 @@ where
.resolve_address(resolver.as_ref(), self.default_resolution_context())
.await?;
let storage = self
.platform_information
.node
.latest_state_proof(address, Default::default())
.await?;
let storage =
self.platform_information.node.latest_state_proof(address, Default::default()).await?;
let is_empty = storage.storage_hash == EMPTY_ROOT_HASH;
let expected = step.is_storage_empty;
@@ -818,9 +795,8 @@ where
})
.map(|driver| driver.execute_all())
.collect::<Vec<_>>();
let res = futures::future::try_join_all(tasks)
.await
.context("Repetition execution failed")?;
let res =
futures::future::try_join_all(tasks).await.context("Repetition execution failed")?;
Ok(res.first().copied().unwrap_or_default())
}
@@ -838,9 +814,7 @@ where
let account = private_key.address();
let variable = U256::from_be_slice(account.0.as_slice());
self.execution_state
.variables
.insert(variable_name.to_string(), variable);
self.execution_state.variables.insert(variable_name.to_string(), variable);
Ok(1)
}
@@ -863,10 +837,8 @@ where
calldata: Option<&Calldata>,
value: Option<EtherValue>,
) -> Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
if let Some((_, address, abi)) = self
.execution_state
.deployed_contracts
.get(contract_instance)
if let Some((_, address, abi)) =
self.execution_state.deployed_contracts.get(contract_instance)
{
info!(
@@ -876,6 +848,7 @@ where
Ok((*address, abi.clone(), None))
} else {
info!("Contract instance requires deployment.");
let (address, abi, receipt) = self
.deploy_contract(contract_instance, deployer, calldata, value)
.await
@@ -907,16 +880,9 @@ where
let Some(ContractPathAndIdent {
contract_source_path,
contract_ident,
}) = self
.test_definition
.metadata
.contract_sources()?
.remove(contract_instance)
}) = self.test_definition.metadata.contract_sources()?.remove(contract_instance)
else {
anyhow::bail!(
"Contract source not found for instance {:?}",
contract_instance
)
anyhow::bail!("Contract source not found for instance {:?}", contract_instance)
};
let Some((code, abi)) = self
@@ -926,10 +892,7 @@ where
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
.cloned()
else {
anyhow::bail!(
"Failed to find information for contract {:?}",
contract_instance
)
anyhow::bail!("Failed to find information for contract {:?}", contract_instance)
};
let mut code = match alloy::hex::decode(&code) {
@@ -947,13 +910,13 @@ where
if let Some(calldata) = calldata {
let resolver = self.platform_information.node.resolver().await?;
let calldata = calldata
.calldata(resolver.as_ref(), self.default_resolution_context())
.await?;
let calldata =
calldata.calldata(resolver.as_ref(), self.default_resolution_context()).await?;
code.extend(calldata);
}
let tx = {
let deployer = self.platform_information.node.resolve_signer_or_default(deployer);
let tx = TransactionRequest::default().from(deployer);
let tx = match value {
Some(ref value) => tx.value(value.into_inner()),
@@ -982,10 +945,9 @@ where
.reporter
.report_contract_deployed_event(contract_instance.clone(), address)?;
self.execution_state.deployed_contracts.insert(
contract_instance.clone(),
(contract_ident, address, abi.clone()),
);
self.execution_state
.deployed_contracts
.insert(contract_instance.clone(), (contract_ident, address, abi.clone()));
Ok((address, abi, receipt))
}
@@ -998,9 +960,7 @@ where
match step_address {
StepAddress::Address(address) => Ok(*address),
StepAddress::ResolvableAddress(resolvable) => {
let Some(instance) = resolvable
.strip_suffix(".address")
.map(ContractInstance::new)
let Some(instance) = resolvable.strip_suffix(".address").map(ContractInstance::new)
else {
bail!("Not an address variable");
};
@@ -7,10 +7,10 @@ use std::{
time::{Duration, Instant},
};
use crate::Platform;
use anyhow::Context as _;
use futures::{FutureExt, StreamExt};
use revive_dt_common::types::PrivateKeyAllocator;
use crate::Platform;
use tokio::sync::{Mutex, RwLock, Semaphore};
use tracing::{Instrument, error, info, info_span, instrument};
@@ -37,12 +37,8 @@ pub async fn handle_differential_tests(
info!(len = metadata_files.len(), "Discovered metadata files");
// Discover the list of platforms that the tests should run on based on the context.
let platforms = context
.platforms
.iter()
.copied()
.map(Into::<&dyn Platform>::into)
.collect::<Vec<_>>();
let platforms =
context.platforms.iter().copied().map(Into::<&dyn Platform>::into).collect::<Vec<_>>();
// Starting the nodes of the various platforms specified in the context.
let platforms_and_nodes = {
@@ -85,13 +81,8 @@ pub async fn handle_differential_tests(
// Creating everything else required for the driver to run.
let cached_compiler = CachedCompiler::new(
context
.working_directory
.as_path()
.join("compilation_cache"),
context
.compilation_configuration
.invalidate_compilation_cache,
context.working_directory.as_path().join("compilation_cache"),
context.compilation_configuration.invalidate_compilation_cache,
)
.await
.map(Arc::new)
@@ -101,11 +92,8 @@ pub async fn handle_differential_tests(
)));
// Creating the driver and executing all of the steps.
let semaphore = context
.concurrency_configuration
.concurrency_limit()
.map(Semaphore::new)
.map(Arc::new);
let semaphore =
context.concurrency_configuration.concurrency_limit().map(Semaphore::new).map(Arc::new);
let running_task_list = Arc::new(RwLock::new(BTreeSet::<usize>::new()));
let driver_task = futures::future::join_all(test_definitions.iter().enumerate().map(
|(test_id, test_definition)| {
@@ -172,20 +160,14 @@ pub async fn handle_differential_tests(
))
.inspect(|_| {
info!("Finished executing all test cases");
reporter_clone
.report_completion_event()
.expect("Can't fail")
reporter_clone.report_completion_event().expect("Can't fail")
});
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"
);
info!(count = remaining_tasks.len(), ?remaining_tasks, "Remaining Tests");
tokio::time::sleep(Duration::from_secs(10)).await
}
});
@@ -227,7 +209,9 @@ async fn start_cli_reporting_task(reporter: Reporter) {
for (case_idx, case_status) in case_status.into_iter() {
let _ = write!(buf, "\tCase Index {case_idx:>3}: ");
let _ = match case_status {
TestCaseStatus::Succeeded { steps_executed } => {
TestCaseStatus::Succeeded {
steps_executed,
} => {
number_of_successes += 1;
writeln!(
buf,
@@ -235,7 +219,9 @@ async fn start_cli_reporting_task(reporter: Reporter) {
GREEN, BOLD, BOLD_RESET, steps_executed, COLOR_RESET
)
}
TestCaseStatus::Failed { reason } => {
TestCaseStatus::Failed {
reason,
} => {
number_of_failures += 1;
writeln!(
buf,
@@ -247,7 +233,10 @@ async fn start_cli_reporting_task(reporter: Reporter) {
COLOR_RESET,
)
}
TestCaseStatus::Ignored { reason, .. } => writeln!(
TestCaseStatus::Ignored {
reason,
..
} => writeln!(
buf,
"{}{}Case Ignored{} - Reason: {}{}",
GREY,
+9 -13
View File
@@ -8,10 +8,10 @@ use std::{
sync::{Arc, LazyLock},
};
use crate::Platform;
use futures::FutureExt;
use revive_dt_common::{iterators::FilesWithExtensionIterator, types::CompilerIdentifier};
use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler};
use crate::Platform;
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
@@ -225,9 +225,7 @@ async fn compile_contracts(
.flat_map(|value| value.iter())
.map(|(instance, (ident, address, abi))| (instance, ident, address, abi))
.flat_map(|(_, ident, address, _)| {
all_sources_in_dir
.iter()
.map(move |path| (ident, address, path))
all_sources_in_dir.iter().map(move |path| (ident, address, path))
})
.fold(compiler, |compiler, (ident, address, path)| {
compiler.with_library(path, ident.as_str(), *address)
@@ -309,19 +307,15 @@ impl ArtifactsCache {
pub async fn insert(&self, key: &CacheKey<'_>, value: &CacheValue) -> Result<()> {
let key = bson::to_vec(key).context("Failed to serialize cache key (bson)")?;
let value = bson::to_vec(value).context("Failed to serialize cache value (bson)")?;
cacache::write(self.path.as_path(), key.encode_hex(), value)
.await
.with_context(|| {
format!("Failed to write cache entry under {}", self.path.display())
})?;
cacache::write(self.path.as_path(), key.encode_hex(), value).await.with_context(|| {
format!("Failed to write cache entry under {}", self.path.display())
})?;
Ok(())
}
pub async fn get(&self, key: &CacheKey<'_>) -> Option<CacheValue> {
let key = bson::to_vec(key).ok()?;
let value = cacache::read(self.path.as_path(), key.encode_hex())
.await
.ok()?;
let value = cacache::read(self.path.as_path(), key.encode_hex()).await.ok()?;
let value = bson::from_slice::<CacheValue>(&value).ok()?;
Some(value)
}
@@ -370,6 +364,8 @@ struct CacheValue {
impl CacheValue {
pub fn new(compiler_output: CompilerOutput) -> Self {
Self { compiler_output }
Self {
compiler_output,
}
}
}
+3 -5
View File
@@ -2,9 +2,9 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::Platform;
use anyhow::Context as _;
use revive_dt_config::*;
use crate::Platform;
use revive_dt_node_interaction::EthereumNode;
/// The node pool starts one or more [Node] which then can be accessed
@@ -37,10 +37,8 @@ impl NodePool {
);
}
let pre_transactions_tasks = nodes
.iter_mut()
.map(|node| node.pre_transactions())
.collect::<Vec<_>>();
let pre_transactions_tasks =
nodes.iter_mut().map(|node| node.pre_transactions()).collect::<Vec<_>>();
futures::future::try_join_all(pre_transactions_tasks)
.await
.context("Failed to run the pre-transactions task")?;
+56 -69
View File
@@ -1,28 +1,22 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use std::{borrow::Cow, path::Path};
use std::{borrow::Cow, collections::BTreeMap, path::Path, sync::Arc};
use futures::{Stream, StreamExt, stream};
use indexmap::{IndexMap, indexmap};
use revive_dt_common::iterators::EitherIter;
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_common::{iterators::EitherIter, types::PlatformIdentifier};
use revive_dt_config::Context;
use revive_dt_format::mode::ParsedMode;
use serde_json::{Value, json};
use revive_dt_compiler::Mode;
use revive_dt_compiler::SolidityCompiler;
use revive_dt_compiler::{Mode, SolidityCompiler};
use revive_dt_format::{
case::{Case, CaseIdx},
metadata::MetadataFile,
};
use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::{ExecutionSpecificReporter, Reporter};
use revive_dt_report::{TestSpecificReporter, TestSpecifier};
use revive_dt_report::{ExecutionSpecificReporter, Reporter, TestSpecificReporter, TestSpecifier};
use tracing::{debug, error, info};
use crate::Platform;
use crate::helpers::NodePool;
use crate::{Platform, helpers::NodePool};
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
@@ -72,72 +66,68 @@ pub async fn create_test_definitions_stream<'a>(
// Inform the reporter of each one of the test cases that were discovered which we expect to
// run.
.inspect(|(_, _, _, _, reporter)| {
reporter
.report_test_case_discovery_event()
.expect("Can't fail");
reporter.report_test_case_discovery_event().expect("Can't fail");
}),
)
// Creating the Test Definition objects from all of the various objects we have and creating
// their required dependencies (e.g., compiler).
.filter_map(
move |(metadata_file, case_idx, case, mode, reporter)| async move {
let mut platforms = BTreeMap::new();
for (platform, node_pool) in platforms_and_nodes.values() {
let node = node_pool.round_robbin();
let compiler = platform
.new_compiler(context.clone(), mode.version.clone().map(Into::into))
.await
.inspect_err(|err| {
error!(
?err,
platform_identifier = %platform.platform_identifier(),
"Failed to instantiate the compiler"
)
})
.ok()?;
reporter
.report_node_assigned_event(
node.id(),
platform.platform_identifier(),
node.connection_string(),
.filter_map(move |(metadata_file, case_idx, case, mode, reporter)| async move {
let mut platforms = BTreeMap::new();
for (platform, node_pool) in platforms_and_nodes.values() {
let node = node_pool.round_robbin();
let compiler = platform
.new_compiler(context.clone(), mode.version.clone().map(Into::into))
.await
.inspect_err(|err| {
error!(
?err,
platform_identifier = %platform.platform_identifier(),
"Failed to instantiate the compiler"
)
.expect("Can't fail");
})
.ok()?;
let reporter =
reporter.execution_specific_reporter(node.id(), platform.platform_identifier());
platforms.insert(
reporter
.report_node_assigned_event(
node.id(),
platform.platform_identifier(),
TestPlatformInformation {
platform: *platform,
node,
compiler,
reporter,
},
);
}
node.connection_string(),
)
.expect("Can't fail");
Some(TestDefinition {
/* Metadata file information */
metadata: metadata_file,
metadata_file_path: metadata_file.metadata_file_path.as_path(),
let reporter =
reporter.execution_specific_reporter(node.id(), platform.platform_identifier());
/* Mode Information */
mode: mode.clone(),
platforms.insert(
platform.platform_identifier(),
TestPlatformInformation {
platform: *platform,
node,
compiler,
reporter,
},
);
}
/* Case Information */
case_idx: CaseIdx::new(case_idx),
case,
Some(TestDefinition {
/* Metadata file information */
metadata: metadata_file,
metadata_file_path: metadata_file.metadata_file_path.as_path(),
/* Platform and Node Assignment Information */
platforms,
/* Mode Information */
mode: mode.clone(),
/* Reporter */
reporter,
})
},
)
/* Case Information */
case_idx: CaseIdx::new(case_idx),
case,
/* Platform and Node Assignment Information */
platforms,
/* Reporter */
reporter,
})
})
// Filter out the test cases which are incompatible or that can't run in the current setup.
.filter_map(move |test| async move {
match test.check_compatibility() {
@@ -280,10 +270,7 @@ impl<'a> TestDefinition<'a> {
if is_allowed {
Ok(())
} else {
Err((
"EVM version is incompatible for the platforms specified",
error_map,
))
Err(("EVM version is incompatible for the platforms specified", error_map))
}
}
+20 -39
View File
@@ -3,8 +3,8 @@
//! This crate defines the testing configuration and
//! provides a helper utility to execute tests.
pub mod helpers;
pub mod differential_tests;
pub mod helpers;
use std::{
pin::Pin,
@@ -17,9 +17,11 @@ use revive_dt_common::types::*;
use revive_dt_compiler::{SolidityCompiler, revive_resolc::Resolc, solc::Solc};
use revive_dt_config::*;
use revive_dt_node::{
Node, node_implementations::geth::GethNode,
node_implementations::lighthouse_geth::LighthouseGethNode,
node_implementations::substrate::SubstrateNode, node_implementations::zombienet::ZombieNode,
Node,
node_implementations::{
geth::GethNode, lighthouse_geth::LighthouseGethNode, substrate::SubstrateNode,
zombienet::ZombieNode,
},
};
use revive_dt_node_interaction::EthereumNode;
use tracing::info;
@@ -36,11 +38,7 @@ pub trait Platform {
/// Returns a full identifier for the platform.
fn full_identifier(&self) -> (NodeIdentifier, VmIdentifier, CompilerIdentifier) {
(
self.node_identifier(),
self.vm_identifier(),
self.compiler_identifier(),
)
(self.node_identifier(), self.vm_identifier(), self.compiler_identifier())
}
/// Returns the identifier of the node used.
@@ -182,9 +180,7 @@ impl Platform for KitchensinkPolkavmResolcPlatform {
context: Context,
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context)
.path
.clone();
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context).path.clone();
let genesis = genesis_configuration.genesis()?.clone();
Ok(thread::spawn(move || {
let node = SubstrateNode::new(
@@ -234,9 +230,7 @@ impl Platform for KitchensinkRevmSolcPlatform {
context: Context,
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context)
.path
.clone();
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context).path.clone();
let genesis = genesis_configuration.genesis()?.clone();
Ok(thread::spawn(move || {
let node = SubstrateNode::new(
@@ -286,9 +280,8 @@ impl Platform for ReviveDevNodePolkavmResolcPlatform {
context: Context,
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let revive_dev_node_path = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context)
.path
.clone();
let revive_dev_node_path =
AsRef::<ReviveDevNodeConfiguration>::as_ref(&context).path.clone();
let genesis = genesis_configuration.genesis()?.clone();
Ok(thread::spawn(move || {
let node = SubstrateNode::new(
@@ -338,9 +331,8 @@ impl Platform for ReviveDevNodeRevmSolcPlatform {
context: Context,
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let revive_dev_node_path = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context)
.path
.clone();
let revive_dev_node_path =
AsRef::<ReviveDevNodeConfiguration>::as_ref(&context).path.clone();
let genesis = genesis_configuration.genesis()?.clone();
Ok(thread::spawn(move || {
let node = SubstrateNode::new(
@@ -390,9 +382,8 @@ impl Platform for ZombienetPolkavmResolcPlatform {
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 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);
@@ -438,9 +429,8 @@ impl Platform for ZombienetRevmSolcPlatform {
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 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);
@@ -519,17 +509,8 @@ fn spawn_node<T: Node + EthereumNode + Send + Sync>(
mut node: T,
genesis: Genesis,
) -> anyhow::Result<T> {
info!(
id = node.id(),
connection_string = node.connection_string(),
"Spawning node"
);
node.spawn(genesis)
.context("Failed to spawn node process")?;
info!(
id = node.id(),
connection_string = node.connection_string(),
"Spawned node"
);
info!(id = node.id(), connection_string = node.connection_string(), "Spawning node");
node.spawn(genesis).context("Failed to spawn node process")?;
info!(id = node.id(), connection_string = node.connection_string(), "Spawned node");
Ok(node)
}
+19 -25
View File
@@ -54,39 +54,33 @@ pub struct Case {
impl Case {
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
let steps_len = self.steps.len();
self.steps
.clone()
.into_iter()
.enumerate()
.map(move |(idx, mut step)| {
let Step::FunctionCall(ref mut input) = step else {
return step;
};
self.steps.clone().into_iter().enumerate().map(move |(idx, mut step)| {
let Step::FunctionCall(ref mut input) = step else {
return step;
};
if idx + 1 == steps_len {
if input.expected.is_none() {
input.expected = self.expected.clone();
}
// TODO: What does it mean for us to have an `expected` field on the case itself
// but the final input also has an expected field that doesn't match the one on
// the case? What are we supposed to do with that final expected field on the
// case?
step
} else {
step
if idx + 1 == steps_len {
if input.expected.is_none() {
input.expected = self.expected.clone();
}
})
// TODO: What does it mean for us to have an `expected` field on the case itself
// but the final input also has an expected field that doesn't match the one on
// the case? What are we supposed to do with that final expected field on the
// case?
step
} else {
step
}
})
}
pub fn steps_iterator_for_benchmarks(
&self,
default_repeat_count: usize,
) -> Box<dyn Iterator<Item = Step> + '_> {
let contains_repeat = self
.steps_iterator()
.any(|step| matches!(&step, Step::Repeat(..)));
let contains_repeat = self.steps_iterator().any(|step| matches!(&step, Step::Repeat(..)));
if contains_repeat {
Box::new(self.steps_iterator()) as Box<_>
} else {
+40 -22
View File
@@ -13,8 +13,14 @@ use anyhow::Context as _;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Corpus {
SinglePath { name: String, path: PathBuf },
MultiplePaths { name: String, paths: Vec<PathBuf> },
SinglePath {
name: String,
path: PathBuf,
},
MultiplePaths {
name: String,
paths: Vec<PathBuf>,
},
}
impl Corpus {
@@ -86,46 +92,58 @@ impl Corpus {
.collect::<Vec<_>>();
tests.sort_by(|a, b| a.metadata_file_path.cmp(&b.metadata_file_path));
tests.dedup_by(|a, b| a.metadata_file_path == b.metadata_file_path);
info!(
len = tests.len(),
corpus_name = self.name(),
"Found tests in Corpus"
);
info!(len = tests.len(), corpus_name = self.name(), "Found tests in Corpus");
tests
}
pub fn name(&self) -> &str {
match self {
Corpus::SinglePath { name, .. } | Corpus::MultiplePaths { name, .. } => name.as_str(),
Corpus::SinglePath {
name,
..
}
| Corpus::MultiplePaths {
name,
..
} => name.as_str(),
}
}
pub fn paths_iter(&self) -> impl Iterator<Item = &Path> {
match self {
Corpus::SinglePath { path, .. } => {
Box::new(std::iter::once(path.as_path())) as Box<dyn Iterator<Item = _>>
}
Corpus::MultiplePaths { paths, .. } => {
Box::new(paths.iter().map(|path| path.as_path())) as Box<dyn Iterator<Item = _>>
}
Corpus::SinglePath {
path,
..
} => Box::new(std::iter::once(path.as_path())) as Box<dyn Iterator<Item = _>>,
Corpus::MultiplePaths {
paths,
..
} => Box::new(paths.iter().map(|path| path.as_path())) as Box<dyn Iterator<Item = _>>,
}
}
pub fn paths_iter_mut(&mut self) -> impl Iterator<Item = &mut PathBuf> {
match self {
Corpus::SinglePath { path, .. } => {
Box::new(std::iter::once(path)) as Box<dyn Iterator<Item = _>>
}
Corpus::MultiplePaths { paths, .. } => {
Box::new(paths.iter_mut()) as Box<dyn Iterator<Item = _>>
}
Corpus::SinglePath {
path,
..
} => Box::new(std::iter::once(path)) as Box<dyn Iterator<Item = _>>,
Corpus::MultiplePaths {
paths,
..
} => Box::new(paths.iter_mut()) as Box<dyn Iterator<Item = _>>,
}
}
pub fn path_count(&self) -> usize {
match self {
Corpus::SinglePath { .. } => 1,
Corpus::MultiplePaths { paths, .. } => paths.len(),
Corpus::SinglePath {
..
} => 1,
Corpus::MultiplePaths {
paths,
..
} => paths.len(),
}
}
}
+5 -17
View File
@@ -44,9 +44,7 @@ impl MetadataFile {
if self.corpus_file_path.is_file() {
&self.corpus_file_path
} else {
self.metadata_file_path
.strip_prefix(&self.corpus_file_path)
.unwrap()
self.metadata_file_path.strip_prefix(&self.corpus_file_path).unwrap()
}
}
}
@@ -167,10 +165,8 @@ impl Metadata {
) in contracts
{
let alias = alias.clone();
let absolute_path = directory
.join(contract_source_path)
.canonicalize()
.map_err(|error| {
let absolute_path =
directory.join(contract_source_path).canonicalize().map_err(|error| {
anyhow::anyhow!(
"Failed to canonicalize contract source path '{}': {error}",
directory.join(contract_source_path).display()
@@ -335,12 +331,7 @@ pub struct ContractPathAndIdent {
impl Display for ContractPathAndIdent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}",
self.contract_source_path.display(),
self.contract_ident.as_ref()
)
write!(f, "{}:{}", self.contract_source_path.display(), self.contract_ident.as_ref())
}
}
@@ -596,10 +587,7 @@ mod test {
// Assert
let identifier = identifier.expect("Failed to parse");
assert_eq!(
identifier.contract_source_path.display().to_string(),
"ERC20/ERC20.sol"
);
assert_eq!(identifier.contract_source_path.display().to_string(), "ERC20/ERC20.sol");
assert_eq!(identifier.contract_ident, "ERC20".to_owned().into());
// Act
+16 -18
View File
@@ -1,13 +1,12 @@
use anyhow::Context as _;
use regex::Regex;
use revive_dt_common::iterators::EitherIter;
use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
use revive_dt_common::{
iterators::EitherIter,
types::{Mode, ModeOptimizerSetting, ModePipeline},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
use std::{collections::HashSet, fmt::Display, str::FromStr, sync::LazyLock};
/// This represents a mode that has been parsed from test metadata.
///
@@ -94,7 +93,11 @@ impl Display for ParsedMode {
if let Some(pipeline) = self.pipeline {
pipeline.fmt(f)?;
if let Some(optimize_flag) = self.optimize_flag {
f.write_str(if optimize_flag { "+" } else { "-" })?;
f.write_str(if optimize_flag {
"+"
} else {
"-"
})?;
}
has_written = true;
}
@@ -158,13 +161,11 @@ impl ParsedMode {
);
pipeline_iter.flat_map(move |pipeline| {
optimize_settings_iter
.clone()
.map(move |optimize_setting| Mode {
pipeline,
optimize_setting,
version: self.version.clone(),
})
optimize_settings_iter.clone().map(move |optimize_setting| Mode {
pipeline,
optimize_setting,
version: self.version.clone(),
})
})
}
@@ -236,10 +237,7 @@ mod tests {
("Y+", vec!["Y M3"]),
("Y-", vec!["Y M0"]),
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
(
"<=0.8",
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
),
("<=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"]),
];
for (actual, expected) in strings {
+34 -109
View File
@@ -1,11 +1,10 @@
use std::{collections::HashMap, fmt::Display, str::FromStr};
use alloy::primitives::{FixedBytes, utils::parse_units};
use alloy::{
eips::BlockNumberOrTag,
json_abi::Function,
network::TransactionBuilder,
primitives::{Address, Bytes, U256},
primitives::{Address, Bytes, FixedBytes, U256, utils::parse_units},
rpc::types::TransactionRequest,
};
use anyhow::Context as _;
@@ -17,8 +16,10 @@ use serde::{Deserialize, Serialize};
use revive_dt_common::macros::define_wrapper_type;
use tracing::{Instrument, info_span, instrument};
use crate::traits::ResolverApi;
use crate::{metadata::ContractInstance, traits::ResolutionContext};
use crate::{
metadata::ContractInstance,
traits::{ResolutionContext, ResolverApi},
};
/// A test step.
///
@@ -77,12 +78,7 @@ impl StepPath {
impl Display for StepPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0
.iter()
.map(|idx| idx.to_string())
.collect::<Vec<_>>()
.join(".")
.fmt(f)
self.0.iter().map(|idx| idx.to_string()).collect::<Vec<_>>().join(".").fmt(f)
}
}
@@ -90,10 +86,7 @@ impl FromStr for StepPath {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(".")
.map(StepIdx::from_str)
.collect::<anyhow::Result<Vec<_>>>()
.map(Self)
s.split(".").map(StepIdx::from_str).collect::<anyhow::Result<Vec<_>>>().map(Self)
}
}
@@ -455,9 +448,7 @@ impl StepAddress {
impl FunctionCallStep {
pub const fn default_caller_address() -> Address {
Address(FixedBytes(alloy::hex!(
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
)))
Address(FixedBytes(alloy::hex!("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")))
}
pub const fn default_caller() -> StepAddress {
@@ -547,11 +538,9 @@ impl FunctionCallStep {
.await
.context("Failed to encode input bytes for transaction request")?;
let caller = self.caller.resolve_address(resolver, context).await?;
let transaction_request = TransactionRequest::default().from(caller).value(
self.value
.map(|value| value.into_inner())
.unwrap_or_default(),
);
let transaction_request = TransactionRequest::default()
.from(caller)
.value(self.value.map(|value| value.into_inner()).unwrap_or_default());
match self.method {
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
_ => Ok(transaction_request
@@ -607,11 +596,7 @@ impl Calldata {
pub fn new_compound(items: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
Self::Compound(
items
.into_iter()
.map(|item| item.as_ref().to_owned())
.map(CalldataItem::new)
.collect(),
items.into_iter().map(|item| item.as_ref().to_owned()).map(CalldataItem::new).collect(),
)
}
@@ -633,8 +618,7 @@ impl Calldata {
context: ResolutionContext<'_>,
) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice(&mut buffer, resolver, context)
.await?;
self.calldata_into_slice(&mut buffer, resolver, context).await?;
Ok(buffer)
}
@@ -725,10 +709,7 @@ impl CalldataItem {
) -> anyhow::Result<U256> {
let mut stack = Vec::<CalldataToken<U256>>::new();
for token in self
.calldata_tokens()
.map(|token| token.resolve(resolver, context))
{
for token in self.calldata_tokens().map(|token| token.resolve(resolver, context)) {
let token = token.await?;
let new_token = match token {
CalldataToken::Item(_) => token,
@@ -769,9 +750,7 @@ impl CalldataItem {
// Empty stack means that we got an empty compound calldata which we resolve to zero.
[] => Ok(U256::ZERO),
[CalldataToken::Item(item)] => Ok(*item),
_ => Err(anyhow::anyhow!(
"Invalid calldata arithmetic operation - Invalid stack"
)),
_ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation - Invalid stack")),
}
}
@@ -915,10 +894,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
.await
.map(U256::from)
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
context
.variable(variable_name)
.context("Variable lookup failed")
.copied()
context.variable(variable_name).context("Variable lookup failed").copied()
} else {
U256::from_str_radix(item, 10)
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))
@@ -959,9 +935,12 @@ impl<'de> Deserialize<'de> for EtherValue {
#[cfg(test)]
mod tests {
use alloy::primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address};
use alloy::sol_types::SolValue;
use alloy::{eips::BlockNumberOrTag, 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 super::*;
@@ -1045,13 +1024,7 @@ mod tests {
"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_metadata).unwrap();
let selector = parsed_abi
.function("store")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let selector = parsed_abi.function("store").unwrap().first().unwrap().selector().0;
let input = FunctionCallStep {
instance: ContractInstance::new("Contract"),
@@ -1089,13 +1062,7 @@ mod tests {
]"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap();
let selector = parsed_abi
.function("send")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let selector = parsed_abi.function("send").unwrap().first().unwrap().selector().0;
let input: FunctionCallStep = FunctionCallStep {
instance: "Contract".to_owned().into(),
@@ -1117,10 +1084,7 @@ mod tests {
type T = (alloy::primitives::Address,);
let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap();
assert_eq!(
decoded.0,
address!("0x1000000000000000000000000000000000000001")
);
assert_eq!(decoded.0, address!("0x1000000000000000000000000000000000000001"));
}
#[tokio::test]
@@ -1136,13 +1100,7 @@ mod tests {
]"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap();
let selector = parsed_abi
.function("send")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let selector = parsed_abi.function("send").unwrap().first().unwrap().selector().0;
let input: FunctionCallStep = FunctionCallStep {
instance: ContractInstance::new("Contract"),
@@ -1164,10 +1122,7 @@ mod tests {
type T = (alloy::primitives::Address,);
let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap();
assert_eq!(
decoded.0,
address!("0x1000000000000000000000000000000000000001")
);
assert_eq!(decoded.0, address!("0x1000000000000000000000000000000000000001"));
}
async fn resolve_calldata_item(
@@ -1204,12 +1159,7 @@ mod tests {
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
MockResolver
.block_gas_limit(Default::default())
.await
.unwrap()
)
U256::from(MockResolver.block_gas_limit(Default::default()).await.unwrap())
)
}
@@ -1226,11 +1176,7 @@ mod tests {
assert_eq!(
resolved,
U256::from_be_slice(
MockResolver
.block_coinbase(Default::default())
.await
.unwrap()
.as_ref()
MockResolver.block_coinbase(Default::default()).await.unwrap().as_ref()
)
)
}
@@ -1245,13 +1191,7 @@ mod tests {
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
MockResolver
.block_difficulty(Default::default())
.await
.unwrap()
)
assert_eq!(resolved, MockResolver.block_difficulty(Default::default()).await.unwrap())
}
#[tokio::test]
@@ -1266,11 +1206,7 @@ mod tests {
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
MockResolver
.block_base_fee(Default::default())
.await
.map(U256::from)
.unwrap()
MockResolver.block_base_fee(Default::default()).await.map(U256::from).unwrap()
)
}
@@ -1300,10 +1236,7 @@ mod tests {
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(MockResolver.last_block_number().await.unwrap())
)
assert_eq!(resolved, U256::from(MockResolver.last_block_number().await.unwrap()))
}
#[tokio::test]
@@ -1318,12 +1251,7 @@ mod tests {
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
MockResolver
.block_timestamp(Default::default())
.await
.unwrap()
)
U256::from(MockResolver.block_timestamp(Default::default()).await.unwrap())
)
}
@@ -1401,10 +1329,7 @@ mod tests {
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(MockResolver.last_block_number().await.unwrap() + 10)
);
assert_eq!(resolved, U256::from(MockResolver.last_block_number().await.unwrap() + 10));
}
#[tokio::test]
+8 -10
View File
@@ -1,10 +1,10 @@
use std::collections::HashMap;
use std::pin::Pin;
use std::{collections::HashMap, pin::Pin};
use alloy::eips::BlockNumberOrTag;
use alloy::json_abi::JsonAbi;
use alloy::primitives::TxHash;
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
use alloy::{
eips::BlockNumberOrTag,
json_abi::JsonAbi,
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, U256},
};
use anyhow::Result;
use crate::metadata::{ContractIdent, ContractInstance};
@@ -149,8 +149,7 @@ impl<'a> ResolutionContext<'a> {
&self,
instance: &ContractInstance,
) -> Option<&(ContractIdent, Address, JsonAbi)> {
self.deployed_contracts
.and_then(|deployed_contracts| deployed_contracts.get(instance))
self.deployed_contracts.and_then(|deployed_contracts| deployed_contracts.get(instance))
}
pub fn deployed_contract_address(&self, instance: &ContractInstance) -> Option<&Address> {
@@ -162,8 +161,7 @@ impl<'a> ResolutionContext<'a> {
}
pub fn variable(&self, name: impl AsRef<str>) -> Option<&U256> {
self.variables
.and_then(|variables| variables.get(name.as_ref()))
self.variables.and_then(|variables| variables.get(name.as_ref()))
}
pub fn tip_block_number(&self) -> Option<&'a BlockNumber> {
+14 -36
View File
@@ -11,7 +11,6 @@ use revive_dt_format::{
corpus::Corpus,
metadata::{Metadata, MetadataFile},
};
use revive_dt_node_interaction::EthereumNode;
use std::{
borrow::Cow,
collections::{BTreeMap, HashSet},
@@ -130,10 +129,7 @@ async fn run(args: MlTestRunnerArgs) -> anyhow::Result<()> {
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),
));
failures.push((file_display.clone(), format!("Error loading metadata: {}", e)));
if args.bail {
break;
}
@@ -233,10 +229,7 @@ fn discover_test_files(path: &Path) -> anyhow::Result<Vec<PathBuf>> {
files.push(metadata.metadata_file_path);
}
}
_ => anyhow::bail!(
"Unsupported file extension: {}. Expected .sol or .json",
extension
),
_ => anyhow::bail!("Unsupported file extension: {}. Expected .sol or .json", extension),
}
} else if path.is_dir() {
// Walk directory recursively for .sol files
@@ -291,12 +284,10 @@ async fn execute_test_file(
let test_context = TestExecutionContext::default();
let context = revive_dt_config::Context::Test(Box::new(test_context));
// Optionally start a node based on the --start-platform flag
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")?;
let node_handle =
platform.new_node(context.clone()).context("Failed to spawn node thread")?;
info!("Waiting for node to start...");
let node = node_handle
@@ -304,18 +295,12 @@ async fn execute_test_file(
.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()
);
info!("Node started with ID: {}, connection: {}", node.id(), node.connection_string());
// Run pre-transactions on the node
let node = Box::leak(node); // Leak to get 'static lifetime for simplicity
info!("Running pre-transactions...");
node.pre_transactions()
.await
.context("Failed to run pre-transactions")?;
node.pre_transactions().await.context("Failed to run pre-transactions")?;
info!("Pre-transactions completed");
node
@@ -340,9 +325,8 @@ async fn execute_test_file(
.context("Failed to create cached compiler")?;
// Create a private key allocator
let private_key_allocator = Arc::new(Mutex::new(PrivateKeyAllocator::new(
alloy::primitives::U256::from(100),
)));
let private_key_allocator =
Arc::new(Mutex::new(PrivateKeyAllocator::new(alloy::primitives::U256::from(100))));
// Create reporter infrastructure (minimal, just for the Driver API)
// Note: We need to keep the report_task alive, otherwise the reporter channel closes
@@ -352,10 +336,7 @@ async fn execute_test_file(
// Spawn the report task in the background to keep the channel open
tokio::spawn(report_task);
info!(
"Building test definitions for {} case(s)",
metadata_file.cases.len()
);
info!("Building test definitions for {} case(s)", metadata_file.cases.len());
// Build all test definitions upfront
let mut test_definitions = Vec::new();
for (case_idx, case) in metadata_file.cases.iter().enumerate() {
@@ -404,20 +385,17 @@ async fn execute_test_file(
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
))?;
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()
);
info!("All {} test case(s) executed successfully", test_definitions.len());
Ok(())
}
+11 -12
View File
@@ -1,11 +1,14 @@
//! This crate implements all node interactions.
use std::pin::Pin;
use std::sync::Arc;
use std::{pin::Pin, sync::Arc};
use alloy::primitives::{Address, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256};
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
use alloy::{
primitives::{Address, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256},
rpc::types::{
EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest,
trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace},
},
};
use anyhow::Result;
use futures::Stream;
@@ -75,13 +78,9 @@ pub trait EthereumNode {
>,
>;
/// Creates a node instance from an existing running node.
fn new_existing() -> Self
where
Self: Sized,
{
panic!("new_existing is not implemented for this node type")
}
/// 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)]
+9 -24
View File
@@ -33,10 +33,7 @@ impl Process {
let log_file_prefix = log_file_prefix.into();
let (stdout_file_name, stderr_file_name) = match log_file_prefix {
Some(prefix) => (
format!("{prefix}_stdout.log"),
format!("{prefix}_stderr.log"),
),
Some(prefix) => (format!("{prefix}_stdout.log"), format!("{prefix}_stderr.log")),
None => ("stdout.log".to_string(), "stderr.log".to_string()),
};
@@ -57,20 +54,16 @@ impl Process {
.context("Failed to open the stderr logs file")?;
let mut command = {
let stdout_logs_file = stdout_logs_file
.try_clone()
.context("Failed to clone the stdout logs file")?;
let stderr_logs_file = stderr_logs_file
.try_clone()
.context("Failed to clone the stderr logs file")?;
let stdout_logs_file =
stdout_logs_file.try_clone().context("Failed to clone the stdout logs file")?;
let stderr_logs_file =
stderr_logs_file.try_clone().context("Failed to clone the stderr logs file")?;
let mut command = Command::new(binary_path.as_ref());
command_building_callback(&mut command, stdout_logs_file, stderr_logs_file);
command
};
let mut child = command
.spawn()
.context("Failed to spawn the built command")?;
let mut child = command.spawn().context("Failed to spawn the built command")?;
match process_readiness_wait_behavior {
ProcessReadinessWaitBehavior::NoStartupWait => {}
@@ -128,11 +121,7 @@ impl Process {
}
}
ProcessReadinessWaitBehavior::WaitForCommandToExit => {
if !child
.wait()
.context("Failed waiting for process to finish")?
.success()
{
if !child.wait().context("Failed waiting for process to finish")?.success() {
anyhow::bail!("Failed to spawn command");
}
}
@@ -149,12 +138,8 @@ impl Process {
impl Drop for Process {
fn drop(&mut self) {
self.child.kill().expect("Failed to kill the process");
self.stdout_logs_file
.flush()
.expect("Failed to flush the stdout logs file");
self.stderr_logs_file
.flush()
.expect("Failed to flush the stderr logs file");
self.stdout_logs_file.flush().expect("Failed to flush the stdout logs file");
self.stderr_logs_file.flush().expect("Failed to flush the stderr logs file");
}
}
+54 -81
View File
@@ -18,7 +18,9 @@ use alloy::{
eips::BlockNumberOrTag,
genesis::{Genesis, GenesisAccount},
network::{Ethereum, EthereumWallet, NetworkWallet},
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256},
primitives::{
Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, StorageKey, TxHash, U256,
},
providers::{
Provider,
ext::DebugApi,
@@ -75,6 +77,7 @@ pub struct GethNode {
wallet: Arc<EthereumWallet>,
nonce_manager: CachedNonceManager,
provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>,
chain_id: ChainId,
}
impl GethNode {
@@ -105,9 +108,7 @@ impl GethNode {
let wallet_configuration = AsRef::<WalletConfiguration>::as_ref(&context);
let geth_configuration = AsRef::<GethConfiguration>::as_ref(&context);
let geth_directory = working_directory_configuration
.as_path()
.join(Self::BASE_DIRECTORY);
let geth_directory = working_directory_configuration.as_path().join(Self::BASE_DIRECTORY);
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = geth_directory.join(id.to_string());
@@ -125,6 +126,25 @@ impl GethNode {
wallet: wallet.clone(),
nonce_manager: Default::default(),
provider: Default::default(),
chain_id: CHAIN_ID,
}
}
pub fn new_existing() -> Self {
let wallet_config = revive_dt_config::WalletConfiguration::default();
Self {
connection_string: "http://localhost:8545".to_string(),
base_directory: PathBuf::new(),
data_directory: PathBuf::new(),
logs_directory: PathBuf::new(),
geth: PathBuf::new(),
id: 0,
chain_id: 1337,
handle: None,
start_timeout: Duration::from_secs(0),
wallet: wallet_config.wallet(),
nonce_manager: Default::default(),
provider: Default::default(),
}
}
@@ -176,11 +196,7 @@ impl GethNode {
.read_to_string(&mut stderr)
.context("Failed to read geth --init stderr")?;
if !child
.wait()
.context("Failed waiting for geth --init process to finish")?
.success()
{
if !child.wait().context("Failed waiting for geth --init process to finish")?.success() {
anyhow::bail!("failed to initialize geth node #{:?}: {stderr}", &self.id);
}
@@ -240,8 +256,7 @@ impl GethNode {
Ok(process) => self.handle = Some(process),
Err(err) => {
error!(?err, "Failed to start geth, shutting down gracefully");
self.shutdown()
.context("Failed to gracefully shutdown after geth start error")?;
self.shutdown().context("Failed to gracefully shutdown after geth start error")?;
return Err(err);
}
}
@@ -255,7 +270,7 @@ impl GethNode {
construct_concurrency_limited_provider::<Ethereum, _>(
self.connection_string.as_str(),
FallbackGasFiller::default(),
ChainIdFiller::new(Some(CHAIN_ID)),
ChainIdFiller::new(Some(self.chain_id)),
NonceFiller::new(self.nonce_manager.clone()),
self.wallet.clone(),
)
@@ -386,10 +401,7 @@ impl EthereumNode for GethNode {
}
},
)
.instrument(tracing::info_span!(
"Awaiting transaction receipt",
?transaction_hash
))
.instrument(tracing::info_span!("Awaiting transaction receipt", ?transaction_hash))
.await
})
}
@@ -401,10 +413,8 @@ impl EthereumNode for GethNode {
trace_options: GethDebugTracingOptions,
) -> Pin<Box<dyn Future<Output = anyhow::Result<GethTrace>> + '_>> {
Box::pin(async move {
let provider = self
.provider()
.await
.context("Failed to create provider for tracing")?;
let provider =
self.provider().await.context("Failed to create provider for tracing")?;
poll(
Self::TRACE_POLLING_DURATION,
PollingWaitBehavior::Constant(Duration::from_millis(200)),
@@ -412,10 +422,7 @@ impl EthereumNode for GethNode {
let provider = provider.clone();
let trace_options = trace_options.clone();
async move {
match provider
.debug_trace_transaction(tx_hash, trace_options)
.await
{
match provider.debug_trace_transaction(tx_hash, trace_options).await {
Ok(trace) => Ok(ControlFlow::Break(trace)),
Err(error) => {
let error_string = error.to_string();
@@ -495,7 +502,10 @@ impl EthereumNode for GethNode {
Box::pin(async move {
let id = self.id;
let provider = self.provider().await?;
Ok(Arc::new(GethNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
Ok(Arc::new(GethNodeResolver {
id,
provider,
}) as Arc<dyn ResolverApi>)
})
}
@@ -543,19 +553,13 @@ impl EthereumNode for GethNode {
})
}
fn new_existing() -> Self {
Self {
connection_string: "http://localhost:8545".to_string(),
base_directory: PathBuf::new(),
data_directory: PathBuf::new(),
logs_directory: PathBuf::new(),
geth: PathBuf::new(),
id: 0,
handle: None,
start_timeout: Duration::from_secs(0),
wallet: Arc::new(EthereumWallet::default()),
nonce_manager: Default::default(),
provider: Default::default(),
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()
}
}
}
@@ -644,10 +648,7 @@ impl ResolverApi for GethNodeResolver {
.context("Failed to get the geth block")?
.context("Failed to get the Geth block, perhaps there are no blocks?")
.and_then(|block| {
block
.header
.base_fee_per_gas
.context("Failed to get the base fee per gas")
block.header.base_fee_per_gas.context("Failed to get the base fee per gas")
})
})
}
@@ -764,11 +765,7 @@ mod tests {
// Arrange
let (context, node) = shared_state();
let account_address = context
.wallet_configuration
.wallet()
.default_signer()
.address();
let account_address = context.wallet_configuration.wallet().default_signer().address();
let transaction = TransactionRequest::default()
.to(account_address)
.value(U256::from(100_000_000_000_000u128));
@@ -791,10 +788,7 @@ mod tests {
// Assert
let version = version.expect("Failed to get the version");
assert!(
version.starts_with("geth version"),
"expected version string, got: '{version}'"
);
assert!(version.starts_with("geth version"), "expected version string, got: '{version}'");
}
#[tokio::test]
@@ -818,12 +812,8 @@ mod tests {
let node = shared_node();
// Act
let gas_limit = node
.resolver()
.await
.unwrap()
.block_gas_limit(BlockNumberOrTag::Latest)
.await;
let gas_limit =
node.resolver().await.unwrap().block_gas_limit(BlockNumberOrTag::Latest).await;
// Assert
let _ = gas_limit.expect("Failed to get the gas limit");
@@ -836,12 +826,8 @@ mod tests {
let node = shared_node();
// Act
let coinbase = node
.resolver()
.await
.unwrap()
.block_coinbase(BlockNumberOrTag::Latest)
.await;
let coinbase =
node.resolver().await.unwrap().block_coinbase(BlockNumberOrTag::Latest).await;
// Assert
let _ = coinbase.expect("Failed to get the coinbase");
@@ -854,12 +840,8 @@ mod tests {
let node = shared_node();
// Act
let block_difficulty = node
.resolver()
.await
.unwrap()
.block_difficulty(BlockNumberOrTag::Latest)
.await;
let block_difficulty =
node.resolver().await.unwrap().block_difficulty(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_difficulty.expect("Failed to get the block difficulty");
@@ -872,12 +854,7 @@ mod tests {
let node = shared_node();
// Act
let block_hash = node
.resolver()
.await
.unwrap()
.block_hash(BlockNumberOrTag::Latest)
.await;
let block_hash = node.resolver().await.unwrap().block_hash(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_hash.expect("Failed to get the block hash");
@@ -890,12 +867,8 @@ mod tests {
let node = shared_node();
// Act
let block_timestamp = node
.resolver()
.await
.unwrap()
.block_timestamp(BlockNumberOrTag::Latest)
.await;
let block_timestamp =
node.resolver().await.unwrap().block_timestamp(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_timestamp.expect("Failed to get the block timestamp");
@@ -132,9 +132,7 @@ impl LighthouseGethNode {
let wallet_configuration = AsRef::<WalletConfiguration>::as_ref(&context);
let kurtosis_configuration = AsRef::<KurtosisConfiguration>::as_ref(&context);
let geth_directory = working_directory_configuration
.as_path()
.join(Self::BASE_DIRECTORY);
let geth_directory = working_directory_configuration.as_path().join(Self::BASE_DIRECTORY);
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = geth_directory.join(id.to_string());
@@ -147,10 +145,7 @@ impl LighthouseGethNode {
http_connection_string: String::default(),
enclave_name: format!(
"enclave-{}-{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Must not fail")
.as_nanos(),
SystemTime::now().duration_since(UNIX_EPOCH).expect("Must not fail").as_nanos(),
id
),
@@ -183,8 +178,7 @@ impl LighthouseGethNode {
fn init(&mut self, _: Genesis) -> anyhow::Result<&mut Self> {
self.init_directories()
.context("Failed to initialize the directories of the Lighthouse Geth node.")?;
self.init_kurtosis_config_file()
.context("Failed to write the config file to the FS")?;
self.init_kurtosis_config_file().context("Failed to write the config file to the FS")?;
Ok(self)
}
@@ -426,9 +420,8 @@ impl LighthouseGethNode {
.context("Full block subscriber")?;
let mut tx_hashes = futures::future::try_join_all(
NetworkWallet::<Ethereum>::signer_addresses(self.wallet.as_ref())
.enumerate()
.map(|(nonce, address)| async move {
NetworkWallet::<Ethereum>::signer_addresses(self.wallet.as_ref()).enumerate().map(
|(nonce, address)| async move {
let mut transaction = TransactionRequest::default()
.from(self.prefunded_account_address)
.to(address)
@@ -436,7 +429,8 @@ impl LighthouseGethNode {
.value(INITIAL_BALANCE.try_into().unwrap());
transaction.chain_id = Some(CHAIN_ID);
self.submit_transaction(transaction).await
}),
},
),
)
.await
.context("Failed to submit all transactions")?
@@ -531,10 +525,7 @@ impl LighthouseGethNode {
}
},
)
.instrument(tracing::info_span!(
"Awaiting transaction receipt",
?transaction_hash
))
.instrument(tracing::info_span!("Awaiting transaction receipt", ?transaction_hash))
.await
})
}
@@ -623,9 +614,7 @@ impl EthereumNode for LighthouseGethNode {
) -> Pin<Box<dyn Future<Output = anyhow::Result<GethTrace>> + '_>> {
Box::pin(async move {
let provider = Arc::new(
self.http_provider()
.await
.context("Failed to create provider for tracing")?,
self.http_provider().await.context("Failed to create provider for tracing")?,
);
poll(
Self::TRACE_POLLING_DURATION,
@@ -634,10 +623,7 @@ impl EthereumNode for LighthouseGethNode {
let provider = provider.clone();
let trace_options = trace_options.clone();
async move {
match provider
.debug_trace_transaction(tx_hash, trace_options)
.await
{
match provider.debug_trace_transaction(tx_hash, trace_options).await {
Ok(trace) => Ok(ControlFlow::Break(trace)),
Err(error) => {
let error_string = error.to_string();
@@ -717,7 +703,10 @@ impl EthereumNode for LighthouseGethNode {
Box::pin(async move {
let id = self.id;
let provider = self.ws_provider().await?;
Ok(Arc::new(LighthouseGethNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
Ok(Arc::new(LighthouseGethNodeResolver {
id,
provider,
}) as Arc<dyn ResolverApi>)
})
}
@@ -761,6 +750,16 @@ impl EthereumNode for LighthouseGethNode {
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>> {
@@ -849,10 +848,7 @@ impl<F: TxFiller<Ethereum>, P: Provider<Ethereum>> ResolverApi
.context("Failed to get the geth block")?
.context("Failed to get the Geth block, perhaps there are no blocks?")
.and_then(|block| {
block
.header
.base_fee_per_gas
.context("Failed to get the base fee per gas")
block.header.base_fee_per_gas.context("Failed to get the base fee per gas")
})
})
}
@@ -906,11 +902,7 @@ impl Node for LighthouseGethNode {
.spawn()
.expect("Failed to spawn the enclave kill command");
if !child
.wait()
.expect("Failed to wait for the enclave kill command")
.success()
{
if !child.wait().expect("Failed to wait for the enclave kill command").success() {
let stdout = {
let mut stdout = String::default();
child
@@ -1136,11 +1128,7 @@ mod tests {
let (context, node) = new_node();
node.fund_all_accounts().await.expect("Failed");
let account_address = context
.wallet_configuration
.wallet()
.default_signer()
.address();
let account_address = context.wallet_configuration.wallet().default_signer().address();
let transaction = TransactionRequest::default()
.to(account_address)
.value(U256::from(100_000_000_000_000u128));
@@ -1163,10 +1151,7 @@ mod tests {
// Assert
let version = version.expect("Failed to get the version");
assert!(
version.starts_with("CLI Version"),
"expected version string, got: '{version}'"
);
assert!(version.starts_with("CLI Version"), "expected version string, got: '{version}'");
}
#[tokio::test]
@@ -1190,12 +1175,8 @@ mod tests {
let (_context, node) = new_node();
// Act
let gas_limit = node
.resolver()
.await
.unwrap()
.block_gas_limit(BlockNumberOrTag::Latest)
.await;
let gas_limit =
node.resolver().await.unwrap().block_gas_limit(BlockNumberOrTag::Latest).await;
// Assert
let _ = gas_limit.expect("Failed to get the gas limit");
@@ -1208,12 +1189,8 @@ mod tests {
let (_context, node) = new_node();
// Act
let coinbase = node
.resolver()
.await
.unwrap()
.block_coinbase(BlockNumberOrTag::Latest)
.await;
let coinbase =
node.resolver().await.unwrap().block_coinbase(BlockNumberOrTag::Latest).await;
// Assert
let _ = coinbase.expect("Failed to get the coinbase");
@@ -1226,12 +1203,8 @@ mod tests {
let (_context, node) = new_node();
// Act
let block_difficulty = node
.resolver()
.await
.unwrap()
.block_difficulty(BlockNumberOrTag::Latest)
.await;
let block_difficulty =
node.resolver().await.unwrap().block_difficulty(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_difficulty.expect("Failed to get the block difficulty");
@@ -1244,12 +1217,7 @@ mod tests {
let (_context, node) = new_node();
// Act
let block_hash = node
.resolver()
.await
.unwrap()
.block_hash(BlockNumberOrTag::Latest)
.await;
let block_hash = node.resolver().await.unwrap().block_hash(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_hash.expect("Failed to get the block hash");
@@ -1262,12 +1230,8 @@ mod tests {
let (_context, node) = new_node();
// Act
let block_timestamp = node
.resolver()
.await
.unwrap()
.block_timestamp(BlockNumberOrTag::Latest)
.await;
let block_timestamp =
node.resolver().await.unwrap().block_timestamp(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_timestamp.expect("Failed to get the block timestamp");
+74 -103
View File
@@ -108,9 +108,7 @@ impl SubstrateNode {
) -> Self {
let working_directory_path =
AsRef::<WorkingDirectoryConfiguration>::as_ref(&context).as_path();
let eth_rpc_path = AsRef::<EthRpcConfiguration>::as_ref(&context)
.path
.as_path();
let eth_rpc_path = AsRef::<EthRpcConfiguration>::as_ref(&context).path.as_path();
let wallet = AsRef::<WalletConfiguration>::as_ref(&context).wallet();
let substrate_directory = working_directory_path.join(Self::BASE_DIRECTORY);
@@ -134,6 +132,24 @@ impl SubstrateNode {
}
}
pub fn new_existing() -> Self {
let wallet_config = revive_dt_config::WalletConfiguration::default();
Self {
id: 0,
node_binary: PathBuf::new(),
eth_proxy_binary: PathBuf::new(),
export_chainspec_command: String::new(),
rpc_url: "http://localhost:8545".to_string(),
base_directory: PathBuf::new(),
logs_directory: PathBuf::new(),
substrate_process: None,
eth_proxy_process: None,
wallet: wallet_config.wallet(),
nonce_manager: Default::default(),
provider: Default::default(),
}
}
fn init(&mut self, mut genesis: Genesis) -> anyhow::Result<&mut Self> {
let _ = remove_dir_all(self.base_directory.as_path());
let _ = clear_directory(&self.base_directory);
@@ -309,15 +325,12 @@ impl SubstrateNode {
&self,
genesis: &Genesis,
) -> anyhow::Result<Vec<(String, u128)>> {
genesis
.alloc
.iter()
.try_fold(Vec::new(), |mut vec, (address, acc)| {
let substrate_address = Self::eth_to_substrate_address(address);
let balance = acc.balance.try_into()?;
vec.push((substrate_address, balance));
Ok(vec)
})
genesis.alloc.iter().try_fold(Vec::new(), |mut vec, (address, acc)| {
let substrate_address = Self::eth_to_substrate_address(address);
let balance = acc.balance.try_into()?;
vec.push((substrate_address, balance));
Ok(vec)
})
}
fn eth_to_substrate_address(address: &Address) -> String {
@@ -412,10 +425,7 @@ impl EthereumNode for SubstrateNode {
transaction: TransactionRequest,
) -> Pin<Box<dyn Future<Output = anyhow::Result<TransactionReceipt>> + '_>> {
Box::pin(async move {
let provider = self
.provider()
.await
.context("Failed to create the provider")?;
let provider = self.provider().await.context("Failed to create the provider")?;
execute_transaction(provider, transaction).await
})
}
@@ -492,7 +502,10 @@ impl EthereumNode for SubstrateNode {
Box::pin(async move {
let id = self.id;
let provider = self.provider().await?;
Ok(Arc::new(SubstrateNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
Ok(Arc::new(SubstrateNodeResolver {
id,
provider,
}) as Arc<dyn ResolverApi>)
})
}
@@ -513,10 +526,8 @@ impl EthereumNode for SubstrateNode {
.provider()
.await
.context("Failed to create the provider for block subscription")?;
let mut block_subscription = provider
.watch_full_blocks()
.await
.context("Failed to create the blocks stream")?;
let mut block_subscription =
provider.watch_full_blocks().await.context("Failed to create the blocks stream")?;
block_subscription.set_channel_size(0xFFFF);
block_subscription.set_poll_interval(Duration::from_secs(1));
let block_stream = block_subscription.into_stream();
@@ -542,20 +553,13 @@ impl EthereumNode for SubstrateNode {
})
}
fn new_existing() -> Self {
Self {
id: 0,
node_binary: PathBuf::new(),
eth_proxy_binary: PathBuf::new(),
export_chainspec_command: String::new(),
rpc_url: "http://localhost:8545".to_string(),
base_directory: PathBuf::new(),
logs_directory: PathBuf::new(),
substrate_process: None,
eth_proxy_process: None,
wallet: Arc::new(EthereumWallet::default()),
nonce_manager: Default::default(),
provider: Default::default(),
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()
}
}
}
@@ -644,10 +648,7 @@ impl ResolverApi for SubstrateNodeResolver {
.context("Failed to get the substrate block")?
.context("Failed to get the substrate block, perhaps the chain has no blocks?")
.and_then(|block| {
block
.header
.base_fee_per_gas
.context("Failed to get the base fee per gas")
block.header.base_fee_per_gas.context("Failed to get the base fee per gas")
})
})
}
@@ -926,25 +927,26 @@ impl TransactionBuilder<ReviveNetwork> for <Ethereum as Network>::TransactionReq
);
match result {
Ok(unsigned_tx) => Ok(unsigned_tx),
Err(UnbuiltTransactionError { request, error }) => {
Err(UnbuiltTransactionError::<ReviveNetwork> {
request,
error: match error {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items) => {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items)
}
TransactionBuilderError::UnsupportedSignatureType => {
TransactionBuilderError::UnsupportedSignatureType
}
TransactionBuilderError::Signer(error) => {
TransactionBuilderError::Signer(error)
}
TransactionBuilderError::Custom(error) => {
TransactionBuilderError::Custom(error)
}
},
})
}
Err(UnbuiltTransactionError {
request,
error,
}) => Err(UnbuiltTransactionError::<ReviveNetwork> {
request,
error: match error {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items) => {
TransactionBuilderError::InvalidTransactionRequest(tx_type, items)
}
TransactionBuilderError::UnsupportedSignatureType => {
TransactionBuilderError::UnsupportedSignatureType
}
TransactionBuilderError::Signer(error) => {
TransactionBuilderError::Signer(error)
}
TransactionBuilderError::Custom(error) => {
TransactionBuilderError::Custom(error)
}
},
}),
}
}
@@ -1212,11 +1214,7 @@ mod tests {
let provider = node.provider().await.expect("Failed to create provider");
let account_address = context
.wallet_configuration
.wallet()
.default_signer()
.address();
let account_address = context.wallet_configuration.wallet().default_signer().address();
let transaction = TransactionRequest::default()
.to(account_address)
.value(U256::from(100_000_000_000_000u128));
@@ -1256,14 +1254,11 @@ mod tests {
);
// Call `init()`
dummy_node
.init(serde_json::from_str(genesis_content).unwrap())
.expect("init failed");
dummy_node.init(serde_json::from_str(genesis_content).unwrap()).expect("init failed");
// Check that the patched chainspec file was generated
let final_chainspec_path = dummy_node
.base_directory
.join(SubstrateNode::CHAIN_SPEC_JSON_FILE);
let final_chainspec_path =
dummy_node.base_directory.join(SubstrateNode::CHAIN_SPEC_JSON_FILE);
assert!(final_chainspec_path.exists(), "Chainspec file should exist");
let contents = fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec");
@@ -1369,10 +1364,7 @@ mod tests {
for (eth_addr, expected_ss58) in cases {
let result = SubstrateNode::eth_to_substrate_address(&eth_addr.parse().unwrap());
assert_eq!(
result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}"
);
assert_eq!(result, expected_ss58, "Mismatch for Ethereum address {eth_addr}");
}
}
@@ -1423,12 +1415,8 @@ mod tests {
let node = shared_node();
// Act
let gas_limit = node
.resolver()
.await
.unwrap()
.block_gas_limit(BlockNumberOrTag::Latest)
.await;
let gas_limit =
node.resolver().await.unwrap().block_gas_limit(BlockNumberOrTag::Latest).await;
// Assert
let _ = gas_limit.expect("Failed to get the gas limit");
@@ -1441,12 +1429,8 @@ mod tests {
let node = shared_node();
// Act
let coinbase = node
.resolver()
.await
.unwrap()
.block_coinbase(BlockNumberOrTag::Latest)
.await;
let coinbase =
node.resolver().await.unwrap().block_coinbase(BlockNumberOrTag::Latest).await;
// Assert
let _ = coinbase.expect("Failed to get the coinbase");
@@ -1459,12 +1443,8 @@ mod tests {
let node = shared_node();
// Act
let block_difficulty = node
.resolver()
.await
.unwrap()
.block_difficulty(BlockNumberOrTag::Latest)
.await;
let block_difficulty =
node.resolver().await.unwrap().block_difficulty(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_difficulty.expect("Failed to get the block difficulty");
@@ -1477,12 +1457,7 @@ mod tests {
let node = shared_node();
// Act
let block_hash = node
.resolver()
.await
.unwrap()
.block_hash(BlockNumberOrTag::Latest)
.await;
let block_hash = node.resolver().await.unwrap().block_hash(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_hash.expect("Failed to get the block hash");
@@ -1495,12 +1470,8 @@ mod tests {
let node = shared_node();
// Act
let block_timestamp = node
.resolver()
.await
.unwrap()
.block_timestamp(BlockNumberOrTag::Latest)
.await;
let block_timestamp =
node.resolver().await.unwrap().block_timestamp(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_timestamp.expect("Failed to get the block timestamp");
@@ -130,14 +130,10 @@ impl ZombieNode {
+ AsRef<EthRpcConfiguration>
+ AsRef<WalletConfiguration>,
) -> Self {
let eth_proxy_binary = AsRef::<EthRpcConfiguration>::as_ref(&context)
.path
.to_owned();
let eth_proxy_binary = AsRef::<EthRpcConfiguration>::as_ref(&context).path.to_owned();
let working_directory_path = AsRef::<WorkingDirectoryConfiguration>::as_ref(&context);
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
let base_directory = working_directory_path
.join(Self::BASE_DIRECTORY)
.join(id.to_string());
let base_directory = working_directory_path.join(Self::BASE_DIRECTORY).join(id.to_string());
let logs_directory = base_directory.join(Self::LOGS_DIRECTORY);
let wallet = AsRef::<WalletConfiguration>::as_ref(&context).wallet();
@@ -169,10 +165,8 @@ impl ZombieNode {
let template_chainspec_path = self.base_directory.join(Self::CHAIN_SPEC_JSON_FILE);
self.prepare_chainspec(template_chainspec_path.clone(), genesis)?;
let polkadot_parachain_path = self
.polkadot_parachain_path
.to_str()
.context("Invalid polkadot parachain path")?;
let polkadot_parachain_path =
self.polkadot_parachain_path.to_str().context("Invalid polkadot parachain path")?;
let node_rpc_port = Self::NODE_BASE_RPC_PORT + self.id as u16;
@@ -204,10 +198,8 @@ impl ZombieNode {
}
fn spawn_process(&mut self) -> anyhow::Result<()> {
let network_config = self
.network_config
.clone()
.context("Node not initialized, call init() first")?;
let network_config =
self.network_config.clone().context("Node not initialized, call init() first")?;
let rt = tokio::runtime::Runtime::new().unwrap();
let network = rt.block_on(async {
@@ -272,17 +264,12 @@ impl ZombieNode {
mut genesis: Genesis,
) -> anyhow::Result<()> {
let mut cmd: Command = std::process::Command::new(&self.polkadot_parachain_path);
cmd.arg(Self::EXPORT_CHAINSPEC_COMMAND)
.arg("--chain")
.arg("asset-hub-westend-local");
cmd.arg(Self::EXPORT_CHAINSPEC_COMMAND).arg("--chain").arg("asset-hub-westend-local");
let output = cmd.output().context("Failed to export the chain-spec")?;
if !output.status.success() {
anyhow::bail!(
"Build chain-spec failed: {}",
String::from_utf8_lossy(&output.stderr)
);
anyhow::bail!("Build chain-spec failed: {}", String::from_utf8_lossy(&output.stderr));
}
let content = String::from_utf8(output.stdout)
@@ -343,15 +330,12 @@ impl ZombieNode {
&self,
genesis: &Genesis,
) -> anyhow::Result<Vec<(String, u128)>> {
genesis
.alloc
.iter()
.try_fold(Vec::new(), |mut vec, (address, acc)| {
let polkadot_address = Self::eth_to_polkadot_address(address);
let balance = acc.balance.try_into()?;
vec.push((polkadot_address, balance));
Ok(vec)
})
genesis.alloc.iter().try_fold(Vec::new(), |mut vec, (address, acc)| {
let polkadot_address = Self::eth_to_polkadot_address(address);
let balance = acc.balance.try_into()?;
vec.push((polkadot_address, balance));
Ok(vec)
})
}
fn eth_to_polkadot_address(address: &Address) -> String {
@@ -535,7 +519,10 @@ impl EthereumNode for ZombieNode {
let id = self.id;
let provider = self.provider().await?;
Ok(Arc::new(ZombieNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
Ok(Arc::new(ZombieNodeResolver {
id,
provider,
}) as Arc<dyn ResolverApi>)
})
}
@@ -556,10 +543,8 @@ impl EthereumNode for ZombieNode {
.provider()
.await
.context("Failed to create the provider for block subscription")?;
let mut block_subscription = provider
.watch_full_blocks()
.await
.context("Failed to create the blocks stream")?;
let mut block_subscription =
provider.watch_full_blocks().await.context("Failed to create the blocks stream")?;
block_subscription.set_channel_size(0xFFFF);
block_subscription.set_poll_interval(Duration::from_secs(1));
let block_stream = block_subscription.into_stream();
@@ -584,6 +569,16 @@ impl EthereumNode for ZombieNode {
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 ZombieNodeResolver<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> {
@@ -672,10 +667,7 @@ impl<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> ResolverApi
.context("Failed to get the zombie block")?
.context("Failed to get the zombie block, perhaps the chain has no blocks?")
.and_then(|block| {
block
.header
.base_fee_per_gas
.context("Failed to get the base fee per gas")
block.header.base_fee_per_gas.context("Failed to get the base fee per gas")
})
})
}
@@ -787,10 +779,8 @@ mod tests {
pub async fn new_node() -> (TestExecutionContext, ZombieNode) {
let context = test_config();
let mut node = ZombieNode::new(
context.polkadot_parachain_configuration.path.clone(),
&context,
);
let mut node =
ZombieNode::new(context.polkadot_parachain_configuration.path.clone(), &context);
let genesis = context.genesis_configuration.genesis().unwrap().clone();
node.init(genesis).unwrap();
@@ -855,14 +845,11 @@ mod tests {
"#;
let context = test_config();
let mut node = ZombieNode::new(
context.polkadot_parachain_configuration.path.clone(),
&context,
);
let mut node =
ZombieNode::new(context.polkadot_parachain_configuration.path.clone(), &context);
// Call `init()`
node.init(serde_json::from_str(genesis_content).unwrap())
.expect("init failed");
node.init(serde_json::from_str(genesis_content).unwrap()).expect("init failed");
// Check that the patched chainspec file was generated
let final_chainspec_path = node.base_directory.join(ZombieNode::CHAIN_SPEC_JSON_FILE);
@@ -903,10 +890,7 @@ mod tests {
"#;
let context = test_config();
let node = ZombieNode::new(
context.polkadot_parachain_configuration.path.clone(),
&context,
);
let node = ZombieNode::new(context.polkadot_parachain_configuration.path.clone(), &context);
let result = node
.extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
@@ -968,10 +952,7 @@ mod tests {
for (eth_addr, expected_ss58) in cases {
let result = ZombieNode::eth_to_polkadot_address(&eth_addr.parse().unwrap());
assert_eq!(
result, expected_ss58,
"Mismatch for Ethereum address {eth_addr}"
);
assert_eq!(result, expected_ss58, "Mismatch for Ethereum address {eth_addr}");
}
}
@@ -979,10 +960,7 @@ mod tests {
fn eth_rpc_version_works() {
// Arrange
let context = test_config();
let node = ZombieNode::new(
context.polkadot_parachain_configuration.path.clone(),
&context,
);
let node = ZombieNode::new(context.polkadot_parachain_configuration.path.clone(), &context);
// Act
let version = node.eth_rpc_version().unwrap();
@@ -998,10 +976,7 @@ mod tests {
fn version_works() {
// Arrange
let context = test_config();
let node = ZombieNode::new(
context.polkadot_parachain_configuration.path.clone(),
&context,
);
let node = ZombieNode::new(context.polkadot_parachain_configuration.path.clone(), &context);
// Act
let version = node.version().unwrap();
@@ -1039,12 +1014,8 @@ mod tests {
let node = shared_node().await;
// Act
let gas_limit = node
.resolver()
.await
.unwrap()
.block_gas_limit(BlockNumberOrTag::Latest)
.await;
let gas_limit =
node.resolver().await.unwrap().block_gas_limit(BlockNumberOrTag::Latest).await;
// Assert
let _ = gas_limit.expect("Failed to get the gas limit");
@@ -1057,12 +1028,8 @@ mod tests {
let node = shared_node().await;
// Act
let coinbase = node
.resolver()
.await
.unwrap()
.block_coinbase(BlockNumberOrTag::Latest)
.await;
let coinbase =
node.resolver().await.unwrap().block_coinbase(BlockNumberOrTag::Latest).await;
// Assert
let _ = coinbase.expect("Failed to get the coinbase");
@@ -1075,12 +1042,8 @@ mod tests {
let node = shared_node().await;
// Act
let block_difficulty = node
.resolver()
.await
.unwrap()
.block_difficulty(BlockNumberOrTag::Latest)
.await;
let block_difficulty =
node.resolver().await.unwrap().block_difficulty(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_difficulty.expect("Failed to get the block difficulty");
@@ -1093,12 +1056,7 @@ mod tests {
let node = shared_node().await;
// Act
let block_hash = node
.resolver()
.await
.unwrap()
.block_hash(BlockNumberOrTag::Latest)
.await;
let block_hash = node.resolver().await.unwrap().block_hash(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_hash.expect("Failed to get the block hash");
@@ -1111,12 +1069,8 @@ mod tests {
let node = shared_node().await;
// Act
let block_timestamp = node
.resolver()
.await
.unwrap()
.block_timestamp(BlockNumberOrTag::Latest)
.await;
let block_timestamp =
node.resolver().await.unwrap().block_timestamp(BlockNumberOrTag::Latest).await;
// Assert
let _ = block_timestamp.expect("Failed to get the block timestamp");
@@ -55,10 +55,7 @@ where
let future = self.service.call(req);
Box::pin(async move {
let _permit = semaphore
.acquire()
.await
.expect("Semaphore has been closed");
let _permit = semaphore.acquire().await.expect("Semaphore has been closed");
tracing::debug!(
available_permits = semaphore.available_permits(),
"Acquired Semaphore Permit"
+14 -19
View File
@@ -80,10 +80,8 @@ where
NonceFiller: TxFiller<N>,
WalletFiller<W>: TxFiller<N>,
{
let sendable_transaction = provider
.fill(transaction)
.await
.context("Failed to fill transaction")?;
let sendable_transaction =
provider.fill(transaction).await.context("Failed to fill transaction")?;
let transaction_envelope = sendable_transaction
.try_into_envelope()
@@ -105,24 +103,21 @@ where
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}"
))?;
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();
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(())),
}
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}"))
}
+10 -36
View File
@@ -123,12 +123,8 @@ impl ReportAggregator {
file_name.push_str(".json");
file_name
};
let file_path = self
.report
.context
.working_directory_configuration()
.as_path()
.join(file_name);
let file_path =
self.report.context.working_directory_configuration().as_path().join(file_name);
let file = OpenOptions::new()
.create(true)
.write(true)
@@ -136,10 +132,7 @@ impl ReportAggregator {
.read(false)
.open(&file_path)
.with_context(|| {
format!(
"Failed to open report file for writing: {}",
file_path.display()
)
format!("Failed to open report file for writing: {}", file_path.display())
})?;
serde_json::to_writer_pretty(&file, &self.report).with_context(|| {
format!("Failed to serialize report JSON to {}", file_path.display())
@@ -241,10 +234,7 @@ impl ReportAggregator {
.or_default()
.iter()
.map(|(case_idx, case_report)| {
(
*case_idx,
case_report.status.clone().expect("Can't be uninitialized"),
)
(*case_idx, case_report.status.clone().expect("Can't be uninitialized"))
})
.collect::<BTreeMap<_, _>>();
let event = ReporterEvent::MetadataFileSolcModeCombinationExecutionCompleted {
@@ -276,16 +266,8 @@ impl ReportAggregator {
&mut self,
event: PreLinkContractsCompilationSucceededEvent,
) {
let include_input = self
.report
.context
.report_configuration()
.include_compiler_input;
let include_output = self
.report
.context
.report_configuration()
.include_compiler_output;
let include_input = self.report.context.report_configuration().include_compiler_input;
let include_output = self.report.context.report_configuration().include_compiler_output;
let execution_information = self.execution_information(&event.execution_specifier);
@@ -313,16 +295,8 @@ impl ReportAggregator {
&mut self,
event: PostLinkContractsCompilationSucceededEvent,
) {
let include_input = self
.report
.context
.report_configuration()
.include_compiler_input;
let include_output = self
.report
.context
.report_configuration()
.include_compiler_output;
let include_input = self.report.context.report_configuration().include_compiler_input;
let include_output = self.report.context.report_configuration().include_compiler_output;
let execution_information = self.execution_information(&event.execution_specifier);
@@ -375,8 +349,8 @@ impl ReportAggregator {
}
fn handle_libraries_deployed_event(&mut self, event: LibrariesDeployedEvent) {
self.execution_information(&event.execution_specifier)
.deployed_libraries = Some(event.libraries);
self.execution_information(&event.execution_specifier).deployed_libraries =
Some(event.libraries);
}
fn handle_contract_deployed_event(&mut self, event: ContractDeployedEvent) {
+4 -2
View File
@@ -8,8 +8,10 @@ use anyhow::Context as _;
use indexmap::IndexMap;
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_compiler::{CompilerInput, CompilerOutput};
use revive_dt_format::metadata::Metadata;
use revive_dt_format::{corpus::Corpus, metadata::ContractInstance};
use revive_dt_format::{
corpus::Corpus,
metadata::{ContractInstance, Metadata},
};
use semver::Version;
use tokio::sync::{broadcast, oneshot};
+9 -30
View File
@@ -22,9 +22,8 @@ pub(crate) async fn get_or_download(
working_directory: &Path,
downloader: &SolcDownloader,
) -> anyhow::Result<(Version, PathBuf)> {
let target_directory = working_directory
.join(SOLC_CACHE_DIRECTORY)
.join(downloader.version.to_string());
let target_directory =
working_directory.join(SOLC_CACHE_DIRECTORY).join(downloader.version.to_string());
let target_file = target_directory.join(downloader.target);
let mut cache = SOLC_CACHER.lock().await;
@@ -34,19 +33,11 @@ pub(crate) async fn get_or_download(
}
create_dir_all(&target_directory).with_context(|| {
format!(
"Failed to create solc cache directory: {}",
target_directory.display()
)
format!("Failed to create solc cache directory: {}", target_directory.display())
})?;
download_to_file(&target_file, downloader)
.await
.with_context(|| {
format!(
"Failed to write downloaded solc to {}",
target_file.display()
)
})?;
.with_context(|| format!("Failed to write downloaded solc to {}", target_file.display()))?;
cache.insert(target_file.clone());
Ok((downloader.version.clone(), target_file))
@@ -70,15 +61,9 @@ async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::R
}
let mut file = BufWriter::new(file);
file.write_all(
&downloader
.download()
.await
.context("Failed to download solc binary bytes")?,
)
.with_context(|| format!("Failed to write solc binary to {}", path.display()))?;
file.flush()
.with_context(|| format!("Failed to flush file {}", path.display()))?;
file.write_all(&downloader.download().await.context("Failed to download solc binary bytes")?)
.with_context(|| format!("Failed to write solc binary to {}", path.display()))?;
file.flush().with_context(|| format!("Failed to flush file {}", path.display()))?;
drop(file);
#[cfg(target_os = "macos")]
@@ -91,17 +76,11 @@ async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::R
.stdout(std::process::Stdio::null())
.spawn()
.with_context(|| {
format!(
"Failed to spawn xattr to remove quarantine attribute on {}",
path.display()
)
format!("Failed to spawn xattr to remove quarantine attribute on {}", path.display())
})?
.wait()
.with_context(|| {
format!(
"Failed waiting for xattr operation to complete on {}",
path.display()
)
format!("Failed waiting for xattr operation to complete on {}", path.display())
})?;
Ok(())
+8 -41
View File
@@ -130,11 +130,7 @@ impl SolcDownloader {
})?;
let path = build.path.clone();
let expected_digest = build
.sha256
.strip_prefix("0x")
.unwrap_or(&build.sha256)
.to_string();
let expected_digest = build.sha256.strip_prefix("0x").unwrap_or(&build.sha256).to_string();
let url = format!("{}/{}/{}", Self::BASE_URL, self.target, path.display());
let file = reqwest::get(&url)
@@ -159,54 +155,25 @@ mod tests {
#[tokio::test]
async fn try_get_windows() {
let version = List::download(List::WINDOWS_URL)
.await
.unwrap()
.latest_release;
SolcDownloader::windows(version)
.await
.unwrap()
.download()
.await
.unwrap();
let version = List::download(List::WINDOWS_URL).await.unwrap().latest_release;
SolcDownloader::windows(version).await.unwrap().download().await.unwrap();
}
#[tokio::test]
async fn try_get_macosx() {
let version = List::download(List::MACOSX_URL)
.await
.unwrap()
.latest_release;
SolcDownloader::macosx(version)
.await
.unwrap()
.download()
.await
.unwrap();
let version = List::download(List::MACOSX_URL).await.unwrap().latest_release;
SolcDownloader::macosx(version).await.unwrap().download().await.unwrap();
}
#[tokio::test]
async fn try_get_linux() {
let version = List::download(List::LINUX_URL)
.await
.unwrap()
.latest_release;
SolcDownloader::linux(version)
.await
.unwrap()
.download()
.await
.unwrap();
let version = List::download(List::LINUX_URL).await.unwrap().latest_release;
SolcDownloader::linux(version).await.unwrap().download().await.unwrap();
}
#[tokio::test]
async fn try_get_wasm() {
let version = List::download(List::WASM_URL).await.unwrap().latest_release;
SolcDownloader::wasm(version)
.await
.unwrap()
.download()
.await
.unwrap();
SolcDownloader::wasm(version).await.unwrap().download().await.unwrap();
}
}