Пример #1
0
    def handle(self, *args, **options):

        # setup
        payment_threshold_usd = 0
        KYC_THRESHOLD = settings.GRANTS_PAYOUT_CLR_KYC_THRESHOLD
        network = 'mainnet' if not settings.DEBUG else 'rinkeby'
        from_address = settings.GRANTS_PAYOUT_ADDRESS
        from_pk = settings.GRANTS_PAYOUT_PRIVATE_KEY
        DECIMALS = 18
        what = options['what']
        clr_round = options['clr_round']
        DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f' if network=='mainnet' else '0x6a6e8b58dee0ca4b4ee147ad72d3ddd2ef1bf6f7'
        CLR_TOKEN_ADDRESS = '0xe4101d014443af2b7f6f9f603e904adc9faf0de5' if network=='mainnet' else '0xc19b694ebd4309d7a2adcd9970f8d7f424a1528b'

        # get data
        clr_pks = options['clr_pks'].split(',')
        gclrs = GrantCLR.objects.filter(pk__in=clr_pks)
        pks = []
        for gclr in gclrs:
            pks += gclr.grants.values_list('pk', flat=True)
        scheduled_matches = CLRMatch.objects.filter(round_number=clr_round)
        grants = Grant.objects.filter(active=True, network='mainnet', link_to_new_grant__isnull=True, pk__in=pks)
        print(f"got {grants.count()} grants")

        # finalize rankings
        if what == 'finalize':
            total_owed_grants = 0
            for grant in grants:
                try:
                    for gclr in grant.clr_calculations.filter(grantclr__in=gclrs, latest=True):
                        total_owed_grants += gclr.clr_prediction_curve[0][1]
                except:
                    pass
            total_owed_matches = sum(sm.amount for sm in scheduled_matches)
            print(f"there are {grants.count()} grants to finalize worth ${round(total_owed_grants,2)}")
            print(f"there are {scheduled_matches.count()} Match Payments already created worth ${round(total_owed_matches,2)}")
            print('------------------------------')
            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return
            for grant in grants:
                amount = sum(ele.clr_prediction_curve[0][1] for ele in grant.clr_calculations.filter(grantclr__in=gclrs, latest=True))
                has_already_kyc = grant.clr_matches.filter(has_passed_kyc=True).exists()
                if not amount:
                    continue
                already_exists = scheduled_matches.filter(grant=grant).exists()
                if already_exists:
                    continue
                needs_kyc = amount > KYC_THRESHOLD and not has_already_kyc
                comments = "" if not needs_kyc else "Needs KYC"
                ready_for_test_payout = not needs_kyc
                match = CLRMatch.objects.create(
                    round_number=clr_round,
                    amount=amount,
                    grant=grant,
                    comments=comments,
                    ready_for_test_payout=ready_for_test_payout,
                    )
                if needs_kyc:
                    grant_match_distribution_kyc(match)


        # payout rankings (round must be finalized first)
        if what in ['prepare_final_payout']:
            payout_matches = scheduled_matches.exclude(test_payout_tx='').filter(ready_for_payout=False)
            payout_matches_amount = sum(sm.amount for sm in payout_matches)
            print(f"there are {payout_matches.count()} UNPAID Match Payments already created worth ${round(payout_matches_amount,2)} {network} DAI")
            print('------------------------------')
            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return
            for match in payout_matches:
                match.ready_for_payout=True
                match.save()
            print('promoted')


        # payout rankings (round must be finalized first)
        if what in ['payout_test', 'payout_dai']:
            is_real_payout = what == 'payout_dai'
            TOKEN_ADDRESS = DAI_ADDRESS if is_real_payout else CLR_TOKEN_ADDRESS
            kwargs = {}
            token_name = f'CLR{clr_round}' if not is_real_payout else 'DAI'
            key = 'ready_for_test_payout' if not is_real_payout else 'ready_for_payout'
            kwargs[key] = False
            not_ready_scheduled_matches = scheduled_matches.filter(**kwargs)
            kwargs[key] = True
            kwargs2 = {}
            key2 = 'test_payout_tx' if not is_real_payout else 'payout_tx'
            kwargs2[key2] = ''
            unpaid_scheduled_matches = scheduled_matches.filter(**kwargs).filter(**kwargs2)
            paid_scheduled_matches = scheduled_matches.filter(**kwargs).exclude(**kwargs2)
            total_not_ready_matches = sum(sm.amount for sm in not_ready_scheduled_matches)
            total_owed_matches = sum(sm.amount for sm in unpaid_scheduled_matches)
            total_paid_matches = sum(sm.amount for sm in paid_scheduled_matches)
            print(f"there are {not_ready_scheduled_matches.count()} NOT READY Match Payments already created worth ${round(total_not_ready_matches,2)} {network} {token_name}")
            print(f"there are {unpaid_scheduled_matches.count()} UNPAID Match Payments already created worth ${round(total_owed_matches,2)} {network} {token_name}")
            print(f"there are {paid_scheduled_matches.count()} PAID Match Payments already created worth ${round(total_paid_matches,2)} {network} {token_name}")
            print('------------------------------')
            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return

            print(f"continuing with {unpaid_scheduled_matches.count()} unpaid scheduled payouts")

            if is_real_payout:
                user_input = input(F"THIS IS A REAL PAYOUT FOR {network} {token_name}.  ARE YOU DOUBLE SECRET SUPER SURE? (y/n) ")
                if user_input != 'y':
                    return

            for match in unpaid_scheduled_matches.order_by('amount'):

                # issue payment
                print(f"- issuing payout {match.pk} worth {match.amount} {token_name}")
                address = match.grant.admin_address
                amount_owed = match.amount

                w3 = get_web3(network)
                contract = w3.eth.contract(Web3.toChecksumAddress(TOKEN_ADDRESS), abi=abi)
                address = Web3.toChecksumAddress(address)

                amount = int(amount_owed * 10**DECIMALS)
                tx_args = {
                    'nonce': w3.eth.getTransactionCount(from_address),
                    'gas': 100000,
                    'gasPrice': int(float(recommend_min_gas_price_to_confirm_in_time(1)) * 10**9 * 1.4)
                }
                tx = contract.functions.transfer(address, amount).buildTransaction(tx_args)

                signed = w3.eth.account.signTransaction(tx, from_pk)
                tx_id = None
                success = False
                counter = 0
                while not success:
                    try:
                        tx_id = w3.eth.sendRawTransaction(signed.rawTransaction).hex()
                        success = True
                    except Exception as e:
                        counter +=1
                        if 'replacement transaction underpriced' in str(e):
                            print(f'replacement transaction underpriced. retrying {counter}')
                            time.sleep(WAIT_TIME_BETWEEN_PAYOUTS)
                        elif 'nonce too low' in str(e):
                            print(f'nonce too low. retrying {counter}')
                            time.sleep(WAIT_TIME_BETWEEN_PAYOUTS)

                            # rebuild txn
                            tx_args['nonce'] = w3.eth.getTransactionCount(from_address)
                            tx = contract.functions.transfer(address, amount).buildTransaction(tx_args)
                            signed = w3.eth.account.signTransaction(tx, from_pk)
                        else:
                            raise e

                if not tx_id:
                    print("cannot pay advance, did not get a txid")
                    continue

                print("paid via", tx_id)

                # make save state to DB
                if is_real_payout:
                    match.payout_tx = tx_id
                else:
                    match.test_payout_tx = tx_id
                match.save()

                # wait for tx to clear
                while not has_tx_mined(tx_id, network):
                    time.sleep(1)

                # make save state to DB
                if is_real_payout:
                    match.payout_tx_date = timezone.now()
                    grant_match_distribution_final_txn(match)
                else:
                    match.test_payout_tx_date = timezone.now()
                    grant_match_distribution_test_txn(match)
                match.save()

                # create payout obj artifacts
                profile = Profile.objects.get(handle__iexact='gitcoinbot')
                validator_comment = f"created by ingest payout_round_script"
                subscription = Subscription()
                subscription.is_postive_vote = True
                subscription.active = False
                subscription.error = True
                subscription.contributor_address = 'N/A'
                subscription.amount_per_period = match.amount
                subscription.real_period_seconds = 2592000
                subscription.frequency = 30
                subscription.frequency_unit = 'N/A'
                subscription.token_address = TOKEN_ADDRESS
                subscription.token_symbol = token_name
                subscription.gas_price = 0
                subscription.new_approve_tx_id = '0x0'
                subscription.num_tx_approved = 1
                subscription.network = network
                subscription.contributor_profile = profile
                subscription.grant = match.grant
                subscription.comments = validator_comment
                subscription.amount_per_period_usdt = match.amount if is_real_payout else 0
                subscription.save()

                contrib = Contribution.objects.create(
                    success=True,
                    tx_cleared=True,
                    tx_override=True,
                    tx_id=tx_id,
                    subscription=subscription,
                    validator_passed=True,
                    validator_comment=validator_comment,
                    )
                print(f"ingested {subscription.pk} / {contrib.pk}")

                if is_real_payout:
                    match.payout_contribution = contrib
                else:
                    match.test_payout_contribution = contrib
                match.save()

                metadata = {
                    'id': subscription.id,
                    'value_in_token': str(subscription.amount_per_period),
                    'value_in_usdt_now': str(round(subscription.amount_per_period_usdt,2)),
                    'token_name': subscription.token_symbol,
                    'title': subscription.grant.title,
                    'grant_url': subscription.grant.url,
                    'num_tx_approved': subscription.num_tx_approved,
                    'category': 'grant',
                }
                kwargs = {
                    'profile': profile,
                    'subscription': subscription,
                    'grant': subscription.grant,
                    'activity_type': 'new_grant_contribution',
                    'metadata': metadata,
                }

                activity = Activity.objects.create(**kwargs)

                if is_real_payout:
                    comment = f"CLR Round {clr_round} Payout"
                    comment = Comment.objects.create(profile=profile, activity=activity, comment=comment)

                print("SLEEPING")
                time.sleep(WAIT_TIME_BETWEEN_PAYOUTS)
                print("DONE SLEEPING")
