diff --git a/Cargo.lock b/Cargo.lock index 3d855c2..557f23b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4081,6 +4081,7 @@ dependencies = [ "revive-dt-node-interaction", "revive-dt-report", "semver 1.0.26", + "serde_json", "temp-dir", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 2441471..20948e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false, features = [ "arbitrary_precision", "std", + "unbounded_depth", ] } sha2 = { version = "0.10.9" } sp-core = "36.1.0" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 3966618..9c7d91a 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -30,4 +30,5 @@ tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } semver = { workspace = true } +serde_json = { workspace = true } temp-dir = { workspace = true } diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 435bd39..a8e908e 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -11,8 +11,8 @@ use alloy::network::{Ethereum, TransactionBuilder}; use alloy::primitives::U256; use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::trace::geth::{ - CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace, - PreStateConfig, + CallFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, GethDebugTracerType, + GethDebugTracingOptions, GethTrace, PreStateConfig, }; use alloy::{ primitives::Address, @@ -263,6 +263,11 @@ where tracer: Some(GethDebugTracerType::BuiltInTracer( GethDebugBuiltInTracerType::CallTracer, )), + tracer_config: GethDebugTracerConfig(serde_json::json! {{ + "onlyTopCall": true, + "withLog": false, + "withReturnData": false + }}), ..Default::default() }, ) @@ -528,7 +533,7 @@ where ) -> anyhow::Result<()> { let Some(instance) = balance_assertion .address - .strip_prefix(".address") + .strip_suffix(".address") .map(ContractInstance::new) else { return Ok(()); @@ -588,7 +593,7 @@ where ) -> anyhow::Result<()> { let Some(instance) = storage_empty_assertion .address - .strip_prefix(".address") + .strip_suffix(".address") .map(ContractInstance::new) else { return Ok(()); diff --git a/crates/format/src/case.rs b/crates/format/src/case.rs index ccb9394..aafe914 100644 --- a/crates/format/src/case.rs +++ b/crates/format/src/case.rs @@ -11,16 +11,22 @@ use crate::{ pub struct Case { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub modes: Option>, + #[serde(rename = "inputs")] pub steps: Vec, + #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub expected: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub ignore: Option, } diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 1b80e97..aace626 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use alloy::{ eips::BlockNumberOrTag, hex::ToHexExt, + json_abi::Function, network::TransactionBuilder, primitives::{Address, Bytes, U256}, rpc::types::TransactionRequest, @@ -36,19 +37,27 @@ pub enum Step { pub struct Input { #[serde(default = "Input::default_caller")] pub caller: Address, + #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, + #[serde(default = "Input::default_instance")] pub instance: ContractInstance, + pub method: Method, + #[serde(default)] pub calldata: Calldata, + #[serde(skip_serializing_if = "Option::is_none")] pub expected: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub storage: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub variable_assignments: Option, } @@ -271,17 +280,27 @@ impl Input { // 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. + // Overloads are handled by providing the full function signature in the "function + // name". // 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.signature().starts_with(function_name)) - .ok_or_else(|| { - anyhow::anyhow!( - "Function with name {:?} not found in ABI for the instance {:?}", - function_name, - &self.instance - ) - })?; + let selector = if function_name.contains('(') && function_name.contains(')') { + Function::parse(function_name) + .context( + "Failed to parse the provided function name into a function signature", + )? + .selector() + } else { + abi.functions() + .find(|function| function.signature().starts_with(function_name)) + .ok_or_else(|| { + anyhow::anyhow!( + "Function with name {:?} not found in ABI for the instance {:?}", + function_name, + &self.instance + ) + })? + .selector() + }; tracing::trace!("Functions found for instance: {}", self.instance.as_ref()); @@ -297,7 +316,7 @@ impl Input { // 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 + self.calldata.size_requirement()); - calldata.extend(function.selector().0); + calldata.extend(selector.0); self.calldata .calldata_into_slice(&mut calldata, resolver, context) .await?; diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index ad4fac9..7cfb5a4 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -47,23 +47,32 @@ impl Deref for MetadataFile { #[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct Metadata { + /// A comment on the test case that's added for human-readability. #[serde(skip_serializing_if = "Option::is_none")] - pub targets: Option>, - pub cases: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub contracts: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub libraries: Option>>, + pub comment: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub ignore: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub targets: Option>, + + pub cases: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + pub contracts: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub libraries: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] pub modes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub file_path: Option, - /// This field specifies an EVM version requirement that the test case has - /// where the test might be run of the evm version of the nodes match the - /// evm version specified here. + /// This field specifies an EVM version requirement that the test case has where the test might + /// be run of the evm version of the nodes match the evm version specified here. #[serde(skip_serializing_if = "Option::is_none")] pub required_evm_version: Option, } diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 9d91040..09cf6c6 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -92,7 +92,7 @@ impl GethNode { const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60); /// Create the node directory and call `geth init` to configure the genesis. - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> { let _ = clear_directory(&self.base_directory); let _ = clear_directory(&self.logs_directory); @@ -142,7 +142,7 @@ impl GethNode { /// Spawn the go-ethereum node child process. /// /// [Instance::init] must be called prior. - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] fn spawn_process(&mut self) -> anyhow::Result<&mut Self> { // This is the `OpenOptions` that we wish to use for all of the log files that we will be // opening in this method. We need to construct it in this way to: @@ -200,7 +200,7 @@ impl GethNode { /// Wait for the g-ethereum node child process getting ready. /// /// [Instance::spawn_process] must be called priorly. - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] fn wait_ready(&mut self) -> anyhow::Result<&mut Self> { let start_time = Instant::now(); @@ -269,7 +269,7 @@ impl GethNode { } impl EthereumNode for GethNode { - #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn execute_transaction( &self, transaction: TransactionRequest, @@ -325,7 +325,7 @@ impl EthereumNode for GethNode { .await } - #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn trace_transaction( &self, transaction: &TransactionReceipt, @@ -358,7 +358,7 @@ impl EthereumNode for GethNode { .await } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result { let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig { diff_mode: Some(true), @@ -375,7 +375,7 @@ impl EthereumNode for GethNode { } } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn balance_of(&self, address: Address) -> anyhow::Result { self.provider() .await? @@ -384,7 +384,7 @@ impl EthereumNode for GethNode { .map_err(Into::into) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn latest_state_proof( &self, address: Address, @@ -400,7 +400,7 @@ impl EthereumNode for GethNode { } impl ResolverApi for GethNode { - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn chain_id(&self) -> anyhow::Result { self.provider() .await? @@ -409,7 +409,7 @@ impl ResolverApi for GethNode { .map_err(Into::into) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result { self.provider() .await? @@ -419,7 +419,7 @@ impl ResolverApi for GethNode { .map(|receipt| receipt.effective_gas_price) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -429,7 +429,7 @@ impl ResolverApi for GethNode { .map(|block| block.header.gas_limit as _) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result
{ self.provider() .await? @@ -439,7 +439,7 @@ impl ResolverApi for GethNode { .map(|block| block.header.beneficiary) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -449,7 +449,7 @@ impl ResolverApi for GethNode { .map(|block| U256::from_be_bytes(block.header.mix_hash.0)) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -464,7 +464,7 @@ impl ResolverApi for GethNode { }) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -474,7 +474,7 @@ impl ResolverApi for GethNode { .map(|block| block.header.hash) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -484,7 +484,7 @@ impl ResolverApi for GethNode { .map(|block| block.header.timestamp) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] async fn last_block_number(&self) -> anyhow::Result { self.provider() .await? @@ -527,12 +527,12 @@ impl Node for GethNode { } } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] fn connection_string(&self) -> String { self.connection_string.clone() } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] fn shutdown(&mut self) -> anyhow::Result<()> { // Terminate the processes in a graceful manner to allow for the output to be flushed. if let Some(mut child) = self.handle.take() { @@ -554,13 +554,13 @@ impl Node for GethNode { Ok(()) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] fn spawn(&mut self, genesis: String) -> anyhow::Result<()> { self.init(genesis)?.spawn_process()?; Ok(()) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)] fn version(&self) -> anyhow::Result { let output = Command::new(&self.geth) .arg("--version") @@ -573,7 +573,7 @@ impl Node for GethNode { Ok(String::from_utf8_lossy(&output).into()) } - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] fn matches_target(&self, targets: Option<&[String]>) -> bool { match targets { None => true, @@ -587,7 +587,7 @@ impl Node for GethNode { } impl Drop for GethNode { - #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + #[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))] fn drop(&mut self) { self.shutdown().expect("Failed to shutdown") } diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index c5dd254..03a86ff 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -160,7 +160,7 @@ impl KitchensinkNode { Ok(self) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] fn spawn_process(&mut self) -> anyhow::Result<()> { let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16; let proxy_rpc_port = Self::BASE_PROXY_RPC_PORT + self.id as u16; @@ -258,7 +258,7 @@ impl KitchensinkNode { Ok(()) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] fn extract_balance_from_genesis_file( &self, genesis: &Genesis, @@ -307,7 +307,7 @@ impl KitchensinkNode { } } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] pub fn eth_rpc_version(&self) -> anyhow::Result { let output = Command::new(&self.eth_proxy_binary) .arg("--version") @@ -382,7 +382,7 @@ impl KitchensinkNode { } impl EthereumNode for KitchensinkNode { - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn execute_transaction( &self, transaction: alloy::rpc::types::TransactionRequest, @@ -399,7 +399,7 @@ impl EthereumNode for KitchensinkNode { Ok(receipt) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn trace_transaction( &self, transaction: &TransactionReceipt, @@ -413,7 +413,7 @@ impl EthereumNode for KitchensinkNode { .await?) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result { let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig { diff_mode: Some(true), @@ -430,7 +430,7 @@ impl EthereumNode for KitchensinkNode { } } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn balance_of(&self, address: Address) -> anyhow::Result { self.provider() .await? @@ -439,7 +439,7 @@ impl EthereumNode for KitchensinkNode { .map_err(Into::into) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn latest_state_proof( &self, address: Address, @@ -455,7 +455,7 @@ impl EthereumNode for KitchensinkNode { } impl ResolverApi for KitchensinkNode { - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn chain_id(&self) -> anyhow::Result { self.provider() .await? @@ -464,7 +464,7 @@ impl ResolverApi for KitchensinkNode { .map_err(Into::into) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result { self.provider() .await? @@ -474,7 +474,7 @@ impl ResolverApi for KitchensinkNode { .map(|receipt| receipt.effective_gas_price) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -484,7 +484,7 @@ impl ResolverApi for KitchensinkNode { .map(|block| block.header.gas_limit as _) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result
{ self.provider() .await? @@ -494,7 +494,7 @@ impl ResolverApi for KitchensinkNode { .map(|block| block.header.beneficiary) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -504,7 +504,7 @@ impl ResolverApi for KitchensinkNode { .map(|block| U256::from_be_bytes(block.header.mix_hash.0)) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -519,7 +519,7 @@ impl ResolverApi for KitchensinkNode { }) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -529,7 +529,7 @@ impl ResolverApi for KitchensinkNode { .map(|block| block.header.hash) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() .await? @@ -539,7 +539,7 @@ impl ResolverApi for KitchensinkNode { .map(|block| block.header.timestamp) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] async fn last_block_number(&self) -> anyhow::Result { self.provider() .await? @@ -587,7 +587,7 @@ impl Node for KitchensinkNode { self.rpc_url.clone() } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] fn shutdown(&mut self) -> anyhow::Result<()> { // Terminate the processes in a graceful manner to allow for the output to be flushed. if let Some(mut child) = self.process_proxy.take() { @@ -614,12 +614,12 @@ impl Node for KitchensinkNode { Ok(()) } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] fn spawn(&mut self, genesis: String) -> anyhow::Result<()> { self.init(&genesis)?.spawn_process() } - #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)] fn version(&self) -> anyhow::Result { let output = Command::new(&self.substrate_binary) .arg("--version")