예제 #1
0
def test_double_deposit_same_tx(dbsession, eth_network_id, eth_service,
                                eth_asset_id):
    """Check that we have some logic to avoid depositing the same asset twice."""

    # Create ETH holding account under an address
    with transaction.manager:

        # First create the address which holds our account
        network = dbsession.query(AssetNetwork).get(eth_network_id)
        address = CryptoAddress(network=network,
                                address=eth_address_to_bin(TEST_ADDRESS))
        dbsession.add(address)
        dbsession.flush()

    # Create deposit op
    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        txid = txid_to_bin(TEST_TXID)
        op = address.deposit(Decimal(10), asset, txid, bin_to_txid(txid))
        dbsession.add(op)
        dbsession.flush()
        op_id = op.id

    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        txid = txid_to_bin(TEST_TXID)
        op = address.deposit(Decimal(10), asset, txid, bin_to_txid(txid))
        assert op.id == op_id
        assert dbsession.query(CryptoOperation).count() == 1
예제 #2
0
def test_double_deposit_same_tx(dbsession, eth_network_id, eth_service, eth_asset_id):
    """Check that we have some logic to avoid depositing the same asset twice."""

    # Create ETH holding account under an address
    with transaction.manager:

        # First create the address which holds our account
        network = dbsession.query(AssetNetwork).get(eth_network_id)
        address = CryptoAddress(network=network, address=eth_address_to_bin(TEST_ADDRESS))
        dbsession.add(address)
        dbsession.flush()

    # Create deposit op
    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        txid = txid_to_bin(TEST_TXID)
        op = address.deposit(Decimal(10), asset, txid, bin_to_txid(txid))
        dbsession.add(op)
        dbsession.flush()
        op_id = op.id

    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        txid = txid_to_bin(TEST_TXID)
        op = address.deposit(Decimal(10), asset, txid, bin_to_txid(txid))
        assert op.id == op_id
        assert dbsession.query(CryptoOperation).count() == 1
예제 #3
0
    def __str__(self):
        dbsession = Session.object_session(self)
        address = self.external_address and bin_to_eth_address(
            self.external_address) or "-"
        account = self.crypto_account and self.crypto_account.account or "-"
        failure_reason = self.other_data.get("error") or ""

        if self.has_txid():
            network_status = dbsession.query(CryptoNetworkStatus).get(
                self.network_id)
            if network_status:
                nblock = network_status.block_number
            else:
                nblock = "network-missing"

            if self.txid:
                txid = bin_to_txid(self.txid)
            else:
                txid = "-"

            txinfo = "txid:{} block:{} nblock:{}".format(
                txid, self.block, nblock)
        else:
            txinfo = ""

        return "{} {} externaladdress:{} completed:{} confirmed:{} failed:{} acc:{} holding:{} network:{} {}".format(
            self.operation_type, self.state, address, self.completed_at,
            self.confirmed_at, failure_reason, account, self.holding_account,
            self.network.name, txinfo)
예제 #4
0
def give_eth(event):
    """Feed user some test ETH from coinbase."""
    user = event.user

    # TODO: Rework this
    from websauna.wallet.tests.eth.utils import send_balance_to_address, do_faux_deposit

    amount = event.network.other_data["initial_assets"].get("eth_amount")
    if not amount:
        return

    # Supply eth from coinbase
    address = bin_to_eth_address(event.address.address)
    if event.web3:
        txid = send_balance_to_address(event.web3, address, Decimal(amount))
    else:
        # MockEthreumService test
        dbsession = Session.object_session(event.address)
        network = event.address.network
        asset = get_ether_asset(dbsession, network)
        op = do_faux_deposit(event.address, asset.id, Decimal(amount))
        txid = bin_to_txid(op.txid)

    # Record this operation in user data so we can verify it later
    op_txs = user.user_data.get("starter_asset_txs", [])
    op_txs.append({"eth": txid})
    user.user_data["starter_asset_txs"] = op_txs
    def get_monitored_transactions(self) -> Iterable[str]:
        """Get all transactions that are lagging behind the confirmation count."""
        result = set()

        # Transactions that are broadcasted
        txs = self.dbsession.query(CryptoOperation).filter(CryptoOperation.state == CryptoOperationState.broadcasted, CryptoOperation.network_id == self.network_id)
        for tx in txs:
            result.add(bin_to_txid(tx.txid))
        return result
