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)
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)
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
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)