Compare commits

...

13 Commits

Author SHA1 Message Date
Omar Abdulla edbbbab934 Fix tests 2025-07-23 18:48:59 +03:00
Omar 757cbaf61e Merge pull request #57 from paritytech/feature/caller-replacement
Implement caller replacement
2025-07-23 18:46:18 +03:00
Omar Abdulla 2c7e95bb4f Merge remote-tracking branch 'origin/feature/handle-exceptions' into feature/caller-replacement 2025-07-23 18:45:59 +03:00
Omar Abdulla 2b6ee18e40 Better handling for length in equivalency checks 2025-07-23 18:43:39 +03:00
Omar Abdulla ca1ce3e5b4 Fix calldata construction of single calldata 2025-07-23 18:43:39 +03:00
Omar Abdulla 3d28179dde Add support for wildcards in exceptions 2025-07-23 18:43:39 +03:00
Omar Abdulla 6303f3b917 Fix size_requirement underflow 2025-07-23 18:42:10 +03:00
Omar Abdulla e434c163c7 Make initial balance a constant 2025-07-23 18:42:09 +03:00
Omar Abdulla 404d2f7452 Better handling for length in equivalency checks 2025-07-23 14:53:19 +03:00
Omar 750d9fff27 Merge pull request #59 from paritytech/feature/check-target
Honor the target in the metadata files
2025-07-23 12:35:14 +03:00
Omar Abdulla 94afdbecc7 Fix calldata construction of single calldata 2025-07-23 10:13:31 +03:00
Omar Abdulla ce1b4862b4 Add support for wildcards in exceptions 2025-07-23 10:05:07 +03:00
Omar Abdulla 799f07f04f Fix size_requirement underflow 2025-07-23 09:54:58 +03:00
7 changed files with 88 additions and 38 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ pub struct Arguments {
/// This argument controls which private keys the nodes should have access to and be added to
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
/// of the node.
#[arg(short, long = "private-keys-count", default_value_t = 30)]
#[arg(long = "private-keys-count", default_value_t = 30)]
pub private_keys_to_add: usize,
/// The differential testing leader node implementation.
+29 -23
View File
@@ -5,7 +5,6 @@ use std::marker::PhantomData;
use alloy::json_abi::JsonAbi;
use alloy::network::{Ethereum, TransactionBuilder};
use alloy::primitives::Bytes;
use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::{
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
@@ -442,9 +441,8 @@ 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();
let deployed_contracts = self.deployed_contracts.entry(case_idx).or_default();
let chain_state_provider = node;
// Handling the receipt state assertion.
let expected = !expectation.exception;
@@ -458,17 +456,16 @@ where
// Handling the calldata assertion
if let Some(ref expected_calldata) = expectation.return_data {
let expected = expected_calldata
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
.map(Bytes::from)?;
let actual = tracing_result.output.clone().unwrap_or_default();
if !expected.starts_with(&actual) {
let expected = expected_calldata;
let actual = &tracing_result.output.as_ref().unwrap_or_default();
if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
tracing::error!(
%expected,
?execution_receipt,
?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}",);
}
}
@@ -505,17 +502,24 @@ where
}
// Handling the topics assertion.
for (expected_topic, actual_topic) in expected_event
for (expected, actual) in expected_event
.topics
.as_slice()
.iter()
.zip(actual_event.topics())
{
let expected = Calldata::Compound(vec![expected_topic.clone()])
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)?;
let actual = actual_topic.to_vec();
if actual != expected {
tracing::error!(?expected, ?actual, "Event topics assertion failed",);
let expected = Calldata::Compound(vec![expected.clone()]);
if !expected.is_equivalent(
&actual.0,
deployed_contracts,
chain_state_provider,
)? {
tracing::error!(
?execution_receipt,
?expected,
?actual,
"Event topics assertion failed",
);
anyhow::bail!(
"Event topics assertion failed - Expected {expected:?} but got {actual:?}",
);
@@ -523,13 +527,15 @@ where
}
// Handling the values assertion.
let expected = &expected_event
.values
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
.map(Bytes::from)?;
let expected = &expected_event.values;
let actual = &actual_event.data().data;
if !expected.starts_with(actual) {
tracing::error!(?expected, ?actual, "Event value assertion failed",);
if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
tracing::error!(
?execution_receipt,
?expected,
?actual,
"Event value assertion failed",
);
anyhow::bail!(
"Event value assertion failed - Expected {expected:?} but got {actual:?}",
);
+42 -4
View File
@@ -57,7 +57,7 @@ pub struct Event {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum Calldata {
Single(String),
Single(Bytes),
Compound(Vec<String>),
}
@@ -168,8 +168,8 @@ impl Calldata {
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<()> {
match self {
Calldata::Single(string) => {
alloy::hex::decode_to_slice(string, buffer)?;
Calldata::Single(bytes) => {
buffer.extend_from_slice(bytes);
}
Calldata::Compound(items) => {
for (arg_idx, arg) in items.iter().enumerate() {
@@ -190,10 +190,48 @@ impl Calldata {
pub fn size_requirement(&self) -> usize {
match self {
Calldata::Single(single) => (single.len() - 2) / 2,
Calldata::Single(single) => single.len(),
Calldata::Compound(items) => items.len() * 32,
}
}
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
pub fn is_equivalent(
&self,
other: &[u8],
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<bool> {
match self {
Calldata::Single(calldata) => Ok(calldata == other),
Calldata::Compound(items) => {
// Chunking the "other" calldata into 32 byte chunks since each
// one of the items in the compound calldata represents 32 bytes
for (this, other) in items.iter().zip(other.chunks(32)) {
// The matterlabs format supports wildcards and therefore we
// also need to support them.
if this == "*" {
continue;
}
let other = if other.len() < 32 {
let mut vec = other.to_vec();
vec.resize(32, 0);
std::borrow::Cow::Owned(vec)
} else {
std::borrow::Cow::Borrowed(other)
};
let this = resolve_argument(this, deployed_contracts, chain_state_provider)?;
let other = U256::from_be_slice(&other);
if this != other {
return Ok(false);
}
}
Ok(true)
}
}
}
}
impl Input {
+5
View File
@@ -0,0 +1,5 @@
/// This constant defines how much Wei accounts are pre-seeded with in genesis.
///
/// We use [`u128::MAX`] here which means that accounts will be given 2^128 - 1 WEI which is
/// (2^128 - 1) / 10^18 ETH.
pub const INITIAL_BALANCE: u128 = u128::MAX;
+5 -5
View File
@@ -29,7 +29,7 @@ use revive_dt_config::Arguments;
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
use tracing::Level;
use crate::{Node, common::FallbackGasFiller};
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -84,10 +84,10 @@ impl Instance {
for signer_address in
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{
genesis.alloc.entry(signer_address).or_insert(
GenesisAccount::default()
.with_balance(10000000000000000000000u128.try_into().unwrap()),
);
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
}
let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE);
serde_json::to_writer(File::create(&genesis_path)?, &genesis)?;
+5 -5
View File
@@ -39,7 +39,7 @@ use tracing::Level;
use revive_dt_config::Arguments;
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
use crate::{Node, common::FallbackGasFiller};
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -131,10 +131,10 @@ impl KitchensinkNode {
for signer_address in
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
{
genesis.alloc.entry(signer_address).or_insert(
GenesisAccount::default()
.with_balance(10000000000000000000000u128.try_into().unwrap()),
);
genesis
.alloc
.entry(signer_address)
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
}
self.extract_balance_from_genesis_file(&genesis)?
};
+1
View File
@@ -4,6 +4,7 @@ use revive_dt_config::Arguments;
use revive_dt_node_interaction::EthereumNode;
pub mod common;
pub mod constants;
pub mod geth;
pub mod kitchensink;
pub mod pool;