예제 #6
0
    def get_monitored_transactions(self) -> Iterable[str]:
        """Get all transactions that are lagging behind the confirmation count."""
        result = set()

        # Transactions that are broadcasted
        txs = self.dbsession.query(CryptoOperation).filter(CryptoOperation.state == CryptoOperationState.broadcasted, CryptoOperation.network_id == self.network_id)
        for tx in txs:
            result.add(bin_to_txid(tx.txid))
        return result
예제 #7
0
def describe_operation(request, uop: UserOperation) -> dict:
    """Fetch operation details and link data for rendering."""
    assert isinstance(uop, UserOperation)
    detail = {}
    op = uop.uop.crypto_operation

    # Link to the user asset details
    detail["op"] = op
    if op.holding_account and op.holding_account.asset:
        detail["asset_resource"] = get_user_address_asset(
            request, op.address, op.holding_account.asset)

    tx = op.primary_tx

    # From: and To: swap in address rendering
    detail["deposit_like"] = op.operation_type in (
        CryptoOperationType.deposit, )

    confirmations = op.calculate_confirmations()

    if confirmations is not None:
        if confirmations > 30:
            confirmations = "30+"
        detail["confirmations"] = confirmations

    if op.external_address:
        detail["external_address"] = bin_to_eth_address(op.external_address)

    if op.txid:
        detail["txid"] = bin_to_txid(op.txid)

    amount = op.amount

    if amount:
        detail["amount"] = format_asset_amount(amount, op.asset.asset_class)

    detail["uuid"] = str(uop.uop.id)
    detail["resource"] = uop
    detail["tx_name"] = uop.get_title()
    detail["state"] = OP_STATES[op.state]
    detail["address_resource"] = get_user_address_resource(request, op.address)
    detail["network_resource"] = get_network_resource(request, op.network)

    detail[
        "manual_confirmation_needed"] = op.state == CryptoOperationState.confirmation_required

    if tx:
        detail["notes"] = tx.message

    return detail
예제 #8
0
    def get_title(self):
        op = self.uop.crypto_operation
        if op.has_txid():
            if op.txid:
                tx_name = bin_to_txid(op.txid)
            else:
                tx_name = "<transaction hash pending>"
        else:
            if op.external_address:
                tx_name = "bad"
            else:
                tx_name = "foo"

        return tx_name
예제 #9
0
    def get_title(self):
        op = self.uop.crypto_operation
        if op.has_txid():
            if op.txid:
                tx_name = bin_to_txid(op.txid)
            else:
                tx_name = "<transaction hash pending>"
        else:
            if op.external_address:
                tx_name = "bad"
            else:
                tx_name = "foo"

        return tx_name
예제 #10
0
def do_faux_deposit(address: CryptoAddress, asset_id, amount) -> CryptoAddressDeposit:
    """Simulate deposit to address."""

    network = address.network
    txid = network.other_data["test_txid_pool"].pop()
    # txid = "0x00df829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf"
    dbsession = Session.object_session(address)
    asset = dbsession.query(Asset).get(asset_id)
    txid = txid_to_bin(txid)

    op = address.deposit(Decimal(amount), asset, txid, bin_to_txid(txid))
    op.required_confirmation_count = 1
    op.external_address = address.address
    dbsession.add(op)
    dbsession.flush()
    return op
