Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Delegates events on creation/deletion/update #2310

Merged
merged 5 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add the unit, for clarity. Like expiryDateSeconds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done e90cc85



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,
falvaradorodriguez marked this conversation as resolved.
Show resolved Hide resolved
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
falvaradorodriguez marked this conversation as resolved.
Show resolved Hide resolved
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)