Пример #2
0
def process_grant_contribution(self,
                               grant_id,
                               grant_slug,
                               profile_id,
                               package,
                               retry: bool = True) -> None:
    """
    :param self:
    :param grant_id:
    :param grant_slug:
    :param profile_id:
    :param package:
    :return:
    """
    grant = Grant.objects.get(pk=grant_id)
    profile = Profile.objects.get(pk=profile_id)

    if 'contributor_address' in package:
        subscription = Subscription()

        if grant.negative_voting_enabled:
            #is_postive_vote = True if package.get('is_postive_vote', 1) else False
            is_postive_vote = package.get('match_direction', '+') == '+'
        else:
            is_postive_vote = True
        subscription.is_postive_vote = is_postive_vote

        fee_pct = float(package.get('gitcoin-grant-input-amount', 0))

        subscription.active = False
        subscription.contributor_address = package.get('contributor_address',
                                                       '')
        subscription.amount_per_period = package.get('amount_per_period', 0)
        subscription.real_period_seconds = package.get('real_period_seconds',
                                                       2592000)
        subscription.frequency = package.get('frequency', 30)
        subscription.frequency_unit = package.get('frequency_unit', 'days')
        subscription.token_address = package.get('token_address', '')
        subscription.token_symbol = package.get('token_symbol', '')
        subscription.gas_price = (float(subscription.amount_per_period) *
                                  (fee_pct / 100))
        subscription.new_approve_tx_id = package.get('sub_new_approve_tx_id',
                                                     '0x0')
        subscription.split_tx_id = package.get('split_tx_id', '0x0')
        subscription.num_tx_approved = package.get('num_tx_approved', 1)
        subscription.network = package.get('network', '')
        subscription.contributor_profile = profile
        subscription.grant = grant
        subscription.comments = package.get('comment', '')
        subscription.save()

        # one time payments
        activity = None
        if int(subscription.num_tx_approved) == 1:
            subscription.successful_contribution(
                subscription.new_approve_tx_id)
            subscription.error = True  #cancel subs so it doesnt try to bill again
            subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent"
            subscription.save()
            activity = record_subscription_activity_helper(
                'new_grant_contribution', subscription, profile)
        else:
            activity = record_subscription_activity_helper(
                'new_grant_subscription', subscription, profile)

        if 'comment' in package:
            _profile = profile
            comment = package.get('comment')
            if comment and activity:
                if subscription and subscription.negative:
                    _profile = Profile.objects.filter(
                        handle='gitcoinbot').first()
                    comment = f"Comment from contributor: {comment}"
                comment = Comment.objects.create(profile=_profile,
                                                 activity=activity,
                                                 comment=comment)

        if 'hide_wallet_address' in package:
            profile.hide_wallet_address = bool(
                package.get('hide_wallet_address', False))
            profile.save()

        new_supporter(grant, subscription)
        thank_you_for_supporting(grant, subscription)

        update_grant_metadata.delay(grant_id)