예제 #11
0
def describe_operation(request, uop: UserOperation) -> dict:
    """Fetch operation details and link data for rendering."""
    assert isinstance(uop, UserOperation)
    detail = {}
    op = uop.uop.crypto_operation

    # Link to the user asset details
    detail["op"] = op
    if op.holding_account and op.holding_account.asset:
        detail["asset_resource"] = get_user_address_asset(request, op.address, op.holding_account.asset)

    tx = op.primary_tx

    # From: and To: swap in address rendering
    detail["deposit_like"] = op.operation_type in (CryptoOperationType.deposit,)

    confirmations = op.calculate_confirmations()

    if confirmations is not None:
        if confirmations > 30:
            confirmations = "30+"
        detail["confirmations"] = confirmations

    if op.external_address:
        detail["external_address"] = bin_to_eth_address(op.external_address)

    if op.txid:
        detail["txid"] = bin_to_txid(op.txid)

    amount = op.amount

    if amount:
        detail["amount"] = format_asset_amount(amount, op.asset.asset_class)

    detail["uuid"] = str(uop.uop.id)
    detail["resource"] = uop
    detail["tx_name"] = uop.get_title()
    detail["state"] = OP_STATES[op.state]
    detail["address_resource"] = get_user_address_resource(request, op.address)
    detail["network_resource"] = get_network_resource(request, op.network)

    detail["manual_confirmation_needed"] = op.state == CryptoOperationState.confirmation_required

    if tx:
        detail["notes"] = tx.message

    return detail
예제 #12
0
def do_faux_deposit(address: CryptoAddress, asset_id,
                    amount) -> CryptoAddressDeposit:
    """Simulate deposit to address."""

    network = address.network
    txid = network.other_data["test_txid_pool"].pop()
    # txid = "0x00df829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf"
    dbsession = Session.object_session(address)
    asset = dbsession.query(Asset).get(asset_id)
    txid = txid_to_bin(txid)

    op = address.deposit(Decimal(amount), asset, txid, bin_to_txid(txid))
    op.required_confirmation_count = 1
    op.external_address = address.address
    dbsession.add(op)
    dbsession.flush()
    return op
예제 #13
0
def test_deposit_eth_account(dbsession, eth_network_id, eth_service,
                             eth_asset_id):
    """Deposit Ethereums to an account."""

    # Create ETH holding account under an address
    with transaction.manager:

        # First create the address which holds our account
        network = dbsession.query(AssetNetwork).get(eth_network_id)
        address = CryptoAddress(network=network,
                                address=eth_address_to_bin(TEST_ADDRESS))
        dbsession.add(address)
        dbsession.flush()

    # Create deposit op
    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        txid = txid_to_bin(TEST_TXID)
        op = address.deposit(Decimal(10), asset, txid, bin_to_txid(txid))
        dbsession.add(op)
        opid = op.id

    # Resolve deposit op
    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert success_op_count == 1
    assert failed_op_count == 0

    with transaction.manager:
        op = dbsession.query(CryptoOperation).get(opid)
        op.resolve()  # Force resolution regardless on confirmation count

    # Check balances are settled
    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        account = address.get_account(asset)
        op = dbsession.query(CryptoOperation).one()
        assert account.account.get_balance() == Decimal(10)
        assert op.holding_account.get_balance() == 0

        # Transaction label should be the Ethereum txid
        tx = account.account.transactions.one()
        assert tx.message == TEST_TXID
예제 #14
0
def test_deposit_eth_account(dbsession, eth_network_id, eth_service, eth_asset_id):
    """Deposit Ethereums to an account."""

    # Create ETH holding account under an address
    with transaction.manager:

        # First create the address which holds our account
        network = dbsession.query(AssetNetwork).get(eth_network_id)
        address = CryptoAddress(network=network, address=eth_address_to_bin(TEST_ADDRESS))
        dbsession.add(address)
        dbsession.flush()

    # Create deposit op
    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        txid = txid_to_bin(TEST_TXID)
        op = address.deposit(Decimal(10), asset, txid, bin_to_txid(txid))
        dbsession.add(op)
        opid = op.id

    # Resolve deposit op
    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert success_op_count == 1
    assert failed_op_count == 0

    with transaction.manager:
        op = dbsession.query(CryptoOperation).get(opid)
        op.resolve()  # Force resolution regardless on confirmation count

    # Check balances are settled
    with transaction.manager:
        address = dbsession.query(CryptoAddress).one_or_none()
        asset = dbsession.query(Asset).get(eth_asset_id)
        account = address.get_account(asset)
        op = dbsession.query(CryptoOperation).one()
        assert account.account.get_balance() == Decimal(10)
        assert op.holding_account.get_balance() == 0

        # Transaction label should be the Ethereum txid
        tx = account.account.transactions.one()
        assert tx.message == TEST_TXID
