Skip to content

Commit

Permalink
Add events to new/update/delete delegates
Browse files Browse the repository at this point in the history
  • Loading branch information
falvaradorodriguez committed Nov 6, 2024
1 parent 6bbe647 commit 7cfe89d
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 2 deletions.
3 changes: 3 additions & 0 deletions safe_transaction_service/history/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2194,3 +2194,6 @@ class TransactionServiceEventType(Enum):
MESSAGE_CONFIRMATION = 11
DELETED_MULTISIG_TRANSACTION = 12
REORG_DETECTED = 13
NEW_DELEGATE = 14
UPDATED_DELEGATE = 15
DELETED_DELEGATE = 16
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from datetime import timedelta
from typing import Any, Dict, List, Type, TypedDict, Union
from typing import Any, Dict, List, Optional, Type, TypedDict, Union

from django.db.models import Model
from django.utils import timezone
Expand All @@ -15,6 +15,7 @@
MultisigConfirmation,
MultisigTransaction,
SafeContract,
SafeContractDelegate,
TokenTransfer,
TransactionServiceEventType,
)
Expand Down Expand Up @@ -204,3 +205,54 @@ def build_reorg_payload(block_number: int) -> ReorgPayload:
blockNumber=block_number,
chainId=str(get_chain_id()),
)


class DelegatePayload(TypedDict):
type: str
safeAddress: Optional[str]
delegate: str
delegator: str
label: str
expiryDate: Optional[int]


def _build_delegate_payload(
event_type: Union[
TransactionServiceEventType.NEW_DELEGATE,
TransactionServiceEventType.UPDATED_DELEGATE,
TransactionServiceEventType.DELETED_DELEGATE,
],
instance: SafeContractDelegate,
) -> DelegatePayload:
"""
Build a delegate payload with the specified event type and SafeContractDelegate instance data.
:param event_type: The transaction event type, restricted to NEW_DELEGATE, UPDATED_DELEGATE, or DELETED_DELEGATE.
:param instance: An instance of SafeContractDelegate.
:return: A DelegatePayload dictionary with details about the delegate.
"""
return DelegatePayload(
type=event_type.name,
safeAddress=instance.safe_contract.address if instance.safe_contract else None,
delegate=instance.delegate,
delegator=instance.delegator,
label=instance.label,
expiryDate=(
int(instance.expiry_date.timestamp()) if instance.expiry_date else None
),
)


def build_save_delegate_payload(
instance: SafeContractDelegate, created: bool = True
) -> DelegatePayload:
event_type = TransactionServiceEventType.NEW_DELEGATE
if not created:
event_type = TransactionServiceEventType.UPDATED_DELEGATE
return _build_delegate_payload(event_type, instance)


def build_delete_delegate_payload(instance: SafeContractDelegate) -> DelegatePayload:
return _build_delegate_payload(
TransactionServiceEventType.DELETED_DELEGATE, instance
)
37 changes: 36 additions & 1 deletion safe_transaction_service/history/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@
MultisigConfirmation,
MultisigTransaction,
SafeContract,
SafeContractDelegate,
SafeLastStatus,
SafeMasterCopy,
SafeStatus,
TokenTransfer,
)
from .services.notification_service import build_event_payload, is_relevant_notification
from .services.notification_service import (
build_delete_delegate_payload,
build_event_payload,
build_save_delegate_payload,
is_relevant_notification,
)

logger = getLogger(__name__)

