Exemplo n.º 1
0
def delegated_withdrawal(request):
    signature = request.POST.get("signature")
    wallet_address = request.POST.get("wallet")
    amount = int(request.POST.get("amount"))
    token = request.POST.get("token")
    expiry = int(request.POST.get("expiry"))

    message = [
        crypto.address(wallet_address),
        crypto.address(token),
        crypto.uint256(amount),
        crypto.uint256(expiry)
    ]
    message = crypto.hash_array(message)

    v, r, s = crypto.decode_signature(signature)
    trust = crypto.verify_message_signature(crypto.address(wallet_address),
                                            message, (v, r, s))

    if not trust:
        return Response(data="Signature is invalid", status=400)

    wallet = Wallet.objects.filter(address=remove_0x_prefix(wallet_address),
                                   token__address=token).get()
    nc = NOCUSTContractInterface()

    current_eon = nc.get_current_eon_number()
    wallet_view_context = WalletTransferContext(wallet, None)
    available_amount = wallet_view_context.loosely_available_funds_at_eon(
        current_eon, current_eon, False, False)
    if available_amount < amount:
        return Response(data="Not enough funds", status=400)

    nc.delegated_withdraw(token, wallet_address, amount, expiry,
                          crypto.uint256(r), crypto.uint256(s), v)

    return Response(data="Ok", status=200)