Пример #3
0
to = to_address
grant = Grant.objects.filter(admin_address__iexact=to).order_by(
    '-positive_round_contributor_count').first()

#ingest data
currency = symbol
amount = value
usd_val = amount * convert_token_to_usdt(symbol)

# convert formats
date = timezone.now()

# create objects
validator_comment = f"created by ingest grant txn script"
subscription = Subscription()
subscription.is_postive_vote = True
subscription.active = False
subscription.error = True
subscription.contributor_address = '/NA'
subscription.amount_per_period = amount
subscription.real_period_seconds = 2592000
subscription.frequency = 30
subscription.frequency_unit = 'N/A'
subscription.token_address = '0x0'
subscription.token_symbol = currency
subscription.gas_price = 0
subscription.new_approve_tx_id = '0x0'
subscription.num_tx_approved = 1
subscription.network = 'mainnet'
subscription.contributor_profile = profile
subscription.grant = grant
Пример #4
0
    def handle(self, *args, **kwargs):
        import csv
        with open('../scripts/input/givingblock_txns.csv',
                  newline='',
                  encoding="utf-8") as csvfile:
            reader = csv.reader(csvfile, delimiter=',', quotechar='"')
            for row in reader:

                #ingest data
                date = row[0]
                grant = row[1]
                _ = row[2]
                currency = row[3]
                amount = row[4]
                txid = row[5]
                user = row[6]
                usd_val = row[7]

                # convert formats
                try:
                    date = date.split(" ")[0]
                    date = timezone.datetime.strptime(date, '%m/%d/%Y')
                    grant = grant.replace('®', '')
                    grant = Grant.objects.get(title__icontains=grant.strip())
                    profile = Profile.objects.get(handle__iexact=user)

                    # create objects
                    validator_comment = ",".join(row)
                    validator_comment = f"created by ingest givingblock_txns script: {validator_comment}"
                    subscription = Subscription()
                    subscription.is_postive_vote = True
                    subscription.active = False
                    subscription.error = True
                    subscription.contributor_address = '/NA'
                    subscription.amount_per_period = amount
                    subscription.real_period_seconds = 2592000
                    subscription.frequency = 30
                    subscription.frequency_unit = 'N/A'
                    subscription.token_address = '0x0'
                    subscription.token_symbol = currency
                    subscription.gas_price = 0
                    subscription.new_approve_tx_id = '0x0'
                    subscription.num_tx_approved = 1
                    subscription.network = 'mainnet'
                    subscription.contributor_profile = profile
                    subscription.grant = grant
                    subscription.comments = validator_comment
                    subscription.amount_per_period_usdt = usd_val
                    subscription.save()

                    contrib = Contribution.objects.create(
                        success=True,
                        tx_cleared=True,
                        tx_override=True,
                        tx_id=txid,
                        subscription=subscription,
                        validator_passed=True,
                        validator_comment=validator_comment,
                    )
                    print(f"ingested {subscription.pk} / {contrib.pk}")

                    metadata = {
                        'id':
                        subscription.id,
                        'value_in_token':
                        str(subscription.amount_per_period),
                        'value_in_usdt_now':
                        str(round(subscription.amount_per_period_usdt, 2)),
                        'token_name':
                        subscription.token_symbol,
                        'title':
                        subscription.grant.title,
                        'grant_url':
                        subscription.grant.url,
                        'num_tx_approved':
                        subscription.num_tx_approved,
                        'category':
                        'grant',
                    }
                    kwargs = {
                        'profile': profile,
                        'subscription': subscription,
                        'grant': subscription.grant,
                        'activity_type': 'new_grant_contribution',
                        'metadata': metadata,
                    }

                    Activity.objects.create(**kwargs)

                except Exception as e:
                    print(e)
Пример #5
0
    def save_data(self, profile, txid, network, created_on, symbol, value_adjusted, grant, checkout_type):
        """
        Creates contribution and subscription and saves it to database if no matching one exists
        """
        currency = symbol
        amount = value_adjusted
        usd_val = amount * convert_token_to_usdt(symbol)

        # Check that subscription with these parameters does not exist
        existing_subscriptions = Subscription.objects.filter(
            grant__pk=grant.pk, contributor_profile=profile, split_tx_id=txid, token_symbol=currency
        )
        for existing_subscription in existing_subscriptions:
            tolerance = 0.01  # 1% tolerance to account for floating point
            amount_max = amount * (1 + tolerance)
            amount_min = amount * (1 - tolerance)

            if (
                existing_subscription.amount_per_period_minus_gas_price > amount_min
                and existing_subscription.amount_per_period_minus_gas_price < amount_max
            ):
                # Subscription exists
                print("Subscription exists, exiting function\n")
                return

        # No subscription found, so create subscription and contribution
        try:
            # create objects
            validator_comment = f"created by ingest grant txn script"
            subscription = Subscription()
            subscription.is_postive_vote = True
            subscription.active = False
            subscription.error = True
            subscription.contributor_address = "N/A"
            subscription.amount_per_period = amount
            subscription.real_period_seconds = 2592000
            subscription.frequency = 30
            subscription.frequency_unit = "N/A"
            subscription.token_address = "0x0"
            subscription.token_symbol = currency
            subscription.gas_price = 0
            subscription.new_approve_tx_id = "0x0"
            subscription.num_tx_approved = 1
            subscription.network = network
            subscription.contributor_profile = profile
            subscription.grant = grant
            subscription.comments = validator_comment
            subscription.amount_per_period_usdt = usd_val
            subscription.created_on = created_on
            subscription.last_contribution_date = created_on
            subscription.next_contribution_date = created_on
            subscription.split_tx_id = txid
            subscription.save()

            # Create contribution and set the contribution as successful
            contrib = subscription.successful_contribution(
                '0x0', # subscription.new_approve_tx_id,
                True, # include_for_clr
                checkout_type=checkout_type
            )
            contrib.success=True
            contrib.tx_cleared=True
            contrib.tx_override=True
            contrib.validator_comment = validator_comment
            contrib.created_on = created_on
            contrib.save()
            print(f"ingested {subscription.pk} / {contrib.pk}")

            metadata = {
                "id": subscription.id,
                "value_in_token": str(subscription.amount_per_period),
                "value_in_usdt_now": str(round(subscription.amount_per_period_usdt, 2)),
                "token_name": subscription.token_symbol,
                "title": subscription.grant.title,
                "grant_url": subscription.grant.url,
                "num_tx_approved": subscription.num_tx_approved,
                "category": "grant",
            }
            kwargs = {
                "profile": profile,
                "subscription": subscription,
                "grant": subscription.grant,
                "activity_type": "new_grant_contribution",
                "metadata": metadata,
            }

            Activity.objects.create(**kwargs)
            print("Saved!\n")

        except Exception as e:
            print(e)
            print("\n")