Expand Down Expand Up @@ -288,3 +294,32 @@ def add_to_historical_table(
safe_status = SafeStatus.from_status_instance(instance)
safe_status.save()
return safe_status


@receiver(
post_save,
sender=SafeContractDelegate,
dispatch_uid="safe_contract_delegate.process_save_delegate_user_event",
)
def process_save_delegate_user_event(
sender: Type[Model],
instance: SafeContractDelegate,
created: bool,
**kwargs,
):
payload_event = build_save_delegate_payload(instance, created)
queue_service = get_queue_service()
queue_service.send_event(payload_event)


@receiver(
post_delete,
sender=SafeContractDelegate,
dispatch_uid="safe_contract_delegate.process_delete_delegate_user_event",
)
def process_delete_delegate_user_event(
sender: Type[Model], instance: SafeContractDelegate, *args, **kwargs
):
payload_event = build_delete_delegate_payload(instance)
queue_service = get_queue_service()
queue_service.send_event(payload_event)
63 changes: 63 additions & 0 deletions safe_transaction_service/history/tests/test_signals.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import datetime
from datetime import timedelta
from unittest import mock
from unittest.mock import MagicMock

from django.db.models.signals import post_save
from django.test import TestCase
from django.utils import timezone

import factory
from safe_eth.eth import EthereumNetwork
Expand All @@ -28,6 +30,8 @@
InternalTxFactory,
MultisigConfirmationFactory,
MultisigTransactionFactory,
SafeContractDelegateFactory,
SafeContractFactory,
)


Expand Down Expand Up @@ -195,3 +199,62 @@ def test_signals_are_correctly_fired(self, send_event_mock: MagicMock):
"chainId": str(EthereumNetwork.GANACHE.value),
}
send_event_mock.assert_called_with(deleted_multisig_transaction_payload)

@mock.patch.object(QueueService, "send_event")
def test_delegates_signals_are_correctly_fired(self, send_event_mock: MagicMock):
# New delegate should fire an event
delegate_for_safe = SafeContractDelegateFactory()
new_delegate_user_payload = {
"type": TransactionServiceEventType.NEW_DELEGATE.name,
"safeAddress": delegate_for_safe.safe_contract.address,
"delegate": delegate_for_safe.delegate,
"delegator": delegate_for_safe.delegator,
"label": delegate_for_safe.label,
"expiryDate": int(delegate_for_safe.expiry_date.timestamp()),
}
send_event_mock.assert_called_with(new_delegate_user_payload)

permanent_delegate_without_safe = SafeContractDelegateFactory(
safe_contract=None, expiry_date=None
)
new_delegate_user_payload = {
"type": TransactionServiceEventType.NEW_DELEGATE.name,
"safeAddress": None,
"delegate": permanent_delegate_without_safe.delegate,
"delegator": permanent_delegate_without_safe.delegator,
"label": permanent_delegate_without_safe.label,
"expiryDate": None,
}
send_event_mock.assert_called_with(new_delegate_user_payload)

# Updated delegate should fire an event
delegate_to_update = SafeContractDelegateFactory()
new_safe = SafeContractFactory()
new_label = "Updated Label"
new_expiry_date = timezone.now() + datetime.timedelta(minutes=5)
delegate_to_update.safe_contract = new_safe
delegate_to_update.label = new_label
delegate_to_update.expiry_date = new_expiry_date
delegate_to_update.save()
updated_delegate_user_payload = {
"type": TransactionServiceEventType.UPDATED_DELEGATE.name,
"safeAddress": new_safe.address,
"delegate": delegate_to_update.delegate,
"delegator": delegate_to_update.delegator,
"label": new_label,
"expiryDate": int(new_expiry_date.timestamp()),
}
send_event_mock.assert_called_with(updated_delegate_user_payload)

# Deleted delegate should fire an event
delegate_to_delete = SafeContractDelegateFactory()
delegate_to_delete.delete()
updated_delegate_user_payload = {
"type": TransactionServiceEventType.DELETED_DELEGATE.name,
"safeAddress": delegate_to_delete.safe_contract.address,
"delegate": delegate_to_delete.delegate,
"delegator": delegate_to_delete.delegator,
"label": delegate_to_delete.label,
"expiryDate": int(delegate_to_delete.expiry_date.timestamp()),
}
send_event_mock.assert_called_with(updated_delegate_user_payload)

0 comments on commit 7cfe89d

Please sign in to comment.