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