def test_process_response_success(mock_withdrawal, mock_xdr, client, acc1_usd_withdrawal_transaction_factory): del mock_withdrawal, mock_xdr transaction = acc1_usd_withdrawal_transaction_factory() json = deepcopy(TRANSACTION_JSON) json["successful"] = True json["id"] = transaction.id json["memo"] = format_memo_horizon(transaction.withdraw_memo) Command.process_response(json) transaction.refresh_from_db() assert transaction.status == Transaction.STATUS.completed
def process_withdrawal(response, transaction): """ Check if a Stellar transaction's memo matches the ID of a database transaction. """ # Validate the Horizon response. try: memo_type = response["memo_type"] response_memo = response["memo"] successful = response["successful"] stellar_transaction_id = response["id"] envelope_xdr = response["envelope_xdr"] except KeyError: return False if memo_type != "hash": return False # The memo on the response will be base 64 string, due to XDR, while # the memo parameter is base 16. Thus, we convert the parameter # from hex to base 64, and then to a string without trailing whitespace. if response_memo != format_memo_horizon(transaction.withdraw_memo): return False horizon_tx = TransactionEnvelope.from_xdr( envelope_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE).transaction found_matching_payment_op = False for operation in horizon_tx.operations: if _check_payment_op(operation, transaction.asset.code, transaction.amount_in): found_matching_payment_op = True break # If no matching payment operation is found in the Stellar response, return. if not found_matching_payment_op: return False # If the Stellar transaction succeeded, we mark the corresponding `Transaction` # accordingly in the database. Else, we mark it `pending_stellar`, so the wallet # knows to resubmit. if successful: transaction.completed_at = now() transaction.status = Transaction.STATUS.completed transaction.status_eta = 0 transaction.amount_out = transaction.amount_in - transaction.amount_fee else: transaction.status = Transaction.STATUS.pending_stellar transaction.stellar_transaction_id = stellar_transaction_id transaction.save() return True
def match_transaction(cls, response: Dict, transaction: Transaction) -> bool: """ Determines whether or not the given ``response`` represents the given ``transaction``. Polaris does this by constructing the transaction memo from the transaction ID passed in the initial withdrawal request to ``/transactions/withdraw/interactive``. To be sure, we also check for ``transaction``'s payment operation in ``response``. :param response: a response body returned from Horizon for the transaction :param transaction: a database model object representing the transaction """ try: memo_type = response["memo_type"] response_memo = response["memo"] successful = response["successful"] stellar_transaction_id = response["id"] envelope_xdr = response["envelope_xdr"] except KeyError: logger.warning( f"Stellar response for transaction missing expected arguments" ) return False if memo_type != "hash": logger.warning( f"Transaction memo for {transaction.id} was not of type hash" ) return False # The memo on the response will be base 64 string, due to XDR, while # the memo parameter is base 16. Thus, we convert the parameter # from hex to base 64, and then to a string without trailing whitespace. if response_memo != format_memo_horizon(transaction.withdraw_memo): return False horizon_tx = TransactionEnvelope.from_xdr( response["envelope_xdr"], network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, ).transaction found_matching_payment_op = False for operation in horizon_tx.operations: if cls._check_payment_op( operation, transaction.asset.code, transaction.amount_in ): transaction.stellar_transaction_id = stellar_transaction_id transaction.from_address = horizon_tx.source.public_key transaction.save() found_matching_payment_op = True break return found_matching_payment_op
def test_withdraw_interactive_success_transaction_successful( mock_check, client, acc1_usd_withdrawal_transaction_factory): """ `GET /transactions/withdraw/webapp` changes transaction to `completed` with successful transaction. """ del mock_check acc1_usd_withdrawal_transaction_factory() response = client.post(WITHDRAW_PATH, {"asset_code": "USD"}, follow=True) content = json.loads(response.content) assert content["type"] == "interactive_customer_info_needed" transaction_id = content["id"] url = content["url"] response = client.get(url) assert response.status_code == 200 assert client.session["authenticated"] is True url, args_str = url.split("?") response = client.post( url + "/submit?" + args_str, { "amount": 50, "bank_account": "123456", "bank": "Bank", "account": "Account" }, ) assert response.status_code == 302 transaction = Transaction.objects.get(id=transaction_id) assert transaction.status == Transaction.STATUS.pending_user_transfer_start withdraw_memo = transaction.withdraw_memo mock_response = { "memo_type": "hash", "memo": format_memo_horizon(withdraw_memo), "successful": True, "id": "c5e8ada72c0e3c248ac7e1ec0ec97e204c06c295113eedbe632020cd6dc29ff8", "envelope_xdr": "AAAAAEU1B1qeJrucdqkbk1mJsnuFaNORfrOAzJyaAy1yzW8TAAAAZAAE2s4AAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAoUKq+1Z2GGB98qurLSmocHafvG6S+YzKNE6oiHIXo6kAAAABVVNEAAAAAACnUE2lfwuFZ+G+dkc+qiL0MwxB0CoR0au324j+JC9exQAAAAAdzWUAAAAAAAAAAAA=", } Command.update_transaction(mock_response, transaction) assert transaction.status == Transaction.STATUS.completed assert transaction.completed_at
def test_process_response_unsuccessful( mock_xdr, client, acc1_usd_withdrawal_transaction_factory): del mock_xdr transaction = acc1_usd_withdrawal_transaction_factory() json = deepcopy(TRANSACTION_JSON) json["successful"] = False json["id"] = transaction.id json["memo"] = format_memo_horizon(transaction.withdraw_memo) Command.process_response(json) transaction.refresh_from_db() assert transaction.status == Transaction.STATUS.error assert (transaction.status_message == "The transaction failed to execute on the Stellar network")
def test_withdraw_interactive_success_transaction_unsuccessful( mock_check, client, acc1_usd_withdrawal_transaction_factory): """ `GET /withdraw/interactive_withdraw` changes transaction to `pending_stellar` with unsuccessful transaction. """ del mock_check acc1_usd_withdrawal_transaction_factory() response = client.get(f"/withdraw?asset_code=USD", follow=True) content = json.loads(response.content) assert response.status_code == 403 assert content["type"] == "interactive_customer_info_needed" transaction_id = content["id"] url = content["url"] response = client.post(url, { "amount": 50, "bank_account": "123456", "bank": "Bank" }) assert response.status_code == 200 transaction = Transaction.objects.get(id=transaction_id) assert transaction.status == Transaction.STATUS.pending_user_transfer_start withdraw_memo = transaction.withdraw_memo mock_response = { "memo_type": "hash", "memo": format_memo_horizon(withdraw_memo), "successful": False, "id": "c5e8ada72c0e3c248ac7e1ec0ec97e204c06c295113eedbe632020cd6dc29ff8", "envelope_xdr": "AAAAAEU1B1qeJrucdqkbk1mJsnuFaNORfrOAzJyaAy1yzW8TAAAAZAAE2s4AAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAoUKq+1Z2GGB98qurLSmocHafvG6S+YzKNE6oiHIXo6kAAAABVVNEAAAAAACnUE2lfwuFZ+G+dkc+qiL0MwxB0CoR0au324j+JC9exQAAAAAdzWUAAAAAAAAAAAA=", } process_withdrawal(mock_response, transaction) assert (Transaction.objects.get( id=transaction_id).status == Transaction.STATUS.pending_stellar)