From 48ed8db4db5d93ad0cf38936c5b9c94496e30f79 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Thu, 3 Jul 2025 15:12:23 +0300 Subject: [PATCH 1/9] Parsed ABI field in order to get method parameter --- crates/core/Cargo.toml | 1 + crates/core/src/driver/mod.rs | 83 ++++++++++++++++++++++++++++------ crates/format/src/input.rs | 84 +++++++++++++++++++++++++++++++++-- 3 files changed, 150 insertions(+), 18 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 7d2f9467..9cdac1c6 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -27,4 +27,5 @@ log = { workspace = true } env_logger = { workspace = true } rayon = { workspace = true } revive-solc-json-interface = { 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 3e5ce55f..d4583a58 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -1,12 +1,13 @@ //! The test driver handles the compilation and execution of the test cases. +use alloy::json_abi::JsonAbi; use alloy::primitives::Bytes; use alloy::rpc::types::TransactionInput; use alloy::{ primitives::{Address, TxKind, map::HashMap}, rpc::types::{ - TransactionReceipt, TransactionRequest, - trace::geth::{AccountState, DiffMode, GethTrace}, + TransactionRequest, + trace::geth::{AccountState, DiffMode}, }, }; use revive_dt_compiler::{Compiler, CompilerInput, SolidityCompiler}; @@ -15,6 +16,7 @@ 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 crate::Platform; @@ -28,6 +30,7 @@ pub struct State<'a, T: Platform> { span: Span, contracts: Contracts, deployed_contracts: HashMap, + deployed_abis: HashMap, } impl<'a, T> State<'a, T> @@ -40,6 +43,7 @@ where span, contracts: Default::default(), deployed_contracts: Default::default(), + deployed_abis: Default::default(), } } @@ -126,15 +130,21 @@ where std::any::type_name::() ); - let tx = - match input.legacy_transaction(self.config.network_id, nonce, &self.deployed_contracts) - { - Ok(tx) => tx, - Err(err) => { - log::error!("Failed to construct legacy transaction: {err:?}"); - return Err(err); - } - }; + let tx = match input.legacy_transaction( + self.config.network_id, + nonce, + &self.deployed_contracts, + &self.deployed_abis, + ) { + Ok(tx) => { + log::debug!("Legacy transaction data: {:#?}", tx); + tx + } + Err(err) => { + log::error!("Failed to construct legacy transaction: {err:?}"); + return Err(err); + } + }; log::trace!("Executing transaction for input: {input:?}"); @@ -191,9 +201,6 @@ where &contract_name, &input.instance ); - if contract_name != &input.instance { - continue; - } let bytecode = contract .evm @@ -270,6 +277,54 @@ where address, std::any::type_name::() ); + + if let Some(Value::String(metadata_json_str)) = &contract.metadata { + log::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) => { + log::trace!( + "ABI found in metadata for contract {}", + &contract_name + ); + self.deployed_abis + .insert(contract_name.clone(), parsed_abi); + } + Err(err) => { + log::debug!( + "Failed to parse ABI from metadata for {}: {}", + contract_name, + err + ); + } + } + } else { + log::debug!( + "No ABI found in metadata for contract {}", + contract_name + ); + } + } + Err(err) => { + log::debug!( + "Failed to parse metadata JSON string for contract {}: {}", + contract_name, + err + ); + } + } + } else { + log::debug!("No metadata found for contract {}", contract_name); + } } } } diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index a9d35624..b88e136f 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; use alloy::{ - json_abi::Function, - primitives::{Address, TxKind}, - rpc::types::TransactionRequest, + hex, + json_abi::{Function, JsonAbi}, + primitives::{Address, Bytes, TxKind}, + rpc::types::{TransactionInput, TransactionRequest}, }; use semver::VersionReq; use serde::{Deserialize, de::Deserializer}; @@ -44,7 +45,14 @@ pub struct ExpectedOutput { #[serde(untagged)] pub enum Calldata { Single(String), - Compound(Vec), + Compound(Vec), +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum CalldataArg { + Literal(String), + AddressRef(String), // will be "Contract.address" } /// Specify how the contract is called. @@ -102,12 +110,77 @@ impl Input { .ok_or_else(|| anyhow::anyhow!("instance {instance} not deployed")) } + pub fn encoded_input( + &self, + deployed_abis: &HashMap, + deployed_contracts: &HashMap, + ) -> anyhow::Result { + let Method::Function(selector) = self.method else { + return Ok(Bytes::default()); // fallback or deployer — no input + }; + + // ABI + let abi = deployed_abis + .get(&self.instance) + .ok_or_else(|| anyhow::anyhow!("ABI for instance '{}' not found", &self.instance))?; + + // Find function by selector + let function = abi + .functions() + .find(|f| f.selector().0 == selector) + .ok_or_else(|| { + anyhow::anyhow!( + "Function with selector {:?} not found in ABI for the instance {:?}", + selector, + &self.instance + ) + })?; + + // Parse calldata + let calldata_args = match &self.calldata { + Some(Calldata::Compound(args)) => args, + _ => anyhow::bail!("Expected compound calldata for function call"), + }; + + // Convert each argument + let mut tokens = Vec::new(); + for (i, param) in function.inputs.iter().enumerate() { + let arg = calldata_args + .get(i) + .ok_or_else(|| anyhow::anyhow!("Missing calldata argument {}", i))?; + let token = match arg { + CalldataArg::Literal(value) => match param.ty.to_string().as_str() { + "uint256" | "uint" => Token::Uint(value.parse()?), + "address" => Token::Address(value.parse()?), + _ => anyhow::bail!("Unsupported literal type {}", param.ty), + }, + CalldataArg::AddressRef(name) => { + let addr = if name.ends_with(".address") { + let contract_name = name.trim_end_matches(".address"); + deployed_contracts.get(contract_name).copied() + } else { + None + }; + Token::Address( + addr.ok_or_else(|| anyhow::anyhow!("Address for '{}' not found", name))?, + ) + } + }; + tokens.push(token); + } + + // Encode + let encoded = function.encode_input(&tokens)?; + Ok(Bytes::from(encoded)) + } + /// Parse this input into a legacy transaction. pub fn legacy_transaction( &self, chain_id: u64, nonce: u64, deployed_contracts: &HashMap, + deployed_abis: &HashMap, ) -> anyhow::Result { let to = match self.method { Method::Deployer => Some(TxKind::Create), @@ -116,6 +189,8 @@ impl Input { )), }; + let input_data = self.encoded_input(deployed_abis, deployed_contracts)?; + Ok(TransactionRequest { from: Some(self.caller), to, @@ -123,6 +198,7 @@ impl Input { chain_id: Some(chain_id), gas_price: Some(5_000_000), gas: Some(5_000_000), + input: TransactionInput::new(input_data), ..Default::default() }) } From a0279f0c0c857a18350dcb30f8a57ac323a9114c Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Fri, 4 Jul 2025 12:09:21 +0300 Subject: [PATCH 2/9] Added logic for ABI --- crates/core/src/driver/mod.rs | 8 ++-- crates/format/src/input.rs | 79 ++++++++++++++++++++++++---------- crates/node/src/geth.rs | 7 ++- crates/node/src/kitchensink.rs | 7 ++- 4 files changed, 72 insertions(+), 29 deletions(-) diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index d4583a58..581457a5 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -2,7 +2,8 @@ use alloy::json_abi::JsonAbi; use alloy::primitives::Bytes; -use alloy::rpc::types::TransactionInput; +use alloy::rpc::types::trace::geth::GethTrace; +use alloy::rpc::types::{TransactionInput, TransactionReceipt}; use alloy::{ primitives::{Address, TxKind, map::HashMap}, rpc::types::{ @@ -17,6 +18,7 @@ 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 as StdHashMap; use crate::Platform; @@ -29,8 +31,8 @@ pub struct State<'a, T: Platform> { config: &'a Arguments, span: Span, contracts: Contracts, - deployed_contracts: HashMap, - deployed_abis: HashMap, + deployed_contracts: StdHashMap, + deployed_abis: StdHashMap, } impl<'a, T> State<'a, T> diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index b88e136f..f1ad8f46 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -6,6 +6,8 @@ use alloy::{ primitives::{Address, Bytes, TxKind}, rpc::types::{TransactionInput, TransactionRequest}, }; +use alloy_primitives::U256; +use alloy_sol_types::SolValue; use semver::VersionReq; use serde::{Deserialize, de::Deserializer}; use serde_json::Value; @@ -142,35 +144,68 @@ impl Input { _ => anyhow::bail!("Expected compound calldata for function call"), }; - // Convert each argument - let mut tokens = Vec::new(); + if calldata_args.len() != function.inputs.len() { + anyhow::bail!( + "Function expects {} args, but got {}", + function.inputs.len(), + calldata_args.len() + ); + } + + let mut encoded = selector.to_vec(); + for (i, param) in function.inputs.iter().enumerate() { - let arg = calldata_args - .get(i) - .ok_or_else(|| anyhow::anyhow!("Missing calldata argument {}", i))?; - let token = match arg { - CalldataArg::Literal(value) => match param.ty.to_string().as_str() { - "uint256" | "uint" => Token::Uint(value.parse()?), - "address" => Token::Address(value.parse()?), - _ => anyhow::bail!("Unsupported literal type {}", param.ty), + let arg = calldata_args.get(i).unwrap(); + let encoded_arg = match arg { + CalldataArg::Literal(value) => match param.ty.as_str() { + "uint256" | "uint" => { + let val: U256 = value.parse()?; + val.abi_encode() + } + "uint24" => { + let val: u32 = value.parse()?; + (val & 0xFFFFFF).abi_encode() + } + "bool" => { + let val: bool = value.parse()?; + val.abi_encode() + } + "address" => { + let addr: Address = value.parse()?; + addr.abi_encode() + } + "string" => value.abi_encode(), + "bytes32" => { + let val = hex::decode(value.trim_start_matches("0x"))?; + let mut fixed = [0u8; 32]; + fixed[..val.len()].copy_from_slice(&val); + fixed.abi_encode() + } + "uint256[]" | "uint[]" => { + let nums: Vec = serde_json::from_str(value)?; + nums.abi_encode() + } + "bytes" => { + let val = hex::decode(value.trim_start_matches("0x"))?; + val.abi_encode() + } + _ => anyhow::bail!("Unsupported type: {}", param.ty), }, CalldataArg::AddressRef(name) => { - let addr = if name.ends_with(".address") { - let contract_name = name.trim_end_matches(".address"); - deployed_contracts.get(contract_name).copied() - } else { - None - }; - Token::Address( - addr.ok_or_else(|| anyhow::anyhow!("Address for '{}' not found", name))?, - ) + let contract_name = name.trim_end_matches(".address"); + let addr = deployed_contracts + .get(contract_name) + .copied() + .ok_or_else(|| { + anyhow::anyhow!("Address for '{}' not found", contract_name) + })?; + addr.abi_encode() } }; - tokens.push(token); + + encoded.extend(encoded_arg); } - // Encode - let encoded = function.encode_input(&tokens)?; Ok(Bytes::from(encoded)) } diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index f35dea5f..29c2b667 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -1,6 +1,7 @@ //! The go-ethereum node implementation. use std::{ + collections::HashMap as StdHashMap, fs::{File, create_dir_all, remove_dir_all}, io::{BufRead, BufReader, Read, Write}, path::PathBuf, @@ -50,7 +51,7 @@ pub struct Instance { network_id: u64, start_timeout: u64, wallet: EthereumWallet, - nonces: Mutex>, + nonces: Mutex>, } impl Instance { @@ -158,6 +159,8 @@ impl EthereumNode for Instance { let connection_string = self.connection_string(); let wallet = self.wallet.clone(); + log::debug!("Submitting transaction: {:#?}", transaction); + execute_transaction(Box::pin(async move { Ok(ProviderBuilder::new() .wallet(wallet) @@ -235,7 +238,7 @@ impl Node for Instance { network_id: config.network_id, start_timeout: config.geth_start_timeout, wallet: config.wallet(), - nonces: Mutex::new(HashMap::new()), + nonces: Mutex::new(StdHashMap::new()), } } diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index 0285d241..d9fa8863 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap as StdHashMap, fs::create_dir_all, io::BufRead, path::PathBuf, @@ -44,7 +45,7 @@ pub struct KitchensinkNode { base_directory: PathBuf, process_substrate: Option, process_proxy: Option, - nonces: Mutex>, + nonces: Mutex>, } impl KitchensinkNode { @@ -251,6 +252,8 @@ impl EthereumNode for KitchensinkNode { let url = self.rpc_url.clone(); let wallet = self.wallet.clone(); + log::debug!("Submitting transaction: {:#?}", transaction); + execute_transaction(Box::pin(async move { Ok(ProviderBuilder::new() .wallet(wallet) @@ -325,7 +328,7 @@ impl Node for KitchensinkNode { base_directory, process_substrate: None, process_proxy: None, - nonces: Mutex::new(HashMap::new()), + nonces: Mutex::new(StdHashMap::new()), } } From c5ab27ed8292cdec05abe07e5003c72bf23b83ab Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Fri, 4 Jul 2025 12:37:56 +0300 Subject: [PATCH 3/9] Refactored dependencies --- Cargo.lock | 35 ++++++++++++++++++---------------- Cargo.toml | 2 ++ crates/format/Cargo.toml | 2 ++ crates/format/src/input.rs | 6 ++++++ crates/node/src/geth.rs | 2 +- crates/node/src/kitchensink.rs | 2 +- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ef31e22..dd3b36e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "15516116086325c157c18261d768a20677f0f699348000ed391d4ad0dcb82530" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "6177ed26655d4e84e00b65cb494d4e0b8830e7cae7ef5d63087d445a2600fb55" dependencies = [ "alloy-rlp", "bytes", @@ -575,9 +575,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "a14f21d053aea4c6630687c2f4ad614bed4c81e14737a9b904798b24f30ea849" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -589,9 +589,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "34d99282e7c9ef14eb62727981a985a01869e586d1dec729d3bb33679094c100" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -608,9 +608,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "eda029f955b78e493360ee1d7bd11e1ab9f2a220a5715449babc79d6d0a01105" dependencies = [ "alloy-json-abi", "const-hex", @@ -626,9 +626,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "10db1bd7baa35bc8d4a1b07efbf734e73e5ba09f2580fb8cee3483a36087ceb2" dependencies = [ "serde", "winnow", @@ -636,9 +636,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "58377025a47d8b8426b3e4846a251f2c1991033b27f517aade368146f6ab1dfe" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4034,6 +4034,7 @@ dependencies = [ "revive-dt-node-interaction", "revive-dt-report", "revive-solc-json-interface", + "serde_json", "temp-dir", ] @@ -4042,6 +4043,8 @@ name = "revive-dt-format" version = "0.1.0" dependencies = [ "alloy", + "alloy-primitives", + "alloy-sol-types", "anyhow", "log", "semver 1.0.26", @@ -5143,9 +5146,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "b9ac494e7266fcdd2ad80bf4375d55d27a117ea5c866c26d0e97fe5b3caeeb75" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 597ef15a..9e7f8736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ revive-dt-node-pool = { version = "0.1.0", path = "crates/node-pool" } revive-dt-report = { version = "0.1.0", path = "crates/report" } revive-dt-solc-binaries = { version = "0.1.0", path = "crates/solc-binaries" } +alloy-primitives = "1.2.1" +alloy-sol-types = "1.2.1" anyhow = "1.0" clap = { version = "4", features = ["derive"] } env_logger = "0.11.8" diff --git a/crates/format/Cargo.toml b/crates/format/Cargo.toml index d8fc3452..e0db5ccf 100644 --- a/crates/format/Cargo.toml +++ b/crates/format/Cargo.toml @@ -10,6 +10,8 @@ rust-version.workspace = true [dependencies] alloy = { workspace = true } +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } anyhow = { workspace = true } log = { workspace = true } semver = { workspace = true } diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index f1ad8f46..61778305 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -126,6 +126,8 @@ impl Input { .get(&self.instance) .ok_or_else(|| anyhow::anyhow!("ABI for instance '{}' not found", &self.instance))?; + log::trace!("ABI found for instance: {}", &self.instance); + // Find function by selector let function = abi .functions() @@ -138,6 +140,8 @@ impl Input { ) })?; + log::trace!("Functions found for instance: {}", &self.instance); + // Parse calldata let calldata_args = match &self.calldata { Some(Calldata::Compound(args)) => args, @@ -152,6 +156,8 @@ impl Input { ); } + log::trace!("Starting encoding ABI's paramters for instance: {}", &self.instance); + let mut encoded = selector.to_vec(); for (i, param) in function.inputs.iter().enumerate() { diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 29c2b667..673021e0 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -16,7 +16,7 @@ use std::{ use alloy::{ network::EthereumWallet, - primitives::{Address, map::HashMap}, + primitives::{Address}, providers::{Provider, ProviderBuilder, ext::DebugApi}, rpc::types::{ TransactionReceipt, TransactionRequest, diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index d9fa8863..3bb3ad1a 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -14,7 +14,7 @@ use std::{ use alloy::{ hex, network::EthereumWallet, - primitives::{Address, map::HashMap}, + primitives::{Address}, providers::{Provider, ProviderBuilder, ext::DebugApi}, rpc::types::{ TransactionReceipt, From 9d2084834c27177d01bc40373e8482354d7feaa8 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Mon, 7 Jul 2025 12:28:05 +0300 Subject: [PATCH 4/9] Small refactoring --- crates/node/src/geth.rs | 8 ++++---- crates/node/src/kitchensink.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 673021e0..bf45c37e 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -1,7 +1,7 @@ //! The go-ethereum node implementation. use std::{ - collections::HashMap as StdHashMap, + collections::HashMap, fs::{File, create_dir_all, remove_dir_all}, io::{BufRead, BufReader, Read, Write}, path::PathBuf, @@ -16,7 +16,7 @@ use std::{ use alloy::{ network::EthereumWallet, - primitives::{Address}, + primitives::Address, providers::{Provider, ProviderBuilder, ext::DebugApi}, rpc::types::{ TransactionReceipt, TransactionRequest, @@ -51,7 +51,7 @@ pub struct Instance { network_id: u64, start_timeout: u64, wallet: EthereumWallet, - nonces: Mutex>, + nonces: Mutex>, } impl Instance { @@ -238,7 +238,7 @@ impl Node for Instance { network_id: config.network_id, start_timeout: config.geth_start_timeout, wallet: config.wallet(), - nonces: Mutex::new(StdHashMap::new()), + nonces: Mutex::new(HashMap::new()), } } diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index 3bb3ad1a..321637b2 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap as StdHashMap, + collections::HashMap, fs::create_dir_all, io::BufRead, path::PathBuf, @@ -14,7 +14,7 @@ use std::{ use alloy::{ hex, network::EthereumWallet, - primitives::{Address}, + primitives::Address, providers::{Provider, ProviderBuilder, ext::DebugApi}, rpc::types::{ TransactionReceipt, @@ -45,7 +45,7 @@ pub struct KitchensinkNode { base_directory: PathBuf, process_substrate: Option, process_proxy: Option, - nonces: Mutex>, + nonces: Mutex>, } impl KitchensinkNode { @@ -328,7 +328,7 @@ impl Node for KitchensinkNode { base_directory, process_substrate: None, process_proxy: None, - nonces: Mutex::new(StdHashMap::new()), + nonces: Mutex::new(HashMap::new()), } } From 36acad927d6932573e3cc8eb8bd86ab05f957514 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Tue, 8 Jul 2025 15:59:49 +0300 Subject: [PATCH 5/9] Added unit tests for ABI parameter extraction logic --- crates/core/src/driver/mod.rs | 41 +++++-- crates/format/src/input.rs | 205 +++++++++++++++++++++++++++++++++- 2 files changed, 236 insertions(+), 10 deletions(-) diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 581457a5..1327da8d 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -325,7 +325,7 @@ where } } } else { - log::debug!("No metadata found for contract {}", contract_name); + anyhow::bail!("No metadata found for contract {}", contract_name); } } } @@ -400,14 +400,41 @@ where for case in &self.metadata.cases { for input in &case.inputs { log::debug!("Starting deploying contract {}", &input.instance); - leader_state.deploy_contracts(input, self.leader_node)?; - follower_state.deploy_contracts(input, self.follower_node)?; + if let Err(err) = leader_state.deploy_contracts(input, self.leader_node) { + log::error!("Leader deployment failed for {}: {err}", input.instance); + continue; + } else { + log::debug!("Leader deployment succeeded for {}", &input.instance); + } + + if let Err(err) = follower_state.deploy_contracts(input, self.follower_node) { + log::error!("Follower deployment failed for {}: {err}", input.instance); + continue; + } else { + log::debug!("Follower deployment succeeded for {}", &input.instance); + } log::debug!("Starting executing contract {}", &input.instance); - let (leader_receipt, _, leader_diff) = - leader_state.execute_input(input, self.leader_node)?; - let (follower_receipt, _, follower_diff) = - follower_state.execute_input(input, self.follower_node)?; + + let (leader_receipt, _, leader_diff) = match leader_state + .execute_input(input, self.leader_node) + { + Ok(result) => result, + Err(err) => { + log::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) => { + log::error!("Follower execution failed for {}: {err}", input.instance); + continue; + } + }; if leader_diff == follower_diff { log::debug!("State diffs match between leader and follower."); diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 61778305..4d048774 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -121,7 +121,6 @@ impl Input { return Ok(Bytes::default()); // fallback or deployer — no input }; - // ABI let abi = deployed_abis .get(&self.instance) .ok_or_else(|| anyhow::anyhow!("ABI for instance '{}' not found", &self.instance))?; @@ -142,7 +141,6 @@ impl Input { log::trace!("Functions found for instance: {}", &self.instance); - // Parse calldata let calldata_args = match &self.calldata { Some(Calldata::Compound(args)) => args, _ => anyhow::bail!("Expected compound calldata for function call"), @@ -156,7 +154,10 @@ impl Input { ); } - log::trace!("Starting encoding ABI's paramters for instance: {}", &self.instance); + log::trace!( + "Starting encoding ABI's parameters for instance: {}", + &self.instance + ); let mut encoded = selector.to_vec(); @@ -252,3 +253,201 @@ fn default_instance() -> String { fn default_caller() -> Address { "90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap() } + +#[cfg(test)] +mod tests { + + use super::*; + use alloy::json_abi::JsonAbi; + use alloy_primitives::{address, keccak256}; + use std::collections::HashMap; + + #[test] + fn test_encoded_input_uint256() { + let raw_metadata = r#" + [ + { + "inputs": [{"name": "value", "type": "uint256"}], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] + "#; + + let parsed_abi: JsonAbi = serde_json::from_str(raw_metadata).unwrap(); + let selector = keccak256("store(uint256)".as_bytes())[0..4] + .try_into() + .unwrap(); + + let input = Input { + instance: "Contract".to_string(), + method: Method::Function(selector), + calldata: Some(Calldata::Compound(vec![CalldataArg::Literal( + "42".to_string(), + )])), + ..Default::default() + }; + + let mut deployed_abis = HashMap::new(); + deployed_abis.insert("Contract".to_string(), parsed_abi); + let deployed_contracts = HashMap::new(); + + let encoded = input + .encoded_input(&deployed_abis, &deployed_contracts) + .unwrap(); + assert!(encoded.0.starts_with(&selector)); + + type T = (u64,); + let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap(); + assert_eq!(decoded.0, 42); + } + + #[test] + fn test_encoded_input_bool() { + let raw_abi = r#"[ + { + "inputs": [{"name": "flag", "type": "bool"}], + "name": "toggle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap(); + let selector = keccak256("toggle(bool)".as_bytes())[0..4] + .try_into() + .unwrap(); + + let input = Input { + instance: "Contract".to_string(), + method: Method::Function(selector), + calldata: Some(Calldata::Compound(vec![CalldataArg::Literal( + "true".to_string(), + )])), + ..Default::default() + }; + + let mut abis = HashMap::new(); + abis.insert("Contract".to_string(), parsed_abi); + let contracts = HashMap::new(); + + let encoded = input.encoded_input(&abis, &contracts).unwrap(); + assert!(encoded.0.starts_with(&selector)); + + type T = (bool,); + let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap(); + assert_eq!(decoded.0, true); + } + + #[test] + fn test_encoded_input_string() { + let raw_abi = r#"[ + { + "inputs": [{"name": "msg", "type": "string"}], + "name": "echo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap(); + let selector = keccak256("echo(string)".as_bytes())[0..4] + .try_into() + .unwrap(); + + let input = Input { + instance: "Contract".to_string(), + method: Method::Function(selector), + calldata: Some(Calldata::Compound(vec![CalldataArg::Literal( + "hello".to_string(), + )])), + ..Default::default() + }; + + let mut abis = HashMap::new(); + abis.insert("Contract".to_string(), parsed_abi); + let contracts = HashMap::new(); + + let encoded = input.encoded_input(&abis, &contracts).unwrap(); + assert!(encoded.0.starts_with(&selector)); + } + + #[test] + fn test_encoded_input_uint256_array() { + let raw_abi = r#"[ + { + "inputs": [{"name": "arr", "type": "uint256[]"}], + "name": "sum", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap(); + let selector = keccak256("sum(uint256[])".as_bytes())[0..4] + .try_into() + .unwrap(); + + let input = Input { + instance: "Contract".to_string(), + method: Method::Function(selector), + calldata: Some(Calldata::Compound(vec![CalldataArg::Literal( + "[1,2,3]".to_string(), + )])), + ..Default::default() + }; + + let mut abis = HashMap::new(); + abis.insert("Contract".to_string(), parsed_abi); + let contracts = HashMap::new(); + + let encoded = input.encoded_input(&abis, &contracts).unwrap(); + assert!(encoded.0.starts_with(&selector)); + } + + #[test] + fn test_encoded_input_address() { + let raw_abi = r#"[ + { + "inputs": [{"name": "recipient", "type": "address"}], + "name": "send", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap(); + let selector = keccak256("send(address)".as_bytes())[0..4] + .try_into() + .unwrap(); + + let input = Input { + instance: "Contract".to_string(), + method: Method::Function(selector), + calldata: Some(Calldata::Compound(vec![CalldataArg::Literal( + "0x1000000000000000000000000000000000000001".to_string(), + )])), + ..Default::default() + }; + + let mut abis = HashMap::new(); + abis.insert("Contract".to_string(), parsed_abi); + let contracts = HashMap::new(); + + let encoded = input.encoded_input(&abis, &contracts).unwrap(); + assert!(encoded.0.starts_with(&selector)); + + type T = (alloy_primitives::Address,); + let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap(); + assert_eq!( + decoded.0, + address!("0x1000000000000000000000000000000000000001") + ); + } +} From 08d29f41edfb061de8ee13e9c1e1048339209ed3 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Tue, 8 Jul 2025 16:06:15 +0300 Subject: [PATCH 6/9] Fixed format issues --- crates/node/src/geth.rs | 2 +- crates/node/src/kitchensink.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index bf45c37e..0b594847 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -159,7 +159,7 @@ impl EthereumNode for Instance { let connection_string = self.connection_string(); let wallet = self.wallet.clone(); - log::debug!("Submitting transaction: {:#?}", transaction); + log::debug!("Submitting transaction: {transaction:#?}"); execute_transaction(Box::pin(async move { Ok(ProviderBuilder::new() diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index 321637b2..638b326c 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -252,7 +252,7 @@ impl EthereumNode for KitchensinkNode { let url = self.rpc_url.clone(); let wallet = self.wallet.clone(); - log::debug!("Submitting transaction: {:#?}", transaction); + log::debug!("Submitting transaction: {transaction:#?}"); execute_transaction(Box::pin(async move { Ok(ProviderBuilder::new() From 60831d980dd1cf3f17804032476faf2f51747396 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Tue, 8 Jul 2025 17:49:07 +0300 Subject: [PATCH 7/9] Fixed format --- crates/core/src/driver/mod.rs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 1327da8d..690d53fb 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -139,7 +139,7 @@ where &self.deployed_abis, ) { Ok(tx) => { - log::debug!("Legacy transaction data: {:#?}", tx); + log::debug!("Legacy transaction data: {tx:#?}"); tx } Err(err) => { @@ -281,11 +281,7 @@ where ); if let Some(Value::String(metadata_json_str)) = &contract.metadata { - log::trace!( - "metadata found for contract {}, {}", - contract_name, - metadata_json_str - ); + log::trace!("metadata found for contract {contract_name}, {metadata_json_str}"); match serde_json::from_str::(metadata_json_str) { Ok(metadata_json) => { @@ -302,26 +298,17 @@ where .insert(contract_name.clone(), parsed_abi); } Err(err) => { - log::debug!( - "Failed to parse ABI from metadata for {}: {}", - contract_name, - err - ); + log::debug!("Failed to parse ABI from metadata for {contract_name}: {err}"); } } } else { log::debug!( - "No ABI found in metadata for contract {}", - contract_name - ); + "No ABI found in metadata for contract {contract_name}"); } } Err(err) => { log::debug!( - "Failed to parse metadata JSON string for contract {}: {}", - contract_name, - err - ); + "Failed to parse metadata JSON string for contract {contract_name}: {err}"); } } } else { From f1a1ae1e9fe6500b7283cccfc5c72366f7e61ed2 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Tue, 8 Jul 2025 18:00:48 +0300 Subject: [PATCH 8/9] Added new changes to format --- crates/core/src/driver/mod.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 690d53fb..ee59d4af 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -281,7 +281,9 @@ where ); if let Some(Value::String(metadata_json_str)) = &contract.metadata { - log::trace!("metadata found for contract {contract_name}, {metadata_json_str}"); + log::trace!( + "metadata found for contract {contract_name}, {metadata_json_str}" + ); match serde_json::from_str::(metadata_json_str) { Ok(metadata_json) => { @@ -298,17 +300,21 @@ where .insert(contract_name.clone(), parsed_abi); } Err(err) => { - log::debug!("Failed to parse ABI from metadata for {contract_name}: {err}"); + log::debug!( + "Failed to parse ABI from metadata for {contract_name}: {err}" + ); } } } else { log::debug!( - "No ABI found in metadata for contract {contract_name}"); + "No ABI found in metadata for contract {contract_name}" + ); } } Err(err) => { log::debug!( - "Failed to parse metadata JSON string for contract {contract_name}: {err}"); + "Failed to parse metadata JSON string for contract {contract_name}: {err}" + ); } } } else { From 77606bf5decf2b6dfc31997efa8903c2d6e399e4 Mon Sep 17 00:00:00 2001 From: activecoder10 Date: Wed, 9 Jul 2025 11:03:50 +0300 Subject: [PATCH 9/9] Added bail to stop execution when we have an error during deployment --- crates/core/src/driver/mod.rs | 17 +++++++++++------ crates/format/src/input.rs | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index ee59d4af..12b0a824 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -300,20 +300,25 @@ where .insert(contract_name.clone(), parsed_abi); } Err(err) => { - log::debug!( - "Failed to parse ABI from metadata for {contract_name}: {err}" + anyhow::bail!( + "Failed to parse ABI from metadata for contract {}: {}", + contract_name, + err ); } } } else { - log::debug!( - "No ABI found in metadata for contract {contract_name}" + anyhow::bail!( + "No ABI found in metadata for contract {}", + contract_name ); } } Err(err) => { - log::debug!( - "Failed to parse metadata JSON string for contract {contract_name}: {err}" + anyhow::bail!( + "Failed to parse metadata JSON string for contract {}: {}", + contract_name, + err ); } } diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 4d048774..bc7b6e52 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -54,7 +54,8 @@ pub enum Calldata { #[serde(untagged)] pub enum CalldataArg { Literal(String), - AddressRef(String), // will be "Contract.address" + /// For example: `Contract.address` + AddressRef(String), } /// Specify how the contract is called.