From c49a57f18eebc42b1c82a4f6e8564f3d6edb5e21 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:19:02 +0000 Subject: [PATCH] CodeRabbit Generated Unit Tests: Add generated unit tests for PR changes --- hypersync-format/src/types/mod.rs | 310 +++++++++++++++++++++++++++++- 1 file changed, 309 insertions(+), 1 deletion(-) diff --git a/hypersync-format/src/types/mod.rs b/hypersync-format/src/types/mod.rs index 5edc3c4..343eb63 100644 --- a/hypersync-format/src/types/mod.rs +++ b/hypersync-format/src/types/mod.rs @@ -72,6 +72,22 @@ pub struct Block { pub transactions: Vec, } +/// Deserialize a Quantity that may be null or missing, defaulting to zero. +fn deserialize_quantity_or_null<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) +} + +/// Deserialize Data that may be null or missing, defaulting to empty. +fn deserialize_data_or_null<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) +} + /// Evm transaction object /// /// See ethereum rpc spec for the meaning of fields @@ -85,10 +101,14 @@ pub struct Transaction { pub gas: Quantity, pub gas_price: Option, pub hash: Hash, + // In the Tempo blockchain, transactions don't need to have an input, and don't if they are of type 0x76 + #[serde(default, deserialize_with = "deserialize_data_or_null")] pub input: Data, pub nonce: Quantity, pub to: Option
, pub transaction_index: TransactionIndex, + // In the Tempo blockchain, transactions don't need to have an input, and don't if they are of type 0x76 + #[serde(default, deserialize_with = "deserialize_quantity_or_null")] pub value: Quantity, #[serde(rename = "type")] pub type_: Option, @@ -339,6 +359,294 @@ mod tests { use super::*; + // Minimal valid Transaction JSON base (without input and value, which now have defaults). + // blockHash and hash are Hash = FixedSizeData<32>, requiring 32 bytes (64 hex chars). + fn minimal_transaction_json() -> Value { + json!({ + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1", + "gas": "0x5208", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "nonce": "0x0", + "transactionIndex": "0x0" + }) + } + + // ---- Tests for deserialize_data_or_null (Transaction.input) ---- + + #[test] + fn transaction_input_null_defaults_to_empty_data() { + let mut json = minimal_transaction_json(); + json["input"] = json!(null); + + let tx: Transaction = + serde_json::from_value(json).expect("should deserialize transaction with null input"); + + assert_eq!(tx.input, Data::default()); + } + + #[test] + fn transaction_input_missing_defaults_to_empty_data() { + // Field omitted entirely — serde(default) should trigger the deserializer with default + let json = minimal_transaction_json(); + + let tx: Transaction = serde_json::from_value(json) + .expect("should deserialize transaction with missing input"); + + assert_eq!(tx.input, Data::default()); + } + + #[test] + fn transaction_input_valid_hex_is_parsed() { + let mut json = minimal_transaction_json(); + json["input"] = json!("0x1234"); + + let tx: Transaction = + serde_json::from_value(json).expect("should deserialize transaction with hex input"); + + assert_eq!(tx.input, Data::from([0x12u8, 0x34u8])); + } + + #[test] + fn transaction_input_empty_hex_is_empty_data() { + let mut json = minimal_transaction_json(); + json["input"] = json!("0x"); + + let tx: Transaction = serde_json::from_value(json) + .expect("should deserialize transaction with empty hex input"); + + assert_eq!(tx.input, Data::default()); + } + + #[test] + fn transaction_input_nonempty_data_roundtrips() { + let mut json = minimal_transaction_json(); + json["input"] = json!("0xdeadbeef"); + + let tx: Transaction = + serde_json::from_value(json).expect("should deserialize transaction with data input"); + + assert_eq!(tx.input, Data::from([0xdeu8, 0xadu8, 0xbeu8, 0xefu8])); + } + + // ---- Tests for deserialize_quantity_or_null (Transaction.value) ---- + + #[test] + fn transaction_value_null_defaults_to_zero_quantity() { + let mut json = minimal_transaction_json(); + json["value"] = json!(null); + + let tx: Transaction = + serde_json::from_value(json).expect("should deserialize transaction with null value"); + + assert_eq!(tx.value, Quantity::default()); + } + + #[test] + fn transaction_value_missing_defaults_to_zero_quantity() { + let json = minimal_transaction_json(); + + let tx: Transaction = serde_json::from_value(json) + .expect("should deserialize transaction with missing value"); + + assert_eq!(tx.value, Quantity::default()); + } + + #[test] + fn transaction_value_valid_hex_is_parsed() { + let mut json = minimal_transaction_json(); + json["value"] = json!("0x1a"); + + let tx: Transaction = + serde_json::from_value(json).expect("should deserialize transaction with hex value"); + + assert_eq!(tx.value, Quantity::from(26u64)); + } + + #[test] + fn transaction_value_zero_hex_is_zero_quantity() { + let mut json = minimal_transaction_json(); + json["value"] = json!("0x0"); + + let tx: Transaction = serde_json::from_value(json) + .expect("should deserialize transaction with zero hex value"); + + assert_eq!(tx.value, Quantity::default()); + } + + // ---- Tempo blockchain scenario (type 0x76, both input and value absent/null) ---- + + #[test] + fn tempo_transaction_type_0x76_both_input_and_value_null() { + let mut json = minimal_transaction_json(); + json["type"] = json!("0x76"); + json["input"] = json!(null); + json["value"] = json!(null); + + let tx: Transaction = serde_json::from_value(json) + .expect("Tempo 0x76 transaction with null input and value should deserialize"); + + assert_eq!(tx.input, Data::default()); + assert_eq!(tx.value, Quantity::default()); + assert_eq!(tx.type_, Some(TransactionType::from(0x76u8))); + } + + #[test] + fn tempo_transaction_type_0x76_both_input_and_value_missing() { + let mut json = minimal_transaction_json(); + json["type"] = json!("0x76"); + + let tx: Transaction = serde_json::from_value(json) + .expect("Tempo 0x76 transaction with missing input and value should deserialize"); + + assert_eq!(tx.input, Data::default()); + assert_eq!(tx.value, Quantity::default()); + } + + // ---- Regression: normal transaction still parses input and value correctly ---- + + #[test] + fn normal_transaction_with_both_input_and_value_parses_correctly() { + let mut json = minimal_transaction_json(); + json["input"] = json!("0xabcd"); + json["value"] = json!("0xff"); + + let tx: Transaction = serde_json::from_value(json) + .expect("transaction with both input and value should deserialize"); + + assert_eq!(tx.input, Data::from([0xabu8, 0xcdu8])); + assert_eq!(tx.value, Quantity::from(255u64)); + } + + // ---- Negative / error cases ---- + + #[test] + fn transaction_input_invalid_hex_no_prefix_is_error() { + // Data requires "0x" prefix; bare hex should fail deserialization + let mut json = minimal_transaction_json(); + json["input"] = json!("deadbeef"); // missing 0x prefix + + let result: Result = serde_json::from_value(json); + assert!( + result.is_err(), + "input without 0x prefix should fail deserialization" + ); + } + + #[test] + fn transaction_value_empty_quantity_hex_is_error() { + // "0x" alone is not valid for Quantity (only valid for Data) + let mut json = minimal_transaction_json(); + json["value"] = json!("0x"); // empty Quantity is invalid + + let result: Result = serde_json::from_value(json); + assert!( + result.is_err(), + "empty 0x quantity should fail deserialization" + ); + } + + // ---- Mixed null / valid combinations ---- + + #[test] + fn transaction_null_input_with_valid_value() { + let mut json = minimal_transaction_json(); + json["input"] = json!(null); + json["value"] = json!("0x1"); + + let tx: Transaction = serde_json::from_value(json) + .expect("null input with valid value should deserialize"); + + assert_eq!(tx.input, Data::default()); + assert_eq!(tx.value, Quantity::from(1u64)); + } + + #[test] + fn transaction_valid_input_with_null_value() { + let mut json = minimal_transaction_json(); + json["input"] = json!("0xab"); + json["value"] = json!(null); + + let tx: Transaction = serde_json::from_value(json) + .expect("valid input with null value should deserialize"); + + assert_eq!(tx.input, Data::from([0xabu8])); + assert_eq!(tx.value, Quantity::default()); + } + + // ---- Boundary value tests ---- + + #[test] + fn transaction_value_large_boundary() { + // A 256-bit value (common max in EVM for wei amounts) + let mut json = minimal_transaction_json(); + json["value"] = json!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + let tx: Transaction = + serde_json::from_value(json).expect("large boundary value should deserialize"); + + // Should parse to 32 bytes of 0xff + assert_eq!(tx.value.as_ref(), &[0xffu8; 32]); + } + + #[test] + fn transaction_input_large_boundary() { + // A long input data payload (simulate contract calldata) + let mut json = minimal_transaction_json(); + // 32-byte input: function selector + 28 bytes of zero-padded argument + json["input"] = json!("0xa9059cbb0000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000000000000000000000000000000de0b6b3a7640000"); + + let tx: Transaction = + serde_json::from_value(json).expect("large input calldata should deserialize"); + + assert_eq!(tx.input.as_ref().len(), 68); // 4 + 32 + 32 bytes + } + + // ---- Serialization roundtrip ---- + + #[test] + fn transaction_default_input_value_serde_roundtrip() { + // Build a transaction via JSON where input and value are both present + let mut json = minimal_transaction_json(); + json["input"] = json!("0x1234"); + json["value"] = json!("0x64"); + + let tx: Transaction = + serde_json::from_value(json).expect("transaction should deserialize"); + + // Serialize back to JSON and deserialize again + let serialized = serde_json::to_value(&tx).expect("transaction should serialize"); + let tx2: Transaction = + serde_json::from_value(serialized).expect("roundtrip should deserialize"); + + assert_eq!(tx.input, tx2.input); + assert_eq!(tx.value, tx2.value); + assert_eq!(tx, tx2); + } + + #[test] + fn transaction_defaulted_fields_roundtrip_serialization() { + // When input and value are omitted (defaulted), the serialized form should have them + // as "0x" and "0x0" respectively, and roundtrip should preserve equality + let json = minimal_transaction_json(); + + let tx: Transaction = + serde_json::from_value(json).expect("minimal transaction should deserialize"); + + let serialized = serde_json::to_value(&tx).expect("should serialize"); + + // After serialization, input is "0x" and value is "0x0" + assert_eq!(serialized["input"], json!("0x")); + assert_eq!(serialized["value"], json!("0x0")); + + // Roundtrip should produce the same struct + let tx2: Transaction = + serde_json::from_value(serialized).expect("serialized form should deserialize"); + + assert_eq!(tx.input, tx2.input); + assert_eq!(tx.value, tx2.value); + } + #[test] fn handle_zeta_null_effective_gas_price() { // real world breaking example on zeta @@ -383,4 +691,4 @@ mod tests { let _: TransactionReceipt = serde_json::from_value(json).expect("should handle undefined effective gas price"); } -} +} \ No newline at end of file