예제 #15
0
def test_buy_crowdfund_not_enough_gas(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, token, toycrowd, withdraw_address):
    """Perform a crowdfundn buy operation without giving enough gas for the transaction."""

    with transaction.manager:

        # Create withdraw operation
        caccount = dbsession.query(CryptoAddressAccount).one()

        # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish
        asset = toycrowd()

        caccount.withdraw(Decimal(0.005), asset.external_id, "Buying Toycrowd", required_confirmation_count=1)

    print("Withdrawing")

    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert success_op_count == 1
    assert failed_op_count == 0

    with transaction.manager:
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 3  # Create + deposit + withdraw
        op = ops[-1]
        txid = bin_to_txid(op.txid)

    # This should make the tx to included in a block
    confirm_transaction(web3, txid)

    # This should trigger incoming notification
    eth_service.run_listener_operations()

    # Now we should get block number for the withdraw
    updates, failures = eth_service.run_confirmation_updates()
    assert failures == 1

    with transaction.manager:
        op = dbsession.query(CryptoOperation).all()[-1]
        assert op.is_failed()
        assert "gas" in op.get_failure_reason()
def test_withdraw_eth_data(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, withdraw_address: str, target_account: str, decode_data_contract):
    """Perform a withdraw operation with data and gas set.
    """

    # First check what's our balance before sending coins back
    current_balance = wei_to_eth(web3.eth.getBalance(target_account))
    assert current_balance == 0

    with transaction.manager:

        # Create withdraw operation
        caccount = dbsession.query(CryptoAddressAccount).one()

        #: We are going to withdraw the full amount on the account
        assert caccount.account.get_balance() == TEST_VALUE

        # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish
        op = caccount.withdraw(TEST_VALUE, eth_address_to_bin(decode_data_contract.address), "Getting all the moneys", required_confirmation_count=4)

        op.other_data["gas"] = 1000000
        op.other_data["data"] = "0x001234"

    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert success_op_count == 1
    assert failed_op_count == 0

    with transaction.manager:
        # create + deposit + withdraw
        op = dbsession.query(CryptoOperation).all()[2]
        txid = bin_to_txid(op.txid)

    # This should make the tx to included in a block
    confirm_transaction(web3, txid)

    value = decode_data_contract.call().value()
    data = decode_data_contract.call().data()

    assert value == 10000000000000000
    assert data == '0x001234'
예제 #17
0
    def __str__(self):
        dbsession = Session.object_session(self)
        address = self.external_address and bin_to_eth_address(self.external_address) or "-"
        account = self.crypto_account and self.crypto_account.account or "-"
        failure_reason = self.other_data.get("error") or ""

        if self.has_txid():
            network_status = dbsession.query(CryptoNetworkStatus).get(self.network_id)
            if network_status:
                nblock = network_status.block_number
            else:
                nblock = "network-missing"

            if self.txid:
                txid = bin_to_txid(self.txid)
            else:
                txid = "-"

            txinfo = "txid:{} block:{} nblock:{}".format(txid, self.block, nblock)
        else:
            txinfo = ""

        return "{} {} externaladdress:{} completed:{} confirmed:{} failed:{} acc:{} holding:{} network:{} {}".format(self.operation_type, self.state, address, self.completed_at, self.confirmed_at, failure_reason, account, self.holding_account, self.network.name, txinfo)
