def test_deposit_stellar_no_account(acc1_usd_deposit_transaction_factory): """ `create_stellar_deposit` sets the transaction with the provided `transaction_id` to status `pending_trust` if the provided transaction's `stellar_account` does not exist yet. This condition is mocked by throwing an error when attempting to load information for the provided account. Normally, this function creates the account. We have mocked out that functionality, as it relies on network calls to Horizon. """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.asset.distribution_account_signers = json.dumps( mock_account.signers) deposit.asset.distribution_account_thresholds = json.dumps({ "low_threshold": mock_account.thresholds.low_threshold, "med_threshold": mock_account.thresholds.med_threshold, "high_threshold": mock_account.thresholds.high_threshold, }) deposit.asset.distribution_account_master_signer = json.dumps( mock_account.signers[0]) deposit.asset.save() deposit.save() create_stellar_deposit(deposit) assert mock_server_no_account.submit_transaction.was_called # it would be pending_trust if the call to fetch the created account was not # mocked to raise an exception. Since the exception is raised, the transaction # is put in error status but the functionality works. assert Transaction.objects.get( id=deposit.id).status == Transaction.STATUS.error mock_server_no_account.reset_mock()
def execute_deposit(transaction: Transaction) -> bool: """ The external deposit has been completed, so the transaction status must now be updated to *pending_anchor*. Executes the transaction by calling :func:`create_stellar_deposit`. :param transaction: the transaction to be executed :returns a boolean of whether or not the transaction was completed successfully on the Stellar network. """ if transaction.kind != transaction.KIND.deposit: raise ValueError("Transaction not a deposit") elif transaction.status != transaction.STATUS.pending_user_transfer_start: raise ValueError( f"Unexpected transaction status: {transaction.status}, expecting " f"{transaction.STATUS.pending_user_transfer_start}") elif transaction.amount_fee is None: if registered_fee_func == calculate_fee: transaction.amount_fee = calculate_fee({ "amount": transaction.amount_in, "operation": settings.OPERATION_DEPOSIT, "asset_code": transaction.asset.code, }) else: transaction.amount_fee = Decimal(0) transaction.status = Transaction.STATUS.pending_anchor transaction.status_eta = 5 # Ledger close time. transaction.save() logger.info( f"Transaction {transaction.id} now pending_anchor, initiating deposit") # launch the deposit Stellar transaction. return create_stellar_deposit(transaction.id)
def test_deposit_stellar_no_trustline(acc1_usd_deposit_transaction_factory): """ `create_stellar_deposit` sets the transaction with the provided `transaction_id` to status `pending_trust` if the provided transaction's Stellar account has no trustline for its asset. (We assume the asset's issuer is the server Stellar account.) """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.asset.distribution_account_signers = json.dumps( mock_account.signers) deposit.asset.distribution_account_thresholds = json.dumps({ "low_threshold": mock_account.thresholds.low_threshold, "med_threshold": mock_account.thresholds.med_threshold, "high_threshold": mock_account.thresholds.high_threshold, }) deposit.asset.distribution_account_master_signer = json.dumps( mock_account.signers[0]) deposit.asset.save() deposit.save() assert not create_stellar_deposit(deposit) assert (Transaction.objects.get( id=deposit.id).status == Transaction.STATUS.pending_trust)
def check_trustlines(): """ Create Stellar transaction for deposit transactions marked as pending trust, if a trustline has been created. """ module = sys.modules[__name__] transactions = Transaction.objects.filter( kind=Transaction.KIND.deposit, status=Transaction.STATUS.pending_trust) server = settings.HORIZON_SERVER for transaction in transactions: if module.TERMINATE: break try: account = (server.accounts().account_id( transaction.stellar_account).call()) except BaseHorizonError: logger.warning( f"could not load account {transaction.stellar_account} using provided horizon URL" ) continue try: balances = account["balances"] except KeyError: logger.debug( f"horizon account {transaction.stellar_account} response had no balances" ) continue for balance in balances: if balance.get("asset_type") == "native": continue try: asset_code = balance["asset_code"] asset_issuer = balance["asset_issuer"] except KeyError: logger.debug( f"horizon balance had no asset_code for account {account['id']}" ) continue if (asset_code == transaction.asset.code and asset_issuer == transaction.asset.issuer): if check_for_multisig(transaction): # Now we're waiting for signatures to be collected on real deposit transaction logger.info( f"Account {account['id']} has established a trustline for {asset_code}" ) continue logger.info( f"Account {account['id']} has established a trustline for {asset_code}, " f"initiating deposit for {transaction.id}") if create_stellar_deposit(transaction, destination_exists=True): transaction.refresh_from_db() try: rdi.after_deposit(transaction) except Exception: logger.exception( "An unexpected error was raised from " "after_deposit() in check_trustlines")
def test_deposit_stellar_success(acc1_usd_deposit_transaction_factory): """ `create_stellar_deposit` succeeds if the provided transaction's `stellar_account` has a trustline to the issuer for its `asset`, and the Stellar transaction completes successfully. All of these conditions and actions are mocked in this test to avoid network calls. """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.asset.distribution_account_signers = json.dumps( mock_account.signers) deposit.asset.distribution_account_thresholds = json.dumps({ "low_threshold": mock_account.thresholds.low_threshold, "med_threshold": mock_account.thresholds.med_threshold, "high_threshold": mock_account.thresholds.high_threshold, }) deposit.asset.distribution_account_master_signer = json.dumps( mock_account.signers[0]) deposit.asset.save() deposit.save() assert create_stellar_deposit(deposit) assert Transaction.objects.get( id=deposit.id).status == Transaction.STATUS.completed
def test_deposit_stellar_success(acc1_usd_deposit_transaction_factory): """ `create_stellar_deposit` succeeds if the provided transaction's `stellar_account` has a trustline to the issuer for its `asset`, and the Stellar transaction completes successfully. All of these conditions and actions are mocked in this test to avoid network calls. """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.save() assert create_stellar_deposit(deposit) assert Transaction.objects.get(id=deposit.id).status == Transaction.STATUS.completed
def test_deposit_stellar_no_trustline(acc1_usd_deposit_transaction_factory): """ `create_stellar_deposit` sets the transaction with the provided `transaction_id` to status `pending_trust` if the provided transaction's Stellar account has no trustline for its asset. (We assume the asset's issuer is the server Stellar account.) """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.save() assert not create_stellar_deposit(deposit.id) assert (Transaction.objects.get( id=deposit.id).status == Transaction.STATUS.pending_trust)
def test_deposit_stellar_no_account(acc1_usd_deposit_transaction_factory): """ `create_stellar_deposit` sets the transaction with the provided `transaction_id` to status `pending_trust` if the provided transaction's `stellar_account` does not exist yet. This condition is mocked by throwing an error when attempting to load information for the provided account. Normally, this function creates the account. We have mocked out that functionality, as it relies on network calls to Horizon. """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.save() assert not create_stellar_deposit(deposit.id) assert mock_server_no_account.submit_transaction.was_called assert (Transaction.objects.get( id=deposit.id).status == Transaction.STATUS.pending_trust) mock_server_no_account.reset_mock()
def execute_deposit(transaction: Transaction) -> bool: valid_statuses = [ Transaction.STATUS.pending_user_transfer_start, Transaction.STATUS.pending_anchor, ] if transaction.kind != transaction.KIND.deposit: raise ValueError("Transaction not a deposit") elif transaction.status not in valid_statuses: raise ValueError( f"Unexpected transaction status: {transaction.status}, expecting " f"{' or '.join(valid_statuses)}.") if transaction.status != Transaction.STATUS.pending_anchor: transaction.status = Transaction.STATUS.pending_anchor transaction.status_eta = 5 # Ledger close time. transaction.save() logger.info(f"Initiating Stellar deposit for {transaction.id}") # launch the deposit Stellar transaction. return create_stellar_deposit(transaction)
def test_deposit_stellar_no_trustline_with_claimable_bal( acc1_usd_deposit_transaction_factory, ): """ This is the flow when a wallet/client submits a deposit and the sends "claimable_balance_supported=True" in their POST request body. Such that the deposit transaction is can continue as a claimable balance operation rather than a payment operation `execute_deposits` from `poll_pending_deposits.py` if the provided transaction's Stellar account has no trustline and sees claimable_balance_supported set to True. it will create_stellar_deposit() where it wil complete the deposit flow as a claimable balance """ deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.claimable_balance_supported = True deposit.save() assert create_stellar_deposit(deposit) assert Transaction.objects.get(id=deposit.id).claimable_balance_id assert Transaction.objects.get( id=deposit.id).status == Transaction.STATUS.completed
def test_bad_status(acc1_usd_deposit_transaction_factory): deposit = acc1_usd_deposit_transaction_factory() with pytest.raises(ValueError): create_stellar_deposit(deposit)