Пример #6
0
    def handle(self, *args, **options):

        # Parse inputs
        what = options['what']
        network = options['network']
        process_all = options['process_all']

        valid_whats = [
            'finalize', 'payout_test', 'prepare_final_payout', 'verify',
            'set_payouts_test', 'set_payouts'
        ]
        if what not in valid_whats:
            raise Exception(f"Invalid value {what} for 'what' arg")
        if network not in ['rinkeby', 'mainnet']:
            raise Exception(f"Invalid value {network} for 'network' arg")
        if not options['clr_round'] or not options['clr_pks']:
            raise Exception('Must provide clr_round and clr_pks')

        # Define parameters that vary by network. The expected total DAI amount uses the value here
        # if one is not available in the database
        from_block = 11466409 if network == 'mainnet' else 7731622  # block contract was deployed at
        dai_address = '0x6B175474E89094C44Da98b954EedeAC495271d0F' if network == 'mainnet' else '0x2e055eEe18284513B993dB7568A592679aB13188'
        expected_total_dai_amount = 100_000 if network == 'mainnet' else 5000  # in dollars, not wei, e.g. 500 = 500e18

        # Get contract instances
        PROVIDER = "wss://" + network + ".infura.io/ws/v3/" + settings.INFURA_V3_PROJECT_ID
        w3 = Web3(Web3.WebsocketProvider(PROVIDER))

        match_payouts = w3.eth.contract(address=match_payouts_address,
                                        abi=match_payouts_abi)
        dai = w3.eth.contract(address=dai_address, abi=erc20_abi)

        # Setup
        clr_round = options['clr_round']
        clr_pks = options['clr_pks'].split(',')
        KYC_THRESHOLD = settings.GRANTS_PAYOUT_CLR_KYC_THRESHOLD

        # Get data
        gclrs = GrantCLR.objects.filter(pk__in=clr_pks)
        pks = []
        for gclr in gclrs:
            pks += gclr.grants.values_list('pk', flat=True)
        scheduled_matches = CLRMatch.objects.filter(round_number=clr_round)
        grants = Grant.objects.filter(active=True,
                                      network='mainnet',
                                      is_clr_eligible=True,
                                      link_to_new_grant__isnull=True,
                                      pk__in=pks)
        print(f"got {grants.count()} grants")

        # Finalize rankings ------------------------------------------------------------------------
        if what == 'finalize':
            total_owed_grants = 0
            for grant in grants:
                try:
                    for gclr in grant.clr_calculations.filter(
                            grantclr__in=gclrs, latest=True):
                        total_owed_grants += gclr.clr_prediction_curve[0][1]
                except:
                    pass
            total_owed_matches = sum(sm.amount for sm in scheduled_matches)
            print(
                f"there are {grants.count()} grants to finalize worth ${round(total_owed_grants,2)}"
            )
            print(
                f"there are {scheduled_matches.count()} Match Payments already created worth ${round(total_owed_matches,2)}"
            )
            print('------------------------------')
            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return
            for grant in grants:
                amount = sum(ele.clr_prediction_curve[0][1]
                             for ele in grant.clr_calculations.filter(
                                 grantclr__in=gclrs, latest=True))
                has_already_kyc = grant.clr_matches.filter(
                    has_passed_kyc=True).exists()
                if not amount:
                    continue
                already_exists = scheduled_matches.filter(grant=grant).exists()
                if already_exists:
                    continue
                needs_kyc = amount > KYC_THRESHOLD and not has_already_kyc
                comments = "" if not needs_kyc else "Needs KYC"
                ready_for_test_payout = not needs_kyc
                match = CLRMatch.objects.create(
                    round_number=clr_round,
                    amount=amount,
                    grant=grant,
                    comments=comments,
                    ready_for_test_payout=ready_for_test_payout,
                )
                if needs_kyc:
                    grant_match_distribution_kyc(match)

        # Payout rankings (round must be finalized first) ------------------------------------------
        if what in ['prepare_final_payout']:
            payout_matches = scheduled_matches.filter(ready_for_payout=False)
            payout_matches_amount = sum(sm.amount for sm in payout_matches)
            print(
                f"there are {payout_matches.count()} UNPAID Match Payments already created worth ${round(payout_matches_amount,2)} {network} DAI"
            )
            print('------------------------------')
            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return
            for match in payout_matches:
                match.ready_for_payout = True
                match.save()
            print('promoted')

        # Set payouts (round must be finalized first) ----------------------------------------------
        if what in ['set_payouts_test', 'set_payouts']:
            is_real_payout = what == 'set_payouts'

            kwargs = {}
            token_name = 'DAI'
            key = 'ready_for_test_payout' if not is_real_payout else 'ready_for_payout'
            kwargs[key] = False
            not_ready_scheduled_matches = scheduled_matches.filter(**kwargs)
            kwargs[key] = True
            kwargs2 = {}
            key2 = 'test_payout_tx' if not is_real_payout else 'payout_tx'
            kwargs2[key2] = ''
            unpaid_scheduled_matches = scheduled_matches.filter(
                **kwargs).filter(**kwargs2)
            paid_scheduled_matches = scheduled_matches.filter(
                **kwargs).exclude(**kwargs2)
            total_not_ready_matches = sum(
                sm.amount for sm in not_ready_scheduled_matches)
            total_owed_matches = sum(sm.amount
                                     for sm in unpaid_scheduled_matches)
            total_paid_matches = sum(sm.amount
                                     for sm in paid_scheduled_matches)
            print(
                f"there are {not_ready_scheduled_matches.count()} NOT READY Match Payments already created worth ${round(total_not_ready_matches,2)} {network} {token_name}"
            )
            print(
                f"there are {unpaid_scheduled_matches.count()} UNPAID Match Payments already created worth ${round(total_owed_matches,2)} {network} {token_name}"
            )
            print(
                f"there are {paid_scheduled_matches.count()} PAID Match Payments already created worth ${round(total_paid_matches,2)} {network} {token_name}"
            )
            print('------------------------------')
            target_matches = unpaid_scheduled_matches if not process_all else scheduled_matches
            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return

            print(
                f"continuing with {target_matches.count()} unpaid scheduled payouts"
            )

            if is_real_payout:
                user_input = input(
                    F"THIS IS A REAL PAYOUT FOR {network} {token_name}.  ARE YOU DOUBLE SECRET SUPER SURE? (y/n) "
                )
                if user_input != 'y':
                    return

            # Generate dict of payout mapping that we'll use to set the contract's payout mapping
            full_payouts_mapping_dict = {}
            for match in target_matches.order_by('amount'):
                # Amounts to set
                recipient = w3.toChecksumAddress(match.grant.admin_address)
                amount = Decimal(match.amount) * SCALE  # convert to wei

                # This ensures that even when multiple grants have the same receiving address,
                # all match funds are accounted for
                if recipient in full_payouts_mapping_dict.keys():
                    full_payouts_mapping_dict[recipient] += amount
                else:
                    full_payouts_mapping_dict[recipient] = amount

            # Convert dict to array to use it as inputs to the contract
            full_payouts_mapping = []
            for key, value in full_payouts_mapping_dict.items():
                full_payouts_mapping.append([key, str(int(value))])
            total_amount = sum(int(ele[1]) for ele in full_payouts_mapping)

            # In tests, it took 68,080 gas to set 2 payout values. Let's be super conservative
            # and say it's 50k gas per payout mapping. If we are ok using 6M gas per transaction,
            # that means we can set 6M / 50k = 120 payouts per transaction. So we chunk the
            # payout mapping into sub-arrays with max length of 120 each
            # KO 12/21 - edited with Matt to make 2.1x that
            def chunks(lst, n):
                """Yield successive n-sized chunks from lst. https://stackoverflow.com/a/312464"""
                for i in range(0, len(lst), n):
                    yield lst[i:i + n]

            chunk_size = 250 if not settings.DEBUG else 120
            chunked_payouts_mapping = chunks(full_payouts_mapping, chunk_size)
            # Set payouts
            from_address = settings.GRANTS_PAYOUT_ADDRESS
            from_pk = settings.GRANTS_PAYOUT_PRIVATE_KEY
            for payout_mapping in chunked_payouts_mapping:

                #tx = match_payouts.functions.setPayouts(payout_mapping).buildTransaction(tx_args)

                print(
                    f"#TODO: Send this txn view etherscan {match_payouts_address}"
                )
                print(json.dumps(payout_mapping))

                # Pause until the next one
                print("SLEEPING")
                time.sleep(WAIT_TIME_BETWEEN_TXS)
                print("DONE SLEEPING")

            user_input = input("continue? (y/n) ")
            if user_input != 'y':
                return

            tx_id = input("enter a txid:  ")

            # All payouts have been successfully set, so now we update the database
            for match in target_matches.order_by('amount'):
                # make save state to DB
                if is_real_payout:
                    match.payout_tx = tx_id
                    match.payout_tx_date = timezone.now()
                    grant_match_distribution_final_txn(match, True)
                else:
                    match.test_payout_tx = tx_id
                    match.test_payout_tx_date = timezone.now()
                    #grant_match_distribution_test_txn(match)
                match.save()

                # create payout obj artifacts
                profile = Profile.objects.get(handle__iexact='gitcoinbot')
                validator_comment = f"created by ingest payout_round_script"
                subscription = Subscription()
                subscription.is_postive_vote = True
                subscription.active = False
                subscription.error = True
                subscription.contributor_address = 'N/A'
                subscription.amount_per_period = match.amount
                subscription.real_period_seconds = 2592000
                subscription.frequency = 30
                subscription.frequency_unit = 'N/A'
                subscription.token_address = dai_address
                subscription.token_symbol = token_name
                subscription.gas_price = 0
                subscription.new_approve_tx_id = '0x0'
                subscription.num_tx_approved = 1
                subscription.network = network
                subscription.contributor_profile = profile
                subscription.grant = match.grant
                subscription.comments = validator_comment
                subscription.amount_per_period_usdt = match.amount if is_real_payout else 0
                subscription.save()

                contrib = Contribution.objects.create(
                    success=True,
                    tx_cleared=True,
                    tx_override=True,
                    tx_id=tx_id,
                    subscription=subscription,
                    validator_passed=True,
                    validator_comment=validator_comment,
                )
                print(f"ingested {subscription.pk} / {contrib.pk}")

                if is_real_payout:
                    match.payout_contribution = contrib
                else:
                    match.test_payout_contribution = contrib
                match.save()

                metadata = {
                    'id':
                    subscription.id,
                    'value_in_token':
                    str(subscription.amount_per_period),
                    'value_in_usdt_now':
                    str(round(subscription.amount_per_period_usdt, 2)),
                    'token_name':
                    subscription.token_symbol,
                    'title':
                    subscription.grant.title,
                    'grant_url':
                    subscription.grant.url,
                    'num_tx_approved':
                    subscription.num_tx_approved,
                    'category':
                    'grant',
                }
                kwargs = {
                    'profile': profile,
                    'subscription': subscription,
                    'grant': subscription.grant,
                    'activity_type': 'new_grant_contribution',
                    'metadata': metadata,
                }

                activity = Activity.objects.create(**kwargs)

                if is_real_payout:
                    comment = f"CLR Round {clr_round} Payout"
                    comment = Comment.objects.create(profile=profile,
                                                     activity=activity,
                                                     comment=comment)

        # Verify contract is set properly ----------------------------------------------------------
        if what == 'verify':
            # Get expected total match amount
            total_owed_grants = 0
            for grant in grants:
                try:
                    for gclr in grant.clr_calculations.filter(
                            grantclr__in=gclrs, latest=True):
                        total_owed_grants += gclr.clr_prediction_curve[0][1]
                except:
                    pass
            expected_total_dai_amount = sum(sm.amount
                                            for sm in scheduled_matches)

            # Get PayoutAdded events
            payout_added_filter = match_payouts.events.PayoutAdded.createFilter(
                fromBlock=from_block)
            payout_added_logs = payout_added_filter.get_all_entries(
            )  # print these if you need to inspect them

            # Sort payout logs by ascending block number, this way if a recipient appears in multiple blocks
            # we use the value from the latest block
            sorted_payout_added_logs = sorted(
                payout_added_logs,
                key=lambda log: log['blockNumber'],
                reverse=False)

            # Get total required DAI balance based on PayoutAdded events. Events will be sorted chronologically,
            # so if a recipient is duplicated we only keep the latest entry. We do this by storing our own
            # mapping from recipients to match amount and overwriting it as needed just like the contract would.
            # We keep another dict that maps the recipient's addresses to the block it was found in. If we find
            # two entries for the same user in the same block, we throw, since we don't know which is the
            # correct one
            payment_dict = {}
            user_block_dict = {}

            for log in sorted_payout_added_logs:
                # Parse parameters from logs
                recipient = log['args']['recipient']
                amount = Decimal(log['args']['amount'])
                block = log['blockNumber']

                # Check if recipient's payout has already been set in this block
                if recipient in user_block_dict and user_block_dict[
                        recipient] == block:
                    raise Exception(
                        f'Recipient {recipient} payout was set twice in block {block}, so unclear which to use'
                    )

                # Recipient not seen in this block, so save data
                payment_dict[recipient] = amount
                user_block_dict[recipient] = block

            # Sum up each entry to get the total required amount
            total_dai_required_wei = sum(payment_dict[recipient]
                                         for recipient in payment_dict.keys())

            # Convert to human units
            total_dai_required = total_dai_required_wei / SCALE

            # Verify that total DAI required (from event logs) equals the expected amount
            if round(expected_total_dai_amount, 0) != round(
                    total_dai_required, 0):
                print(
                    '\n* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
                )
                print(
                    'Total DAI payout amount in the contract does not equal the expected value!'
                )
                print('  Total expected amount:  ', expected_total_dai_amount)
                print('  Total amount from logs: ', total_dai_required)
                print(
                    '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n'
                )
                raise Exception(
                    'Total payout amount in the contract does not equal the expected value!'
                )
            print('Total payout amount in the contracts is the expected value')

            # Get contract DAI balance
            dai_balance = Decimal(
                dai.functions.balanceOf(match_payouts_address).call()) / SCALE

            # Verify that contract has sufficient DAI balance to cover all payouts
            print(
                '\n* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
            )
            if dai_balance == total_dai_required:
                print(
                    f'Contract balance of {dai_balance} DAI is exactly equal to the required amount'
                )

            elif dai_balance < total_dai_required:
                shortage = total_dai_required - dai_balance
                print('Contract DAI balance is insufficient')
                print('  Required balance: ', total_dai_required)
                print('  Current balance:  ', dai_balance)
                print('  Extra DAI needed: ', shortage)
                print(f'\n Contract needs another {shortage} DAI')

            elif dai_balance > total_dai_required:
                excess = dai_balance - total_dai_required
                print('Contract has excess DAI balance')
                print('  Required balance:  ', total_dai_required)
                print('  Current balance:   ', dai_balance)
                print('  Excess DAI amount: ', excess)
                print(f'\n Contract has an excess of {excess} DAI')
            print(
                '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n'
            )