예제 #18
0
def test_buy_crowdfund_with_gas(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, token, toycrowd, rich_withdraw_address):
    """Perform a crowdfunnd buy operation without giving enough gas for the transaction."""

    with transaction.manager:

        # Create withdraw operation
        caccount = dbsession.query(CryptoAddressAccount).one()

        # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish
        asset = toycrowd()

        op = caccount.withdraw(Decimal(3), asset.external_id, "Buying Toycrowd", required_confirmation_count=1)
        op.other_data["gas"] = 2500333  # Limit should be ~100k

    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert failed_op_count == 0
    assert success_op_count == 1

    with transaction.manager:
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 3  # Create + deposit + withdraw
        op = ops[-1]
        txid = bin_to_txid(op.txid)

    # This should make the tx to included in a block
    confirm_transaction(web3, txid)

    # Set op.block
    eth_service.run_confirmation_updates()

    # Grab block number where out tx is
    with transaction.manager:
        ops = list(dbsession.query(CryptoOperation).all())
        op = ops[-1]
        block_num = op.block

    wallet = get_wallet(web3, rich_withdraw_address)
    token_events = token.get_all_events()
    wallet_events = wallet.get_all_events()

    # Confirm we got it all right
    receipt = web3.eth.getTransactionReceipt(txid)
    logs = receipt["logs"]
    assert logs[0]["topics"][0] == token_events["Buy"]
    assert logs[1]["topics"][0] == token_events["Transfer"]
    assert logs[2]["topics"][0] == wallet_events["Withdraw"]

    data = get_crowdsale_data(token)
    assert data["wei_raised"] == to_wei("3", "ether")

    # Give tx time to confirm, so all confirmations will be there for db update run
    required_conf = 3
    wait_for_block_number(web3, block_num + required_conf + 1, timeout=60)

    # This should trigger incoming notification
    eth_service.run_listener_operations()
    updates, failures = eth_service.run_confirmation_updates()
    assert failures == 0
    assert updates == 2  # 1 eth withdraw, 1 token deposit

    # Check our db is updated
    with transaction.manager:

        # There is now new operation to deposit tokens
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 4
        op = ops[-1]  # type: CryptoOperation

        assert op.operation_type == CryptoOperationType.deposit
        assert op.state == CryptoOperationState.success
        assert op.amount == 6

        asset = toycrowd()
        crypto_address = dbsession.query(CryptoAddress).one()  # type: CryptoAddress
        caccount = crypto_address.get_account(asset)
        assert caccount.account.get_balance() == 6
def test_withdraw_eth(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, withdraw_address: str, target_account: str):
    """Perform a withdraw operation.

    Create a database address with balance.
    """

    # First check what's our balance before sending coins back
    current_balance = wei_to_eth(web3.eth.getBalance(target_account))
    assert current_balance == 0

    with transaction.manager:

        # Create withdraw operation
        caccount = dbsession.query(CryptoAddressAccount).one()

        #: We are going to withdraw the full amount on the account
        assert caccount.account.get_balance() == TEST_VALUE

        # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish
        op = caccount.withdraw(TEST_VALUE, eth_address_to_bin(target_account), "Getting all the moneys", required_confirmation_count=4)

    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert success_op_count == 1
    assert failed_op_count == 0

    with transaction.manager:
        # We should have now three ops
        # One for creating the address
        # One for depositing value for the test
        # One for withdraw

        # We have one complete operation
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 3  # Create + deposit + withdraw
        op = ops[-1]
        assert isinstance(op, CryptoAddressWithdraw)
        assert op.broadcasted_at is not None  # This completes instantly, cannot be cancelled
        assert op.completed_at is None  # We need at least one confirmation
        assert op.block is None
        assert op.txid is not None
        txid = bin_to_txid(op.txid)

    # This should make the tx to included in a block
    confirm_transaction(web3, txid)

    # Now we should get block number for the withdraw
    eth_service.run_confirmation_updates()

    # Geth reflects the deposit instantly internally, doesn't wait for blocks
    fee = get_withdrawal_fee(web3)
    new_balance = wei_to_eth(web3.eth.getBalance(target_account))
    assert new_balance == current_balance + TEST_VALUE - fee

    current_block = web3.eth.blockNumber
    with transaction.manager:
        # Check we get block and txid

        # We have one complete operation
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 3  # Create + deposit + withdraw
        op = ops[-1]
        assert op.broadcasted_at is not None  # This completes instantly, cannot be cancelled
        assert op.completed_at is None, "Got confirmation for block {}, current {}, requires {}".format(op.block, current_block, op.required_confirmation_count)
        assert op.block is not None
        assert op.txid is not None
        block_num = op.block
        required_conf = op.required_confirmation_count

    # Wait block to make the confirmation happen
    wait_for_block_number(web3, block_num + required_conf + 1, timeout=60)

    # Now we should have enough blocks to mark the transaction as confirmed
    eth_service.run_confirmation_updates()

    with transaction.manager:
        # Check we get block and txid

        # We have one complete operation
        ops = list(dbsession.query(CryptoOperation).all())
        op = ops[-1]
        assert op.state == CryptoOperationState.success
        assert op.completed_at is not None
