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 DELETED_MULTISIG_TRANSACTION event #1810

Merged
merged 5 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions safe_transaction_service/history/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2019,6 +2019,7 @@ class WebHookType(Enum):
OUTGOING_TOKEN = 9
MESSAGE_CREATED = 10
MESSAGE_CONFIRMATION = 11
DELETED_MULTISIG_TRANSACTION = 12


class WebHookQuerySet(models.QuerySet):
Expand Down
10 changes: 10 additions & 0 deletions safe_transaction_service/history/services/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ def build_webhook_payload(
instance: Union[
TokenTransfer, InternalTx, MultisigConfirmation, MultisigTransaction
],
deleted: bool = False,
) -> List[Dict[str, Any]]:
"""
:param sender: Sender type
:param instance: Sender instance
:param deleted: If the instance has been deleted
:return: A list of webhooks generated from the instance provided
"""
payloads: List[Dict[str, Any]] = []
Expand All @@ -48,6 +50,14 @@ def build_webhook_payload(
).hex(),
}
]
elif sender == MultisigTransaction and deleted:
payloads = [
{
"address": instance.safe,
"type": WebHookType.DELETED_MULTISIG_TRANSACTION.name,
"safeTxHash": HexBytes(instance.safe_tx_hash).hex(),
}
]
elif sender == MultisigTransaction:
payload = {
"address": instance.safe,
Expand Down
86 changes: 58 additions & 28 deletions safe_transaction_service/history/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Type, Union

from django.db.models import Model
from django.db.models.signals import post_save
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.utils import timezone

Expand Down Expand Up @@ -112,30 +112,7 @@ def safe_master_copy_clear_cache(
SafeMasterCopy.objects.get_version_for_address.cache_clear()


@receiver(
post_save,
sender=ModuleTransaction,
dispatch_uid="module_transaction.process_webhook",
)
@receiver(
post_save,
sender=MultisigConfirmation,
dispatch_uid="multisig_confirmation.process_webhook",
)
@receiver(
post_save,
sender=MultisigTransaction,
dispatch_uid="multisig_transaction.process_webhook",
)
@receiver(
post_save, sender=ERC20Transfer, dispatch_uid="erc20_transfer.process_webhook"
)
@receiver(
post_save, sender=ERC721Transfer, dispatch_uid="erc721_transfer.process_webhook"
)
@receiver(post_save, sender=InternalTx, dispatch_uid="internal_tx.process_webhook")
@receiver(post_save, sender=SafeContract, dispatch_uid="safe_contract.process_webhook")
def process_webhook(
def _process_webhook(
sender: Type[Model],
instance: Union[
TokenTransfer,
Expand All @@ -145,10 +122,14 @@ def process_webhook(
SafeContract,
],
created: bool,
**kwargs,
) -> None:
deleted: bool,
):
assert not (
created and deleted
), "An instance cannot be created and deleted at the same time"

logger.debug("Start building payloads for created=%s object=%s", created, instance)
payloads = build_webhook_payload(sender, instance)
payloads = build_webhook_payload(sender, instance, deleted=deleted)
logger.debug(
"End building payloads %s for created=%s object=%s", payloads, created, instance
)
Expand Down Expand Up @@ -177,6 +158,55 @@ def process_webhook(
)


@receiver(
post_save,
sender=ModuleTransaction,
dispatch_uid="module_transaction.process_webhook",
)
@receiver(
post_save,
sender=MultisigConfirmation,
dispatch_uid="multisig_confirmation.process_webhook",
)
@receiver(
post_save,
sender=MultisigTransaction,
dispatch_uid="multisig_transaction.process_webhook",
)
@receiver(
post_save, sender=ERC20Transfer, dispatch_uid="erc20_transfer.process_webhook"
)
@receiver(
post_save, sender=ERC721Transfer, dispatch_uid="erc721_transfer.process_webhook"
)
@receiver(post_save, sender=InternalTx, dispatch_uid="internal_tx.process_webhook")
@receiver(post_save, sender=SafeContract, dispatch_uid="safe_contract.process_webhook")
def process_webhook(
sender: Type[Model],
instance: Union[
TokenTransfer,
InternalTx,
MultisigConfirmation,
MultisigTransaction,
SafeContract,
],
created: bool,
**kwargs,
) -> None:
return _process_webhook(sender, instance, created, False)


@receiver(
post_delete,
sender=MultisigTransaction,
dispatch_uid="multisig_transaction.process_delete_webhook",
)
def process_delete_webhook(
sender: Type[Model], instance: MultisigTransaction, *args, **kwargs
):
return _process_webhook(sender, instance, False, True)


@receiver(
post_save,
sender=SafeLastStatus,
Expand Down
58 changes: 58 additions & 0 deletions safe_transaction_service/history/tests/test_signals.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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
Expand Down Expand Up @@ -83,6 +84,14 @@ def test_build_webhook_payload(self):
self.assertEqual(payload["type"], WebHookType.PENDING_MULTISIG_TRANSACTION.name)
self.assertEqual(payload["chainId"], str(EthereumNetwork.GANACHE.value))

payload = build_webhook_payload(
MultisigTransaction,
MultisigTransactionFactory(ethereum_tx=None),
deleted=True,
)[0]
self.assertEqual(payload["type"], WebHookType.DELETED_MULTISIG_TRANSACTION.name)
self.assertEqual(payload["chainId"], str(EthereumNetwork.GANACHE.value))

safe_address = self.deploy_test_safe().address
safe_message = SafeMessageFactory(safe=safe_address)
payload = build_webhook_payload(SafeMessage, safe_message)[0]
Expand Down Expand Up @@ -166,3 +175,52 @@ def test_is_relevant_notification_multisig_transaction(self):
self.assertFalse(
is_relevant_notification(multisig_tx.__class__, multisig_tx, created=False)
)

@mock.patch.object(send_webhook_task, "apply_async")
@mock.patch.object(send_event_to_queue_task, "delay")
def test_signals_are_correctly_fired(
self,
send_event_to_queue_task_mock: MagicMock,
webhook_task_mock: MagicMock,
):
# Not trusted txs should not fire any event
MultisigTransactionFactory(trusted=False)
webhook_task_mock.assert_not_called()
send_event_to_queue_task_mock.assert_not_called()

# Trusted txs should fire an event
multisig_tx: MultisigTransaction = MultisigTransactionFactory(trusted=True)
pending_multisig_transaction_payload = {
"address": multisig_tx.safe,
"safeTxHash": multisig_tx.safe_tx_hash,
"type": WebHookType.EXECUTED_MULTISIG_TRANSACTION.name,
"failed": "false",
"txHash": multisig_tx.ethereum_tx_id,
"chainId": str(EthereumNetwork.GANACHE.value),
}
webhook_task_mock.assert_called_with(
args=(multisig_tx.safe, pending_multisig_transaction_payload), priority=2
)
send_event_to_queue_task_mock.assert_called_with(
pending_multisig_transaction_payload
)

# Deleting a tx should fire an event
webhook_task_mock.reset_mock()
send_event_to_queue_task_mock.reset_mock()
safe_tx_hash = multisig_tx.safe_tx_hash
multisig_tx.delete()

deleted_multisig_transaction_payload = {
"address": multisig_tx.safe,
"safeTxHash": safe_tx_hash,
"type": WebHookType.DELETED_MULTISIG_TRANSACTION.name,
"chainId": str(EthereumNetwork.GANACHE.value),
}

webhook_task_mock.assert_called_with(
args=(multisig_tx.safe, deleted_multisig_transaction_payload), priority=2
)
send_event_to_queue_task_mock.assert_called_with(
deleted_multisig_transaction_payload
)
9 changes: 5 additions & 4 deletions safe_transaction_service/safe_messages/tests/test_signals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from unittest import mock
from unittest.mock import MagicMock

from django.db.models.signals import post_save
from django.test import TestCase
Expand Down Expand Up @@ -28,8 +29,8 @@ class TestSafeMessageSignals(SafeTestCaseMixin, TestCase):
@mock.patch.object(send_event_to_queue_task, "delay")
def test_process_webhook(
self,
send_event_to_queue_task_mock,
webhook_task_mock,
send_event_to_queue_task_mock: MagicMock,
webhook_task_mock: MagicMock,
):
safe_address = self.deploy_test_safe().address
safe_message = SafeMessageFactory(safe=safe_address)
Expand Down Expand Up @@ -64,8 +65,8 @@ def test_process_webhook(
@mock.patch.object(send_event_to_queue_task, "delay")
def test_signals_are_correctly_fired(
self,
send_event_to_queue_task_mock,
webhook_task_mock,
send_event_to_queue_task_mock: MagicMock,
webhook_task_mock: MagicMock,
):
safe_address = self.deploy_test_safe().address
# Create a confirmation should fire a signal and webhooks should be sended
Expand Down