From f8e8752a30ca115ec6d0a084e17823a8e66710b1 Mon Sep 17 00:00:00 2001 From: Mohamad Bastin Date: Fri, 27 Oct 2023 12:13:31 +0200 Subject: [PATCH] change gastap claimstrategy | change global settings fields --- core/utils.py | 90 ++++++------ faucet/constraints.py | 35 +++-- .../faucet_manager/brightid_user_registry.py | 68 ++++----- faucet/faucet_manager/claim_manager.py | 44 +++--- faucet/faucet_manager/credit_strategy.py | 138 ++++++++++-------- faucet/faucet_manager/fund_manager.py | 61 +++----- faucet/models.py | 48 ++---- faucet/serializers.py | 132 +++++++---------- faucet/test.py | 81 +++++----- faucet/urls.py | 23 ++- faucet/views.py | 123 ++++++++-------- permissions/admin.py | 36 ++--- ...eetverification_permission_ptr_and_more.py | 51 +++++++ permissions/migrations/0007_initial.py | 63 ++++++++ permissions/models.py | 41 +++--- permissions/serializers.py | 50 +++---- permissions/tests.py | 43 +++--- .../migrations/0003_raffle_permissions.py | 1 - tokenTap/constraints.py | 18 +-- tokenTap/helpers.py | 20 +-- .../0002_tokendistribution_permissions.py | 1 - tokenTap/models.py | 36 ++--- tokenTap/serializers.py | 21 ++- tokenTap/tests.py | 60 ++++---- tokenTap/views.py | 84 +++++------ 25 files changed, 719 insertions(+), 649 deletions(-) create mode 100644 permissions/migrations/0006_remove_brightidmeetverification_permission_ptr_and_more.py create mode 100644 permissions/migrations/0007_initial.py diff --git a/core/utils.py b/core/utils.py index 21879d7e..98916608 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,47 +1,46 @@ import datetime + import pytz -from time import time from web3 import Web3 -from web3.middleware import geth_poa_middleware from web3.contract.contract import Contract, ContractFunction -from web3.types import Type, TxParams -from django.utils import timezone +from web3.middleware import geth_poa_middleware +from web3.types import TxParams, Type class TimeUtils: - @staticmethod - def get_last_monday(): - now = int(time()) - day = 86400 # seconds in a day - week = 7 * day - weeks = now // week # number of weeks since epoch - monday = 345600 # first monday midnight - last_monday_midnight = monday + (weeks * week) - - # last monday could be off by one week - if last_monday_midnight > now: - last_monday_midnight -= week - - return timezone.make_aware( - datetime.datetime.fromtimestamp(last_monday_midnight) - ) - - @staticmethod - def get_second_last_monday(): - now = int(time()) - day = 86400 # seconds in a day - week = 7 * day - weeks = now // week # number of weeks since epoch - monday = 345600 # first monday midnight - last_monday_midnight = monday + (weeks * week) - - # last monday could be off by one week - if last_monday_midnight > now: - last_monday_midnight -= week - - return timezone.make_aware( - datetime.datetime.fromtimestamp(last_monday_midnight - week) - ) + # @staticmethod + # def get_last_monday(): + # now = int(time()) + # day = 86400 # seconds in a day + # week = 7 * day + # weeks = now // week # number of weeks since epoch + # monday = 345600 # first monday midnight + # last_monday_midnight = monday + (weeks * week) + + # # last monday could be off by one week + # if last_monday_midnight > now: + # last_monday_midnight -= week + + # return timezone.make_aware( + # datetime.datetime.fromtimestamp(last_monday_midnight) + # ) + + # @staticmethod + # def get_second_last_monday(): + # now = int(time()) + # day = 86400 # seconds in a day + # week = 7 * day + # weeks = now // week # number of weeks since epoch + # monday = 345600 # first monday midnight + # last_monday_midnight = monday + (weeks * week) + + # # last monday could be off by one week + # if last_monday_midnight > now: + # last_monday_midnight -= week + + # return timezone.make_aware( + # datetime.datetime.fromtimestamp(last_monday_midnight - week) + # ) @staticmethod def get_first_day_of_the_month(): @@ -49,9 +48,17 @@ def get_first_day_of_the_month(): first_day = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) return first_day + @staticmethod + def get_first_day_of_last_month(): + now = datetime.datetime.now(pytz.timezone("UTC")) + first_day = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + last_month = first_day - datetime.timedelta(days=1) + first_day_of_last_month = last_month.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + return first_day_of_last_month + class Web3Utils: - def __init__(self, rpc_url, poa = False) -> None: + def __init__(self, rpc_url, poa=False) -> None: self._rpc_url = rpc_url self._w3 = None self._account = None @@ -71,7 +78,7 @@ def w3(self) -> Web3: return self._w3 raise Exception(f"RPC provider is not connected ({self._rpc_url})") - + @property def poa(self): return self._poa @@ -102,8 +109,7 @@ def contract_call(self, func: Type[ContractFunction], from_address=None): def build_contract_txn(self, func: Type[ContractFunction]): nonce = self.w3.eth.get_transaction_count(self.account.address) - tx_data = func.build_transaction( - {"from": self.account.address, "nonce": nonce}) + tx_data = func.build_transaction({"from": self.account.address, "nonce": nonce}) return self.sign_tx(tx_data) def sign_tx(self, tx_data: TxParams): @@ -117,6 +123,6 @@ def wait_for_transaction_receipt(self, tx_hash): def current_block(self): return self.w3.eth.block_number - + def get_transaction_by_hash(self, hash): return self.w3.eth.get_transaction(hash) diff --git a/faucet/constraints.py b/faucet/constraints.py index b21c75d2..6164305d 100644 --- a/faucet/constraints.py +++ b/faucet/constraints.py @@ -1,8 +1,12 @@ +import logging + import requests -from core.constraints import * + +from core.constraints import ConstraintParam, ConstraintVerification from core.utils import Web3Utils -from faucet.faucet_manager.credit_strategy import WeeklyCreditStrategy -from .models import DonationReceipt, Chain, ClaimReceipt +from faucet.faucet_manager.credit_strategy import RoundCreditStrategy + +from .models import Chain, ClaimReceipt, DonationReceipt class DonationConstraint(ConstraintVerification): @@ -24,7 +28,8 @@ class OptimismDonationConstraint(DonationConstraint): def is_observed(self, *args, **kwargs): try: chain = Chain.objects.get(chain_id=10) - except: + except Exception as e: + logging.error(e) return False self._param_values[ConstraintParam.CHAIN] = chain.pk return super().is_observed(*args, **kwargs) @@ -38,12 +43,11 @@ def is_observed(self, *args, **kwargs): chain = Chain.objects.get(pk=chain_pk) w3 = Web3Utils(chain.rpc_url_private, chain.poa) current_block = w3.current_block() - user_address = self.user_profile.wallets.get( - wallet_type=chain.chain_type - ).address + user_address = self.user_profile.wallets.get(wallet_type=chain.chain_type).address first_internal_tx = requests.get( - f"{chain.explorer_api_url}/api?module=account&action=txlistinternal&address={user_address}&startblock=0&endblock={current_block}&page=1&offset=1&sort=asc&apikey={chain.explorer_api_key}" + f"{chain.explorer_api_url}/api?module=account&action=txlistinternal&address={user_address}&start" + f"block=0&endblock={current_block}&page=1&offset=1&sort=asc&apikey={chain.explorer_api_key}" ) first_internal_tx = first_internal_tx.json() if first_internal_tx and first_internal_tx["status"] == "1": @@ -54,16 +58,15 @@ def is_observed(self, *args, **kwargs): and first_internal_tx["isError"] == "0" ): first_tx = requests.get( - f"{chain.explorer_api_url}/api?module=account&action=txlist&address={user_address}&startblock=0&endblock={current_block}&page=1&offset=1&sort=asc&apikey={chain.explorer_api_key}" + f"{chain.explorer_api_url}/api?module=account&action=txlist&address={user_address}&startblock=0&" + f"endblock={current_block}&page=1&offset=1&sort=asc&apikey={chain.explorer_api_key}" ) first_tx = first_tx.json() if first_tx: if not first_tx["result"]: return True first_tx = first_tx["result"][0] - claiming_gas_tx = w3.get_transaction_by_hash( - first_internal_tx["hash"] - ) + claiming_gas_tx = w3.get_transaction_by_hash(first_internal_tx["hash"]) web3_first_tx = w3.get_transaction_by_hash(first_tx["hash"]) return web3_first_tx["blockNumber"] > claiming_gas_tx["blockNumber"] return False @@ -75,7 +78,8 @@ class OptimismClaimingGasConstraint(EvmClaimingGasConstraint): def is_observed(self, *args, **kwargs): try: chain = Chain.objects.get(chain_id=10) - except: + except Exception as e: + logging.error(e) return False self._param_values[ConstraintParam.CHAIN] = chain.pk return super().is_observed(*args, **kwargs) @@ -91,7 +95,7 @@ def is_observed(self, *args, **kwargs): user_profile=self.user_profile, chain=chain, _status=ClaimReceipt.VERIFIED, - datetime__gte=WeeklyCreditStrategy.get_last_monday(), + datetime__gte=RoundCreditStrategy.get_start_of_the_round(), ).exists() @@ -101,7 +105,8 @@ class OptimismHasClaimedGasInThisRound(HasClaimedGasInThisRound): def is_observed(self, *args, **kwargs): try: chain = Chain.objects.get(chain_id=10) - except: + except Exception as e: + logging.error(e) return False self._param_values[ConstraintParam.CHAIN] = chain.pk return super().is_observed(*args, **kwargs) diff --git a/faucet/faucet_manager/brightid_user_registry.py b/faucet/faucet_manager/brightid_user_registry.py index 50fb9c35..d89c14c8 100644 --- a/faucet/faucet_manager/brightid_user_registry.py +++ b/faucet/faucet_manager/brightid_user_registry.py @@ -1,40 +1,40 @@ -from eth_account.signers.local import LocalAccount -from web3 import Web3 -from web3.exceptions import TimeExhausted -from web3.gas_strategies.rpc import rpc_gas_price_strategy -from web3.middleware import geth_poa_middleware -from faucet.faucet_manager.brightid_user_registry_abi import bright_id_user_registry_abi -from faucet.models import Chain, BrightUser +# from eth_account.signers.local import LocalAccount +# from web3 import Web3 +# from web3.exceptions import TimeExhausted +# from web3.gas_strategies.rpc import rpc_gas_price_strategy +# from web3.middleware import geth_poa_middleware +# from faucet.faucet_manager.brightid_user_registry_abi import bright_id_user_registry_abi +# from faucet.models import Chain, BrightUser -class BrightIdUserRegistry: - def __init__(self, chain: Chain, bright_id_user_registry_address: str): - self.chain = chain - self.abi = bright_id_user_registry_abi - self.bright_id_user_registry_address = bright_id_user_registry_address +# class BrightIdUserRegistry: +# def __init__(self, chain: Chain, bright_id_user_registry_address: str): +# self.chain = chain +# self.abi = bright_id_user_registry_abi +# self.bright_id_user_registry_address = bright_id_user_registry_address - @property - def w3(self) -> Web3: - assert self.chain.rpc_url_private is not None - _w3 = Web3(Web3.HTTPProvider(self.chain.rpc_url_private)) - if self.chain.poa: - _w3.middleware_onion.inject(geth_poa_middleware, layer=0) - if _w3.isConnected(): - _w3.eth.set_gas_price_strategy(rpc_gas_price_strategy) - return _w3 - raise Exception(f"Could not connect to rpc {self.chain.rpc_url_private}") +# @property +# def w3(self) -> Web3: +# assert self.chain.rpc_url_private is not None +# _w3 = Web3(Web3.HTTPProvider(self.chain.rpc_url_private)) +# if self.chain.poa: +# _w3.middleware_onion.inject(geth_poa_middleware, layer=0) +# if _w3.isConnected(): +# _w3.eth.set_gas_price_strategy(rpc_gas_price_strategy) +# return _w3 +# raise Exception(f"Could not connect to rpc {self.chain.rpc_url_private}") - @property - def contract(self): - return self.w3.eth.contract( - address=self.get_checksum_address(self.bright_id_user_registry_address), - abi=self.abi, - ) +# @property +# def contract(self): +# return self.w3.eth.contract( +# address=self.get_checksum_address(self.bright_id_user_registry_address), +# abi=self.abi, +# ) - def get_checksum_address(self, address): - return Web3.toChecksumAddress(address.lower()) +# def get_checksum_address(self, address): +# return Web3.toChecksumAddress(address.lower()) - def is_verified_user(self, address): - return self.contract.functions.isVerifiedUser( - self.get_checksum_address(address) - ).call() +# def is_verified_user(self, address): +# return self.contract.functions.isVerifiedUser( +# self.get_checksum_address(address) +# ).call() diff --git a/faucet/faucet_manager/claim_manager.py b/faucet/faucet_manager/claim_manager.py index 75ca3951..d445dc07 100644 --- a/faucet/faucet_manager/claim_manager.py +++ b/faucet/faucet_manager/claim_manager.py @@ -1,19 +1,19 @@ -import logging import abc +import logging from abc import ABC + +from django.db import transaction from django.utils import timezone -from authentication.models import UserProfile -from authentication.models import NetworkTypes -from faucet.faucet_manager.lnpay_client import LNPayClient +from authentication.models import NetworkTypes, UserProfile from faucet.faucet_manager.credit_strategy import ( CreditStrategy, CreditStrategyFactory, - WeeklyCreditStrategy, + RoundCreditStrategy, ) from faucet.faucet_manager.fund_manager import EVMFundManager -from faucet.models import ClaimReceipt, BrightUser, GlobalSettings -from django.db import transaction +from faucet.faucet_manager.lnpay_client import LNPayClient +from faucet.models import BrightUser, ClaimReceipt, GlobalSettings class ClaimManager(ABC): @@ -36,9 +36,7 @@ def fund_manager(self): def claim(self, amount, passive_address=None): with transaction.atomic(): - user_profile = UserProfile.objects.select_for_update().get( - pk=self.credit_strategy.user_profile.pk - ) + user_profile = UserProfile.objects.select_for_update().get(pk=self.credit_strategy.user_profile.pk) self.assert_pre_claim_conditions(amount, user_profile) return self.create_pending_claim_receipt( amount, passive_address @@ -46,8 +44,7 @@ def claim(self, amount, passive_address=None): def assert_pre_claim_conditions(self, amount, user_profile): assert amount <= self.credit_strategy.get_unclaimed() - # TODO: uncomment this - assert self.user_is_meet_verified() == True + assert self.user_is_meet_verified() is True assert not ClaimReceipt.objects.filter( chain=self.credit_strategy.chain, user_profile=user_profile, @@ -72,13 +69,13 @@ def user_is_meet_verified(self) -> bool: class LimitedChainClaimManager(SimpleClaimManager): - def get_weekly_limit(self): - limit = GlobalSettings.objects.first().weekly_chain_claim_limit + def get_round_limit(self): + limit = GlobalSettings.objects.first().gastap_round_claim_limit return limit @staticmethod - def get_total_weekly_claims(user_profile): - last_monday = WeeklyCreditStrategy.get_last_monday() + def get_total_round_claims(user_profile): + start_of_the_round = RoundCreditStrategy.get_start_of_the_round() return ClaimReceipt.objects.filter( user_profile=user_profile, _status__in=[ @@ -87,27 +84,28 @@ def get_total_weekly_claims(user_profile): BrightUser.PENDING, BrightUser.VERIFIED, ], - datetime__gte=last_monday, + datetime__gte=start_of_the_round, ).count() def assert_pre_claim_conditions(self, amount, user_profile): super().assert_pre_claim_conditions(amount, user_profile) - total_claims = self.get_total_weekly_claims(user_profile) - assert total_claims < self.get_weekly_limit() + total_claims = self.get_total_round_claims(user_profile) + assert total_claims < self.get_round_limit() + class LightningClaimManger(LimitedChainClaimManager): def claim(self, amount, passive_address): try: lnpay_client = LNPayClient( - self.credit_strategy.chain.rpc_url_private, - self.credit_strategy.chain.wallet.main_key, - self.credit_strategy.chain.fund_manager_address + self.credit_strategy.chain.rpc_url_private, + self.credit_strategy.chain.wallet.main_key, + self.credit_strategy.chain.fund_manager_address, ) decoded_invoice = lnpay_client.decode_invoice(passive_address) except Exception as e: logging.error(e) raise AssertionError("Could not decode the invoice") - assert int(decoded_invoice['num_satoshis']) == amount, "Invalid amount" + assert int(decoded_invoice["num_satoshis"]) == amount, "Invalid amount" return super().claim(amount, passive_address) diff --git a/faucet/faucet_manager/credit_strategy.py b/faucet/faucet_manager/credit_strategy.py index 1fb51ecd..261c9548 100644 --- a/faucet/faucet_manager/credit_strategy.py +++ b/faucet/faucet_manager/credit_strategy.py @@ -1,16 +1,12 @@ import abc -from abc import ABC -from time import time import datetime +from abc import ABC -from django.db.models import Sum -from django.utils import timezone import pytz -from authentication.models import UserProfile +from django.db.models import Sum -from brightIDfaucet import settings -from faucet.faucet_manager.brightid_user_registry import BrightIdUserRegistry -from faucet.models import ClaimReceipt, BrightUser, Chain +from authentication.models import UserProfile +from faucet.models import Chain, ClaimReceipt class CreditStrategy(ABC): @@ -51,12 +47,68 @@ def get_claimed(self): return _sum def get_unclaimed(self): - # print("max_claim_amount", self.chain.max_claim_amount) - # print("get_claimed", self.get_claimed()) return int(self.chain.max_claim_amount) - int(self.get_claimed()) -class WeeklyCreditStrategy(SimpleCreditStrategy): +# class WeeklyCreditStrategy(SimpleCreditStrategy): +# def __int__(self, chain: Chain, user_profile: UserProfile): +# self.chain = chain +# self.user_profile = user_profile + +# def get_claim_receipts(self): +# return ClaimReceipt.objects.filter( +# chain=self.chain, +# user_profile=self.user_profile, +# _status=ClaimReceipt.VERIFIED, +# datetime__gte=self.get_last_monday(), +# ) + +# @staticmethod +# def get_last_monday(): +# now = int(time()) +# day = 86400 # seconds in a day +# week = 7 * day +# weeks = now // week # number of weeks since epoch +# monday = 345600 # first monday midnight +# last_monday_midnight = monday + (weeks * week) + +# # last monday could be off by one week +# if last_monday_midnight > now: +# last_monday_midnight -= week + +# return timezone.make_aware(datetime.datetime.fromtimestamp(last_monday_midnight)) + +# @staticmethod +# def get_second_last_monday(): +# now = int(time()) +# day = 86400 # seconds in a day +# week = 7 * day +# weeks = now // week # number of weeks since epoch +# monday = 345600 # first monday midnight +# last_monday_midnight = monday + (weeks * week) + +# # last monday could be off by one week +# if last_monday_midnight > now: +# last_monday_midnight -= week + +# return timezone.make_aware(datetime.datetime.fromtimestamp(last_monday_midnight - week)) + + +# class ArbitrumCreditStrategy(WeeklyCreditStrategy): +# def get_unclaimed(self): +# contract_address = "0x631a12430F94207De980D9b6A744AEB4093DCeC1" +# max_claim_amount = self.chain.max_claim_amount +# is_verified_user = BrightIdUserRegistry(self.chain, contract_address).is_verified_user( +# self.user_profile.initial_context_id +# ) + +# if is_verified_user: +# max_claim_amount = 5000000000000000 + +# return max_claim_amount - self.get_claimed() + + +class RoundCreditStrategy(SimpleCreditStrategy): def __int__(self, chain: Chain, user_profile: UserProfile): self.chain = chain self.user_profile = user_profile @@ -66,62 +118,30 @@ def get_claim_receipts(self): chain=self.chain, user_profile=self.user_profile, _status=ClaimReceipt.VERIFIED, - datetime__gte=self.get_last_monday(), + datetime__gte=self._get_first_day_of_the_month(), ) @staticmethod - def get_last_monday(): - now = int(time()) - day = 86400 # seconds in a day - week = 7 * day - weeks = now // week # number of weeks since epoch - monday = 345600 # first monday midnight - last_monday_midnight = monday + (weeks * week) - - # last monday could be off by one week - if last_monday_midnight > now: - last_monday_midnight -= week - - return timezone.make_aware( - datetime.datetime.fromtimestamp(last_monday_midnight) - ) + def get_start_of_the_round(): + return RoundCreditStrategy._get_first_day_of_the_month() @staticmethod - def get_second_last_monday(): - now = int(time()) - day = 86400 # seconds in a day - week = 7 * day - weeks = now // week # number of weeks since epoch - monday = 345600 # first monday midnight - last_monday_midnight = monday + (weeks * week) - - # last monday could be off by one week - if last_monday_midnight > now: - last_monday_midnight -= week - - return timezone.make_aware( - datetime.datetime.fromtimestamp(last_monday_midnight - week) - ) + def get_start_of_previous_round(): + return RoundCreditStrategy._get_first_day_of_last_month() - @staticmethod - def get_first_day_of_the_month(): + @classmethod + def _get_first_day_of_the_month(cls): now = datetime.datetime.now(pytz.timezone("UTC")) first_day = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) return first_day - -class ArbitrumCreditStrategy(WeeklyCreditStrategy): - def get_unclaimed(self): - contract_address = "0x631a12430F94207De980D9b6A744AEB4093DCeC1" - max_claim_amount = self.chain.max_claim_amount - is_verified_user = BrightIdUserRegistry( - self.chain, contract_address - ).is_verified_user(self.user_profile.initial_context_id) - - if is_verified_user: - max_claim_amount = 5000000000000000 - - return max_claim_amount - self.get_claimed() + @classmethod + def _get_first_day_of_last_month(cls): + now = datetime.datetime.now(pytz.timezone("UTC")) + first_day = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + last_month = first_day - datetime.timedelta(days=1) + first_day_of_last_month = last_month.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + return first_day_of_last_month class CreditStrategyFactory: @@ -130,9 +150,7 @@ def __init__(self, chain, user_profile): self.user_profile = user_profile def get_strategy_class(self): - return WeeklyCreditStrategy - if self.chain.chain_id == "42161": - return ArbitrumCreditStrategy + return RoundCreditStrategy def get_strategy(self) -> CreditStrategy: _Strategy = self.get_strategy_class() diff --git a/faucet/faucet_manager/fund_manager.py b/faucet/faucet_manager/fund_manager.py index 5df163ad..e35e9909 100644 --- a/faucet/faucet_manager/fund_manager.py +++ b/faucet/faucet_manager/fund_manager.py @@ -1,15 +1,9 @@ -import time -import os import logging +import os +import time + from django.core.cache import cache from eth_account.signers.local import LocalAccount -from web3 import Web3 -from web3.gas_strategies.rpc import rpc_gas_price_strategy -from web3.middleware import geth_poa_middleware -from faucet.faucet_manager.fund_manager_abi import manager_abi -from faucet.models import Chain, BrightUser, LightningConfig -from faucet.helpers import memcache_lock -from faucet.constants import * from solana.rpc.api import Client from solana.rpc.core import RPCException, RPCNoResultException from solana.transaction import Transaction @@ -17,10 +11,19 @@ from solders.pubkey import Pubkey from solders.signature import Signature from solders.transaction_status import TransactionConfirmationStatus -from .anchor_client.accounts.lock_account import LockAccount +from web3 import Web3 +from web3.gas_strategies.rpc import rpc_gas_price_strategy +from web3.middleware import geth_poa_middleware + +from faucet.constants import MEMCACHE_LIGHTNING_LOCK_KEY +from faucet.faucet_manager.fund_manager_abi import manager_abi +from faucet.helpers import memcache_lock +from faucet.models import BrightUser, Chain, LightningConfig + from .anchor_client import instructions -from .solana_client import SolanaClient +from .anchor_client.accounts.lock_account import LockAccount from .lnpay_client import LNPayClient +from .solana_client import SolanaClient class FundMangerException: @@ -48,9 +51,7 @@ def w3(self) -> Web3: return _w3 except Exception as e: logging.error(e) - raise FundMangerException.RPCError( - f"Could not connect to rpc {self.chain.rpc_url_private}" - ) + raise FundMangerException.RPCError(f"Could not connect to rpc {self.chain.rpc_url_private}") @property def is_gas_price_too_high(self): @@ -77,9 +78,7 @@ def get_fund_manager_checksum_address(self): @property def contract(self): - return self.w3.eth.contract( - address=self.get_fund_manager_checksum_address(), abi=self.abi - ) + return self.w3.eth.contract(address=self.get_fund_manager_checksum_address(), abi=self.abi) def transfer(self, bright_user: BrightUser, amount: int): tx = self.single_eth_transfer_signed_tx(amount, bright_user.address) @@ -153,9 +152,7 @@ def w3(self) -> Client: return _w3 except Exception as e: logging.error(e) - raise FundMangerException.RPCError( - f"Could not connect to rpc {self.chain.rpc_url_private}" - ) + raise FundMangerException.RPCError(f"Could not connect to rpc {self.chain.rpc_url_private}") @property def account(self) -> Keypair: @@ -171,9 +168,7 @@ def lock_account_seed(self) -> bytes: @property def lock_account_address(self) -> Pubkey: - lock_account_address, nonce = Pubkey.find_program_address( - [self.lock_account_seed], self.program_id - ) + lock_account_address, nonce = Pubkey.find_program_address([self.lock_account_seed], self.program_id) return lock_account_address @property @@ -247,23 +242,17 @@ def multi_transfer(self, data): def is_tx_verified(self, tx_hash): try: confirmation_status = ( - self.w3.get_signature_statuses([Signature.from_string(tx_hash)]) - .value[0] - .confirmation_status + self.w3.get_signature_statuses([Signature.from_string(tx_hash)]).value[0].confirmation_status ) return confirmation_status in [ TransactionConfirmationStatus.Confirmed, TransactionConfirmationStatus.Finalized, ] except RPCException: - logging.warning( - "Solana raised the RPCException at get_signature_statuses()" - ) + logging.warning("Solana raised the RPCException at get_signature_statuses()") return False except RPCNoResultException: - logging.warning( - "Solana raised the RPCNoResultException at get_signature_statuses()" - ) + logging.warning("Solana raised the RPCNoResultException at get_signature_statuses()") return False except Exception: raise @@ -285,9 +274,7 @@ def api_key(self): @property def lnpay_client(self): - return LNPayClient( - self.chain.rpc_url_private, self.api_key, self.chain.fund_manager_address - ) + return LNPayClient(self.chain.rpc_url_private, self.api_key, self.chain.fund_manager_address) def __check_max_cap_exceeds(self, amount) -> bool: try: @@ -309,9 +296,7 @@ def multi_transfer(self, data): assert acquired, "Could not acquire Lightning multi-transfer lock" item = data[0] - assert not self.__check_max_cap_exceeds( - item["amount"] - ), "Lightning periodic max cap exceeded" + assert not self.__check_max_cap_exceeds(item["amount"]), "Lightning periodic max cap exceeded" try: pay_result = client.pay_invoice(item["to"]) diff --git a/faucet/models.py b/faucet/models.py index 6e6a8e6e..affaa022 100644 --- a/faucet/models.py +++ b/faucet/models.py @@ -361,55 +361,35 @@ def total_claims(self): return total_claims @property - def total_claims_since_last_monday(self): - cached_total_claims_since_last_monday = cache.get(f"gas_tap_chain_total_claims_since_last_monday_{self.pk}") - if cached_total_claims_since_last_monday: - return cached_total_claims_since_last_monday - from faucet.faucet_manager.claim_manager import WeeklyCreditStrategy + def total_claims_this_round(self): + cached_total_claims_this_round = cache.get(f"gas_tap_chain_total_claims_this_round_{self.pk}") + if cached_total_claims_this_round: + return cached_total_claims_this_round + from faucet.faucet_manager.claim_manager import RoundCreditStrategy - total_claims_since_last_monday = ClaimReceipt.objects.filter( + total_claims_this_round = ClaimReceipt.objects.filter( chain=self, - datetime__gte=WeeklyCreditStrategy.get_last_monday(), - _status__in=[ClaimReceipt.VERIFIED, BrightUser.VERIFIED], + datetime__gte=RoundCreditStrategy.get_start_of_the_round(), + _status__in=[ClaimReceipt.VERIFIED], ).count() cache.set( - f"gas_tap_chain_total_claims_since_last_monday_{self.pk}", - total_claims_since_last_monday, + f"gas_tap_chain_total_claims_this_round_{self.pk}", + total_claims_this_round, get_cache_time(self.pk), ) - return total_claims_since_last_monday - - @property - def total_claims_for_last_round(self): - cached_total_claims_for_last_round = cache.get(f"gas_tap_chain_total_claims_for_last_round_{self.pk}") - if cached_total_claims_for_last_round: - return cached_total_claims_for_last_round - from faucet.faucet_manager.claim_manager import WeeklyCreditStrategy - - total_claims_for_last_round = ClaimReceipt.objects.filter( - chain=self, - datetime__gte=WeeklyCreditStrategy.get_second_last_monday(), - datetime__lte=WeeklyCreditStrategy.get_last_monday(), - _status__in=[ClaimReceipt.VERIFIED, BrightUser.VERIFIED], - ).count() - cache.set( - f"gas_tap_chain_total_claims_for_last_round_{self.pk}", - total_claims_for_last_round, - get_cache_time(self.pk), - ) - return total_claims_for_last_round + return total_claims_this_round @property def total_claims_since_last_round(self): cached_total_claims_since_last_round = cache.get(f"gas_tap_chain_total_claims_since_last_round_{self.pk}") if cached_total_claims_since_last_round: return cached_total_claims_since_last_round - from faucet.faucet_manager.claim_manager import WeeklyCreditStrategy + from faucet.faucet_manager.claim_manager import RoundCreditStrategy total_claims_since_last_round = ClaimReceipt.objects.filter( chain=self, - datetime__gte=WeeklyCreditStrategy.get_second_last_monday(), - _status__in=[ClaimReceipt.VERIFIED, BrightUser.VERIFIED], + datetime__gte=RoundCreditStrategy.get_start_of_previous_round(), + _status__in=[ClaimReceipt.VERIFIED], ).count() cache.set( f"gas_tap_chain_total_claims_since_last_round_{self.pk}", diff --git a/faucet/serializers.py b/faucet/serializers.py index 1fb19b25..5c42a209 100644 --- a/faucet/serializers.py +++ b/faucet/serializers.py @@ -1,46 +1,43 @@ from rest_framework import serializers -from faucet.faucet_manager.claim_manager import LimitedChainClaimManager -from faucet.faucet_manager.credit_strategy import CreditStrategyFactory -from faucet.models import BrightUser, Chain, ClaimReceipt, GlobalSettings, DonationReceipt - - -class UserSerializer(serializers.ModelSerializer): - total_weekly_claims_remaining = serializers.SerializerMethodField() - - class Meta: - model = BrightUser - fields = [ - "pk", - "context_id", - "address", - "verification_url", - "verification_status", - "total_weekly_claims_remaining", - ] - read_only_fields = ["context_id"] - - def get_total_weekly_claims_remaining(self, instance): - gs = GlobalSettings.objects.first() - if gs is not None: - return ( - gs.weekly_chain_claim_limit - - LimitedChainClaimManager.get_total_weekly_claims(instance) - ) - - def create(self, validated_data): - address = validated_data["address"] - bright_user = BrightUser.objects.get_or_create(address) - return bright_user +from faucet.models import Chain, ClaimReceipt, DonationReceipt, GlobalSettings + +# class UserSerializer(serializers.ModelSerializer): +# total_weekly_claims_remaining = serializers.SerializerMethodField() + +# class Meta: +# model = BrightUser +# fields = [ +# "pk", +# "context_id", +# "address", +# "verification_url", +# "verification_status", +# "total_weekly_claims_remaining", +# ] +# read_only_fields = ["context_id"] + +# def get_total_weekly_claims_remaining(self, instance): +# gs = GlobalSettings.objects.first() +# if gs is not None: +# return ( +# gs.weekly_chain_claim_limit +# - LimitedChainClaimManager.get_total_weekly_claims(instance) +# ) + +# def create(self, validated_data): +# address = validated_data["address"] +# bright_user = BrightUser.objects.get_or_create(address) +# return bright_user class GlobalSettingsSerializer(serializers.ModelSerializer): class Meta: model = GlobalSettings fields = [ - "weekly_chain_claim_limit", - "tokentap_weekly_claim_limit", - "prizetap_weekly_claim_limit", + "gastap_round_claim_limit", + "tokentap_round_claim_limit", + "prizetap_round_claim_limit", "is_gas_tap_available", ] @@ -104,8 +101,8 @@ class Meta: class ChainSerializer(serializers.ModelSerializer): - claimed = serializers.SerializerMethodField() - unclaimed = serializers.SerializerMethodField() + # claimed = serializers.SerializerMethodField() + # unclaimed = serializers.SerializerMethodField() class Meta: model = Chain @@ -123,11 +120,10 @@ class Meta: "modal_url", "gas_image_url", "max_claim_amount", - "claimed", - "unclaimed", - # "order", + # "claimed", + # "unclaimed", "total_claims", - "total_claims_since_last_monday", + "total_claims_this_round", "tokentap_contract_address", "needs_funding", "is_testnet", @@ -135,21 +131,21 @@ class Meta: "block_scan_address", ] - def get_claimed(self, chain) -> int: - user = self.context["request"].user + # def get_claimed(self, chain) -> int: + # user = self.context["request"].user - if not user.is_authenticated: - return "N/A" - user_profile = user.profile - return CreditStrategyFactory(chain, user_profile).get_strategy().get_claimed() + # if not user.is_authenticated: + # return "N/A" + # user_profile = user.profile + # return CreditStrategyFactory(chain, user_profile).get_strategy().get_claimed() - def get_unclaimed(self, chain) -> int: - user = self.context["request"].user + # def get_unclaimed(self, chain) -> int: + # user = self.context["request"].user - if not user.is_authenticated: - return "N/A" - user_profile = user.profile - return CreditStrategyFactory(chain, user_profile).get_strategy().get_unclaimed() + # if not user.is_authenticated: + # return "N/A" + # user_profile = user.profile + # return CreditStrategyFactory(chain, user_profile).get_strategy().get_unclaimed() class ReceiptSerializer(serializers.ModelSerializer): @@ -173,39 +169,23 @@ class DonationReceiptSerializer(serializers.ModelSerializer): chain = SmallChainSerializer(read_only=True) def validate(self, attrs): - chain = self._validate_chain(attrs.pop('chain_pk')) - attrs['user_profile'] = self.context.get('user') - attrs['chain'] = chain + chain = self._validate_chain(attrs.pop("chain_pk")) + attrs["user_profile"] = self.context.get("user") + attrs["chain"] = chain return attrs def _validate_chain(self, pk: str): try: - chain: Chain = Chain.objects.get(pk=pk, chain_type='EVM') + chain: Chain = Chain.objects.get(pk=pk, chain_type="EVM") except Chain.DoesNotExist: - raise serializers.ValidationError({'chain': 'chain is not EVM or does not exist.'}) + raise serializers.ValidationError({"chain": "chain is not EVM or does not exist."}) return chain class Meta: model = DonationReceipt depth = 1 - fields = [ - "tx_hash", - "chain", - "datetime", - "total_price", - "value", - "chain_pk", - "status", - "user_profile" - ] - read_only_fields = [ - 'value', - 'datetime', - 'total_price', - 'chain', - 'status', - "user_profile" - ] + fields = ["tx_hash", "chain", "datetime", "total_price", "value", "chain_pk", "status", "user_profile"] + read_only_fields = ["value", "datetime", "total_price", "chain", "status", "user_profile"] class LeaderboardSerializer(serializers.Serializer): diff --git a/faucet/test.py b/faucet/test.py index e8321f01..d7a651ef 100644 --- a/faucet/test.py +++ b/faucet/test.py @@ -9,14 +9,14 @@ from django.utils import timezone from rest_framework.test import APITestCase -from authentication.models import UserProfile +from authentication.models import UserProfile, Wallet from brightIDfaucet.settings import DEBUG from faucet.constants import MEMCACHE_LIGHTNING_LOCK_KEY from faucet.constraints import OptimismClaimingGasConstraint, OptimismDonationConstraint from faucet.faucet_manager.claim_manager import ClaimManagerFactory, SimpleClaimManager from faucet.faucet_manager.credit_strategy import ( + RoundCreditStrategy, SimpleCreditStrategy, - WeeklyCreditStrategy, ) from faucet.faucet_manager.fund_manager import LightningFundManager from faucet.helpers import memcache_lock @@ -28,7 +28,6 @@ LightningConfig, NetworkTypes, TransactionBatch, - Wallet, WalletAccount, ) from faucet.views import CustomException @@ -198,36 +197,36 @@ def test_list_chains(self): response = self.request_chain_list() self.assertEqual(response.status_code, 200) - def test_list_chain_should_show_NA_if_no_addresses_provided(self): - chains = self.request_chain_list() - chains_list = json.loads(chains.content) + # def test_list_chain_should_show_NA_if_no_addresses_provided(self): + # chains = self.request_chain_list() + # chains_list = json.loads(chains.content) - for chain_data in chains_list: - self.assertEqual(chain_data["claimed"], "N/A") - self.assertEqual(chain_data["unclaimed"], "N/A") - if chain_data["symbol"] == "XDAI": - self.assertEqual(chain_data["maxClaimAmount"], x_dai_max_claim) - elif chain_data["symbol"] == "eidi": - self.assertEqual(chain_data["maxClaimAmount"], eidi_max_claim) + # for chain_data in chains_list: + # self.assertEqual(chain_data["claimed"], "N/A") + # self.assertEqual(chain_data["unclaimed"], "N/A") + # if chain_data["symbol"] == "XDAI": + # self.assertEqual(chain_data["maxClaimAmount"], x_dai_max_claim) + # elif chain_data["symbol"] == "eidi": + # self.assertEqual(chain_data["maxClaimAmount"], eidi_max_claim) - def test_chain_list_without_token(self): - endpoint = reverse("FAUCET:chain-list") - chain_list_response = self.client.get(endpoint) - chain_list = json.loads(chain_list_response.content) + # def test_chain_list_without_token(self): + # endpoint = reverse("FAUCET:chain-list") + # chain_list_response = self.client.get(endpoint) + # chain_list = json.loads(chain_list_response.content) - for chain_data in chain_list: - self.assertEqual(chain_data["claimed"], "N/A") - self.assertEqual(chain_data["unclaimed"], "N/A") + # for chain_data in chain_list: + # self.assertEqual(chain_data["claimed"], "N/A") + # self.assertEqual(chain_data["unclaimed"], "N/A") - def test_chain_list_with_token(self): - endpoint = reverse("FAUCET:chain-list") - self.client.force_authenticate(user=self.new_user.user) - chain_list_response = self.client.get(endpoint) - chain_list = json.loads(chain_list_response.content) + # def test_chain_list_with_token(self): + # endpoint = reverse("FAUCET:chain-list") + # self.client.force_authenticate(user=self.new_user.user) + # chain_list_response = self.client.get(endpoint) + # chain_list = json.loads(chain_list_response.content) - for chain_data in chain_list: - self.assertEqual(chain_data["claimed"], 0) - self.assertEqual(chain_data["unclaimed"], chain_data["maxClaimAmount"]) + # for chain_data in chain_list: + # self.assertEqual(chain_data["claimed"], 0) + # self.assertEqual(chain_data["unclaimed"], chain_data["maxClaimAmount"]) class TestClaim(APITestCase): @@ -238,11 +237,11 @@ def setUp(self) -> None: self.x_dai = create_xDai_chain(self.wallet) self.idChain = create_idChain_chain(self.wallet) self.test_chain = create_test_chain(self.wallet) - GlobalSettings.objects.create(weekly_chain_claim_limit=2) + GlobalSettings.objects.create(gastap_round_claim_limit=2) def test_get_claimed_should_be_zero(self): - credit_strategy_xdai = WeeklyCreditStrategy(self.x_dai, self.new_user) - credit_strategy_id_chain = WeeklyCreditStrategy(self.idChain, self.new_user) + credit_strategy_xdai = RoundCreditStrategy(self.x_dai, self.new_user) + credit_strategy_id_chain = RoundCreditStrategy(self.idChain, self.new_user) self.assertEqual(credit_strategy_xdai.get_claimed(), 0) self.assertEqual(credit_strategy_id_chain.get_claimed(), 0) @@ -259,8 +258,8 @@ def test_x_dai_claimed_be_zero_eth_be_100(self): amount=claim_amount, ) - credit_strategy_xdai = WeeklyCreditStrategy(self.x_dai, self.new_user) - credit_strategy_id_chain = WeeklyCreditStrategy(self.idChain, self.new_user) + credit_strategy_xdai = RoundCreditStrategy(self.x_dai, self.new_user) + credit_strategy_id_chain = RoundCreditStrategy(self.idChain, self.new_user) self.assertEqual(credit_strategy_xdai.get_claimed(), 0) self.assertEqual(credit_strategy_id_chain.get_claimed(), claim_amount) @@ -268,7 +267,7 @@ def test_x_dai_claimed_be_zero_eth_be_100(self): self.assertEqual(credit_strategy_id_chain.get_unclaimed(), eidi_max_claim - claim_amount) def test_claim_manager_fail_if_claim_amount_exceeds_unclaimed(self): - claim_manager_x_dai = SimpleClaimManager(WeeklyCreditStrategy(self.x_dai, self.new_user)) + claim_manager_x_dai = SimpleClaimManager(RoundCreditStrategy(self.x_dai, self.new_user)) try: claim_manager_x_dai.claim(x_dai_max_claim + 10) @@ -282,7 +281,7 @@ def test_claim_manager_fail_if_claim_amount_exceeds_unclaimed(self): ) def test_claim_unverified_user_should_fail(self): claim_amount = 100 - claim_manager_x_dai = SimpleClaimManager(WeeklyCreditStrategy(self.x_dai, self.new_user)) + claim_manager_x_dai = SimpleClaimManager(RoundCreditStrategy(self.x_dai, self.new_user)) try: claim_manager_x_dai.claim(claim_amount) @@ -396,7 +395,7 @@ def setUp(self) -> None: self.password = "test" self._address = "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9" - GlobalSettings.objects.create(weekly_chain_claim_limit=2) + GlobalSettings.objects.create(gastap_round_claim_limit=2) LightningConfig.objects.create( period=86800, period_max_cap=100, current_round=int(int(time.time()) / 86800) * 86800 ) @@ -566,15 +565,15 @@ def setUp(self) -> None: self.password = "test" self._address = "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9" - GlobalSettings.objects.create(weekly_chain_claim_limit=2) + GlobalSettings.objects.create(gastap_round_claim_limit=2) self.user_profile = create_new_user(self._address) self.client.force_authenticate(user=self.user_profile.user) - self.strategy = WeeklyCreditStrategy(self.test_chain, self.user_profile) + self.strategy = RoundCreditStrategy(self.test_chain, self.user_profile) def test_last_monday(self): now = timezone.now() - last_monday = WeeklyCreditStrategy.get_last_monday() + last_monday = RoundCreditStrategy.get_start_of_the_round() self.assertGreaterEqual(now, last_monday) def create_claim_receipt(self, date, amount=10): @@ -591,7 +590,7 @@ def create_claim_receipt(self, date, amount=10): ) def test_last_week_claims(self): - last_monday = WeeklyCreditStrategy.get_last_monday() + last_monday = RoundCreditStrategy.get_start_of_the_round() last_sunday = last_monday - datetime.timedelta(days=1) tuesday = last_monday + datetime.timedelta(days=1) wednesday = last_monday + datetime.timedelta(days=2) @@ -606,7 +605,7 @@ def test_last_week_claims(self): self.assertEqual(total_claimed, 30) def test_unclaimed(self): - last_monday = WeeklyCreditStrategy.get_last_monday() + last_monday = RoundCreditStrategy.get_start_of_the_round() last_sunday = last_monday - datetime.timedelta(days=1) tuesday = last_monday + datetime.timedelta(days=1) diff --git a/faucet/urls.py b/faucet/urls.py index d4d2449e..a58a80e2 100644 --- a/faucet/urls.py +++ b/faucet/urls.py @@ -1,25 +1,24 @@ from django.urls import path +from drf_yasg import openapi +from drf_yasg.views import get_schema_view from faucet.views import ( + ChainBalanceView, ChainListView, ClaimCountView, ClaimMaxView, + DonationReceiptView, + GetTotalWeeklyClaimsRemainingView, GlobalSettingsView, LastClaimView, + LeaderboardView, ListClaims, - GetTotalWeeklyClaimsRemainingView, + SmallChainListView, + UserLeaderboardView, artwork_video, error500, - ChainBalanceView, - SmallChainListView, - DonationReceiptView, - LeaderboardView, - UserLeaderboardView ) -from drf_yasg.views import get_schema_view -from drf_yasg import openapi - schema_view = get_schema_view( openapi.Info( title="BrightID Gas Faucet API", @@ -42,9 +41,7 @@ path("user/last-claim/", LastClaimView.as_view(), name="last-claim"), path("user/claims/", ListClaims.as_view(), name="claims"), path("claims/count/", ClaimCountView.as_view(), name="claims-count"), - path( - "chain/list/", ChainListView.as_view(), name="chain-list" - ), # can have auth token for more user specific info + path("chain/list/", ChainListView.as_view(), name="chain-list"), # can have auth token for more user specific info path("chain/small-list/", SmallChainListView.as_view(), name="small-chain-list"), path( "chain//claim-max/", @@ -67,5 +64,5 @@ ), path("user/donation/", DonationReceiptView.as_view(), name="donation-receipt"), path("gas-tap/leaderboard/", LeaderboardView.as_view(), name="gas-tap-leaderboard"), - path("user/gas-tap/leaderboard/", UserLeaderboardView.as_view(), name="user-gas-tap-leaderboard") + path("user/gas-tap/leaderboard/", UserLeaderboardView.as_view(), name="user-gas-tap-leaderboard"), ] diff --git a/faucet/views.py b/faucet/views.py index 8548dc7a..0d91a139 100644 --- a/faucet/views.py +++ b/faucet/views.py @@ -1,41 +1,42 @@ import json -from django.http import FileResponse +import logging import os + import rest_framework.exceptions -from django.http import Http404 +from django.conf import settings +from django.contrib.postgres.expressions import ArraySubquery +from django.db.models import FloatField, OuterRef, Subquery, Sum +from django.db.models.functions import Cast +from django.http import FileResponse, Http404, HttpResponse +from django.urls import reverse from rest_framework.generics import ( - RetrieveAPIView, ListAPIView, - ListCreateAPIView, get_object_or_404, + ListCreateAPIView, + RetrieveAPIView, + get_object_or_404, ) +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from django.http import HttpResponse -from rest_framework.permissions import IsAuthenticated -from django.urls import reverse from authentication.models import UserProfile, Wallet +from core.filters import ChainFilterBackend, IsOwnerFilterBackend +from core.paginations import StandardResultsSetPagination from faucet.faucet_manager.claim_manager import ( ClaimManagerFactory, LimitedChainClaimManager, ) -from faucet.faucet_manager.claim_manager import WeeklyCreditStrategy -from faucet.models import Chain, ClaimReceipt, GlobalSettings, DonationReceipt +from faucet.faucet_manager.credit_strategy import RoundCreditStrategy +from faucet.models import Chain, ClaimReceipt, DonationReceipt, GlobalSettings from faucet.serializers import ( ChainBalanceSerializer, + ChainSerializer, + DonationReceiptSerializer, GlobalSettingsSerializer, + LeaderboardSerializer, ReceiptSerializer, - ChainSerializer, SmallChainSerializer, - DonationReceiptSerializer, LeaderboardSerializer, ) -from core.paginations import StandardResultsSetPagination -from core.filters import ChainFilterBackend, IsOwnerFilterBackend -# import BASE_DIR from django settings -from django.conf import settings -from django.db.models import FloatField, Sum, OuterRef, Subquery, Window, F, Count -from django.db.models.functions import Cast -from django.contrib.postgres.expressions import ArraySubquery class CustomException(Exception): @@ -55,11 +56,7 @@ class LastClaimView(RetrieveAPIView): def get_object(self): user_profile = self.request.user.profile try: - return ( - ClaimReceipt.objects.filter(user_profile=user_profile) - .order_by("pk") - .last() - ) + return ClaimReceipt.objects.filter(user_profile=user_profile).order_by("pk").last() except ClaimReceipt.DoesNotExist: raise Http404("Claim Receipt for this user does not exist") @@ -83,7 +80,7 @@ def get_queryset(self): ClaimReceipt.PENDING, ClaimReceipt.REJECTED, ], - datetime__gte=WeeklyCreditStrategy.get_last_monday(), + datetime__gte=RoundCreditStrategy.get_start_of_the_round(), ).order_by("-pk") @@ -98,10 +95,7 @@ def get(self, request, *args, **kwargs): user_profile = request.user.profile gs = GlobalSettings.objects.first() if gs is not None: - result = ( - gs.weekly_chain_claim_limit - - LimitedChainClaimManager.get_total_weekly_claims(user_profile) - ) + result = gs.weekly_chain_claim_limit - LimitedChainClaimManager.get_total_weekly_claims(user_profile) return Response({"total_weekly_claims_remaining": result}, status=200) else: raise Http404("Global Settings Not Found") @@ -119,9 +113,7 @@ class ChainListView(ListAPIView): def get_queryset(self): queryset = Chain.objects.filter(is_active=True, show_in_gastap=True) - sorted_queryset = sorted( - queryset, key=lambda obj: obj.total_claims_since_last_round, reverse=True - ) + sorted_queryset = sorted(queryset, key=lambda obj: obj.total_claims_since_last_round, reverse=True) return sorted_queryset @@ -169,13 +161,11 @@ def wallet_address_is_set(self): chain = self.get_chain() try: - _wallet = Wallet.objects.get( - user_profile=self.get_user(), wallet_type=chain.chain_type - ) + Wallet.objects.get(user_profile=self.get_user(), wallet_type=chain.chain_type) return True, None except Exception as e: + logging.error("wallet address not set", e) raise CustomException("wallet address not set") - # return Response({"message": "wallet address not set"}, status=403) def get_chain(self) -> Chain: chain_pk = self.kwargs.get("chain_pk", None) @@ -194,7 +184,7 @@ def claim_max(self, passive_address) -> ClaimReceipt: assert max_credit > 0 return manager.claim(max_credit, passive_address=passive_address) except AssertionError as e: - # return Response({"message": "no credit left"}, status=403) + logging.error("no credit left for user", e) raise CustomException("no credit left") except ValueError as e: raise rest_framework.exceptions.APIException(e) @@ -228,7 +218,7 @@ class DonationReceiptView(ListCreateAPIView): def get_serializer_context(self): context = super().get_serializer_context() - context.update({'user': self.get_user()}) + context.update({"user": self.get_user()}) return context def get_queryset(self): @@ -249,20 +239,24 @@ def get_user(self) -> UserProfile: def get_object(self): queryset = self.filter_queryset(self.get_queryset()) - queryset = queryset.filter(status=ClaimReceipt.VERIFIED) \ - .annotate( - total_price_float=Cast('total_price', FloatField())).values('user_profile') \ - .annotate( - sum_total_price=Sum('total_price_float')) + queryset = ( + queryset.filter(status=ClaimReceipt.VERIFIED) + .annotate(total_price_float=Cast("total_price", FloatField())) + .values("user_profile") + .annotate(sum_total_price=Sum("total_price_float")) + ) user_obj = get_object_or_404(queryset, user_profile=self.get_user().pk) - user_rank = queryset.filter(sum_total_price__gt=user_obj.get('sum_total_price')).count() + 1 - user_obj['rank'] = user_rank - user_obj['username'] = self.get_user().username - user_obj['wallet'] = self.get_user().wallets.all()[0].address - interacted_chains = list(DonationReceipt.objects.filter( - user_profile=self.get_user()).filter(status=ClaimReceipt.VERIFIED).values_list( - 'chain', flat=True).distinct()) - user_obj['interacted_chains'] = interacted_chains + user_rank = queryset.filter(sum_total_price__gt=user_obj.get("sum_total_price")).count() + 1 + user_obj["rank"] = user_rank + user_obj["username"] = self.get_user().username + user_obj["wallet"] = self.get_user().wallets.all()[0].address + interacted_chains = list( + DonationReceipt.objects.filter(user_profile=self.get_user()) + .filter(status=ClaimReceipt.VERIFIED) + .values_list("chain", flat=True) + .distinct() + ) + user_obj["interacted_chains"] = interacted_chains return user_obj @@ -275,15 +269,22 @@ class LeaderboardView(ListAPIView): def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) - donation_receipt = queryset.filter(status=ClaimReceipt.VERIFIED).annotate( - total_price_float=Cast('total_price', FloatField())).values('user_profile').annotate( - sum_total_price=Sum('total_price_float')).order_by('-sum_total_price') - subquery_interacted_chains = DonationReceipt.objects.filter( - user_profile=OuterRef('user_profile')).filter(status=ClaimReceipt.VERIFIED).values_list( - 'chain', flat=True).distinct() + donation_receipt = ( + queryset.filter(status=ClaimReceipt.VERIFIED) + .annotate(total_price_float=Cast("total_price", FloatField())) + .values("user_profile") + .annotate(sum_total_price=Sum("total_price_float")) + .order_by("-sum_total_price") + ) + subquery_interacted_chains = ( + DonationReceipt.objects.filter(user_profile=OuterRef("user_profile")) + .filter(status=ClaimReceipt.VERIFIED) + .values_list("chain", flat=True) + .distinct() + ) queryset = donation_receipt.annotate(interacted_chains=ArraySubquery(subquery_interacted_chains)) - subquery_username = UserProfile.objects.filter(pk=OuterRef('user_profile')).values('username') - subquery_wallet = Wallet.objects.filter(user_profile=OuterRef('user_profile')).values('address') + subquery_username = UserProfile.objects.filter(pk=OuterRef("user_profile")).values("username") + subquery_wallet = Wallet.objects.filter(user_profile=OuterRef("user_profile")).values("address") queryset = queryset.annotate(username=Subquery(subquery_username), wallet=Subquery(subquery_wallet)) page = self.paginate_queryset(queryset) if page is not None: @@ -295,7 +296,7 @@ def list(self, request, *args, **kwargs): def artwork_video(request): - video_file = os.path.join(settings.BASE_DIR, f"faucet/artwork.mp4") + video_file = os.path.join(settings.BASE_DIR, "faucet/artwork.mp4") return FileResponse(open(video_file, "rb"), content_type="video/mp4") @@ -305,7 +306,9 @@ def artwork_view(request, token_id): response = { "name": "Unitap Pass", - "description": "Unitap is an onboarding tool for networks and communities and a gateway for users to web3. https://unitap.app . Unitap Pass is a VIP pass for Unitap. Holders will enjoy various benefits as Unitap grows.", + "description": "Unitap is an onboarding tool for networks and communities and a gateway for users to web3." + " https://unitap.app . Unitap Pass is a VIP pass for Unitap. Holders" + " will enjoy various benefits as Unitap grows.", "image": artwork_video_url, "animation_url": artwork_video_url, } diff --git a/permissions/admin.py b/permissions/admin.py index 7933cfe0..030a68e4 100644 --- a/permissions/admin.py +++ b/permissions/admin.py @@ -1,31 +1,31 @@ -from django.contrib import admin -from .models import * +# from django.contrib import admin +# from .models import * -# Register your models here. +# # Register your models here. -class BrightIDMeetVerificationAdmin(admin.ModelAdmin): - list_display = ["pk", "name"] +# class BrightIDMeetVerificationAdmin(admin.ModelAdmin): +# list_display = ["pk", "name"] -class BrightIDAuraVerificationAdmin(admin.ModelAdmin): - list_display = ["pk", "name"] +# class BrightIDAuraVerificationAdmin(admin.ModelAdmin): +# list_display = ["pk", "name"] -class OncePerWeekVerificationAdmin(admin.ModelAdmin): - list_display = ["pk", "name"] +# class OncePerWeekVerificationAdmin(admin.ModelAdmin): +# list_display = ["pk", "name"] -class OncePerMonthVerificationAdmin(admin.ModelAdmin): - list_display = ["pk", "name"] +# class OncePerMonthVerificationAdmin(admin.ModelAdmin): +# list_display = ["pk", "name"] -class OnceInALifeTimeVerificationAdmin(admin.ModelAdmin): - list_display = ["pk", "name"] +# class OnceInALifeTimeVerificationAdmin(admin.ModelAdmin): +# list_display = ["pk", "name"] -admin.site.register(BrightIDMeetVerification, BrightIDMeetVerificationAdmin) -admin.site.register(BrightIDAuraVerification, BrightIDAuraVerificationAdmin) -admin.site.register(OncePerWeekVerification, OncePerWeekVerificationAdmin) -admin.site.register(OncePerMonthVerification, OncePerMonthVerificationAdmin) -admin.site.register(OnceInALifeTimeVerification, OnceInALifeTimeVerificationAdmin) +# admin.site.register(BrightIDMeetVerification, BrightIDMeetVerificationAdmin) +# admin.site.register(BrightIDAuraVerification, BrightIDAuraVerificationAdmin) +# admin.site.register(OncePerWeekVerification, OncePerWeekVerificationAdmin) +# admin.site.register(OncePerMonthVerification, OncePerMonthVerificationAdmin) +# admin.site.register(OnceInALifeTimeVerification, OnceInALifeTimeVerificationAdmin) diff --git a/permissions/migrations/0006_remove_brightidmeetverification_permission_ptr_and_more.py b/permissions/migrations/0006_remove_brightidmeetverification_permission_ptr_and_more.py new file mode 100644 index 00000000..0ae46b1b --- /dev/null +++ b/permissions/migrations/0006_remove_brightidmeetverification_permission_ptr_and_more.py @@ -0,0 +1,51 @@ +# Generated by Django 4.0.4 on 2023-10-26 21:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('permissions', '0005_onceinalifetimeverification'), + ] + + operations = [ + migrations.RemoveField( + model_name='brightidmeetverification', + name='permission_ptr', + ), + migrations.RemoveField( + model_name='onceinalifetimeverification', + name='permission_ptr', + ), + migrations.RemoveField( + model_name='oncepermonthverification', + name='permission_ptr', + ), + migrations.RemoveField( + model_name='onceperweekverification', + name='permission_ptr', + ), + migrations.RemoveField( + model_name='permission', + name='polymorphic_ctype', + ), + migrations.DeleteModel( + name='BrightIDAuraVerification', + ), + migrations.DeleteModel( + name='BrightIDMeetVerification', + ), + migrations.DeleteModel( + name='OnceInALifeTimeVerification', + ), + migrations.DeleteModel( + name='OncePerMonthVerification', + ), + migrations.DeleteModel( + name='OncePerWeekVerification', + ), + migrations.DeleteModel( + name='Permission', + ), + ] diff --git a/permissions/migrations/0007_initial.py b/permissions/migrations/0007_initial.py new file mode 100644 index 00000000..44aafc09 --- /dev/null +++ b/permissions/migrations/0007_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 4.0.4 on 2023-10-26 21:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('permissions', '0006_remove_brightidmeetverification_permission_ptr_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Permission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(blank=True, null=True)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='BrightIDAuraVerification', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='permissions.permission')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('permissions.permission',), + ), + migrations.CreateModel( + name='BrightIDMeetVerification', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='permissions.permission')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('permissions.permission',), + ), + migrations.CreateModel( + name='OnceInALifeTimeVerification', + fields=[ + ('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='permissions.permission')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('permissions.permission',), + ), + ] diff --git a/permissions/models.py b/permissions/models.py index 61ffc1fb..1a80cc56 100644 --- a/permissions/models.py +++ b/permissions/models.py @@ -1,11 +1,8 @@ from django.db import models from polymorphic.models import PolymorphicModel -from faucet.faucet_manager.credit_strategy import WeeklyCreditStrategy - class Permission(PolymorphicModel): - name = models.CharField(max_length=200) description = models.TextField(null=True, blank=True) @@ -33,28 +30,28 @@ def response(self): return "You must be Aura verified on BrightID to claim this token." -class OncePerWeekVerification(Permission): - def is_valid(self, user_profile, *args, **kwargs): - token_distribution = kwargs.get("token_distribution") - return not token_distribution.claims.filter( - user_profile=user_profile, - created_at__gte=WeeklyCreditStrategy.get_last_monday(), - ).exists() - - def response(self): - return "You have already claimed this token this week" +# class OncePerWeekVerification(Permission): +# def is_valid(self, user_profile, *args, **kwargs): +# token_distribution = kwargs.get("token_distribution") +# return not token_distribution.claims.filter( +# user_profile=user_profile, +# created_at__gte=WeeklyCreditStrategy.get_last_monday(), +# ).exists() +# def response(self): +# return "You have already claimed this token this week" -class OncePerMonthVerification(Permission): - def is_valid(self, user_profile, *args, **kwargs): - token_distribution = kwargs.get("token_distribution") - return not token_distribution.claims.filter( - user_profile=user_profile, - created_at__gte=WeeklyCreditStrategy.get_first_day_of_the_month(), - ).exists() - def response(self): - return "You have already claimed this token this month" +# class OncePerMonthVerification(Permission): +# def is_valid(self, user_profile, *args, **kwargs): +# token_distribution = kwargs.get("token_distribution") +# return not token_distribution.claims.filter( +# user_profile=user_profile, +# created_at__gte=WeeklyCreditStrategy.get_first_day_of_the_month(), +# ).exists() + +# def response(self): +# return "You have already claimed this token this month" class OnceInALifeTimeVerification(Permission): diff --git a/permissions/serializers.py b/permissions/serializers.py index e815efb4..1e89ca06 100644 --- a/permissions/serializers.py +++ b/permissions/serializers.py @@ -1,35 +1,35 @@ -from rest_framework import serializers -from rest_polymorphic.serializers import PolymorphicSerializer +# from rest_framework import serializers +# from rest_polymorphic.serializers import PolymorphicSerializer -from permissions.models import ( - Permission, - BrightIDMeetVerification, - BrightIDAuraVerification, -) +# from permissions.models import ( +# Permission, +# BrightIDMeetVerification, +# BrightIDAuraVerification, +# ) -class PermissionBaseSerializer(serializers.ModelSerializer): - class Meta: - model = Permission - fields = ["id", "name", "description"] +# class PermissionBaseSerializer(serializers.ModelSerializer): +# class Meta: +# model = Permission +# fields = ["id", "name", "description"] -class BrightIDMeetVerificationSerializer(PermissionBaseSerializer): - class Meta: - model = BrightIDMeetVerification - fields = ["id", "name", "description"] +# class BrightIDMeetVerificationSerializer(PermissionBaseSerializer): +# class Meta: +# model = BrightIDMeetVerification +# fields = ["id", "name", "description"] -class BrightIDAuraVerificationSerializer(PermissionBaseSerializer): - class Meta: - model = BrightIDAuraVerification - fields = ["id", "name", "description"] +# class BrightIDAuraVerificationSerializer(PermissionBaseSerializer): +# class Meta: +# model = BrightIDAuraVerification +# fields = ["id", "name", "description"] -class PermissionSerializer(PolymorphicSerializer): - model_serializer_mapping = { - Permission: PermissionBaseSerializer, - BrightIDMeetVerification: BrightIDMeetVerificationSerializer, - BrightIDAuraVerification: BrightIDAuraVerificationSerializer, - } +# class PermissionSerializer(PolymorphicSerializer): +# model_serializer_mapping = { +# Permission: PermissionBaseSerializer, +# BrightIDMeetVerification: BrightIDMeetVerificationSerializer, +# BrightIDAuraVerification: BrightIDAuraVerificationSerializer, +# } diff --git a/permissions/tests.py b/permissions/tests.py index 23086d7d..3487ab0b 100644 --- a/permissions/tests.py +++ b/permissions/tests.py @@ -1,29 +1,28 @@ -from rest_framework.test import APITestCase -from .models import * +# from rest_framework.test import APITestCase +# from .models import * -class PermissionsTestCase(APITestCase): - def setUp(self) -> None: - pass +# class PermissionsTestCase(APITestCase): +# def setUp(self) -> None: +# pass - def test_permissions_creation(self): - p = BrightIDMeetVerification.objects.create(name="BrightID Meet Verification") - self.assertEqual(Permission.objects.count(), 1) - self.assertEqual(Permission.objects.first(), p) +# def test_permissions_creation(self): +# p = BrightIDMeetVerification.objects.create(name="BrightID Meet Verification") +# self.assertEqual(Permission.objects.count(), 1) +# self.assertEqual(Permission.objects.first(), p) - p2 = BrightIDAuraVerification.objects.create(name="BrightID Aura Verification") - self.assertEqual(Permission.objects.count(), 2) - self.assertEqual(Permission.objects.last(), p2) +# p2 = BrightIDAuraVerification.objects.create(name="BrightID Aura Verification") +# self.assertEqual(Permission.objects.count(), 2) +# self.assertEqual(Permission.objects.last(), p2) - p3 = OncePerWeekVerification.objects.create(name="Once Per Week Verification") - self.assertEqual(Permission.objects.count(), 3) - self.assertEqual(Permission.objects.last(), p3) +# p3 = OncePerWeekVerification.objects.create(name="Once Per Week Verification") +# self.assertEqual(Permission.objects.count(), 3) +# self.assertEqual(Permission.objects.last(), p3) - p4 = OncePerMonthVerification.objects.create(name="Once Per Month Verification") - self.assertEqual(Permission.objects.count(), 4) - self.assertEqual(Permission.objects.last(), p4) +# p4 = OncePerMonthVerification.objects.create(name="Once Per Month Verification") +# self.assertEqual(Permission.objects.count(), 4) +# self.assertEqual(Permission.objects.last(), p4) - p5 = OnceInALifeTimeVerification.objects.create(name="Once In A Life Time Verification") - self.assertEqual(Permission.objects.count(), 5) - self.assertEqual(Permission.objects.last(), p5) - +# p5 = OnceInALifeTimeVerification.objects.create(name="Once In A Life Time Verification") +# self.assertEqual(Permission.objects.count(), 5) +# self.assertEqual(Permission.objects.last(), p5) diff --git a/prizetap/migrations/0003_raffle_permissions.py b/prizetap/migrations/0003_raffle_permissions.py index 201b70e2..9461baf4 100644 --- a/prizetap/migrations/0003_raffle_permissions.py +++ b/prizetap/migrations/0003_raffle_permissions.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): dependencies = [ - ('permissions', '0004_oncepermonthverification'), ('prizetap', '0002_raffle_is_prize_nft'), ] diff --git a/tokenTap/constraints.py b/tokenTap/constraints.py index 5e76522f..626ab7c1 100644 --- a/tokenTap/constraints.py +++ b/tokenTap/constraints.py @@ -1,16 +1,14 @@ -from core.constraints import * +from core.constraints import ConstraintVerification from core.utils import TimeUtils -from faucet.constraints import OptimismHasClaimedGasInThisRound from faucet.models import ClaimReceipt - -class OncePerWeekVerification(ConstraintVerification): - def is_observed(self, *args, **kwargs): - token_distribution = kwargs["token_distribution"] - return not token_distribution.claims.filter( - user_profile=self.user_profile, - created_at__gte=TimeUtils.get_last_monday(), - ).exists() +# class OncePerWeekVerification(ConstraintVerification): +# def is_observed(self, *args, **kwargs): +# token_distribution = kwargs["token_distribution"] +# return not token_distribution.claims.filter( +# user_profile=self.user_profile, +# created_at__gte=TimeUtils.get_first_day_of_the_month(), +# ).exists() class OncePerMonthVerification(ConstraintVerification): diff --git a/tokenTap/helpers.py b/tokenTap/helpers.py index b7d0f254..eb6fe373 100644 --- a/tokenTap/helpers.py +++ b/tokenTap/helpers.py @@ -1,11 +1,13 @@ import random -from web3 import Web3, Account -from faucet.faucet_manager.credit_strategy import WeeklyCreditStrategy -from faucet.models import GlobalSettings -from .models import TokenDistributionClaim -from authentication.models import NetworkTypes -from faucet.models import WalletAccount + from eth_account.messages import encode_defunct +from web3 import Account, Web3 + +from authentication.models import NetworkTypes +from faucet.faucet_manager.credit_strategy import RoundCreditStrategy +from faucet.models import GlobalSettings, WalletAccount + +from .models import TokenDistributionClaim def create_uint32_random_nonce(): @@ -19,7 +21,6 @@ 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], @@ -30,7 +31,6 @@ def hash_message(user, token, amount, nonce): def sign_hashed_message(hashed_message): - private_key = WalletAccount.objects.get(network_type=NetworkTypes.EVM).private_key account = Account.from_key(private_key) @@ -43,7 +43,7 @@ def has_weekly_credit_left(user_profile): return ( TokenDistributionClaim.objects.filter( user_profile=user_profile, - created_at__gte=WeeklyCreditStrategy.get_last_monday(), + created_at__gte=RoundCreditStrategy.get_start_of_the_round(), ).count() - < GlobalSettings.objects.first().tokentap_weekly_claim_limit + < GlobalSettings.objects.first().tokentap_round_claim_limit ) diff --git a/tokenTap/migrations/0002_tokendistribution_permissions.py b/tokenTap/migrations/0002_tokendistribution_permissions.py index 3666fc02..a1698c55 100644 --- a/tokenTap/migrations/0002_tokendistribution_permissions.py +++ b/tokenTap/migrations/0002_tokendistribution_permissions.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): dependencies = [ - ('permissions', '0001_initial'), ('tokenTap', '0001_initial'), ] diff --git a/tokenTap/models.py b/tokenTap/models.py index 57b3dbd5..310fedf6 100644 --- a/tokenTap/models.py +++ b/tokenTap/models.py @@ -1,16 +1,21 @@ -from django.utils import timezone +from django.core.cache import cache from django.db import models +from django.utils import timezone + from authentication.models import NetworkTypes, UserProfile -from faucet.models import Chain, ClaimReceipt -from faucet.constraints import OptimismHasClaimedGasInThisRound from core.models import UserConstraint -from .constraints import * -from django.core.cache import cache +from faucet.constraints import OptimismHasClaimedGasInThisRound +from faucet.models import Chain, ClaimReceipt + +from .constraints import ( + OnceInALifeTimeVerification, + OncePerMonthVerification, + TimeUtils, +) class Constraint(UserConstraint): constraints = UserConstraint.constraints + [ - OncePerWeekVerification, OncePerMonthVerification, OnceInALifeTimeVerification, OptimismHasClaimedGasInThisRound, @@ -31,9 +36,7 @@ class TokenDistribution(models.Model): token = models.CharField(max_length=100) token_address = models.CharField(max_length=255) amount = models.BigIntegerField() - chain = models.ForeignKey( - Chain, on_delete=models.CASCADE, related_name="token_distribution" - ) + chain = models.ForeignKey(Chain, on_delete=models.CASCADE, related_name="token_distribution") permissions = models.ManyToManyField(Constraint, blank=True) @@ -73,11 +76,10 @@ def total_claims_since_last_round(self): ) if cached_total_claims_since_last_round: return cached_total_claims_since_last_round - from faucet.faucet_manager.claim_manager import WeeklyCreditStrategy total_claims_since_last_round = TokenDistributionClaim.objects.filter( token_distribution=self, - created_at__gte=TimeUtils.get_second_last_monday(), + created_at__gte=TimeUtils.get_first_day_of_last_month(), status__in=[ClaimReceipt.VERIFIED, ClaimReceipt.PENDING], ).count() cache.set( @@ -92,12 +94,8 @@ def __str__(self): class TokenDistributionClaim(models.Model): - token_distribution = models.ForeignKey( - TokenDistribution, on_delete=models.CASCADE, related_name="claims" - ) - user_profile = models.ForeignKey( - UserProfile, on_delete=models.CASCADE, related_name="tokentap_claims" - ) + token_distribution = models.ForeignKey(TokenDistribution, on_delete=models.CASCADE, related_name="claims") + user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name="tokentap_claims") created_at = models.DateTimeField(auto_now_add=True, editable=True) notes = models.TextField(null=True, blank=True) @@ -105,9 +103,7 @@ class TokenDistributionClaim(models.Model): signature = models.CharField(max_length=1024, blank=True, null=True) nonce = models.BigIntegerField(null=True, blank=True) - status = models.CharField( - max_length=30, choices=ClaimReceipt.states, default=ClaimReceipt.PENDING - ) + status = models.CharField(max_length=30, choices=ClaimReceipt.states, default=ClaimReceipt.PENDING) tx_hash = models.CharField(max_length=255, null=True, blank=True) diff --git a/tokenTap/serializers.py b/tokenTap/serializers.py index e772d17f..06ae3fe8 100644 --- a/tokenTap/serializers.py +++ b/tokenTap/serializers.py @@ -1,8 +1,23 @@ -from faucet.serializers import SmallChainSerializer from rest_framework import serializers + +from core.constraints import ( # noqa: F401 + BrightIDAuraVerification, + BrightIDMeetVerification, +) from core.serializers import UserConstraintBaseSerializer -from tokenTap.models import * -from .constraints import * +from faucet.serializers import SmallChainSerializer +from tokenTap.models import ( + Constraint, + TokenDistribution, + TokenDistributionClaim, + UserConstraint, +) + +from .constraints import ( # noqa: F401 + ConstraintVerification, + OnceInALifeTimeVerification, + OncePerMonthVerification, +) class ConstraintSerializer(UserConstraintBaseSerializer, serializers.ModelSerializer): diff --git a/tokenTap/tests.py b/tokenTap/tests.py index 23265e63..0b5bd35a 100644 --- a/tokenTap/tests.py +++ b/tokenTap/tests.py @@ -4,18 +4,9 @@ from django.contrib.auth.models import User from django.urls import reverse from django.utils import timezone - -# from permissions.models import ( -# BrightIDAuraVerification, -# BrightIDMeetVerification, -# OncePerWeekVerification, -# OncePerMonthVerification, -# OnceInALifeTimeVerification, -# ) from rest_framework.test import APITestCase, override_settings from authentication.models import NetworkTypes, UserProfile, Wallet -from faucet.faucet_manager.credit_strategy import WeeklyCreditStrategy from faucet.models import ( Chain, ClaimReceipt, @@ -217,7 +208,6 @@ def setUp(self) -> None: ) self.permission1 = Constraint.objects.create(name="BrightIDMeetVerification", title="BrightID Meet", type="VER") self.permission2 = Constraint.objects.create(name="BrightIDAuraVerification", title="BrightID Aura", type="VER") - self.permission3 = Constraint.objects.create(name="OncePerWeekVerification", title="Once per Week", type="TIME") self.permission4 = Constraint.objects.create( name="OncePerMonthVerification", title="Once per Month", type="TIME" ) @@ -225,7 +215,7 @@ def setUp(self) -> None: name="OnceInALifeTimeVerification", title="Once per Lifetime", type="TIME" ) - self.td.permissions.set([self.permission1, self.permission2, self.permission3, self.permission4]) + self.td.permissions.set([self.permission1, self.permission2, self.permission4]) self.btc_td = TokenDistribution.objects.create( name="Test Distribution", @@ -322,29 +312,29 @@ def test_token_distribution_not_claimable_already_claimed(self): # response.data["detail"], "You have already claimed this token this week" # ) - @patch( - "authentication.helpers.BrightIDSoulboundAPIInterface.get_verification_status", - lambda a, b, c: (True, None), - ) - def test_token_distribution_not_claimable_already_claimed_month(self): - tdc = TokenDistributionClaim.objects.create( - user_profile=self.user_profile, - token_distribution=self.td, - # Claimed 2 weeks ago - created_at=WeeklyCreditStrategy.get_first_day_of_the_month(), - ) - tdc.created_at = WeeklyCreditStrategy.get_first_day_of_the_month() - tdc.save() - - self.client.force_authenticate(user=self.user_profile.user) - response = self.client.post( - reverse("token-distribution-claim", kwargs={"pk": self.td.pk}), - ) - - self.assertEqual(response.status_code, 403) - # self.assertEqual( - # response.data["detail"], "You have already claimed this token this month" - # ) + # @patch( + # "authentication.helpers.BrightIDSoulboundAPIInterface.get_verification_status", + # lambda a, b, c: (True, None), + # ) + # def test_token_distribution_not_claimable_already_claimed_month(self): + # tdc = TokenDistributionClaim.objects.create( + # user_profile=self.user_profile, + # token_distribution=self.td, + # # Claimed 2 weeks ago + # created_at=WeeklyCreditStrategy.get_first_day_of_the_month(), + # ) + # tdc.created_at = WeeklyCreditStrategy.get_first_day_of_the_month() + # tdc.save() + + # self.client.force_authenticate(user=self.user_profile.user) + # response = self.client.post( + # reverse("token-distribution-claim", kwargs={"pk": self.td.pk}), + # ) + + # self.assertEqual(response.status_code, 403) + # # self.assertEqual( + # # response.data["detail"], "You have already claimed this token this month" + # # ) @patch( "authentication.helpers.BrightIDSoulboundAPIInterface.get_verification_status", @@ -357,7 +347,7 @@ def test_token_distribution_not_claimable_false_permissions(self): self.assertEqual(response.status_code, 403) def test_token_distribution_not_claimable_weekly_credit_limit_reached(self): - self.global_settings.tokentap_weekly_claim_limit = 0 + self.global_settings.tokentap_round_claim_limit = 0 self.global_settings.save() self.client.force_authenticate(user=self.user_profile.user) diff --git a/tokenTap/views.py b/tokenTap/views.py index 0d6c8a95..20100018 100644 --- a/tokenTap/views.py +++ b/tokenTap/views.py @@ -1,16 +1,23 @@ import json -from django.shortcuts import get_object_or_404 +import logging + import rest_framework.exceptions -from rest_framework.generics import CreateAPIView, RetrieveAPIView, ListAPIView -from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated +from django.shortcuts import get_object_or_404 +from django.utils import timezone +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema from rest_framework.exceptions import PermissionDenied -from django.urls import reverse -from authentication.models import NetworkTypes, UserProfile, Wallet -from authentication.serializers import MessageResponseSerializer -from faucet.models import Chain, ClaimReceipt, GlobalSettings -from permissions.models import Permission +from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveAPIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response from rest_framework.views import APIView + +from authentication.models import NetworkTypes +from core.constraints import ( # noqa: F401 + BrightIDAuraVerification, + BrightIDMeetVerification, +) +from faucet.models import ClaimReceipt from tokenTap.models import TokenDistribution, TokenDistributionClaim from tokenTap.serializers import ( ConstraintSerializer, @@ -19,16 +26,18 @@ TokenDistributionClaimSerializer, TokenDistributionSerializer, ) -from django.utils import timezone -from drf_yasg.utils import swagger_auto_schema -from drf_yasg import openapi + +from .constraints import ( # noqa: F401 + ConstraintVerification, + OnceInALifeTimeVerification, + OncePerMonthVerification, +) from .helpers import ( create_uint32_random_nonce, + has_weekly_credit_left, hash_message, sign_hashed_message, - has_weekly_credit_left, ) -from .constraints import * class TokenDistributionListView(ListAPIView): @@ -38,9 +47,7 @@ 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 @@ -50,9 +57,7 @@ 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(): @@ -63,15 +68,11 @@ 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={ @@ -150,7 +151,7 @@ def post(self, request, *args, **kwargs): signature=lightning_invoice, token_distribution=token_distribution, ) - gas_tap_claim = ClaimReceipt.objects.create( + ClaimReceipt.objects.create( chain=token_distribution.chain, user_profile=user_profile, datetime=timezone.now(), @@ -172,11 +173,14 @@ class GetTokenDistributionConstraintsView(APIView): permission_classes = [IsAuthenticated] def get(self, request, td_id): + # from .constraints import + user_profile = request.user.profile td = get_object_or_404(TokenDistribution, pk=td_id) try: param_values = json.loads(td.constraint_params) - except: + except Exception as e: + logging.error("Error parsing constraint params", e) param_values = {} response_constraints = [] @@ -191,13 +195,9 @@ 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): @@ -210,18 +210,12 @@ 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() @@ -247,6 +241,4 @@ 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)