def test_withdraw_eth(dbsession: Session, eth_network_id: UUID, web3: Web3, eth_service: EthereumService, withdraw_address: str, target_account: str):
    """Perform a withdraw operation.

    Create a database address with balance.
    """

    # First check what's our balance before sending coins back
    current_balance = wei_to_eth(web3.eth.getBalance(target_account))
    assert current_balance == 0

    with transaction.manager:

        # Create withdraw operation
        caccount = dbsession.query(CryptoAddressAccount).one()

        #: We are going to withdraw the full amount on the account
        assert caccount.account.get_balance() == TEST_VALUE

        # Use 4 as the heurestics for block account that doesn't happen right away, but still sensible to wait for it soonish
        op = caccount.withdraw(TEST_VALUE, eth_address_to_bin(target_account), "Getting all the moneys", required_confirmation_count=4)

    success_op_count, failed_op_count = eth_service.run_waiting_operations()
    assert success_op_count == 1
    assert failed_op_count == 0

    with transaction.manager:
        # We should have now three ops
        # One for creating the address
        # One for depositing value for the test
        # One for withdraw

        # We have one complete operation
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 3  # Create + deposit + withdraw
        op = ops[-1]
        assert isinstance(op, CryptoAddressWithdraw)
        assert op.broadcasted_at is not None  # This completes instantly, cannot be cancelled
        assert op.completed_at is None  # We need at least one confirmation
        assert op.block is None
        assert op.txid is not None
        txid = bin_to_txid(op.txid)

    # This should make the tx to included in a block
    confirm_transaction(web3, txid)

    # Now we should get block number for the withdraw
    eth_service.run_confirmation_updates()

    # Geth reflects the deposit instantly internally, doesn't wait for blocks
    fee = get_withdrawal_fee(web3)
    new_balance = wei_to_eth(web3.eth.getBalance(target_account))
    assert new_balance == current_balance + TEST_VALUE - fee

    current_block = web3.eth.blockNumber
    with transaction.manager:
        # Check we get block and txid

        # We have one complete operation
        ops = list(dbsession.query(CryptoOperation).all())
        assert len(ops) == 3  # Create + deposit + withdraw
        op = ops[-1]
        assert op.broadcasted_at is not None  # This completes instantly, cannot be cancelled
        assert op.completed_at is None, "Got confirmation for block {}, current {}, requires {}".format(op.block, current_block, op.required_confirmation_count)
        assert op.block is not None
        assert op.txid is not None
        block_num = op.block
        required_conf = op.required_confirmation_count

    # Wait block to make the confirmation happen
    wait_for_block_number(web3, block_num + required_conf + 1, timeout=60)

    # Now we should have enough blocks to mark the transaction as confirmed
    eth_service.run_confirmation_updates()

    with transaction.manager:
        # Check we get block and txid

        # We have one complete operation
        ops = list(dbsession.query(CryptoOperation).all())
        op = ops[-1]
        assert op.state == CryptoOperationState.success
        assert op.completed_at is not None