Exemplo n.º 2
0
def place_parallel_withdrawals(test_case: RPCTestCase,
                               token,
                               wallet_address,
                               current_eon,
                               dishonest=False):
    token_commitment = TokenCommitment.objects.get(
        token=token, root_commitment__eon_number=current_eon - 1)

    wallet = Wallet.objects.get(token=token,
                                address=remove_0x_prefix(wallet_address))
    wallet_transfer_context = WalletTransferContext(wallet=wallet,
                                                    transfer=None)
    allotment = wallet_transfer_context.balance_as_of_eon(
        eon_number=current_eon - 1)

    passive_checksum, passive_amount, passive_marker = wallet_transfer_context.get_passive_values(
        eon_number=current_eon - 1)

    available_balance = wallet_transfer_context.loosely_available_funds_at_eon(
        eon_number=current_eon,
        current_eon_number=current_eon,
        is_checkpoint_created=True,
        only_appended=True)

    overdraw = dishonest and available_balance < allotment.amount()

    if overdraw:
        total_draw = max(available_balance, allotment.amount()) // 4
    else:
        total_draw = min(available_balance, allotment.amount()) // 4

    total_amount = 0

    if (total_draw == 0):
        return (total_amount, [], False)

    withdrawal_amounts = [total_draw // 4, total_draw // 2, total_draw // 4]

    for withdrawal_amount in withdrawal_amounts:

        cyan([
            wallet.address, wallet.token.address, withdrawal_amount,
            available_balance
        ])

        test_case.contract_interface.withdraw(
            token_address=wallet.token.address,
            wallet=wallet.address,
            active_state_checksum=crypto.zfill(
                allotment.active_state_checksum()),
            trail=int(allotment.merkle_proof_trail),
            allotment_chain=[
                crypto.zfill(crypto.decode_hex(checksum))
                for checksum in long_string_to_list(
                    allotment.merkle_proof_hashes, 64)
            ],
            membership_chain=[
                crypto.zfill(crypto.decode_hex(checksum))
                for checksum in long_string_to_list(
                    token_commitment.membership_hashes, 64)
            ],
            values=csf_to_list(allotment.merkle_proof_values, int),
            exclusive_allotment_interval=[
                int(allotment.left), int(allotment.right)
            ],
            withdrawal_amount=int(withdrawal_amount),
            passive_checksum=passive_checksum,
            passive_amount=passive_amount,
            passive_marker=passive_marker)

        total_amount += int(withdrawal_amount)

    return (total_amount, withdrawal_amounts, overdraw)
Exemplo n.º 3
0
def should_void_swap(swap: Transfer,
                     wallet_view_context: WalletTransferContext,
                     recipient_view_context: WalletTransferContext,
                     operator_eon_number: int, is_checkpoint_created: bool):
    if not settings.SWAPS_ENABLED:
        logger.error('Swaps disabled. Voiding {}'.format(swap.id))
        return True

    if swap.amount < 1:
        logger.error('Swap {} has less than 1 amount'.format(swap.id))
        return True

    if swap.amount_swapped < 1:
        logger.error('Swap {} has less than 1 amount swapped'.format(swap.id))
        return True

    # Unauthorized transfer
    if swap.sender_active_state is None:
        logger.error('Swap {} no authorization'.format(swap.id))
        return True

    # Invalid signature by sender
    if not swap.sender_active_state.wallet_signature.is_valid():
        logger.error('Swap {} invalid sender signature.'.format(swap.id))
        return True

    # Unreceived transaction
    if swap.recipient_active_state is None:
        logger.error('Swap receipt for {} not provided.'.format(swap.id))
        return True

    # Invalid signature by recipient
    if not swap.recipient_active_state.wallet_signature.is_valid():
        logger.error('Swap {} invalid receipt signature.'.format(swap.id))
        return True

    # Ensure log consistency
    can_append_to_sender_log = wallet_view_context.can_append_transfer()
    if can_append_to_sender_log is not True:
        logger.error('Sender: {}'.format(can_append_to_sender_log))
        return True
    can_append_to_recipient_log = recipient_view_context.can_append_transfer()
    if can_append_to_recipient_log is not True:
        logger.error('Recipient: {}'.format(can_append_to_recipient_log))
        return True

    # Skip consistency checks since they were done at least once before.
    if swap.appended:
        return False

    # Overspending
    sender_funds_remaining = wallet_view_context.loosely_available_funds_at_eon(
        eon_number=swap.eon_number,
        current_eon_number=operator_eon_number,
        is_checkpoint_created=is_checkpoint_created,
        only_appended=True)

    # sender remaining funds should be more than remaining amount in order
    matched_out, matched_in = swap.matched_amounts(all_eons=True)
    if sender_funds_remaining < swap.amount - matched_out:
        logger.error('Swap {} overspending.'.format(swap.id))
        return True

    # Prevent future overdrawing
    # if swap.sender_balance_marker.amount > sender_funds_remaining - swap.amount:
    if swap.sender_balance_marker.amount != 0:
        logger.error('Swap {} invalid concise marker balance.'.format(swap.id))
        return True

    concise_balance_marker = MinimumAvailableBalanceMarker(
        wallet=swap.wallet,
        eon_number=swap.eon_number,
        amount=swap.sender_balance_marker.amount)
    concise_balance_marker_checksum = crypto.hex_value(
        concise_balance_marker.checksum())
    if swap.sender_balance_marker.signature.checksum != concise_balance_marker_checksum:
        logger.error('Swap {} invalid concise marker checksum for {}.'.format(
            swap.id, swap.sender_balance_marker.amount))
        return True

    highest_spendings, highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts(
        eon_number=swap.eon_number, only_appended=True)

    # if this is a multi eon swap
    if Transfer.objects.filter(eon_number=swap.eon_number - 1,
                               tx_id=swap.tx_id).exists():
        # set balances to initial fixed balances stored in transfer eon state
        sender_starting_balance = swap.sender_starting_balance
        recipient_starting_balance = swap.recipient_starting_balance

        # make sure this eon's starting balance is exactly  the initial stored balance
        # when matched amount is taken into consideration for both sender and receiver
        if wallet_view_context.starting_balance_in_eon(
                swap.eon_number) != sender_starting_balance - matched_out:
            logger.error(
                'Swap {} invalid sender starting balance of future state {} != {} - {}.'
                .format(
                    swap.id,
                    wallet_view_context.starting_balance_in_eon(
                        swap.eon_number), sender_starting_balance,
                    matched_out))
        if recipient_view_context.starting_balance_in_eon(
                swap.eon_number) != recipient_starting_balance + matched_in:
            logger.error(
                'Swap {} invalid recipient starting balance of future state {} != {} + {}.'
                .format(
                    swap.id,
                    recipient_view_context.starting_balance_in_eon(
                        swap.eon_number), recipient_starting_balance,
                    matched_out))
        assert (wallet_view_context.starting_balance_in_eon(
            swap.eon_number) == sender_starting_balance - matched_out)
        assert (recipient_view_context.starting_balance_in_eon(
            swap.eon_number) == recipient_starting_balance + matched_in)
    else:
        sender_starting_balance = int(
            wallet_view_context.starting_balance_in_eon(swap.eon_number))
        recipient_starting_balance = int(
            recipient_view_context.starting_balance_in_eon(swap.eon_number))

    # Debit Authorization
    tx_set_tree = wallet_view_context.optimized_authorized_transfers_tree(
        only_appended=True, starting_balance=sender_starting_balance)
    tx_set_hash = crypto.hex_value(tx_set_tree.root_hash())
    transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce)
    transfer_proof = tx_set_tree.proof(transfer_index)

    highest_spendings, highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts(
        eon_number=swap.eon_number, only_appended=True)
    debiting_active_state = ActiveState(wallet=swap.wallet,
                                        updated_spendings=highest_spendings +
                                        swap.amount,
                                        updated_gains=highest_gains,
                                        tx_set_hash=tx_set_hash,
                                        tx_set_proof_hashes=transfer_proof,
                                        tx_set_index=transfer_index,
                                        eon_number=swap.eon_number)

    debiting_active_state_checksum = crypto.hex_value(
        debiting_active_state.checksum())
    if swap.sender_active_state.wallet_signature.checksum != debiting_active_state_checksum:
        logger.error('Swap {} invalid debit active state checksum.'.format(
            swap.id))
        return True

    # Credit Authorization
    tx_set_tree = recipient_view_context.optimized_authorized_transfers_tree(
        only_appended=True, starting_balance=recipient_starting_balance)
    tx_set_hash = crypto.hex_value(tx_set_tree.root_hash())
    transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce)
    transfer_proof = tx_set_tree.proof(transfer_index)

    highest_spendings, highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts(
        eon_number=swap.eon_number, only_appended=True)

    crediting_active_state = ActiveState(wallet=swap.recipient,
                                         updated_spendings=highest_spendings,
                                         updated_gains=highest_gains,
                                         tx_set_hash=tx_set_hash,
                                         tx_set_proof_hashes=transfer_proof,
                                         tx_set_index=transfer_index,
                                         eon_number=swap.eon_number)

    crediting_active_state_checksum = crypto.hex_value(
        crediting_active_state.checksum())
    if swap.recipient_active_state.wallet_signature.checksum != crediting_active_state_checksum:
        logger.error('Swap {} invalid credit active state checksum.'.format(
            swap.id))
        return True

    # Finality Authorization
    swap.complete = True
    tx_set_tree = recipient_view_context.optimized_authorized_transfers_tree(
        only_appended=True, starting_balance=recipient_starting_balance)
    swap.complete = False
    tx_set_hash = crypto.hex_value(tx_set_tree.root_hash())
    transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce)
    transfer_proof = tx_set_tree.proof(transfer_index)

    recipient_fulfillment_active_state = ActiveState(
        wallet=swap.recipient,
        updated_spendings=highest_spendings,
        updated_gains=highest_gains + swap.amount_swapped,
        tx_set_hash=tx_set_hash,
        tx_set_proof_hashes=transfer_proof,
        tx_set_index=transfer_index,
        eon_number=swap.eon_number)

    recipient_fulfillment_active_state_checksum = crypto.hex_value(
        recipient_fulfillment_active_state.checksum())
    if swap.recipient_fulfillment_active_state.wallet_signature.checksum != recipient_fulfillment_active_state_checksum:
        logger.error(
            'Swap {} invalid finalization active state checksum.'.format(
                swap.id))
        return True

    return False