Пример #7
0
def process_grant_contribution(self,
                               grant_id,
                               grant_slug,
                               profile_id,
                               package,
                               send_supporter_mail: bool = True,
                               retry: bool = True):
    """
    :param self:
    :param grant_id:
    :param grant_slug:
    :param profile_id:
    :param package:
    :param send_supporter_mail:
    :return:
    """
    from grants.views import record_subscription_activity_helper

    grant = Grant.objects.get(pk=grant_id)
    profile = Profile.objects.get(pk=profile_id)

    if 'contributor_address' in package:
        subscription = Subscription()

        if grant.negative_voting_enabled:
            #is_postive_vote = True if package.get('is_postive_vote', 1) else False
            is_postive_vote = package.get('match_direction', '+') == '+'
        else:
            is_postive_vote = True
        subscription.is_postive_vote = is_postive_vote

        fee_pct = float(package.get('gitcoin-grant-input-amount', 0))

        subscription.active = False
        subscription.contributor_address = package.get('contributor_address',
                                                       '')
        subscription.amount_per_period = package.get('amount_per_period', 0)
        subscription.real_period_seconds = package.get('real_period_seconds',
                                                       2592000)
        subscription.frequency = package.get('frequency', 30)
        subscription.frequency_unit = package.get('frequency_unit', 'days')
        subscription.token_address = package.get('token_address', '')
        subscription.token_symbol = package.get('token_symbol', '')
        subscription.gas_price = (float(subscription.amount_per_period) *
                                  (fee_pct / 100))
        subscription.new_approve_tx_id = package.get('sub_new_approve_tx_id',
                                                     '0x0')
        subscription.split_tx_id = package.get('split_tx_id', '0x0')
        subscription.num_tx_approved = package.get('num_tx_approved', 1)
        subscription.network = package.get('network', '')
        subscription.visitorId = package.get('visitorId', '')
        if subscription.network == 'undefined':
            # we unfortunately cannot trust the frontend to give us a valid network name
            # so this handles that case.  more details are available at
            # https://gitcoincore.slack.com/archives/C01FQV4FX4J/p1607980714026400
            if not settings.DEBUG:
                subscription.network = 'mainnet'
        subscription.contributor_profile = profile
        subscription.grant = grant
        subscription.comments = package.get('comment', '')
        subscription.save()

        value_usdt = subscription.get_converted_amount(True)
        include_for_clr = package.get('include_for_clr')
        if value_usdt < 1 or subscription.contributor_profile.shadowbanned:
            include_for_clr = False

        subscription.successful_contribution(subscription.new_approve_tx_id,
                                             include_for_clr,
                                             checkout_type=package.get(
                                                 'checkout_type', None))

        # one time payments
        activity = None
        subscription.error = True  #cancel subs so it doesnt try to bill again
        subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent"
        subscription.save()

        if 'hide_wallet_address' in package:
            profile.hide_wallet_address = bool(
                package.get('hide_wallet_address', False))
            profile.save()

        if 'anonymize_gitcoin_grants_contributions' in package:
            profile.anonymize_gitcoin_grants_contributions = package.get(
                'anonymize_gitcoin_grants_contributions')
            profile.save()

        activity_profile = profile if not profile.anonymize_gitcoin_grants_contributions else Profile.objects.get(
            handle='gitcoinbot')

        activity = record_subscription_activity_helper(
            'new_grant_contribution', subscription, activity_profile)

        if 'comment' in package:
            _profile = profile
            comment = package.get('comment')
            if value_usdt >= 1 and comment and activity:
                if profile.anonymize_gitcoin_grants_contributions:
                    _profile = Profile.objects.filter(
                        handle='gitcoinbot').first()
                    comment = f"Comment from contributor: {comment}"
                comment = Comment.objects.create(profile=_profile,
                                                 activity=activity,
                                                 comment=comment)

        # emails to contributor
        if value_usdt >= 1 and send_supporter_mail:
            grants_with_subscription = [{
                'grant': grant,
                'subscription': subscription
            }]
            try:
                thank_you_for_supporting(grants_with_subscription)
            except Exception as e:
                logger.exception(e)

        update_grant_metadata.delay(grant_id)
        return grant, subscription
