mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 21:57:58 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4afa579031 | |||
| 7ad8eca64b | |||
| a4670a0114 | |||
| 1948cb4bca | |||
| 49b414aabf | |||
| da31e66fb4 | |||
| 68be320645 | |||
| 3045468742 | |||
| 6351cbbc4f |
@@ -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 = "5000")]
|
#[arg(long = "geth-start-timeout", default_value = "2000")]
|
||||||
pub geth_start_timeout: u64,
|
pub geth_start_timeout: u64,
|
||||||
|
|
||||||
/// The test network chain ID.
|
/// The test network chain ID.
|
||||||
|
|||||||
+62
-123
@@ -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,10 +55,6 @@ 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
|
||||||
@@ -81,7 +77,6 @@ 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(),
|
||||||
@@ -221,16 +216,7 @@ 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)?;
|
||||||
let tracing_result = self.handle_input_call_frame_tracing(&execution_receipt, node)?;
|
self.handle_input_expectations(case_idx, input, &execution_receipt, node, mode)?;
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,12 +237,7 @@ 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
|
if !self.deployed_contracts(case_idx).contains_key(&instance) {
|
||||||
.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,13 +289,7 @@ 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(
|
let tx = match input.legacy_transaction(self.deployed_contracts(case_idx), node) {
|
||||||
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
|
||||||
@@ -342,56 +317,6 @@ 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,
|
||||||
@@ -399,7 +324,6 @@ 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();
|
||||||
@@ -432,13 +356,29 @@ 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,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@@ -465,24 +405,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let deployed_contracts = self
|
let deployed_contracts = self.deployed_contracts(case_idx);
|
||||||
.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!(
|
tracing::error!(expected, actual, "Transaction status assertion failed",);
|
||||||
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}",
|
||||||
);
|
);
|
||||||
@@ -492,12 +422,7 @@ 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(
|
if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
|
||||||
actual,
|
|
||||||
deployed_contracts,
|
|
||||||
&*variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)? {
|
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?execution_receipt,
|
?execution_receipt,
|
||||||
?expected,
|
?expected,
|
||||||
@@ -526,12 +451,17 @@ 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 = Address::from_slice(
|
let expected = if let Some(contract_instance) = expected_address
|
||||||
Calldata::new_compound([expected_address])
|
.strip_suffix(".address")
|
||||||
.calldata(deployed_contracts, &*variables, node)?
|
.map(ContractInstance::new)
|
||||||
.get(12..32)
|
{
|
||||||
.expect("Can't fail"),
|
deployed_contracts
|
||||||
);
|
.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!(
|
||||||
@@ -556,7 +486,6 @@ 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!(
|
||||||
@@ -574,12 +503,7 @@ 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(
|
if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
|
||||||
&actual.0,
|
|
||||||
deployed_contracts,
|
|
||||||
&*variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)? {
|
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?execution_receipt,
|
?execution_receipt,
|
||||||
?expected,
|
?expected,
|
||||||
@@ -617,6 +541,19 @@ 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.
|
||||||
///
|
///
|
||||||
@@ -635,17 +572,18 @@ 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();
|
||||||
|
|
||||||
let deployed_contracts = match case_idx {
|
if let Some((address, abi)) = self.deployed_libraries.get(contract_instance) {
|
||||||
Some(case_idx) => self
|
|
||||||
.deployed_contracts
|
|
||||||
.entry(case_idx)
|
|
||||||
.or_insert_with(|| self.deployed_libraries.clone()),
|
|
||||||
None => &mut self.deployed_libraries,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((address, abi)) = deployed_contracts.get(contract_instance) {
|
|
||||||
return Ok((*address, abi.clone(), None));
|
return Ok((*address, abi.clone(), None));
|
||||||
}
|
}
|
||||||
|
if let Some(case_idx) = case_idx {
|
||||||
|
if let Some((address, abi)) = self
|
||||||
|
.deployed_contracts
|
||||||
|
.get(&case_idx)
|
||||||
|
.and_then(|contracts| contracts.get(contract_instance))
|
||||||
|
{
|
||||||
|
return Ok((*address, abi.clone(), None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let Some(ContractPathAndIdent {
|
let Some(ContractPathAndIdent {
|
||||||
contract_source_path,
|
contract_source_path,
|
||||||
@@ -690,7 +628,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(calldata) = calldata {
|
if let Some(calldata) = calldata {
|
||||||
let calldata = calldata.calldata(deployed_contracts, None, node)?;
|
let calldata = calldata.calldata(self.deployed_contracts(case_idx), node)?;
|
||||||
code.extend(calldata);
|
code.extend(calldata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,7 +663,8 @@ where
|
|||||||
"Deployed contract"
|
"Deployed contract"
|
||||||
);
|
);
|
||||||
|
|
||||||
deployed_contracts.insert(contract_instance.clone(), (address, abi.clone()));
|
self.deployed_contracts(case_idx)
|
||||||
|
.insert(contract_instance.clone(), (address, abi.clone()));
|
||||||
|
|
||||||
Ok((address, abi, Some(receipt)))
|
Ok((address, abi, Some(receipt)))
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-54
@@ -17,25 +17,7 @@ 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)?;
|
||||||
let mut corpus: Corpus = serde_json::from_reader(file)?;
|
Ok(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.
|
||||||
@@ -53,47 +35,33 @@ 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>) {
|
||||||
if path.is_dir() {
|
let dir_entry = match std::fs::read_dir(path) {
|
||||||
let dir_entry = match std::fs::read_dir(path) {
|
Ok(dir_entry) => dir_entry,
|
||||||
Ok(dir_entry) => dir_entry,
|
Err(error) => {
|
||||||
|
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!("failed to read dir '{}': {error}", path.display());
|
tracing::error!("error reading dir entry: {error}");
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in dir_entry {
|
|
||||||
let entry = match entry {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("error reading dir entry: {error}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
|
||||||
collect_metadata(&path, tests);
|
|
||||||
continue;
|
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) {
|
let path = entry.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)
|
tests.push(metadata)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tracing::error!(?extension, "Unsupported file extension");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-88
@@ -2,7 +2,6 @@ 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},
|
||||||
@@ -31,7 +30,6 @@ 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)]
|
||||||
@@ -138,8 +136,6 @@ enum Operation {
|
|||||||
BitwiseAnd,
|
BitwiseAnd,
|
||||||
BitwiseOr,
|
BitwiseOr,
|
||||||
BitwiseXor,
|
BitwiseXor,
|
||||||
ShiftLeft,
|
|
||||||
ShiftRight,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify how the contract is called.
|
/// Specify how the contract is called.
|
||||||
@@ -168,14 +164,6 @@ 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!(
|
||||||
@@ -198,17 +186,16 @@ impl Input {
|
|||||||
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
|
.ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encoded_input<'a>(
|
pub fn encoded_input(
|
||||||
&'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<Bytes> {
|
) -> anyhow::Result<Bytes> {
|
||||||
match self.method {
|
match self.method {
|
||||||
Method::Deployer | Method::Fallback => {
|
Method::Deployer | Method::Fallback => {
|
||||||
let calldata =
|
let calldata = self
|
||||||
self.calldata
|
.calldata
|
||||||
.calldata(deployed_contracts, variables, chain_state_provider)?;
|
.calldata(deployed_contracts, chain_state_provider)?;
|
||||||
|
|
||||||
Ok(calldata.into())
|
Ok(calldata.into())
|
||||||
}
|
}
|
||||||
@@ -257,7 +244,6 @@ 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,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -267,13 +253,12 @@ impl Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse this input into a legacy transaction.
|
/// Parse this input into a legacy transaction.
|
||||||
pub fn legacy_transaction<'a>(
|
pub fn legacy_transaction(
|
||||||
&'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<TransactionRequest> {
|
) -> anyhow::Result<TransactionRequest> {
|
||||||
let input_data = self.encoded_input(deployed_contracts, variables, chain_state_provider)?;
|
let input_data = self.encoded_input(deployed_contracts, 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())
|
||||||
@@ -351,27 +336,20 @@ impl Calldata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calldata<'a>(
|
pub fn calldata(
|
||||||
&'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<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(
|
self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?;
|
||||||
&mut buffer,
|
|
||||||
deployed_contracts,
|
|
||||||
variables,
|
|
||||||
chain_state_provider,
|
|
||||||
)?;
|
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calldata_into_slice<'a>(
|
pub fn calldata_into_slice(
|
||||||
&'a self,
|
&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 {
|
||||||
@@ -380,7 +358,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, variables.clone(), chain_state_provider) {
|
match arg.resolve(deployed_contracts, chain_state_provider) {
|
||||||
Ok(resolved) => {
|
Ok(resolved) => {
|
||||||
buffer.extend(resolved.to_be_bytes::<32>());
|
buffer.extend(resolved.to_be_bytes::<32>());
|
||||||
}
|
}
|
||||||
@@ -403,11 +381,10 @@ 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<'a>(
|
pub fn is_equivalent(
|
||||||
&'a self,
|
&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 {
|
||||||
@@ -430,8 +407,7 @@ impl Calldata {
|
|||||||
std::borrow::Cow::Borrowed(other)
|
std::borrow::Cow::Borrowed(other)
|
||||||
};
|
};
|
||||||
|
|
||||||
let this =
|
let this = this.resolve(deployed_contracts, chain_state_provider)?;
|
||||||
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);
|
||||||
@@ -444,17 +420,16 @@ impl Calldata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CalldataItem {
|
impl CalldataItem {
|
||||||
fn resolve<'a>(
|
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<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, variables.clone(), chain_state_provider))
|
.map(|token| token.resolve(deployed_contracts, chain_state_provider))
|
||||||
{
|
{
|
||||||
let token = token?;
|
let token = token?;
|
||||||
let new_token = match token {
|
let new_token = match token {
|
||||||
@@ -477,14 +452,8 @@ 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 - Invalid operation")?;
|
.context("Invalid calldata arithmetic operation")?;
|
||||||
|
|
||||||
CalldataToken::Item(result)
|
CalldataToken::Item(result)
|
||||||
}
|
}
|
||||||
@@ -495,17 +464,8 @@ 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)] => {
|
[CalldataToken::Item(item)] => Ok(*item),
|
||||||
tracing::debug!(
|
_ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation")),
|
||||||
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"
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,8 +478,6 @@ 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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -536,7 +494,6 @@ 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 {
|
||||||
@@ -555,10 +512,9 @@ 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<'a>(
|
fn resolve(
|
||||||
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 {
|
||||||
@@ -623,16 +579,6 @@ 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))?)
|
||||||
@@ -753,9 +699,7 @@ mod tests {
|
|||||||
(Address::ZERO, parsed_abi),
|
(Address::ZERO, parsed_abi),
|
||||||
);
|
);
|
||||||
|
|
||||||
let encoded = input
|
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
|
||||||
.encoded_input(&contracts, None, &MockResolver)
|
|
||||||
.unwrap();
|
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
type T = (u64,);
|
type T = (u64,);
|
||||||
@@ -797,9 +741,7 @@ mod tests {
|
|||||||
(Address::ZERO, parsed_abi),
|
(Address::ZERO, parsed_abi),
|
||||||
);
|
);
|
||||||
|
|
||||||
let encoded = input
|
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
|
||||||
.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,);
|
||||||
@@ -844,9 +786,7 @@ mod tests {
|
|||||||
(Address::ZERO, parsed_abi),
|
(Address::ZERO, parsed_abi),
|
||||||
);
|
);
|
||||||
|
|
||||||
let encoded = input
|
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
|
||||||
.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,);
|
||||||
@@ -862,7 +802,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, None, chain_state_provider)
|
CalldataItem::new(input).resolve(deployed_contracts, chain_state_provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -152,10 +152,6 @@ 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()?
|
||||||
@@ -298,10 +294,7 @@ impl EthereumNode for Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match provider.get_transaction_receipt(*transaction_hash).await {
|
match provider.get_transaction_receipt(*transaction_hash).await {
|
||||||
Ok(Some(receipt)) => {
|
Ok(Some(receipt)) => break Ok(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();
|
||||||
|
|||||||
Reference in New Issue
Block a user