Exemplo n.º 4
0
def slash_bad_withdrawals():
    contract_interface = NOCUSTContractInterface()

    current_eon = contract_interface.get_current_eon_number()

    # This lock is needed because some Withdrawal objects might be mutated, which can affect the checkpoint.
    with RootCommitment.global_lock():
        checkpoint_created = RootCommitment.objects.filter(
            eon_number=current_eon).exists()

        # fetch all wallets that issued a pending withdrawal
        # pending withdrawals that were not slashed yet and issued during this eon or the previous one
        withdrawal_wallets = WithdrawalRequest.objects \
            .filter(slashed=False, eon_number__gte=current_eon-1) \
            .values_list('wallet', flat=True).distinct()

        for wallet_id in withdrawal_wallets:
            # fetch wallet object
            wallet = Wallet.objects.get(id=wallet_id)

            with wallet.lock(auto_renewal=True), transaction.atomic():
                pending_withdrawals = WithdrawalRequest.objects \
                    .filter(wallet=wallet, slashed=False, eon_number__gte=current_eon-1) \
                    .select_for_update()

                # skip slashing if no slashable pending withdrawals are found
                if pending_withdrawals.count() == 0:
                    logger.warning('Skipping withdrawal slash for {}. No Slashable Pending Withdrawals.'
                                   .format(wallet.address))
                    continue

                withdrawal_aggregate_query = pending_withdrawals.aggregate(
                    Sum('amount'), Min('eon_number'))
                withdrawal_amount = withdrawal_aggregate_query['amount__sum']
                min_eon = withdrawal_aggregate_query['eon_number__min']

                withdrawals_in_current_eon = WithdrawalRequest.objects.filter(
                    wallet=wallet, eon_number=current_eon).count()
                # create a tag for the tuple
                # (current eon, number of pending withdrawals in this eon, wallet id, total withdrawal amount )
                tag = 'withdrawal_request_{}_{}_{}_{}' \
                    .format(current_eon, withdrawals_in_current_eon, wallet.id, withdrawal_amount)

                if EthereumTransaction.objects.filter(tag=tag).exists():
                    logger.warning(
                        'Skipping withdrawal slash for address {} and token {}. Slashing transaction already enqueued.'
                        .format(
                            wallet.address,
                            wallet.token.address))
                    continue

                wallet_transfer_context = WalletTransferContext(
                    wallet=wallet, transfer=None)
                available_balance = wallet_transfer_context.loosely_available_funds_at_eon(
                    eon_number=current_eon,
                    current_eon_number=current_eon,
                    is_checkpoint_created=checkpoint_created,
                    only_appended=True)

                if available_balance >= 0:
                    logger.warning(
                        'Skipping withdrawal slash for address {} and token {}. Available balance covers amount.'
                        .format(
                            wallet.address,
                            wallet.token.address))
                    continue

                # find minimum balance marker
                # start before min possible eon
                # until current eon
                minimum_balance = MinimumAvailableBalanceMarker.objects \
                    .filter(
                        wallet=wallet,
                        eon_number__gte=min_eon-1,
                        eon_number__lte=current_eon) \
                    .order_by('amount') \
                    .first()
                if minimum_balance is None or minimum_balance.amount >= withdrawal_amount:
                    logger.warning(
                        'Skipping withdrawal slash for address {} and token {}. Minimum balance within two epochs covers amount.'
                        .format(
                            wallet.address, wallet.token.address))
                    continue

                logger.warning('{}: Slashing withdrawals of {} > {} >= {}.'
                               .format(
                                   wallet.address,
                                   withdrawal_amount,
                                   available_balance,
                                   minimum_balance.amount))

                v, r, s = minimum_balance.signature.vrs()

                # slash all withdrawals for this wallet-token pair
                contract_interface.queue_slash_withdrawal(
                    token_address=wallet.token.address,
                    wallet_address=wallet.address,
                    eon_number=minimum_balance.eon_number,
                    available=int(minimum_balance.amount),
                    r=crypto.uint256(r),
                    s=crypto.uint256(s),
                    v=v,
                    tag=tag)

                # mark all related withdrawals as slashed
                pending_withdrawals.update(slashed=True)