Compare commits

...

2 Commits

Author SHA1 Message Date
Omar Abdulla e7ebe4fa2f Handle values from the metadata files 2025-07-22 09:24:09 +03:00
Omar Abdulla 5c957e5ac1 Add a way to skip tests if they don't match the target 2025-07-22 09:07:01 +03:00
9 changed files with 115 additions and 37 deletions
+36 -26
View File
@@ -26,6 +26,7 @@ use revive_dt_format::case::CaseIdx;
use revive_dt_format::input::{Calldata, Expected, ExpectedOutput, Method}; use revive_dt_format::input::{Calldata, Expected, ExpectedOutput, Method};
use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier}; use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier};
use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode}; use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
use revive_dt_node::Node;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::reporter::{CompilationTask, Report, Span}; use revive_dt_report::reporter::{CompilationTask, Report, Span};
use revive_solc_json_interface::SolcStandardJsonOutput; use revive_solc_json_interface::SolcStandardJsonOutput;
@@ -250,6 +251,12 @@ where
let tx = { let tx = {
let tx = TransactionRequest::default().from(input.caller); let tx = TransactionRequest::default().from(input.caller);
let tx = match input.value {
Some(ref value) if deploy_with_constructor_arguments => {
tx.value(value.into_inner())
}
_ => tx,
};
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code) TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
}; };
@@ -435,16 +442,15 @@ where
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the // Additionally, what happens if the compiler filter doesn't match? Do we consider that the
// transaction should succeed? Do we just ignore the expectation? // transaction should succeed? Do we just ignore the expectation?
let error_span =
tracing::error_span!("Exception failed", ?tracing_result, ?execution_receipt,);
let _guard = error_span.enter();
// Handling the receipt state assertion. // Handling the receipt state assertion.
let expected = !expectation.exception; let expected = !expectation.exception;
let actual = execution_receipt.status(); let actual = execution_receipt.status();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(expected, actual, "Transaction status assertion failed",);
?execution_receipt,
expected,
actual,
"Transaction status assertion failed",
);
anyhow::bail!( anyhow::bail!(
"Transaction status assertion failed - Expected {expected} but got {actual}", "Transaction status assertion failed - Expected {expected} but got {actual}",
); );
@@ -457,7 +463,11 @@ where
.map(Bytes::from)?; .map(Bytes::from)?;
let actual = tracing_result.output.clone().unwrap_or_default(); let actual = tracing_result.output.clone().unwrap_or_default();
if !expected.starts_with(&actual) { if !expected.starts_with(&actual) {
tracing::error!(?execution_receipt, %expected, %actual, "Calldata assertion failed"); tracing::error!(
%expected,
%actual,
"Calldata assertion failed"
);
anyhow::bail!("Calldata assertion failed - Expected {expected} but got {actual}",); anyhow::bail!("Calldata assertion failed - Expected {expected} but got {actual}",);
} }
} }
@@ -468,12 +478,7 @@ where
let expected = expected_events.len(); let expected = expected_events.len();
let actual = execution_receipt.logs().len(); let actual = execution_receipt.logs().len();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(expected, actual, "Event count assertion failed",);
?execution_receipt,
expected,
actual,
"Event count assertion failed",
);
anyhow::bail!( anyhow::bail!(
"Event count assertion failed - Expected {expected} but got {actual}", "Event count assertion failed - Expected {expected} but got {actual}",
); );
@@ -489,7 +494,6 @@ where
let actual = actual_event.address(); let actual = actual_event.address();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(
?execution_receipt,
%expected, %expected,
%actual, %actual,
"Event emitter assertion failed", "Event emitter assertion failed",
@@ -511,12 +515,7 @@ where
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)?; .calldata(self.deployed_contracts.entry(case_idx).or_default(), node)?;
let actual = actual_topic.to_vec(); let actual = actual_topic.to_vec();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(?expected, ?actual, "Event topics assertion failed",);
?execution_receipt,
?expected,
?actual,
"Event topics assertion failed",
);
anyhow::bail!( anyhow::bail!(
"Event topics assertion failed - Expected {expected:?} but got {actual:?}", "Event topics assertion failed - Expected {expected:?} but got {actual:?}",
); );
@@ -530,12 +529,7 @@ where
.map(Bytes::from)?; .map(Bytes::from)?;
let actual = &actual_event.data().data; let actual = &actual_event.data().data;
if !expected.starts_with(actual) { if !expected.starts_with(actual) {
tracing::error!( tracing::error!(?expected, ?actual, "Event value assertion failed",);
?execution_receipt,
?expected,
?actual,
"Event value assertion failed",
);
anyhow::bail!( anyhow::bail!(
"Event value assertion failed - Expected {expected:?} but got {actual:?}", "Event value assertion failed - Expected {expected:?} but got {actual:?}",
); );
@@ -649,6 +643,22 @@ where
let tracing_span = tracing::info_span!("Handling metadata file"); let tracing_span = tracing::info_span!("Handling metadata file");
let _guard = tracing_span.enter(); let _guard = tracing_span.enter();
// We only execute this input if it's valid for the leader and the follower. Otherwise, we
// skip it with a warning.
if !self
.leader_node
.matches_target(self.metadata.targets.as_deref())
|| !self
.follower_node
.matches_target(self.metadata.targets.as_deref())
{
tracing::warn!(
targets = ?self.metadata.targets,
"Either the leader or follower node do not support the targets of the file"
);
return execution_result;
}
for mode in self.metadata.solc_modes() { for mode in self.metadata.solc_modes() {
let tracing_span = tracing::info_span!("With solc mode", solc_mode = ?mode); let tracing_span = tracing::info_span!("With solc mode", solc_mode = ?mode);
let _guard = tracing_span.enter(); let _guard = tracing_span.enter();
+2 -2
View File
@@ -5,7 +5,7 @@
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc}; use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
use revive_dt_config::TestingPlatform; use revive_dt_config::TestingPlatform;
use revive_dt_node::{geth, kitchensink::KitchensinkNode}; use revive_dt_node::{Node, geth, kitchensink::KitchensinkNode};
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
pub mod common; pub mod common;
@@ -15,7 +15,7 @@ pub mod driver;
/// ///
/// For this we need a blockchain node implementation and a compiler. /// For this we need a blockchain node implementation and a compiler.
pub trait Platform { pub trait Platform {
type Blockchain: EthereumNode; type Blockchain: EthereumNode + Node;
type Compiler: SolidityCompiler; type Compiler: SolidityCompiler;
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments]. /// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
+8
View File
@@ -26,7 +26,15 @@ impl Case {
.enumerate() .enumerate()
.map(move |(idx, mut input)| { .map(move |(idx, mut input)| {
if idx + 1 == inputs_len { if idx + 1 == inputs_len {
if input.expected.is_none() {
input.expected = self.expected.clone(); 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?
input input
} else { } else {
input input
+43 -5
View File
@@ -7,13 +7,16 @@ use alloy::{
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
rpc::types::TransactionRequest, rpc::types::TransactionRequest,
}; };
use alloy_primitives::FixedBytes; use alloy_primitives::{FixedBytes, utils::parse_units};
use semver::VersionReq; use semver::VersionReq;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use crate::metadata::{AddressReplacementMap, ContractInstance}; use crate::{
define_wrapper_type,
metadata::{AddressReplacementMap, ContractInstance},
};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Input { pub struct Input {
@@ -26,7 +29,7 @@ pub struct Input {
#[serde(default)] #[serde(default)]
pub calldata: Calldata, pub calldata: Calldata,
pub expected: Option<Expected>, pub expected: Option<Expected>,
pub value: Option<String>, pub value: Option<EtherValue>,
pub storage: Option<HashMap<String, Calldata>>, pub storage: Option<HashMap<String, Calldata>>,
} }
@@ -82,6 +85,37 @@ pub enum Method {
FunctionName(String), FunctionName(String),
} }
define_wrapper_type!(
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
EtherValue(U256);
);
impl Serialize for EtherValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
format!("{} wei", self.0).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for EtherValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
let mut splitted = string.split(' ');
let (Some(value), Some(unit)) = (splitted.next(), splitted.next()) else {
return Err(serde::de::Error::custom("Failed to parse the value"));
};
let parsed = parse_units(value, unit.replace("eth", "ether"))
.map_err(|_| serde::de::Error::custom("Failed to parse units"))?
.into();
Ok(Self(parsed))
}
}
impl ExpectedOutput { impl ExpectedOutput {
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
@@ -324,7 +358,11 @@ impl Input {
chain_state_provider: &impl EthereumNode, chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?; let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default(); let transaction_request = TransactionRequest::default().from(self.caller).value(
self.value
.map(|value| value.into_inner())
.unwrap_or_default(),
);
match self.method { match self.method {
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)), Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
_ => Ok(transaction_request _ => Ok(transaction_request
+1
View File
@@ -48,6 +48,7 @@ impl Deref for MetadataFile {
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)] #[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
pub struct Metadata { pub struct Metadata {
pub targets: Option<Vec<String>>,
pub cases: Vec<Case>, pub cases: Vec<Case>,
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>, pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>,
// TODO: Convert into wrapper types for clarity. // TODO: Convert into wrapper types for clarity.
+10 -1
View File
@@ -85,7 +85,8 @@ impl Instance {
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet) <EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{ {
genesis.alloc.entry(signer_address).or_insert( genesis.alloc.entry(signer_address).or_insert(
GenesisAccount::default().with_balance(1000000000000000000u128.try_into().unwrap()), GenesisAccount::default()
.with_balance(10000000000000000000000u128.try_into().unwrap()),
); );
} }
let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE); let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE);
@@ -510,6 +511,14 @@ impl Node for Instance {
.stdout; .stdout;
Ok(String::from_utf8_lossy(&output).into()) Ok(String::from_utf8_lossy(&output).into())
} }
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
fn matches_target(&self, targets: Option<&[String]>) -> bool {
match targets {
None => true,
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
}
}
} }
impl Drop for Instance { impl Drop for Instance {
+9 -1
View File
@@ -131,7 +131,7 @@ impl KitchensinkNode {
{ {
genesis.alloc.entry(signer_address).or_insert( genesis.alloc.entry(signer_address).or_insert(
GenesisAccount::default() GenesisAccount::default()
.with_balance(1000000000000000000u128.try_into().unwrap()), .with_balance(10000000000000000000000u128.try_into().unwrap()),
); );
} }
self.extract_balance_from_genesis_file(&genesis)? self.extract_balance_from_genesis_file(&genesis)?
@@ -584,6 +584,14 @@ impl Node for KitchensinkNode {
.stdout; .stdout;
Ok(String::from_utf8_lossy(&output).into()) Ok(String::from_utf8_lossy(&output).into())
} }
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
fn matches_target(&self, targets: Option<&[String]>) -> bool {
match targets {
None => true,
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
}
}
} }
impl Drop for KitchensinkNode { impl Drop for KitchensinkNode {
+4
View File
@@ -35,4 +35,8 @@ pub trait Node: EthereumNode {
/// Returns the node version. /// Returns the node version.
fn version(&self) -> anyhow::Result<String>; fn version(&self) -> anyhow::Result<String>;
/// Given a list of targets from the metadata file, this function determines if the metadata
/// file can be ran on this node or not.
fn matches_target(&self, targets: Option<&[String]>) -> bool;
} }
+1 -1
View File
@@ -35,7 +35,7 @@
"timestamp": "0x00", "timestamp": "0x00",
"alloc": { "alloc": {
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": { "90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": {
"balance": "1000000000000000000" "balance": "10000000000000000000000"
} }
} }
} }