diff --git a/tokenTap/helpers.py b/tokenTap/helpers.py index eb6fe373..132e5e05 100644 --- a/tokenTap/helpers.py +++ b/tokenTap/helpers.py @@ -4,8 +4,9 @@ from web3 import Account, Web3 from authentication.models import NetworkTypes +from core.models import WalletAccount from faucet.faucet_manager.credit_strategy import RoundCreditStrategy -from faucet.models import GlobalSettings, WalletAccount +from faucet.models import GlobalSettings from .models import TokenDistributionClaim @@ -23,7 +24,12 @@ def create_uint32_random_nonce(): def hash_message(user, token, amount, nonce): message_hash = Web3().solidity_keccak( ["address", "address", "uint256", "uint32"], - [Web3.to_checksum_address(user), Web3.to_checksum_address(token), amount, nonce], + [ + Web3.to_checksum_address(user), + Web3.to_checksum_address(token), + amount, + nonce, + ], ) hashed_message = encode_defunct(hexstr=message_hash.hex()) diff --git a/tokenTap/migrations/0024_tokendistribution_contract.py b/tokenTap/migrations/0024_tokendistribution_contract.py new file mode 100644 index 00000000..0c7c24d2 --- /dev/null +++ b/tokenTap/migrations/0024_tokendistribution_contract.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2023-12-11 07:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tokenTap', '0023_alter_tokendistribution_chain'), + ] + + operations = [ + migrations.AddField( + model_name='tokendistribution', + name='contract', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/tokenTap/models.py b/tokenTap/models.py index 36189dfe..7541e889 100644 --- a/tokenTap/models.py +++ b/tokenTap/models.py @@ -39,6 +39,7 @@ class TokenDistribution(models.Model): chain = models.ForeignKey( Chain, on_delete=models.CASCADE, related_name="token_distribution" ) + contract = models.CharField(max_length=255, null=True, blank=True) permissions = models.ManyToManyField(Constraint, blank=True) diff --git a/tokenTap/serializers.py b/tokenTap/serializers.py index e07da802..41fadf11 100644 --- a/tokenTap/serializers.py +++ b/tokenTap/serializers.py @@ -1,8 +1,7 @@ from rest_framework import serializers from core.constraints import ConstraintVerification, get_constraint -from core.serializers import UserConstraintBaseSerializer -from faucet.serializers import SmallChainSerializer +from core.serializers import ChainSerializer, UserConstraintBaseSerializer from tokenTap.models import ( Constraint, TokenDistribution, @@ -32,7 +31,7 @@ def update(self, instance, validated_data): class TokenDistributionSerializer(serializers.ModelSerializer): - chain = SmallChainSerializer() + chain = ChainSerializer() permissions = ConstraintSerializer(many=True) class Meta: @@ -50,6 +49,7 @@ class Meta: "token_address", "amount", "chain", + "contract", "permissions", "created_at", "deadline", @@ -64,7 +64,7 @@ class Meta: class SmallTokenDistributionSerializer(serializers.ModelSerializer): - chain = SmallChainSerializer() + chain = ChainSerializer() permissions = ConstraintSerializer(many=True) class Meta: @@ -81,6 +81,7 @@ class Meta: "token_address", "amount", "chain", + "contract", "permissions", "created_at", "deadline", diff --git a/tokenTap/tests.py b/tokenTap/tests.py index dd272ca4..35a66efa 100644 --- a/tokenTap/tests.py +++ b/tokenTap/tests.py @@ -7,13 +7,10 @@ from rest_framework.test import APITestCase, override_settings from authentication.models import NetworkTypes, UserProfile, Wallet -from faucet.models import ( - Chain, - ClaimReceipt, - GlobalSettings, - TransactionBatch, - WalletAccount, -) +from core.models import Chain, WalletAccount +from faucet.models import Chain as FaucetChain +from faucet.models import ClaimReceipt, GlobalSettings, TransactionBatch +from faucet.models import WalletAccount as FaucetWalletAccount from tokenTap.models import Constraint, TokenDistribution, TokenDistributionClaim from .helpers import create_uint32_random_nonce, hash_message, sign_hashed_message @@ -35,12 +32,9 @@ def setUp(self): network_type=NetworkTypes.EVM, ), rpc_url_private=test_rpc_url_private, - fund_manager_address=fund_manager, native_currency_name="xdai", symbol="XDAI", chain_id="100", - max_claim_amount=x_dai_max_claim, - tokentap_contract_address=gnosis_tokentap_contract_address, ) self.permission = Constraint.objects.create( @@ -59,6 +53,7 @@ def test_token_distribution_creation(self): token_address="0x123456789abcdef", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() + timezone.timedelta(days=7), # permissions=[self.permission], ) @@ -67,7 +62,9 @@ def test_token_distribution_creation(self): self.assertEqual(TokenDistribution.objects.count(), 1) self.assertEqual(TokenDistribution.objects.first(), td) self.assertEqual(TokenDistribution.objects.first().permissions.count(), 1) - self.assertEqual(TokenDistribution.objects.first().permissions.first(), self.permission) + self.assertEqual( + TokenDistribution.objects.first().permissions.first(), self.permission + ) def test_token_distribution_expiration(self): td1 = TokenDistribution.objects.create( @@ -81,6 +78,7 @@ def test_token_distribution_expiration(self): token_address="0x123456789abcdef", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() + timezone.timedelta(days=7), ) self.assertFalse(td1.is_expired) @@ -96,6 +94,7 @@ def test_token_distribution_expiration(self): token_address="0x123456789abcdef", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() - timezone.timedelta(days=7), ) self.assertTrue(td2.is_expired) @@ -117,12 +116,9 @@ def setUp(self) -> None: network_type=NetworkTypes.EVM, ), rpc_url_private=test_rpc_url_private, - fund_manager_address=fund_manager, native_currency_name="xdai", symbol="XDAI", chain_id="100", - max_claim_amount=x_dai_max_claim, - tokentap_contract_address=gnosis_tokentap_contract_address, ) self.td = TokenDistribution.objects.create( @@ -136,6 +132,7 @@ def setUp(self) -> None: token_address="0x123456789abcdef", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() + timezone.timedelta(days=7), ) @@ -164,13 +161,10 @@ def setUp(self) -> None: network_type=NetworkTypes.EVM, ), rpc_url_private=test_rpc_url_private, - fund_manager_address=fund_manager, native_currency_name="xdai", explorer_url="https://blockscout.com/poa/xdai/", symbol="XDAI", chain_id="100", - max_claim_amount=x_dai_max_claim, - tokentap_contract_address=gnosis_tokentap_contract_address, ) self.btc_chain = Chain.objects.create( @@ -181,6 +175,21 @@ def setUp(self) -> None: network_type=NetworkTypes.LIGHTNING, ), rpc_url_private=test_rpc_url_private, + native_currency_name="bitcoin", + explorer_url="https://blockstream.info/testnet/", + symbol="BTC", + chain_id="1010", + chain_type=NetworkTypes.LIGHTNING, + ) + + self.faucet_btc_chain = FaucetChain.objects.create( + chain_name="Bitcoin", + wallet=FaucetWalletAccount.objects.create( + name="Bitcoin Wallet", + private_key=test_wallet_key, + network_type=NetworkTypes.LIGHTNING, + ), + rpc_url_private=test_rpc_url_private, fund_manager_address=fund_manager, native_currency_name="bitcoin", explorer_url="https://blockstream.info/testnet/", @@ -204,6 +213,7 @@ def setUp(self) -> None: token_address="0x83ff60e2f93f8edd0637ef669c69d5fb4f64ca8e", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() + timezone.timedelta(days=7), max_number_of_claims=100, notes="Test Notes", @@ -215,10 +225,14 @@ def setUp(self) -> None: name="core.BrightIDAuraVerification", title="BrightID Aura", type="VER" ) self.permission4 = Constraint.objects.create( - name="tokenTap.OncePerMonthVerification", title="Once per Month", type="TIME" + name="tokenTap.OncePerMonthVerification", + title="Once per Month", + type="TIME", ) self.permission5 = Constraint.objects.create( - name="tokenTap.OnceInALifeTimeVerification", title="Once per Lifetime", type="TIME" + name="tokenTap.OnceInALifeTimeVerification", + title="Once per Lifetime", + type="TIME", ) self.td.permissions.set([self.permission1, self.permission2, self.permission4]) @@ -245,8 +259,12 @@ def test_token_distribution_list(self): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 2) self.assertEqual(response.data[0]["name"], "Test Distribution") - self.assertEqual(response.data[0]["permissions"][0]["name"], "core.BrightIDMeetVerification") - self.assertEqual(response.data[0]["permissions"][1]["name"], "core.BrightIDAuraVerification") + self.assertEqual( + response.data[0]["permissions"][0]["name"], "core.BrightIDMeetVerification" + ) + self.assertEqual( + response.data[0]["permissions"][1]["name"], "core.BrightIDAuraVerification" + ) def test_token_distribution_not_claimable_max_reached(self): ltd = TokenDistribution.objects.create( @@ -260,6 +278,7 @@ def test_token_distribution_not_claimable_max_reached(self): token_address="0x123456789abcdef", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() + timezone.timedelta(days=7), max_number_of_claims=0, notes="Test Notes", @@ -285,6 +304,7 @@ def test_token_distribution_not_claimable_deadline_reached(self): token_address="0x123456789abcdef", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() - timezone.timedelta(days=7), max_number_of_claims=10, notes="Test Notes", @@ -339,7 +359,8 @@ def test_token_distribution_not_claimable_already_claimed(self): # self.assertEqual(response.status_code, 403) # # self.assertEqual( - # # response.data["detail"], "You have already claimed this token this month" + # # response.data["detail"], + # # "You have already claimed this token this month" # # ) @patch( @@ -348,7 +369,9 @@ def test_token_distribution_not_claimable_already_claimed(self): ) def test_token_distribution_not_claimable_false_permissions(self): self.client.force_authenticate(user=self.user_profile.user) - response = self.client.post(reverse("token-distribution-claim", kwargs={"pk": self.td.pk})) + response = self.client.post( + reverse("token-distribution-claim", kwargs={"pk": self.td.pk}) + ) self.assertEqual(response.status_code, 403) @@ -372,7 +395,9 @@ def test_token_distribution_not_claimable_weekly_credit_limit_reached(self): ) def test_token_distribution_not_claimable_no_wallet(self): self.client.force_authenticate(user=self.user_profile.user) - response = self.client.post(reverse("token-distribution-claim", kwargs={"pk": self.td.pk})) + response = self.client.post( + reverse("token-distribution-claim", kwargs={"pk": self.td.pk}) + ) self.assertEqual(response.status_code, 403) self.assertEqual( @@ -391,7 +416,9 @@ def test_token_distribution_claimable(self): address="0xc1cbb2ab97260a8a7d4591045a9fb34ec14e87fb", ) self.client.force_authenticate(user=self.user_profile.user) - response = self.client.post(reverse("token-distribution-claim", kwargs={"pk": self.td.pk})) + response = self.client.post( + reverse("token-distribution-claim", kwargs={"pk": self.td.pk}) + ) self.assertEqual(response.status_code, 200) @@ -414,7 +441,9 @@ def test_btc_lightning_claimable(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.data["signature"]["status"], "Pending") self.assertEqual(ClaimReceipt.objects.count(), 1) - self.assertEqual(ClaimReceipt.objects.first().chain, self.btc_td.chain) + self.assertEqual( + ClaimReceipt.objects.first().chain.chain_id, self.btc_td.chain.chain_id + ) @patch( "authentication.helpers.BrightIDSoulboundAPIInterface.get_verification_status", @@ -436,11 +465,13 @@ def test_btc_lightning_claimable_claim_updates_after_6seconds(self): self.assertEqual(response.data["signature"]["status"], "Pending") self.assertEqual(ClaimReceipt.objects.count(), 1) gas_tap_claim = ClaimReceipt.objects.first() - self.assertEqual(gas_tap_claim.chain, self.btc_td.chain) + self.assertEqual(gas_tap_claim.chain.chain_id, self.btc_td.chain.chain_id) self.assertEqual(gas_tap_claim._status, ClaimReceipt.PENDING) tb = TransactionBatch.objects.create( - chain=self.btc_td.chain, tx_hash="test hash", _status=ClaimReceipt.VERIFIED + chain=self.faucet_btc_chain, + tx_hash="test hash", + _status=ClaimReceipt.VERIFIED, ) gas_tap_claim.batch = tb @@ -454,7 +485,9 @@ def test_btc_lightning_claimable_claim_updates_after_6seconds(self): gas_tap_claim.refresh_from_db() self.assertEqual(gas_tap_claim._status, ClaimReceipt.PROCESSED_FOR_TOKENTAP) self.assertEqual(gas_tap_claim.tx_hash, "test hash") - self.assertEqual(TokenDistributionClaim.objects.first().status, ClaimReceipt.VERIFIED) + self.assertEqual( + TokenDistributionClaim.objects.first().status, ClaimReceipt.VERIFIED + ) self.assertEqual(TokenDistributionClaim.objects.first().tx_hash, "test hash") @@ -499,13 +532,10 @@ def setUp(self) -> None: network_type=NetworkTypes.EVM, ), rpc_url_private=test_rpc_url_private, - fund_manager_address=fund_manager, native_currency_name="xdai", explorer_url="https://blockscout.com/poa/xdai/", symbol="XDAI", chain_id="100", - max_claim_amount=x_dai_max_claim, - tokentap_contract_address=gnosis_tokentap_contract_address, ) self.user_profile = UserProfile.objects.get_or_create("mamad") @@ -521,6 +551,7 @@ def setUp(self) -> None: token_address="0x83ff60e2f93f8edd0637ef669c69d5fb4f64ca8e", amount=1000, chain=self.chain, + contract=gnosis_tokentap_contract_address, deadline=timezone.now() + timezone.timedelta(days=7), max_number_of_claims=100, notes="Test Notes", @@ -551,14 +582,18 @@ def test_token_distribution_claim_list(self): def test_token_distribution_claim_retrieve(self): self.client.force_authenticate(user=self.user_profile.user) - response = self.client.get(reverse("claim-retrieve", kwargs={"pk": self.tdc.pk})) + response = self.client.get( + reverse("claim-retrieve", kwargs={"pk": self.tdc.pk}) + ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data["token_distribution"]["id"], self.td.pk) # Tests that the token distribution claim status is successfully updated def test_successful_update(self): claim = TokenDistributionClaim.objects.create( - token_distribution=TokenDistribution.objects.create(token_address="0x123", amount=100, chain=self.chain), + token_distribution=TokenDistribution.objects.create( + token_address="0x123", amount=100, chain=self.chain + ), user_profile=self.user_profile, status=ClaimReceipt.PENDING, ) @@ -574,7 +609,9 @@ def test_successful_update(self): # Tests that an error is raised when tx_hash is missing from request data def test_missing_tx_hash(self): claim = TokenDistributionClaim.objects.create( - token_distribution=TokenDistribution.objects.create(token_address="0x123", amount=100, chain=self.chain), + token_distribution=TokenDistribution.objects.create( + token_address="0x123", amount=100, chain=self.chain + ), user_profile=self.user_profile, status=ClaimReceipt.PENDING, ) @@ -585,11 +622,14 @@ def test_missing_tx_hash(self): self.assertEqual(response.status_code, 400) assert "tx_hash is a required field" in str(response.content) - # Tests that an error is raised when the token distribution claim does not belong to the user profile + # Tests that an error is raised when the token distribution claim + # does not belong to the user profile def test_claim_not_belonging_to_user_profile(self): other_user_profile = UserProfile.objects.get_or_create("other") claim = TokenDistributionClaim.objects.create( - token_distribution=TokenDistribution.objects.create(token_address="0x123", amount=100, chain=self.chain), + token_distribution=TokenDistribution.objects.create( + token_address="0x123", amount=100, chain=self.chain + ), user_profile=other_user_profile, status=ClaimReceipt.PENDING, ) @@ -599,10 +639,13 @@ def test_claim_not_belonging_to_user_profile(self): response = self.client.post(url, data=data) assert response.status_code == 403 - # Tests that an error is raised when the token distribution claim status is already verified + # Tests that an error is raised when the token distribution claim + # status is already verified def test_already_verified_claim(self): claim = TokenDistributionClaim.objects.create( - token_distribution=TokenDistribution.objects.create(token_address="0x123", amount=100, chain=self.chain), + token_distribution=TokenDistribution.objects.create( + token_address="0x123", amount=100, chain=self.chain + ), user_profile=self.user_profile, status=ClaimReceipt.VERIFIED, ) diff --git a/tokenTap/views.py b/tokenTap/views.py index 5afadf3f..515eb5cb 100644 --- a/tokenTap/views.py +++ b/tokenTap/views.py @@ -12,8 +12,9 @@ from rest_framework.response import Response from rest_framework.views import APIView -from authentication.models import NetworkTypes from core.constraints import ConstraintVerification, get_constraint +from core.models import NetworkTypes +from faucet.models import Chain as FaucetChain from faucet.models import ClaimReceipt from tokenTap.models import TokenDistribution, TokenDistributionClaim from tokenTap.serializers import ( @@ -39,7 +40,9 @@ class TokenDistributionListView(ListAPIView): def get_queryset(self): q = TokenDistribution.objects.filter(is_active=True) - sorted_queryset = sorted(q, key=lambda obj: obj.total_claims_since_last_round, reverse=True) + sorted_queryset = sorted( + q, key=lambda obj: obj.total_claims_since_last_round, reverse=True + ) return sorted_queryset @@ -49,7 +52,9 @@ class TokenDistributionClaimView(CreateAPIView): def check_token_distribution_is_claimable(self, token_distribution): if not token_distribution.is_claimable: - raise rest_framework.exceptions.PermissionDenied("This token is not claimable") + raise rest_framework.exceptions.PermissionDenied( + "This token is not claimable" + ) def check_user_permissions(self, token_distribution, user_profile): for c in token_distribution.permissions.all(): @@ -60,11 +65,15 @@ def check_user_permissions(self, token_distribution, user_profile): def check_user_weekly_credit(self, user_profile): if not has_weekly_credit_left(user_profile): - raise rest_framework.exceptions.PermissionDenied("You have reached your weekly claim limit") + raise rest_framework.exceptions.PermissionDenied( + "You have reached your weekly claim limit" + ) def check_user_has_wallet(self, user_profile): if not user_profile.wallets.filter(wallet_type=NetworkTypes.EVM).exists(): - raise rest_framework.exceptions.PermissionDenied("You have not connected an EVM wallet to your account") + raise rest_framework.exceptions.PermissionDenied( + "You have not connected an EVM wallet to your account" + ) @swagger_auto_schema( responses={ @@ -144,7 +153,7 @@ def post(self, request, *args, **kwargs): token_distribution=token_distribution, ) ClaimReceipt.objects.create( - chain=token_distribution.chain, + chain=FaucetChain.objects.get(chain_type=NetworkTypes.LIGHTNING), user_profile=user_profile, datetime=timezone.now(), amount=token_distribution.amount, @@ -185,9 +194,13 @@ def get(self, request, td_id): is_verified = False if constraint.is_observed(token_distribution=td): is_verified = True - response_constraints.append({**ConstraintSerializer(c).data, "is_verified": is_verified}) + response_constraints.append( + {**ConstraintSerializer(c).data, "is_verified": is_verified} + ) - return Response({"success": True, "constraints": response_constraints}, status=200) + return Response( + {"success": True, "constraints": response_constraints}, status=200 + ) class TokenDistributionClaimStatusUpdateView(CreateAPIView): @@ -200,12 +213,18 @@ def post(self, request, *args, **kwargs): ) tx_hash = request.data.get("tx_hash", None) if tx_hash is None: - raise rest_framework.exceptions.ValidationError("tx_hash is a required field") + raise rest_framework.exceptions.ValidationError( + "tx_hash is a required field" + ) if token_distribution_claim.user_profile != user_profile: - raise rest_framework.exceptions.PermissionDenied("You do not have permission to update this claim") + raise rest_framework.exceptions.PermissionDenied( + "You do not have permission to update this claim" + ) if token_distribution_claim.status != ClaimReceipt.PENDING: - raise rest_framework.exceptions.PermissionDenied("This claim has already been updated") + raise rest_framework.exceptions.PermissionDenied( + "This claim has already been updated" + ) token_distribution_claim.tx_hash = tx_hash token_distribution_claim.status = ClaimReceipt.VERIFIED token_distribution_claim.save() @@ -231,4 +250,6 @@ class TokenDistributionClaimRetrieveView(RetrieveAPIView): def get_object(self): user_profile = self.request.user.profile - return TokenDistributionClaim.objects.get(pk=self.kwargs["pk"], user_profile=user_profile) + return TokenDistributionClaim.objects.get( + pk=self.kwargs["pk"], user_profile=user_profile + )