From 737a42a85c9c5131bc400595e3a33f16d239964b Mon Sep 17 00:00:00 2001 From: Felipe Alvarado Date: Wed, 8 Jan 2025 15:27:08 +0100 Subject: [PATCH] Add get_contract_metadata task call when event is received --- app/services/events.py | 37 ++++++++++------ app/tests/services/test_events.py | 74 +++++++++++++++++++++++-------- 2 files changed, 79 insertions(+), 32 deletions(-) diff --git a/app/services/events.py b/app/services/events.py index 6189cf4..1cac782 100644 --- a/app/services/events.py +++ b/app/services/events.py @@ -2,6 +2,10 @@ import logging from typing import Dict +from safe_eth.eth.utils import fast_is_checksum_address + +from ..workers.tasks import get_contract_metadata_task + class EventsService: @@ -13,24 +17,31 @@ def process_event(self, message: str) -> None: """ try: tx_service_event = json.loads(message) - - if self.is_event_valid(tx_service_event): - # TODO: process event! - pass - else: - logging.error( - f"Unsupported message. A valid message should have at least 'chainId' and 'type': {message}" - ) + if self._is_processable_event(tx_service_event): + chain_id = int(tx_service_event["chainId"]) + contract_address = tx_service_event["to"] + get_contract_metadata_task.send(contract_address, chain_id) except json.JSONDecodeError: logging.error(f"Unsupported message. Cannot parse as JSON: {message}") - def is_event_valid(self, tx_service_event: Dict) -> bool: + @staticmethod + def _is_processable_event(tx_service_event: Dict) -> bool: """ - Validates if the event has the required fields 'chainId' and 'type' as strings. + Validates if the event has the required fields 'chainId', 'type', and 'to' as strings, + and if the event type and address meet the expected criteria. :param tx_service_event: The event object to validate. - :return: True if the event is valid (both 'chainId' and 'type' are strings), False otherwise. + :return: True if the event is valid, False otherwise. """ - return isinstance(tx_service_event.get("chainId"), str) and isinstance( - tx_service_event.get("type"), str + chain_id = tx_service_event.get("chainId") + event_type = tx_service_event.get("type") + address = tx_service_event.get("to") + + return ( + isinstance(chain_id, str) + and chain_id.isdigit() + and isinstance(event_type, str) + and event_type == "EXECUTED_MULTISIG_TRANSACTION" + and isinstance(address, str) + and fast_is_checksum_address(address) ) diff --git a/app/tests/services/test_events.py b/app/tests/services/test_events.py index 3a193fb..2a53be4 100644 --- a/app/tests/services/test_events.py +++ b/app/tests/services/test_events.py @@ -1,5 +1,6 @@ +import json import unittest -from unittest.mock import patch +from unittest.mock import MagicMock, patch from app.services.events import EventsService @@ -7,28 +8,53 @@ class TestEventsService(unittest.TestCase): def test_is_event_valid(self): - valid_event = {"chainId": "123", "type": "transaction"} - self.assertTrue(EventsService().is_event_valid(valid_event)) + valid_event = { + "chainId": "123", + "type": "EXECUTED_MULTISIG_TRANSACTION", + "to": "0x6ED857dc1da2c41470A95589bB482152000773e9", + } + self.assertTrue(EventsService._is_processable_event(valid_event)) + + valid_event = { + "chainId": "123", + "type": "transaction", + "to": "0x6ed857dc1da2c41470A95589bB482152000773e9", + } + self.assertFalse(EventsService._is_processable_event(valid_event)) + + valid_event = { + "chainId": "123", + "type": "EXECUTED_MULTISIG_TRANSACTION", + "to": "0x123456789", + } + self.assertFalse(EventsService._is_processable_event(valid_event)) + + valid_event = { + "chainId": "chainId", + "type": "EXECUTED_MULTISIG_TRANSACTION", + "to": "0x6ED857dc1da2c41470A95589bB482152000773e9", + } + self.assertFalse(EventsService._is_processable_event(valid_event)) invalid_event_missing_chain_id = {"type": "transaction"} - self.assertFalse(EventsService().is_event_valid(invalid_event_missing_chain_id)) + self.assertFalse( + EventsService._is_processable_event(invalid_event_missing_chain_id) + ) invalid_event_missing_type = {"chainId": "123"} - self.assertFalse(EventsService().is_event_valid(invalid_event_missing_type)) + self.assertFalse( + EventsService._is_processable_event(invalid_event_missing_type) + ) invalid_event_invalid_chain_id = {"chainId": 123, "type": "transaction"} - self.assertFalse(EventsService().is_event_valid(invalid_event_invalid_chain_id)) + self.assertFalse( + EventsService._is_processable_event(invalid_event_invalid_chain_id) + ) invalid_event_invalid_type = {"chainId": "123", "type": 123} - self.assertFalse(EventsService().is_event_valid(invalid_event_invalid_type)) - - @patch("logging.error") - def test_process_event_valid_message(self, mock_log): - valid_message = '{"chainId": "123", "type": "transaction"}' - - EventsService().process_event(valid_message) - - mock_log.assert_not_called() + self.assertFalse( + EventsService._is_processable_event(invalid_event_invalid_type) + ) @patch("logging.error") def test_process_event_invalid_json(self, mock_log): @@ -40,10 +66,20 @@ def test_process_event_invalid_json(self, mock_log): 'Unsupported message. Cannot parse as JSON: {"chainId": "123", "type": "transaction"' ) - invalid_message_invalid_type = '{"chainId": "123", "type": 123}' + @patch("app.workers.tasks.get_contract_metadata_task.send") + def test_process_event_calls_send(self, mock_get_contract_metadata_task): + mock_get_contract_metadata_task.send = MagicMock() + + valid_message = json.dumps( + { + "chainId": "1", + "type": "EXECUTED_MULTISIG_TRANSACTION", + "to": "0x6ED857dc1da2c41470A95589bB482152000773e9", + } + ) - EventsService().process_event(invalid_message_invalid_type) + EventsService().process_event(valid_message) - mock_log.assert_called_with( - 'Unsupported message. A valid message should have at least \'chainId\' and \'type\': {"chainId": "123", "type": 123}' + mock_get_contract_metadata_task.assert_called_once_with( + "0x6ED857dc1da2c41470A95589bB482152000773e9", 1 )