Compare commits

..

5 Commits

Author SHA1 Message Date
Omar Abdulla b715228c35 Add variables support 2025-07-30 11:26:34 +03:00
Omar f51693cb9f Support multiple compiler versions (#92)
* Allow for downloader to use version requirements.

We will soon add support for the compiler version requirement from the
metadata files to be honored. The compiler version is specified in the
solc modes section of the file and its specified as a `VersionReq` and
not as a version.

Therefore, we need to have the ability to honor this version requirement
and find the best version that satisfies the requirement.

* Request `VersionOrRequirement` in compiler interface

* Honor the compiler version requirement in metadata

This commit honors the compiler version requirement listed in the solc
modes of the metadata file. If this version requirement is provided then
it overrides what was passed in the CLI. Otherwise, the CLI version will
be used.

* Make compiler IO completely generic.

Before this commit, the types that were used for the compiler input and
output were the resolc compiler types which was a leaky abstraction as
we have traits to abstract the compilers away but we expose their
internal types out to other crates.

This commit did the following:
1. Made the compiler IO types fully generic so that all of the logic for
   constructing the map of compiled contracts is all done by the
   compiler implementation and not by the consuming code.
2. Changed the input types used for Solc to be the forge standard JSON
   types for Solc instead of resolc.

* Fix machete

* Add resolc to CI

* Add resolc to CI

* Add resolc to CI

* Add resolc to CI
2025-07-30 04:56:23 +00:00
James Wilson 4db7009640 Ensure path in corpus is relative to corpus file (#85) 2025-07-29 13:12:16 +00:00
Omar 5a36e242ec Allow for files in corpus definitions (#87)
* Allow for files to be specified in the corpus file

* Attempt to improve the geth tx indexing issue.

We're facing an issue where Geth transaction indexing can sometimes stall
on some of the nodes we're running. The logs show that for all transactions
we always need 1 second of waiting time. However, during certain runs we
sometimes run into an issue with some of the nodes where it seems like
their transaction indexer fails (either at the start or after some amount
of time) which leads us to never get the receipts back from these specific
nodes.

This is not a load issue as it appears like all of the other nodes handle
it just fine. However, it looks like once a node gets into this state it
can not get out of it and its bricked for the entire run.

This commit adds some more command line arguments to the geth command in
hopes of improving this issue.
2025-07-29 13:02:53 +00:00
James Wilson 33329632b5 Increase geth instantiate timeout from 2s to 5s (#86) 2025-07-29 10:34:31 +00:00
5 changed files with 273 additions and 113 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ pub struct Arguments {
pub geth: PathBuf, pub geth: PathBuf,
/// The maximum time in milliseconds to wait for geth to start. /// The maximum time in milliseconds to wait for geth to start.
#[arg(long = "geth-start-timeout", default_value = "2000")] #[arg(long = "geth-start-timeout", default_value = "5000")]
pub geth_start_timeout: u64, pub geth_start_timeout: u64,
/// The test network chain ID. /// The test network chain ID.
+123 -62
View File
@@ -4,10 +4,10 @@ use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use alloy::network::{Ethereum, TransactionBuilder}; use alloy::network::{Ethereum, TransactionBuilder};
use alloy::primitives::U256;
use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::{ use alloy::rpc::types::trace::geth::{
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace, CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
@@ -55,6 +55,10 @@ pub struct State<'a, T: Platform> {
/// files. /// files.
deployed_contracts: HashMap<CaseIdx, HashMap<ContractInstance, (Address, JsonAbi)>>, deployed_contracts: HashMap<CaseIdx, HashMap<ContractInstance, (Address, JsonAbi)>>,
/// This map stores the variables used for each one of the cases contained in the metadata
/// file.
variables: HashMap<CaseIdx, HashMap<String, U256>>,
/// This is a map of the deployed libraries. /// This is a map of the deployed libraries.
/// ///
/// This map is not per case, but rather, per metadata file. This means that we do not redeploy /// This map is not per case, but rather, per metadata file. This means that we do not redeploy
@@ -77,6 +81,7 @@ where
span, span,
compiled_contracts: Default::default(), compiled_contracts: Default::default(),
deployed_contracts: Default::default(), deployed_contracts: Default::default(),
variables: Default::default(),
deployed_libraries: Default::default(), deployed_libraries: Default::default(),
compiler_version: Default::default(), compiler_version: Default::default(),
phantom: Default::default(), phantom: Default::default(),
@@ -216,7 +221,16 @@ where
self.handle_contract_deployment(metadata, case_idx, input, node)?; self.handle_contract_deployment(metadata, case_idx, input, node)?;
let execution_receipt = let execution_receipt =
self.handle_input_execution(case_idx, input, deployment_receipts, node)?; self.handle_input_execution(case_idx, input, deployment_receipts, node)?;
self.handle_input_expectations(case_idx, input, &execution_receipt, node, mode)?; let tracing_result = self.handle_input_call_frame_tracing(&execution_receipt, node)?;
self.handle_input_variable_assignment(case_idx, input, &tracing_result)?;
self.handle_input_expectations(
case_idx,
input,
&execution_receipt,
node,
mode,
&tracing_result,
)?;
self.handle_input_diff(case_idx, execution_receipt, node) self.handle_input_diff(case_idx, execution_receipt, node)
} }
@@ -237,7 +251,12 @@ where
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new(); let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
for instance in input.find_all_contract_instances().into_iter() { for instance in input.find_all_contract_instances().into_iter() {
if !self.deployed_contracts(case_idx).contains_key(&instance) { if !self
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone())
.contains_key(&instance)
{
instances_we_must_deploy.entry(instance).or_insert(false); instances_we_must_deploy.entry(instance).or_insert(false);
} }
} }
@@ -289,7 +308,13 @@ where
.remove(&input.instance) .remove(&input.instance)
.context("Failed to find deployment receipt"), .context("Failed to find deployment receipt"),
Method::Fallback | Method::FunctionName(_) => { Method::Fallback | Method::FunctionName(_) => {
let tx = match input.legacy_transaction(self.deployed_contracts(case_idx), node) { let tx = match input.legacy_transaction(
self.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone()),
&*self.variables.entry(case_idx).or_default(),
node,
) {
Ok(tx) => { Ok(tx) => {
tracing::debug!("Legacy transaction data: {tx:#?}"); tracing::debug!("Legacy transaction data: {tx:#?}");
tx tx
@@ -317,6 +342,56 @@ where
} }
} }
fn handle_input_call_frame_tracing(
&self,
execution_receipt: &TransactionReceipt,
node: &T::Blockchain,
) -> anyhow::Result<CallFrame> {
node.trace_transaction(
execution_receipt,
GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
},
)
.map(|trace| {
trace
.try_into_call_frame()
.expect("Impossible - we requested a callframe trace so we must get it back")
})
}
fn handle_input_variable_assignment(
&mut self,
case_idx: CaseIdx,
input: &Input,
tracing_result: &CallFrame,
) -> anyhow::Result<()> {
let Some(ref assignments) = input.variable_assignments else {
return Ok(());
};
// 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),
) {
let value = U256::from_be_slice(output_word);
self.variables
.entry(case_idx)
.or_default()
.insert(variable_name.clone(), value);
}
Ok(())
}
fn handle_input_expectations( fn handle_input_expectations(
&mut self, &mut self,
case_idx: CaseIdx, case_idx: CaseIdx,
@@ -324,6 +399,7 @@ where
execution_receipt: &TransactionReceipt, execution_receipt: &TransactionReceipt,
node: &T::Blockchain, node: &T::Blockchain,
mode: &SolcMode, mode: &SolcMode,
tracing_result: &CallFrame,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let span = tracing::info_span!("Handling input expectations"); let span = tracing::info_span!("Handling input expectations");
let _guard = span.enter(); let _guard = span.enter();
@@ -356,29 +432,13 @@ where
} }
} }
// Note: we need to do assertions and checks on the output of the last call and this isn't
// available in the receipt. The only way to get this information is through tracing on the
// node.
let tracing_result = node
.trace_transaction(
execution_receipt,
GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
},
)?
.try_into_call_frame()
.expect("Impossible - we requested a callframe trace so we must get it back");
for expectation in expectations.iter() { for expectation in expectations.iter() {
self.handle_input_expectation_item( self.handle_input_expectation_item(
case_idx, case_idx,
execution_receipt, execution_receipt,
node, node,
expectation, expectation,
&tracing_result, tracing_result,
mode, mode,
)?; )?;
} }
@@ -405,14 +465,24 @@ where
} }
} }
let deployed_contracts = self.deployed_contracts(case_idx); let deployed_contracts = self
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone());
let variables = self.variables.entry(case_idx).or_default();
let chain_state_provider = node; 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();
if actual != expected { if actual != expected {
tracing::error!(expected, actual, "Transaction status assertion failed",); tracing::error!(
expected,
actual,
?execution_receipt,
?tracing_result,
"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}",
); );
@@ -422,7 +492,12 @@ where
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;
let actual = &tracing_result.output.as_ref().unwrap_or_default(); let actual = &tracing_result.output.as_ref().unwrap_or_default();
if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? { if !expected.is_equivalent(
actual,
deployed_contracts,
&*variables,
chain_state_provider,
)? {
tracing::error!( tracing::error!(
?execution_receipt, ?execution_receipt,
?expected, ?expected,
@@ -451,17 +526,12 @@ where
{ {
// Handling the emitter assertion. // Handling the emitter assertion.
if let Some(ref expected_address) = expected_event.address { if let Some(ref expected_address) = expected_event.address {
let expected = if let Some(contract_instance) = expected_address let expected = Address::from_slice(
.strip_suffix(".address") Calldata::new_compound([expected_address])
.map(ContractInstance::new) .calldata(deployed_contracts, &*variables, node)?
{ .get(12..32)
deployed_contracts .expect("Can't fail"),
.get(&contract_instance) );
.map(|(address, _)| *address)
} else {
Address::from_str(expected_address).ok()
}
.context("Failed to get the address of the event")?;
let actual = actual_event.address(); let actual = actual_event.address();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(
@@ -486,6 +556,7 @@ where
if !expected.is_equivalent( if !expected.is_equivalent(
&actual.0, &actual.0,
deployed_contracts, deployed_contracts,
&*variables,
chain_state_provider, chain_state_provider,
)? { )? {
tracing::error!( tracing::error!(
@@ -503,7 +574,12 @@ where
// Handling the values assertion. // Handling the values assertion.
let expected = &expected_event.values; let expected = &expected_event.values;
let actual = &actual_event.data().data; let actual = &actual_event.data().data;
if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? { if !expected.is_equivalent(
&actual.0,
deployed_contracts,
&*variables,
chain_state_provider,
)? {
tracing::error!( tracing::error!(
?execution_receipt, ?execution_receipt,
?expected, ?expected,
@@ -541,19 +617,6 @@ where
Ok((execution_receipt, trace, diff)) Ok((execution_receipt, trace, diff))
} }
fn deployed_contracts(
&mut self,
case_idx: impl Into<Option<CaseIdx>>,
) -> &mut HashMap<ContractInstance, (Address, JsonAbi)> {
match case_idx.into() {
Some(case_idx) => self
.deployed_contracts
.entry(case_idx)
.or_insert_with(|| self.deployed_libraries.clone()),
None => &mut self.deployed_libraries,
}
}
/// Gets the information of a deployed contract or library from the state. If it's found to not /// Gets the information of a deployed contract or library from the state. If it's found to not
/// be deployed then it will be deployed. /// be deployed then it will be deployed.
/// ///
@@ -572,17 +635,16 @@ where
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> { ) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
let case_idx = case_idx.into(); let case_idx = case_idx.into();
if let Some((address, abi)) = self.deployed_libraries.get(contract_instance) { let deployed_contracts = match case_idx {
return Ok((*address, abi.clone(), None)); Some(case_idx) => self
}
if let Some(case_idx) = case_idx {
if let Some((address, abi)) = self
.deployed_contracts .deployed_contracts
.get(&case_idx) .entry(case_idx)
.and_then(|contracts| contracts.get(contract_instance)) .or_insert_with(|| self.deployed_libraries.clone()),
{ None => &mut self.deployed_libraries,
return Ok((*address, abi.clone(), None)); };
}
if let Some((address, abi)) = deployed_contracts.get(contract_instance) {
return Ok((*address, abi.clone(), None));
} }
let Some(ContractPathAndIdent { let Some(ContractPathAndIdent {
@@ -628,7 +690,7 @@ where
}; };
if let Some(calldata) = calldata { if let Some(calldata) = calldata {
let calldata = calldata.calldata(self.deployed_contracts(case_idx), node)?; let calldata = calldata.calldata(deployed_contracts, None, node)?;
code.extend(calldata); code.extend(calldata);
} }
@@ -663,8 +725,7 @@ where
"Deployed contract" "Deployed contract"
); );
self.deployed_contracts(case_idx) deployed_contracts.insert(contract_instance.clone(), (address, abi.clone()));
.insert(contract_instance.clone(), (address, abi.clone()));
Ok((address, abi, Some(receipt))) Ok((address, abi, Some(receipt)))
} }
+53 -21
View File
@@ -17,7 +17,25 @@ impl Corpus {
/// Try to read and parse the corpus definition file at given `path`. /// Try to read and parse the corpus definition file at given `path`.
pub fn try_from_path(path: &Path) -> anyhow::Result<Self> { pub fn try_from_path(path: &Path) -> anyhow::Result<Self> {
let file = File::open(path)?; let file = File::open(path)?;
Ok(serde_json::from_reader(file)?) let mut corpus: Corpus = serde_json::from_reader(file)?;
// Ensure that the path mentioned in the corpus is relative to the corpus file.
// Canonicalizing also helps make the path in any errors unambiguous.
corpus.path = path
.parent()
.ok_or_else(|| {
anyhow::anyhow!("Corpus path '{}' does not point to a file", path.display())
})?
.canonicalize()
.map_err(|error| {
anyhow::anyhow!(
"Failed to canonicalize path to corpus '{}': {error}",
path.display()
)
})?
.join(corpus.path);
Ok(corpus)
} }
/// Scan the corpus base directory and return all tests found. /// Scan the corpus base directory and return all tests found.
@@ -35,33 +53,47 @@ impl Corpus {
/// ///
/// `path` is expected to be a directory. /// `path` is expected to be a directory.
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) { pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
let dir_entry = match std::fs::read_dir(path) { if path.is_dir() {
Ok(dir_entry) => dir_entry, let dir_entry = match std::fs::read_dir(path) {
Err(error) => { Ok(dir_entry) => dir_entry,
tracing::error!("failed to read dir '{}': {error}", path.display());
return;
}
};
for entry in dir_entry {
let entry = match entry {
Ok(entry) => entry,
Err(error) => { Err(error) => {
tracing::error!("error reading dir entry: {error}"); tracing::error!("failed to read dir '{}': {error}", path.display());
continue; return;
} }
}; };
let path = entry.path(); for entry in dir_entry {
if path.is_dir() { let entry = match entry {
collect_metadata(&path, tests); Ok(entry) => entry,
continue; Err(error) => {
} tracing::error!("error reading dir entry: {error}");
continue;
}
};
if path.is_file() { let path = entry.path();
if let Some(metadata) = MetadataFile::try_from_file(&path) { if path.is_dir() {
collect_metadata(&path, tests);
continue;
}
if path.is_file() {
if let Some(metadata) = MetadataFile::try_from_file(&path) {
tests.push(metadata)
}
}
}
} else {
let Some(extension) = path.extension() else {
tracing::error!("Failed to get file extension");
return;
};
if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") {
if let Some(metadata) = MetadataFile::try_from_file(path) {
tests.push(metadata) tests.push(metadata)
} }
} else {
tracing::error!(?extension, "Unsupported file extension");
} }
} }
} }
+88 -28
View File
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use alloy::{ use alloy::{
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
hex::ToHexExt,
json_abi::JsonAbi, json_abi::JsonAbi,
network::TransactionBuilder, network::TransactionBuilder,
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
@@ -30,6 +31,7 @@ pub struct Input {
pub expected: Option<Expected>, pub expected: Option<Expected>,
pub value: Option<EtherValue>, pub value: Option<EtherValue>,
pub storage: Option<HashMap<String, Calldata>>, pub storage: Option<HashMap<String, Calldata>>,
pub variable_assignments: Option<VariableAssignments>,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
@@ -136,6 +138,8 @@ enum Operation {
BitwiseAnd, BitwiseAnd,
BitwiseOr, BitwiseOr,
BitwiseXor, BitwiseXor,
ShiftLeft,
ShiftRight,
} }
/// Specify how the contract is called. /// Specify how the contract is called.
@@ -164,6 +168,14 @@ define_wrapper_type!(
pub struct EtherValue(U256); pub struct EtherValue(U256);
); );
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct VariableAssignments {
/// A vector of the variable names to assign to the return data.
///
/// Example: `UniswapV3PoolAddress`
pub return_data: Vec<String>,
}
impl Input { impl Input {
pub const fn default_caller() -> Address { pub const fn default_caller() -> Address {
Address(FixedBytes(alloy::hex!( Address(FixedBytes(alloy::hex!(
@@ -186,16 +198,17 @@ impl Input {
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed")) .ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
} }
pub fn encoded_input( pub fn encoded_input<'a>(
&self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
match self.method { match self.method {
Method::Deployer | Method::Fallback => { Method::Deployer | Method::Fallback => {
let calldata = self let calldata =
.calldata self.calldata
.calldata(deployed_contracts, chain_state_provider)?; .calldata(deployed_contracts, variables, chain_state_provider)?;
Ok(calldata.into()) Ok(calldata.into())
} }
@@ -244,6 +257,7 @@ impl Input {
self.calldata.calldata_into_slice( self.calldata.calldata_into_slice(
&mut calldata, &mut calldata,
deployed_contracts, deployed_contracts,
variables,
chain_state_provider, chain_state_provider,
)?; )?;
@@ -253,12 +267,13 @@ impl Input {
} }
/// Parse this input into a legacy transaction. /// Parse this input into a legacy transaction.
pub fn legacy_transaction( pub fn legacy_transaction<'a>(
&self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> 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, variables, chain_state_provider)?;
let transaction_request = TransactionRequest::default().from(self.caller).value( let transaction_request = TransactionRequest::default().from(self.caller).value(
self.value self.value
.map(|value| value.into_inner()) .map(|value| value.into_inner())
@@ -336,20 +351,27 @@ impl Calldata {
} }
} }
pub fn calldata( pub fn calldata<'a>(
&self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement()); let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?; self.calldata_into_slice(
&mut buffer,
deployed_contracts,
variables,
chain_state_provider,
)?;
Ok(buffer) Ok(buffer)
} }
pub fn calldata_into_slice( pub fn calldata_into_slice<'a>(
&self, &'a self,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
@@ -358,7 +380,7 @@ impl Calldata {
} }
Calldata::Compound(items) => { Calldata::Compound(items) => {
for (arg_idx, arg) in items.iter().enumerate() { for (arg_idx, arg) in items.iter().enumerate() {
match arg.resolve(deployed_contracts, chain_state_provider) { match arg.resolve(deployed_contracts, variables.clone(), chain_state_provider) {
Ok(resolved) => { Ok(resolved) => {
buffer.extend(resolved.to_be_bytes::<32>()); buffer.extend(resolved.to_be_bytes::<32>());
} }
@@ -381,10 +403,11 @@ impl Calldata {
} }
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes. /// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
pub fn is_equivalent( pub fn is_equivalent<'a>(
&self, &'a self,
other: &[u8], other: &[u8],
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
match self { match self {
@@ -407,7 +430,8 @@ impl Calldata {
std::borrow::Cow::Borrowed(other) std::borrow::Cow::Borrowed(other)
}; };
let this = this.resolve(deployed_contracts, chain_state_provider)?; let this =
this.resolve(deployed_contracts, variables.clone(), chain_state_provider)?;
let other = U256::from_be_slice(&other); let other = U256::from_be_slice(&other);
if this != other { if this != other {
return Ok(false); return Ok(false);
@@ -420,16 +444,17 @@ impl Calldata {
} }
impl CalldataItem { impl CalldataItem {
fn resolve( fn resolve<'a>(
&self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
let mut stack = Vec::<CalldataToken<U256>>::new(); let mut stack = Vec::<CalldataToken<U256>>::new();
for token in self for token in self
.calldata_tokens() .calldata_tokens()
.map(|token| token.resolve(deployed_contracts, chain_state_provider)) .map(|token| token.resolve(deployed_contracts, variables.clone(), chain_state_provider))
{ {
let token = token?; let token = token?;
let new_token = match token { let new_token = match token {
@@ -452,8 +477,14 @@ impl CalldataItem {
Operation::BitwiseAnd => Some(left_operand & right_operand), Operation::BitwiseAnd => Some(left_operand & right_operand),
Operation::BitwiseOr => Some(left_operand | right_operand), Operation::BitwiseOr => Some(left_operand | right_operand),
Operation::BitwiseXor => Some(left_operand ^ right_operand), Operation::BitwiseXor => Some(left_operand ^ right_operand),
Operation::ShiftLeft => {
Some(left_operand << usize::try_from(right_operand)?)
}
Operation::ShiftRight => {
Some(left_operand >> usize::try_from(right_operand)?)
}
} }
.context("Invalid calldata arithmetic operation")?; .context("Invalid calldata arithmetic operation - Invalid operation")?;
CalldataToken::Item(result) CalldataToken::Item(result)
} }
@@ -464,8 +495,17 @@ impl CalldataItem {
match stack.as_slice() { match stack.as_slice() {
// Empty stack means that we got an empty compound calldata which we resolve to zero. // Empty stack means that we got an empty compound calldata which we resolve to zero.
[] => Ok(U256::ZERO), [] => Ok(U256::ZERO),
[CalldataToken::Item(item)] => Ok(*item), [CalldataToken::Item(item)] => {
_ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation")), tracing::debug!(
original = self.0,
resolved = item.to_be_bytes::<32>().encode_hex(),
"Resolved a Calldata item"
);
Ok(*item)
}
_ => Err(anyhow::anyhow!(
"Invalid calldata arithmetic operation - Invalid stack"
)),
} }
} }
@@ -478,6 +518,8 @@ impl CalldataItem {
"&" => CalldataToken::Operation(Operation::BitwiseAnd), "&" => CalldataToken::Operation(Operation::BitwiseAnd),
"|" => CalldataToken::Operation(Operation::BitwiseOr), "|" => CalldataToken::Operation(Operation::BitwiseOr),
"^" => CalldataToken::Operation(Operation::BitwiseXor), "^" => CalldataToken::Operation(Operation::BitwiseXor),
"<<" => CalldataToken::Operation(Operation::ShiftLeft),
">>" => CalldataToken::Operation(Operation::ShiftRight),
_ => CalldataToken::Item(item), _ => CalldataToken::Item(item),
}) })
} }
@@ -494,6 +536,7 @@ impl<T> CalldataToken<T> {
const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH"; const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH";
const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER"; const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER";
const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP"; const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP";
const VARIABLE_PREFIX: &str = "$VARIABLE:";
fn into_item(self) -> Option<T> { fn into_item(self) -> Option<T> {
match self { match self {
@@ -512,9 +555,10 @@ impl<T: AsRef<str>> CalldataToken<T> {
/// This piece of code is taken from the matter-labs-tester repository which is licensed under /// This piece of code is taken from the matter-labs-tester repository which is licensed under
/// MIT or Apache. The original source code can be found here: /// MIT or Apache. The original source code can be found here:
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 /// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
fn resolve( fn resolve<'a>(
self, self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<CalldataToken<U256>> { ) -> anyhow::Result<CalldataToken<U256>> {
match self { match self {
@@ -579,6 +623,16 @@ impl<T: AsRef<str>> CalldataToken<T> {
let timestamp = let timestamp =
chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?; chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?;
Ok(U256::from(timestamp)) Ok(U256::from(timestamp))
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
let Some(variables) = variables.into() else {
anyhow::bail!(
"Variable resolution required but no variables were passed in"
);
};
let Some(variable) = variables.get(variable_name) else {
anyhow::bail!("No variable found with the name {}", variable_name)
};
Ok(*variable)
} else { } else {
Ok(U256::from_str_radix(item, 10) Ok(U256::from_str_radix(item, 10)
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?) .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?)
@@ -699,7 +753,9 @@ mod tests {
(Address::ZERO, parsed_abi), (Address::ZERO, parsed_abi),
); );
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap(); let encoded = input
.encoded_input(&contracts, None, &MockResolver)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (u64,); type T = (u64,);
@@ -741,7 +797,9 @@ mod tests {
(Address::ZERO, parsed_abi), (Address::ZERO, parsed_abi),
); );
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap(); let encoded = input
.encoded_input(&contracts, None, &MockResolver)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,); type T = (alloy_primitives::Address,);
@@ -786,7 +844,9 @@ mod tests {
(Address::ZERO, parsed_abi), (Address::ZERO, parsed_abi),
); );
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap(); let encoded = input
.encoded_input(&contracts, None, &MockResolver)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,); type T = (alloy_primitives::Address,);
@@ -802,7 +862,7 @@ mod tests {
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl ResolverApi, chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
CalldataItem::new(input).resolve(deployed_contracts, chain_state_provider) CalldataItem::new(input).resolve(deployed_contracts, None, chain_state_provider)
} }
#[test] #[test]
+8 -1
View File
@@ -152,6 +152,10 @@ impl Instance {
.arg("--nodiscover") .arg("--nodiscover")
.arg("--maxpeers") .arg("--maxpeers")
.arg("0") .arg("0")
.arg("--txlookuplimit")
.arg("0")
.arg("--cache.blocklogs")
.arg("512")
.stderr(stderr_logs_file.try_clone()?) .stderr(stderr_logs_file.try_clone()?)
.stdout(stdout_logs_file.try_clone()?) .stdout(stdout_logs_file.try_clone()?)
.spawn()? .spawn()?
@@ -294,7 +298,10 @@ impl EthereumNode for Instance {
} }
match provider.get_transaction_receipt(*transaction_hash).await { match provider.get_transaction_receipt(*transaction_hash).await {
Ok(Some(receipt)) => break Ok(receipt), Ok(Some(receipt)) => {
tracing::info!(?total_wait_duration, "Found receipt");
break Ok(receipt);
}
Ok(None) => {} Ok(None) => {}
Err(error) => { Err(error) => {
let error_string = error.to_string(); let error_string = error.to_string();