diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 0f76c9b..544307f 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -26,6 +26,7 @@ use revive_dt_format::case::CaseIdx; use revive_dt_format::input::{Calldata, Expected, ExpectedOutput, Method}; use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier}; use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode}; +use revive_dt_node::Node; use revive_dt_node_interaction::EthereumNode; use revive_dt_report::reporter::{CompilationTask, Report, Span}; use revive_solc_json_interface::SolcStandardJsonOutput; @@ -250,6 +251,12 @@ where let tx = { 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::::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 // 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. let expected = !expectation.exception; let actual = execution_receipt.status(); if actual != expected { - tracing::error!( - ?execution_receipt, - expected, - actual, - "Transaction status assertion failed", - ); + tracing::error!(expected, actual, "Transaction status assertion failed",); anyhow::bail!( "Transaction status assertion failed - Expected {expected} but got {actual}", ); @@ -457,7 +463,11 @@ where .map(Bytes::from)?; let actual = tracing_result.output.clone().unwrap_or_default(); 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}",); } } @@ -468,12 +478,7 @@ where let expected = expected_events.len(); let actual = execution_receipt.logs().len(); if actual != expected { - tracing::error!( - ?execution_receipt, - expected, - actual, - "Event count assertion failed", - ); + tracing::error!(expected, actual, "Event count assertion failed",); anyhow::bail!( "Event count assertion failed - Expected {expected} but got {actual}", ); @@ -489,7 +494,6 @@ where let actual = actual_event.address(); if actual != expected { tracing::error!( - ?execution_receipt, %expected, %actual, "Event emitter assertion failed", @@ -511,12 +515,7 @@ where .calldata(self.deployed_contracts.entry(case_idx).or_default(), node)?; let actual = actual_topic.to_vec(); if actual != expected { - tracing::error!( - ?execution_receipt, - ?expected, - ?actual, - "Event topics assertion failed", - ); + tracing::error!(?expected, ?actual, "Event topics assertion failed",); anyhow::bail!( "Event topics assertion failed - Expected {expected:?} but got {actual:?}", ); @@ -530,12 +529,7 @@ where .map(Bytes::from)?; let actual = &actual_event.data().data; if !expected.starts_with(actual) { - tracing::error!( - ?execution_receipt, - ?expected, - ?actual, - "Event value assertion failed", - ); + tracing::error!(?expected, ?actual, "Event value assertion failed",); anyhow::bail!( "Event value assertion failed - Expected {expected:?} but got {actual:?}", ); @@ -649,6 +643,22 @@ where let tracing_span = tracing::info_span!("Handling metadata file"); 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() { let tracing_span = tracing::info_span!("With solc mode", solc_mode = ?mode); let _guard = tracing_span.enter(); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index e9bcdd1..f7df15c 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -5,7 +5,7 @@ use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc}; 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; pub mod common; @@ -15,7 +15,7 @@ pub mod driver; /// /// For this we need a blockchain node implementation and a compiler. pub trait Platform { - type Blockchain: EthereumNode; + type Blockchain: EthereumNode + Node; type Compiler: SolidityCompiler; /// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments]. diff --git a/crates/format/src/case.rs b/crates/format/src/case.rs index 6b2f107..29e4ef5 100644 --- a/crates/format/src/case.rs +++ b/crates/format/src/case.rs @@ -25,7 +25,15 @@ impl Case { .enumerate() .map(move |(idx, mut input)| { if idx + 1 == inputs_len { - input.expected = self.expected.clone(); + 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? + input } else { input diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 14ca93e..59156e5 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -7,13 +7,13 @@ use alloy::{ primitives::{Address, Bytes, U256}, rpc::types::TransactionRequest, }; -use alloy_primitives::FixedBytes; +use alloy_primitives::{FixedBytes, utils::parse_units}; use semver::VersionReq; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use revive_dt_node_interaction::EthereumNode; -use crate::metadata::ContractInstance; +use crate::{define_wrapper_type, metadata::ContractInstance}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] pub struct Input { @@ -26,7 +26,7 @@ pub struct Input { #[serde(default)] pub calldata: Calldata, pub expected: Option, - pub value: Option, + pub value: Option, pub storage: Option>, } @@ -82,6 +82,37 @@ pub enum Method { FunctionName(String), } +define_wrapper_type!( + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + EtherValue(U256); +); + +impl Serialize for EtherValue { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + format!("{} wei", self.0).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for EtherValue { + fn deserialize(deserializer: D) -> Result + 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 { pub fn new() -> Self { Default::default() @@ -250,7 +281,11 @@ impl Input { chain_state_provider: &impl EthereumNode, ) -> anyhow::Result { 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 { Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)), _ => Ok(transaction_request diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index abd4150..c1cef62 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -44,6 +44,7 @@ impl Deref for MetadataFile { #[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)] pub struct Metadata { + pub targets: Option>, pub cases: Vec, pub contracts: Option>, // TODO: Convert into wrapper types for clarity. diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index c0338cc..794650c 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -85,7 +85,8 @@ impl Instance { >::signer_addresses(&self.wallet) { 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); @@ -511,6 +512,14 @@ impl Node for Instance { .stdout; 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 { diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index c21e2ff..3820f08 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -133,7 +133,7 @@ impl KitchensinkNode { { genesis.alloc.entry(signer_address).or_insert( GenesisAccount::default() - .with_balance(1000000000000000000u128.try_into().unwrap()), + .with_balance(10000000000000000000000u128.try_into().unwrap()), ); } self.extract_balance_from_genesis_file(&genesis)? @@ -587,6 +587,14 @@ impl Node for KitchensinkNode { .stdout; 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 { diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 44eaf19..8084150 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -31,4 +31,8 @@ pub trait Node: EthereumNode { /// Returns the node version. fn version(&self) -> anyhow::Result; + + /// 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; } diff --git a/genesis.json b/genesis.json index ae85d1f..9993430 100644 --- a/genesis.json +++ b/genesis.json @@ -35,7 +35,7 @@ "timestamp": "0x00", "alloc": { "90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": { - "balance": "1000000000000000000" + "balance": "10000000000000000000000" } } } \ No newline at end of file