Repository: ApeWorX/ape

Issue:
Add validator to `ContractLog.transaction_hash`
https://github.com/ApeWorX/silverback/pull/175#issuecomment-2646591864

Basically, the type is Any but if we can detect it was given a hex-str, add the 0x prefix.

Discussion:
@linear: <p><a href="https://linear.app/apeworx/issue/APE-1896/add-validator-to-contractlogtransaction-hash">APE-1896 Add validator to `ContractLog.transaction_hash`</a></p>

Solution patch:
diff --git a/src/ape/types/events.py b/src/ape/types/events.py
index 8eb6d106d..632b1a2ff 100644
--- a/src/ape/types/events.py
+++ b/src/ape/types/events.py
@@ -6,7 +6,7 @@ from eth_abi.abi import encode
 from eth_abi.packed import encode_packed
 from eth_pydantic_types import HexBytes
 from eth_typing import Hash32, HexStr
-from eth_utils import encode_hex, keccak, to_hex
+from eth_utils import encode_hex, is_hex, keccak, to_hex
 from ethpm_types.abi import EventABI
 from pydantic import BaseModel, field_serializer, field_validator, model_validator
 from web3.types import FilterParams
@@ -193,6 +193,18 @@ class ContractLog(ExtraAttributesMixin, BaseContractLog):
     Is `None` when from the pending block.
     """
 
+    @field_validator("transaction_hash", mode="before")
+    @classmethod
+    def _validate_transaction_hash(cls, transaction_hash):
+        if (
+            isinstance(transaction_hash, str)
+            and is_hex(transaction_hash)
+            and not transaction_hash.startswith("0x")
+        ):
+            return f"0x{transaction_hash}"
+
+        return transaction_hash
+
     @field_serializer("transaction_hash", "block_hash")
     def _serialize_hashes(self, value, info):
         return self._serialize_value(value, info)


Test patch:
diff --git a/tests/functional/test_contract_event.py b/tests/functional/test_contract_event.py
index 26877d13c..0b5ac112b 100644
--- a/tests/functional/test_contract_event.py
+++ b/tests/functional/test_contract_event.py
@@ -471,3 +471,17 @@ def test_model_dump_json():
         '"MyEvent","log_index":0,'
         '"transaction_hash":"0x00000000000000000000000004d21f074916369e"}'
     )
+
+
+def test_transaction_hash():
+    event_arguments = {"key": 123, "validators": [HexBytes(123)]}
+    txn_hash = "a3430927834bd23"
+    event = ContractLog(
+        block_number=123,
+        block_hash="block-hash",
+        event_arguments=event_arguments,
+        event_name="MyEvent",
+        log_index=0,
+        transaction_hash=txn_hash,
+    )
+    assert event.transaction_hash == f"0x{txn_hash}"