Пример #8
0
def grant_fund(request, grant_id, grant_slug):
    """Handle grant funding."""
    try:
        grant = Grant.objects.get(pk=grant_id, slug=grant_slug)
    except Grant.DoesNotExist:
        raise Http404

    profile = get_profile(request)

    if not grant.active:
        params = {
            'active': 'grant_error',
            'title': _('Fund - Grant Ended'),
            'grant': grant,
            'text': _('This Grant has ended.'),
            'subtext': _('Contributions can no longer be made this grant')
        }
        return TemplateResponse(request, 'grants/shared/error.html', params)

    if is_grant_team_member(grant, profile):
        params = {
            'active': 'grant_error',
            'title': _('Fund - Grant funding blocked'),
            'grant': grant,
            'text': _('This Grant cannot be funded'),
            'subtext': _('Grant team members cannot contribute to their own grant.')
        }
        return TemplateResponse(request, 'grants/shared/error.html', params)

    if grant.link_to_new_grant:
        params = {
            'active': 'grant_error',
            'title': _('Fund - Grant Migrated'),
            'grant': grant.link_to_new_grant,
            'text': f'This Grant has ended',
            'subtext': 'Contributions can no longer be made to this grant. <br> Visit the new grant to contribute.',
            'button_txt': 'View New Grant'
        }
        return TemplateResponse(request, 'grants/shared/error.html', params)

    active_subscription = Subscription.objects.select_related('grant').filter(
        grant=grant_id, active=True, error=False, contributor_profile=request.user.profile, is_postive_vote=True
    )

    if active_subscription:
        params = {
            'active': 'grant_error',
            'title': _('Subscription Exists'),
            'grant': grant,
            'text': _('You already have an active subscription for this grant.')
        }
        return TemplateResponse(request, 'grants/shared/error.html', params)

    if grant.contract_address == '0x0':
        messages.info(
            request,
            _('This grant is not configured to accept funding at this time.  Please contact [email protected] if you believe this message is in error!')
        )
        logger.error(f"Grant {grant.pk} is not properly configured for funding.  Please set grant.contract_address on this grant")
        return redirect(reverse('grants:details', args=(grant.pk, grant.slug)))

    if request.method == 'POST':
        if 'contributor_address' in request.POST:
            subscription = Subscription()

            if grant.negative_voting_enabled:
                #is_postive_vote = True if request.POST.get('is_postive_vote', 1) else False
                is_postive_vote = request.POST.get('match_direction', '+') == '+'
            else:
                is_postive_vote = True
            subscription.is_postive_vote = is_postive_vote

            subscription.active = False
            subscription.contributor_address = request.POST.get('contributor_address', '')
            subscription.amount_per_period = request.POST.get('amount_per_period', 0)
            subscription.real_period_seconds = request.POST.get('real_period_seconds', 2592000)
            subscription.frequency = request.POST.get('frequency', 30)
            subscription.frequency_unit = request.POST.get('frequency_unit', 'days')
            subscription.token_address = request.POST.get('token_address', '')
            subscription.token_symbol = request.POST.get('token_symbol', '')
            subscription.gas_price = request.POST.get('gas_price', 0)
            subscription.new_approve_tx_id = request.POST.get('sub_new_approve_tx_id', '0x0')
            subscription.num_tx_approved = request.POST.get('num_tx_approved', 1)
            subscription.network = request.POST.get('network', '')
            subscription.contributor_profile = profile
            subscription.grant = grant
            subscription.comments = request.POST.get('comment', '')
            subscription.save()

            # one time payments
            activity = None
            if int(subscription.num_tx_approved) == 1:
                subscription.successful_contribution(subscription.new_approve_tx_id);
                subscription.error = True #cancel subs so it doesnt try to bill again
                subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent"
                subscription.save()
                activity = record_subscription_activity_helper('new_grant_contribution', subscription, profile)
            else:
                activity = record_subscription_activity_helper('new_grant_subscription', subscription, profile)

            if 'comment' in request.POST:
                comment = request.POST.get('comment')
                if comment and activity:
                    profile = request.user.profile
                    if subscription and subscription.negative:
                        profile = Profile.objects.filter(handle='gitcoinbot').first()
                        comment = f"Comment from contributor: {comment}"
                    comment = Comment.objects.create(
                        profile=profile,
                        activity=activity,
                        comment=comment)

            message = 'Your contribution has succeeded. Thank you for supporting Public Goods on Gitcoin !'
            if request.session.get('send_notification'):
                msg_html = request.session.get('msg_html')
                cta_text = request.session.get('cta_text')
                cta_url = request.session.get('cta_url')
                to_user = request.user
                send_notification_to_user_from_gitcoinbot(to_user, cta_url, cta_text, msg_html)
            if int(subscription.num_tx_approved) > 1:
                message = 'Your subscription has been created. It will bill within the next 5 minutes or so. Thank you for supporting Public Goods on Gitcoin !'

            messages.info(
                request,
                message
            )

            return JsonResponse({
                'success': True,
            })

        if 'hide_wallet_address' in request.POST:
            profile.hide_wallet_address = bool(request.POST.get('hide_wallet_address', False))
            profile.save()

        if 'signature' in request.POST:
            sub_new_approve_tx_id = request.POST.get('sub_new_approve_tx_id', '')
            subscription = Subscription.objects.filter(new_approve_tx_id=sub_new_approve_tx_id).first()
            subscription.active = True

            subscription.subscription_hash = request.POST.get('subscription_hash', '')
            subscription.contributor_signature = request.POST.get('signature', '')
            if 'split_tx_id' in request.POST:
                subscription.split_tx_id = request.POST.get('split_tx_id', '')
                subscription.save_split_tx_to_contribution()
            if 'split_tx_confirmed' in request.POST:
                subscription.split_tx_confirmed = bool(request.POST.get('split_tx_confirmed', False))
                subscription.save_split_tx_to_contribution()
            subscription.save()

            value_usdt = subscription.get_converted_amount()
            if value_usdt:
                grant.monthly_amount_subscribed += subscription.get_converted_monthly_amount()

            grant.save()
            if not subscription.negative:
                new_supporter(grant, subscription)
                thank_you_for_supporting(grant, subscription)
            return JsonResponse({
                'success': True,
                'url': reverse('grants:details', args=(grant.pk, grant.slug))
            })

    # handle phantom funding
    active_tab = 'normal'
    fund_reward = None
    round_number = clr_round
    can_phantom_fund = request.user.is_authenticated and request.user.groups.filter(name='phantom_funders_round_5').exists() and clr_active
    phantom_funds = PhantomFunding.objects.filter(profile=request.user.profile, round_number=round_number).order_by('created_on').nocache() if request.user.is_authenticated else PhantomFunding.objects.none()
    is_phantom_funding_this_grant = can_phantom_fund and phantom_funds.filter(grant=grant).exists()
    show_tweet_modal = False
    fund_reward = get_fund_reward(request, grant)
    if can_phantom_fund and request.POST.get('toggle_phantom_fund'):
        if is_phantom_funding_this_grant:
            msg = "You are no longer signaling for this grant."
            phantom_funds.filter(grant=grant).delete()
        else:
            msg = "You are now signaling for this grant."
            show_tweet_modal = True
            pt = PhantomFunding.objects.create(grant=grant, profile=request.user.profile, round_number=round_number)
            record_grant_activity_helper('new_grant_contribution', grant, request.user.profile, amount=pt.value, token='DAI')

        messages.info(
            request,
            msg
        )
        is_phantom_funding_this_grant = not is_phantom_funding_this_grant
    images = [
        'new.svg',
        'torchbearer.svg',
        'robots.png',
        'profile/fund.svg',
    ]
    img = random.choice(images)
    params = {
        'profile': profile,
        'active': 'fund_grant',
        'title': matching_live + grant.title + " | Fund Now",
        'card_desc': grant.description,
        'avatar_url': grant.logo.url if grant.logo else None,
        'subscription': {},
        'show_tweet_modal': show_tweet_modal,
        'direction': request.GET.get('direction', '+'),
        'grant_has_no_token': True if grant.token_address == '0x0000000000000000000000000000000000000000' else False,
        'grant': grant,
        'img': img,
        'clr_prediction_curve': [c[1] for c in grant.clr_prediction_curve] if grant.clr_prediction_curve and len(grant.clr_prediction_curve[0]) > 1 else [0, 0, 0, 0, 0, 0],
        'keywords': get_keywords(),
        'recommend_gas_price': recommend_min_gas_price_to_confirm_in_time(4),
        'recommend_gas_price_slow': recommend_min_gas_price_to_confirm_in_time(120),
        'recommend_gas_price_avg': recommend_min_gas_price_to_confirm_in_time(15),
        'recommend_gas_price_fast': recommend_min_gas_price_to_confirm_in_time(1),
        'eth_usd_conv_rate': eth_usd_conv_rate(),
        'conf_time_spread': conf_time_spread(),
        'gas_advisories': gas_advisories(),
        'splitter_contract_address': settings.SPLITTER_CONTRACT_ADDRESS,
        'gitcoin_donation_address': settings.GITCOIN_DONATION_ADDRESS,
        'can_phantom_fund': can_phantom_fund,
        'is_phantom_funding_this_grant': is_phantom_funding_this_grant,
        'active_tab': active_tab,
        'fund_reward': fund_reward,
        'phantom_funds': phantom_funds,
        'clr_round': clr_round,
        'clr_active': clr_active,
        'total_clr_pot': total_clr_pot,
    }
    return TemplateResponse(request, 'grants/fund.html', params)