Add support for wildcards in exceptions

This commit is contained in:
Omar Abdulla
2025-07-23 10:05:07 +03:00
parent 799f07f04f
commit ce1b4862b4
2 changed files with 70 additions and 33 deletions
+37 -25
View File
@@ -5,7 +5,6 @@ use std::marker::PhantomData;
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use alloy::network::{Ethereum, TransactionBuilder}; use alloy::network::{Ethereum, TransactionBuilder};
use alloy::primitives::Bytes;
use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::{ use alloy::rpc::types::trace::geth::{
DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace, PreStateConfig, DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace, PreStateConfig,
@@ -437,6 +436,9 @@ 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 deployed_contracts = self.deployed_contracts.entry(case_idx).or_default();
let chain_state_provider = node;
// 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();
@@ -454,13 +456,16 @@ where
// Handling the calldata assertion // Handling the calldata assertion
if let Some(ref expected_calldata) = expectation.return_data { if let Some(ref expected_calldata) = expectation.return_data {
let expected = expected_calldata let expected = expected_calldata;
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node) let actual = &tracing_result.return_value;
.map(Bytes::from)?; if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
let actual = tracing_result.return_value.clone(); tracing::error!(
if !expected.starts_with(&actual) { ?execution_receipt,
tracing::error!(?execution_receipt, %expected, %actual, "Calldata assertion failed"); ?expected,
anyhow::bail!("Calldata assertion failed - Expected {expected} but got {actual}",); %actual,
"Calldata assertion failed"
);
anyhow::bail!("Calldata assertion failed - Expected {expected:?} but got {actual}",);
} }
} }
@@ -503,27 +508,34 @@ where
} }
// Handling the topics assertion. // Handling the topics assertion.
let expected = expected_event.topics.as_slice(); for (expected, actual) in expected_event
let actual = actual_event.topics(); .topics
if actual != expected { .as_slice()
tracing::error!( .iter()
?execution_receipt, .zip(actual_event.topics())
?expected, {
?actual, let expected = Calldata::Compound(vec![expected.clone()]);
"Event topics assertion failed", if !expected.is_equivalent(
); &actual.0,
anyhow::bail!( deployed_contracts,
"Event topics assertion failed - Expected {expected:?} but got {actual:?}", chain_state_provider,
); )? {
tracing::error!(
?execution_receipt,
?expected,
?actual,
"Event topics assertion failed",
);
anyhow::bail!(
"Event topics assertion failed - Expected {expected:?} but got {actual:?}",
);
}
} }
// Handling the values assertion. // Handling the values assertion.
let expected = &expected_event let expected = &expected_event.values;
.values
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
.map(Bytes::from)?;
let actual = &actual_event.data().data; let actual = &actual_event.data().data;
if !expected.starts_with(actual) { if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
tracing::error!( tracing::error!(
?execution_receipt, ?execution_receipt,
?expected, ?expected,
+33 -8
View File
@@ -7,7 +7,6 @@ use alloy::{
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
rpc::types::TransactionRequest, rpc::types::TransactionRequest,
}; };
use alloy_primitives::B256;
use semver::VersionReq; use semver::VersionReq;
use serde::Deserialize; use serde::Deserialize;
@@ -50,14 +49,14 @@ pub struct ExpectedOutput {
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Event { pub struct Event {
pub address: Option<Address>, pub address: Option<Address>,
pub topics: Vec<B256>, pub topics: Vec<String>,
pub values: Calldata, pub values: Calldata,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(untagged)] #[serde(untagged)]
pub enum Calldata { pub enum Calldata {
Single(String), Single(Bytes),
Compound(Vec<String>), Compound(Vec<String>),
} }
@@ -159,14 +158,40 @@ impl Calldata {
pub fn size_requirement(&self) -> usize { pub fn size_requirement(&self) -> usize {
match self { match self {
Calldata::Single(single) => single Calldata::Single(single) => single.len(),
.len()
.checked_sub(2)
.and_then(|value| value.checked_div(2))
.unwrap_or_default(),
Calldata::Compound(items) => items.len() * 32, 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 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 { impl Input {