From 9a71369e8ad0dcc5c3432382b761d3bed2341f1f Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Thu, 17 Jul 2025 17:46:40 +0300 Subject: [PATCH] Implement the new input handling logic --- crates/core/src/driver/mod.rs | 472 +++++++++++++++++----------------- crates/format/src/case.rs | 8 +- crates/format/src/input.rs | 255 +++++++++++------- crates/format/src/macros.rs | 2 +- crates/format/src/metadata.rs | 16 +- 5 files changed, 406 insertions(+), 347 deletions(-) diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index a3b4f15..6a8c143 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -1,5 +1,8 @@ //! The test driver handles the compilation and execution of the test cases. +use std::collections::HashMap; +use std::marker::PhantomData; + use alloy::json_abi::JsonAbi; use alloy::network::TransactionBuilder; use alloy::rpc::types::TransactionReceipt; @@ -11,30 +14,43 @@ use alloy::{ trace::geth::{AccountState, DiffMode}, }, }; -use revive_dt_compiler::{Compiler, CompilerInput, SolidityCompiler}; +use revive_dt_compiler::{Compiler, SolidityCompiler}; use revive_dt_config::Arguments; +use revive_dt_format::case::CaseIdx; +use revive_dt_format::input::Method; +use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier}; use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode}; use revive_dt_node_interaction::EthereumNode; use revive_dt_report::reporter::{CompilationTask, Report, Span}; use revive_solc_json_interface::SolcStandardJsonOutput; use serde_json::Value; -use std::collections::HashMap; use tracing::Level; use crate::Platform; use crate::common::*; -type Contracts = HashMap< - CompilerInput<<::Compiler as SolidityCompiler>::Options>, - SolcStandardJsonOutput, ->; - pub struct State<'a, T: Platform> { + /// The configuration that the framework was started with. + /// + /// This is currently used to get certain information from it such as the solc mode and other + /// information used at runtime. config: &'a Arguments, + + /// The [`Span`] used in reporting. span: Span, - contracts: Contracts, - deployed_contracts: HashMap, - deployed_abis: HashMap, + + /// A vector of all of the compiled contracts. Each call to [`build_contracts`] adds a new entry + /// to this vector. + /// + /// [`build_contracts`]: StateV2::build_contracts + contracts: Vec, + + /// This map stores the contracts deployments that have been made for each case within a + /// metadata file. Note, this means that the state can't be reused between different metadata + /// files. + deployed_contracts: HashMap>, + + phantom: PhantomData, } impl<'a, T> State<'a, T> @@ -47,7 +63,7 @@ where span, contracts: Default::default(), deployed_contracts: Default::default(), - deployed_abis: Default::default(), + phantom: Default::default(), } } @@ -91,9 +107,9 @@ where Ok(output) => { task.json_output = Some(output.output.clone()); task.error = output.error; - self.contracts.insert(output.input, output.output); + self.contracts.push(output.output); - if let Some(last_output) = self.contracts.values().last() { + if let Some(last_output) = self.contracts.last() { if let Some(contracts) = &last_output.contracts { for (file, contracts_map) in contracts { for contract_name in contracts_map.keys() { @@ -118,25 +134,186 @@ where } } - pub fn execute_input( + pub fn handle_input( &mut self, + metadata: &Metadata, + case_idx: CaseIdx, + input: &Input, + node: &T::Blockchain, + ) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> { + self.handle_contract_deployment(metadata, case_idx, input, node)?; + self.handle_input_execution(case_idx, input, node) + } + + /// Handles the contract deployment for a given input performing it if it needs to be performed. + fn handle_contract_deployment( + &mut self, + metadata: &Metadata, + case_idx: CaseIdx, + input: &Input, + node: &T::Blockchain, + ) -> anyhow::Result<()> { + let span = tracing::debug_span!( + "Handling contract deployment", + ?case_idx, + instance = ?input.instance + ); + let _guard = span.enter(); + + let mut instances_we_must_deploy = HashMap::::new(); + if let Method::Deployer = input.method { + instances_we_must_deploy.insert(input.instance.clone(), true); + } + + for instance in input.find_all_contract_instances().into_iter() { + if !self + .deployed_contracts + .entry(case_idx) + .or_default() + .contains_key(&instance) + { + instances_we_must_deploy.entry(instance).or_insert(false); + } + } + + tracing::debug!( + instances_to_deploy = instances_we_must_deploy.len(), + "Computed the number of required deployments for input" + ); + + for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() { + // What we have at this moment is just a contract instance which is kind of like a variable + // name for an actual underlying contract. So, we need to resolve this instance to the info + // of the contract that it belongs to. + let Some(ContractPathAndIdentifier { + contract_source_path, + contract_ident, + }) = metadata.contract_sources()?.remove(&instance) + else { + tracing::error!("Contract source not found for instance"); + anyhow::bail!("Contract source not found for instance {:?}", instance) + }; + + let compiled_contract = self.contracts.iter().find_map(|output| { + output + .contracts + .as_ref()? + .get(&contract_source_path.display().to_string()) + .and_then(|source_file_contracts| { + source_file_contracts.get(contract_ident.as_ref()) + }) + }); + let Some(code) = compiled_contract + .and_then(|contract| contract.evm.as_ref().and_then(|evm| evm.bytecode.as_ref())) + else { + tracing::error!( + contract_source_path = contract_source_path.display().to_string(), + contract_ident = contract_ident.as_ref(), + "Failed to find bytecode for contract" + ); + anyhow::bail!("Failed to find bytecode for contract {:?}", instance) + }; + + // TODO: When we want to do linking it would be best to do it at this stage here. We have + // the context from the metadata files and therefore know what needs to be linked and in + // what order it needs to happen. + + let mut code = match alloy::hex::decode(&code.object) { + Ok(code) => code, + Err(error) => { + tracing::error!( + ?error, + contract_source_path = contract_source_path.display().to_string(), + contract_ident = contract_ident.as_ref(), + "Failed to hex-decode byte code - This could possibly mean that the bytecode requires linking" + ); + anyhow::bail!("Failed to hex-decode the byte code {}", error) + } + }; + + if deploy_with_constructor_arguments { + let encoded_input = + input.encoded_input(self.deployed_contracts.entry(case_idx).or_default())?; + code.extend(encoded_input.to_vec()); + } + + let tx = TransactionRequest::default() + .from(input.caller) + .with_deploy_code(code); + + let receipt = match node.execute_transaction(tx) { + Ok(receipt) => receipt, + Err(error) => { + tracing::error!( + node = std::any::type_name::(), + ?error, + "Contract deployment transaction failed." + ); + return Err(error); + } + }; + + let Some(address) = receipt.contract_address else { + tracing::error!("Contract deployment transaction didn't return an address"); + anyhow::bail!("Contract deployment didn't return an address"); + }; + tracing::info!( + instance_name = ?instance, + instance_address = ?address, + "Deployed contract" + ); + + let Some(Value::String(metadata)) = + compiled_contract.and_then(|contract| contract.metadata.as_ref()) + else { + tracing::error!("Contract does not have a metadata field"); + anyhow::bail!("Contract does not have a metadata field"); + }; + + let Ok(metadata) = serde_json::from_str::(metadata) else { + tracing::error!(%metadata, "Failed to parse solc metadata into a structured value"); + anyhow::bail!("Failed to parse solc metadata into a structured value {metadata}"); + }; + + let Some(abi) = metadata.get("output").and_then(|value| value.get("abi")) else { + tracing::error!(%metadata, "Failed to access the .output.abi field of the solc metadata"); + anyhow::bail!( + "Failed to access the .output.abi field of the solc metadata {metadata}" + ); + }; + + let Ok(abi) = serde_json::from_value::(abi.clone()) else { + tracing::error!(%metadata, "Failed to deserialize ABI into a structured format"); + anyhow::bail!("Failed to deserialize ABI into a structured format {metadata}"); + }; + + self.deployed_contracts + .entry(case_idx) + .or_default() + .insert(instance.clone(), (address, abi)); + } + + Ok(()) + } + + /// Handles the execution of the input in terms of the calls that need to be made. + fn handle_input_execution( + &mut self, + case_idx: CaseIdx, input: &Input, node: &T::Blockchain, ) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> { tracing::trace!("Calling execute_input for input: {input:?}"); - let nonce = node.fetch_add_nonce(input.caller)?; - tracing::debug!( - "Nonce calculated on the execute contract, calculated nonce {}, for contract {}, having address {} on node: {}", - &nonce, - &input.instance, + "Nonce calculated on the execute contract, for contract {}, having address {} on node: {}", + &*input.instance, &input.caller, std::any::type_name::() ); let tx = - match input.legacy_transaction(nonce, &self.deployed_contracts, &self.deployed_abis) { + match input.legacy_transaction(self.deployed_contracts.entry(case_idx).or_default()) { Ok(tx) => { tracing::debug!("Legacy transaction data: {tx:#?}"); tx @@ -154,7 +331,7 @@ where Err(err) => { tracing::error!( "Failed to execute transaction when executing the contract: {}, {:?}", - &input.instance, + &*input.instance, err ); return Err(err); @@ -163,14 +340,14 @@ where tracing::trace!( "Transaction receipt for executed contract: {} - {:?}", - &input.instance, + &*input.instance, receipt, ); let trace = node.trace_transaction(receipt.clone())?; tracing::trace!( "Trace result for contract: {} - {:?}", - &input.instance, + &*input.instance, trace ); @@ -178,184 +355,6 @@ where Ok((receipt, trace, diff)) } - - pub fn deploy_contracts(&mut self, input: &Input, node: &T::Blockchain) -> anyhow::Result<()> { - let tracing_span = tracing::debug_span!( - "Deploying contracts", - ?input, - node = std::any::type_name::() - ); - let _guard = tracing_span.enter(); - - tracing::debug!(number_of_contracts_to_deploy = self.contracts.len()); - - for output in self.contracts.values() { - let Some(contract_map) = &output.contracts else { - tracing::debug!( - "No contracts in output — skipping deployment for this input {}", - &input.instance - ); - continue; - }; - - for contracts in contract_map.values() { - for (contract_name, contract) in contracts { - let tracing_span = tracing::info_span!("Deploying contract", contract_name); - let _guard = tracing_span.enter(); - - tracing::debug!( - "Contract name is: {:?} and the input name is: {:?}", - &contract_name, - &input.instance - ); - - let bytecode = contract - .evm - .as_ref() - .and_then(|evm| evm.bytecode.as_ref()) - .map(|b| b.object.clone()); - - let Some(code) = bytecode else { - tracing::error!("no bytecode for contract {contract_name}"); - continue; - }; - - let nonce = match node.fetch_add_nonce(input.caller) { - Ok(nonce) => nonce, - Err(error) => { - tracing::error!( - caller = ?input.caller, - ?error, - "Failed to get the nonce for the caller" - ); - return Err(error); - } - }; - - tracing::debug!( - "Calculated nonce {}, for contract {}, having address {} on node: {}", - &nonce, - &input.instance, - &input.caller, - std::any::type_name::() - ); - - // We are using alloy for building and submitting the transactions and it will - // automatically fill in all of the missing fields from the provider that we - // are using. - let code = match alloy::hex::decode(&code) { - Ok(code) => code, - Err(error) => { - tracing::error!( - code, - ?error, - "Failed to hex-decode the code of the contract. (This could possibly mean that it contains '_' and therefore it requires linking to be performed)" - ); - return Err(error.into()); - } - }; - let tx = TransactionRequest::default() - .nonce(nonce) - .from(input.caller) - .with_deploy_code(code); - - let receipt = match node.execute_transaction(tx) { - Ok(receipt) => receipt, - Err(err) => { - tracing::error!( - "Failed to execute transaction when deploying the contract on node : {:?}, {:?}, {:?}", - std::any::type_name::(), - &contract_name, - err - ); - return Err(err); - } - }; - - tracing::debug!( - "Deployment tx sent for {} with nonce {} → tx hash: {:?}, on node: {:?}", - contract_name, - nonce, - receipt.transaction_hash, - std::any::type_name::(), - ); - - tracing::trace!( - "Deployed transaction receipt for contract: {} - {:?}, on node: {:?}", - &contract_name, - receipt, - std::any::type_name::(), - ); - - let Some(address) = receipt.contract_address else { - tracing::error!( - "contract {contract_name} deployment did not return an address" - ); - continue; - }; - - self.deployed_contracts - .insert(contract_name.clone(), address); - tracing::trace!( - "deployed contract `{}` at {:?}, on node {:?}", - contract_name, - address, - std::any::type_name::() - ); - - if let Some(Value::String(metadata_json_str)) = &contract.metadata { - tracing::trace!( - "metadata found for contract {contract_name}, {metadata_json_str}" - ); - - match serde_json::from_str::(metadata_json_str) { - Ok(metadata_json) => { - if let Some(abi_value) = - metadata_json.get("output").and_then(|o| o.get("abi")) - { - match serde_json::from_value::(abi_value.clone()) { - Ok(parsed_abi) => { - tracing::trace!( - "ABI found in metadata for contract {}", - &contract_name - ); - self.deployed_abis - .insert(contract_name.clone(), parsed_abi); - } - Err(err) => { - anyhow::bail!( - "Failed to parse ABI from metadata for contract {}: {}", - contract_name, - err - ); - } - } - } else { - anyhow::bail!( - "No ABI found in metadata for contract {}", - contract_name - ); - } - } - Err(err) => { - anyhow::bail!( - "Failed to parse metadata JSON string for contract {}: {}", - contract_name, - err - ); - } - } - } else { - anyhow::bail!("No metadata found for contract {}", contract_name); - } - } - } - } - - tracing::debug!("Available contracts: {:?}", self.deployed_contracts.keys()); - - Ok(()) - } } pub struct Driver<'a, Leader: Platform, Follower: Platform> { @@ -429,47 +428,40 @@ where ); let _guard = tracing_span.enter(); + let case_idx = CaseIdx::from(case_idx); for input in &case.inputs { - tracing::debug!("Starting deploying contract {}", &input.instance); - if let Err(err) = leader_state.deploy_contracts(input, self.leader_node) { - tracing::error!("Leader deployment failed for {}: {err}", input.instance); - continue; - } else { - tracing::debug!("Leader deployment succeeded for {}", &input.instance); - } + tracing::debug!("Starting executing contract {}", &*input.instance); + let (leader_receipt, _, leader_diff) = match leader_state.handle_input( + self.metadata, + case_idx, + input, + self.leader_node, + ) { + Ok(result) => result, + Err(err) => { + tracing::error!( + "Leader execution failed for {}: {err}", + *input.instance + ); + continue; + } + }; - if let Err(err) = follower_state.deploy_contracts(input, self.follower_node) { - tracing::error!("Follower deployment failed for {}: {err}", input.instance); - continue; - } else { - tracing::debug!("Follower deployment succeeded for {}", &input.instance); - } - - tracing::debug!("Starting executing contract {}", &input.instance); - - let (leader_receipt, _, leader_diff) = - match leader_state.execute_input(input, self.leader_node) { - Ok(result) => result, - Err(err) => { - tracing::error!( - "Leader execution failed for {}: {err}", - input.instance - ); - continue; - } - }; - - let (follower_receipt, _, follower_diff) = - match follower_state.execute_input(input, self.follower_node) { - Ok(result) => result, - Err(err) => { - tracing::error!( - "Follower execution failed for {}: {err}", - input.instance - ); - continue; - } - }; + let (follower_receipt, _, follower_diff) = match follower_state.handle_input( + self.metadata, + case_idx, + input, + self.follower_node, + ) { + Ok(result) => result, + Err(err) => { + tracing::error!( + "Follower execution failed for {}: {err}", + *input.instance + ); + continue; + } + }; if leader_diff == follower_diff { tracing::debug!("State diffs match between leader and follower."); diff --git a/crates/format/src/case.rs b/crates/format/src/case.rs index 5516406..21b620b 100644 --- a/crates/format/src/case.rs +++ b/crates/format/src/case.rs @@ -1,6 +1,6 @@ use serde::Deserialize; -use crate::{input::Input, mode::Mode}; +use crate::{define_wrapper_type, input::Input, mode::Mode}; #[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)] pub struct Case { @@ -10,3 +10,9 @@ pub struct Case { pub inputs: Vec, pub group: Option, } + +define_wrapper_type!( + /// A wrapper type for the index of test cases found in metadata file. + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + CaseIdx(usize); +); diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 9275dca..6c3ea5c 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -10,13 +10,15 @@ use semver::VersionReq; use serde::Deserialize; use serde_json::Value; +use crate::metadata::ContractInstance; + #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] pub struct Input { #[serde(default = "default_caller")] pub caller: Address, pub comment: Option, #[serde(default = "default_instance")] - pub instance: String, + pub instance: ContractInstance, pub method: Method, pub calldata: Option, pub expected: Option, @@ -68,106 +70,137 @@ pub enum Method { FunctionName(String), } +impl Calldata { + pub fn find_all_contract_instances(&self, vec: &mut Vec) { + if let Calldata::Compound(compound) = self { + for item in compound { + if let Some(instance) = item.strip_suffix(".address") { + vec.push(ContractInstance::new_from(instance)) + } + } + } + } +} + +impl ExpectedOutput { + pub fn find_all_contract_instances(&self, vec: &mut Vec) { + if let Some(ref cd) = self.return_data { + cd.find_all_contract_instances(vec); + } + } +} + impl Input { fn instance_to_address( &self, - instance: &str, - deployed_contracts: &HashMap, + instance: &ContractInstance, + deployed_contracts: &HashMap, ) -> anyhow::Result
{ deployed_contracts .get(instance) - .copied() - .ok_or_else(|| anyhow::anyhow!("instance {instance} not deployed")) + .map(|(a, _)| *a) + .ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed")) } pub fn encoded_input( &self, - deployed_abis: &HashMap, - deployed_contracts: &HashMap, + deployed_contracts: &HashMap, ) -> anyhow::Result { - let Method::FunctionName(ref function_name) = self.method else { - return Ok(Bytes::default()); // fallback or deployer — no input - }; + match self.method { + Method::Deployer => { + let calldata_args = match &self.calldata { + Some(Calldata::Compound(args)) => args, + _ => anyhow::bail!("Expected compound calldata for function call"), + }; - let Some(abi) = deployed_abis.get(&self.instance) else { - tracing::error!( - contract_name = self.instance, - available_abis = ?deployed_abis.keys().collect::>(), - "Attempted to lookup ABI of contract but it wasn't found" - ); - anyhow::bail!("ABI for instance '{}' not found", &self.instance); - }; - - tracing::trace!("ABI found for instance: {}", &self.instance); - - // We follow the same logic that's implemented in the matter-labs-tester where they resolve - // the function name into a function selector and they assume that he function doesn't have - // any existing overloads. - // https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190 - let function = abi - .functions() - .find(|function| function.name.starts_with(function_name)) - .ok_or_else(|| { - anyhow::anyhow!( - "Function with name {:?} not found in ABI for the instance {:?}", - function_name, - &self.instance - ) - })?; - - tracing::trace!("Functions found for instance: {}", &self.instance); - - let calldata_args = match &self.calldata { - Some(Calldata::Compound(args)) => args, - _ => anyhow::bail!("Expected compound calldata for function call"), - }; - - if calldata_args.len() != function.inputs.len() { - anyhow::bail!( - "Function expects {} args, but got {}", - function.inputs.len(), - calldata_args.len() - ); - } - - tracing::trace!( - "Starting encoding ABI's parameters for instance: {}", - &self.instance - ); - - // Allocating a vector that we will be using for the calldata. The vector size will be: - // 4 bytes for the function selector. - // function.inputs.len() * 32 bytes for the arguments (each argument is a U256). - // - // We're using indices in the following code in order to avoid the need for us to allocate - // a new buffer for each one of the resolved arguments. - let mut calldata = Vec::::with_capacity(4 + calldata_args.len() * 32); - calldata.extend(function.selector().0); - - for (arg_idx, arg) in calldata_args.iter().enumerate() { - match resolve_argument(arg, deployed_contracts) { - Ok(resolved) => { - calldata.extend(resolved.to_be_bytes::<32>()); + let mut calldata = Vec::::with_capacity(calldata_args.len() * 32); + for (arg_idx, arg) in calldata_args.iter().enumerate() { + match resolve_argument(arg, deployed_contracts) { + Ok(resolved) => { + calldata.extend(resolved.to_be_bytes::<32>()); + } + Err(error) => { + tracing::error!(arg, arg_idx, ?error, "Failed to resolve argument"); + return Err(error); + } + }; } - Err(error) => { - tracing::error!(arg, arg_idx, ?error, "Failed to resolve argument"); - return Err(error); - } - }; - } - Ok(calldata.into()) + Ok(calldata.into()) + } + Method::Fallback => Ok(Default::default()), + Method::FunctionName(ref function_name) => { + let Some(abi) = deployed_contracts.get(&self.instance).map(|(_, a)| a) else { + tracing::error!( + contract_name = self.instance.as_ref(), + available_abis = ?deployed_contracts.keys().collect::>(), + "Attempted to lookup ABI of contract but it wasn't found" + ); + anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref()); + }; + + tracing::trace!("ABI found for instance: {}", &self.instance.as_ref()); + + // We follow the same logic that's implemented in the matter-labs-tester where they resolve + // the function name into a function selector and they assume that he function doesn't have + // any existing overloads. + // https://github.com/matter-labs/era-compiler-tester/blob/1dfa7d07cba0734ca97e24704f12dd57f6990c2c/compiler_tester/src/test/case/input/mod.rs#L158-L190 + let function = abi + .functions() + .find(|function| function.name.starts_with(function_name)) + .ok_or_else(|| { + anyhow::anyhow!( + "Function with name {:?} not found in ABI for the instance {:?}", + function_name, + &self.instance + ) + })?; + + tracing::trace!("Functions found for instance: {}", self.instance.as_ref()); + + let calldata_args = match &self.calldata { + Some(Calldata::Compound(args)) => args, + _ => anyhow::bail!("Expected compound calldata for function call"), + }; + + tracing::trace!( + "Starting encoding ABI's parameters for instance: {}", + self.instance.as_ref() + ); + + // Allocating a vector that we will be using for the calldata. The vector size will be: + // 4 bytes for the function selector. + // function.inputs.len() * 32 bytes for the arguments (each argument is a U256). + // + // We're using indices in the following code in order to avoid the need for us to allocate + // a new buffer for each one of the resolved arguments. + let mut calldata = Vec::::with_capacity(4 + calldata_args.len() * 32); + calldata.extend(function.selector().0); + + for (arg_idx, arg) in calldata_args.iter().enumerate() { + match resolve_argument(arg, deployed_contracts) { + Ok(resolved) => { + calldata.extend(resolved.to_be_bytes::<32>()); + } + Err(error) => { + tracing::error!(arg, arg_idx, ?error, "Failed to resolve argument"); + return Err(error); + } + }; + } + + Ok(calldata.into()) + } + } } /// Parse this input into a legacy transaction. pub fn legacy_transaction( &self, - nonce: u64, - deployed_contracts: &HashMap, - deployed_abis: &HashMap, + deployed_contracts: &HashMap, ) -> anyhow::Result { - let input_data = self.encoded_input(deployed_abis, deployed_contracts)?; - let transaction_request = TransactionRequest::default().nonce(nonce); + let input_data = self.encoded_input(deployed_contracts)?; + let transaction_request = TransactionRequest::default(); match self.method { Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)), _ => Ok(transaction_request @@ -175,10 +208,35 @@ impl Input { .input(input_data.into())), } } + + pub fn find_all_contract_instances(&self) -> Vec { + let mut vec = Vec::new(); + vec.push(self.instance.clone()); + + if let Some(ref cd) = self.calldata { + cd.find_all_contract_instances(&mut vec); + } + match &self.expected { + Some(Expected::Calldata(cd)) => { + cd.find_all_contract_instances(&mut vec); + } + Some(Expected::Expected(expected)) => { + expected.find_all_contract_instances(&mut vec); + } + Some(Expected::ExpectedMany(expected)) => { + for expected in expected { + expected.find_all_contract_instances(&mut vec); + } + } + None => {} + } + + vec + } } -fn default_instance() -> String { - "Test".to_string() +fn default_instance() -> ContractInstance { + ContractInstance::new_from("Test") } fn default_caller() -> Address { @@ -195,12 +253,13 @@ fn default_caller() -> Address { /// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 fn resolve_argument( value: &str, - deployed_contracts: &HashMap, + deployed_contracts: &HashMap, ) -> anyhow::Result { if let Some(instance) = value.strip_suffix(".address") { Ok(U256::from_be_slice( deployed_contracts - .get(instance) + .get(&ContractInstance::new_from(instance)) + .map(|(a, _)| *a) .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? .as_ref(), )) @@ -277,19 +336,19 @@ mod tests { .0; let input = Input { - instance: "Contract".to_string(), + instance: ContractInstance::new_from("Contract"), method: Method::FunctionName("store".to_owned()), calldata: Some(Calldata::Compound(vec!["42".into()])), ..Default::default() }; - let mut deployed_abis = HashMap::new(); - deployed_abis.insert("Contract".to_string(), parsed_abi); - let deployed_contracts = HashMap::new(); + let mut contracts = HashMap::new(); + contracts.insert( + ContractInstance::new_from("Contract"), + (Address::ZERO, parsed_abi), + ); - let encoded = input - .encoded_input(&deployed_abis, &deployed_contracts) - .unwrap(); + let encoded = input.encoded_input(&contracts).unwrap(); assert!(encoded.0.starts_with(&selector)); type T = (u64,); @@ -319,7 +378,7 @@ mod tests { .0; let input: Input = Input { - instance: "Contract".to_string(), + instance: ContractInstance::new_from("Contract"), method: Method::FunctionName("send".to_owned()), calldata: Some(Calldata::Compound(vec![ "0x1000000000000000000000000000000000000001".to_string(), @@ -327,11 +386,13 @@ mod tests { ..Default::default() }; - let mut abis = HashMap::new(); - abis.insert("Contract".to_string(), parsed_abi); - let contracts = HashMap::new(); + let mut contracts = HashMap::new(); + contracts.insert( + ContractInstance::new_from("Contract"), + (Address::ZERO, parsed_abi), + ); - let encoded = input.encoded_input(&abis, &contracts).unwrap(); + let encoded = input.encoded_input(&contracts).unwrap(); assert!(encoded.0.starts_with(&selector)); type T = (alloy_primitives::Address,); diff --git a/crates/format/src/macros.rs b/crates/format/src/macros.rs index ad956a4..0c4996d 100644 --- a/crates/format/src/macros.rs +++ b/crates/format/src/macros.rs @@ -42,7 +42,7 @@ macro_rules! define_wrapper_type { ( $(#[$meta: meta])* - $ident: ident => $ty: ty + $ident: ident($ty: ty) $(;)? ) => { $(#[$meta])* pub struct $ident($ty); diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index bae7fc4..abd4150 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -45,7 +45,7 @@ impl Deref for MetadataFile { #[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)] pub struct Metadata { pub cases: Vec, - pub contracts: Option>, + pub contracts: Option>, // TODO: Convert into wrapper types for clarity. pub libraries: Option>>, pub ignore: Option, @@ -84,7 +84,7 @@ impl Metadata { /// Returns the contract sources with canonicalized paths for the files pub fn contract_sources( &self, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let directory = self.directory()?; let mut sources = BTreeMap::new(); let Some(contracts) = &self.contracts else { @@ -191,7 +191,7 @@ impl Metadata { metadata.file_path = Some(path.to_path_buf()); metadata.contracts = Some( [( - ContractAlias::new_from("test"), + ContractInstance::new_from("test"), ContractPathAndIdentifier { contract_source_path: path.to_path_buf(), contract_ident: ContractIdent::new_from("Test"), @@ -213,21 +213,21 @@ impl Metadata { } define_wrapper_type!( - /// Represents a contract alias found a metadata file. + /// Represents a contract instance found a metadata file. /// /// Typically, this is used as the key to the "contracts" field of metadata files. - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] - ContractAlias => String + ContractInstance(String); ); define_wrapper_type!( /// Represents a contract identifier found a metadata file. /// /// A contract identifier is the name of the contract in the source code. - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] - ContractIdent => String + ContractIdent(String); ); /// Represents an identifier used for contracts.