def test_starter_token(dbsession, registry, eth_network_id, web3: Web3, eth_service: EthereumService, house_address, toybox): """See that a fresh user account is supplied with some play assets.""" with transaction.manager: user = create_user(dbsession, registry) setup_user_account(user) # Let all the events completed success, fail = eth_service.run_event_cycle() assert success == 1 assert fail == 0 # We need another event cycle to process the initial asset transfers with transaction.manager: user = dbsession.query(User).first() opid = user.user_data["starter_asset_txs"][0]["toybox"] assert opid wait_for_op_confirmations(eth_service, opid) # Let the transfer come through eth_service.run_event_cycle() # Make sure we confirm the deposit with transaction.manager: user = dbsession.query(User).first() user_depo = user.owned_crypto_operations.join(CryptoOperation).filter_by(operation_type=CryptoOperationType.deposit).first() opid = user_depo.crypto_operation.id wait_for_op_confirmations(eth_service, opid) with transaction.manager: # Sanity check our token contract posts us logs user = dbsession.query(User).first() client = get_rpc_client(web3) asset = dbsession.query(Asset).get(toybox) address = bin_to_eth_address(asset.external_id) network = dbsession.query(AssetNetwork).get(eth_network_id) user_address = UserCryptoAddress.get_default(user, network).address house_address = dbsession.query(CryptoAddress).get(house_address) house = bin_to_eth_address(house_address.address) token = Token.get(web3, address) # Check we correctly resolved low level logs token_logs = client.get_logs(from_block=0, address=[address]) wallet_logs = client.get_logs(from_block=0, address=[house]) assert len(token_logs) > 0 assert len(wallet_logs) > 0 # Check contract state matches assert token.contract.call().balanceOf(house) == 9990 assert token.contract.call().balanceOf(bin_to_eth_address(user_address.address)) == 10 # Check our internal book keeping matches assert house_address.get_account(asset).account.get_balance() == 9990 assert user_address.get_account(asset).account.get_balance() == 10
def perform_tx(): op = dbsession.query(CryptoOperation).get(opid) address = bin_to_eth_address(op.external_address) token = Token.get(web3, address) network = op.network def gen_error(e: Exception): # Set operation as impossible to complete # Set user readable and technical error explanation op.mark_failed() op.other_data[ "error"] = "Address did not provide EIP-20 token API:" + address op.other_data["exception"] = str(e) logger.exception(e) try: name = token.contract.call().name() symbol = token.contract.call().symbol() supply = Decimal(token.contract.call().totalSupply()) except BadFunctionCallOutput as e: # When we try to access a contract attrib which is not supported by underlying code gen_error(e) return asset = network.create_asset(name=name, symbol=symbol, supply=supply, asset_class=AssetClass.token) asset.external_id = op.external_address # Fill in balances for the addresses we host # TODO: Too much for one transaction for caddress in dbsession.query(CryptoAddress).all(): # Returns 0 for unknown addresses try: amount = token.contract.call().balanceOf( bin_to_eth_address(caddress.address)) except BadFunctionCallOutput as e: # Bad contract doesn't define balanceOf() # This leaves badly imported asset gen_error(e) return if amount > 0: account = caddress.get_or_create_account(asset) account.account.do_withdraw_or_deposit( Decimal(amount), "Token contract import") # This operation immediately closes op.mark_performed() op.mark_broadcasted() op.mark_complete()
def perform_tx(): op = dbsession.query(CryptoOperation).get(opid) address = bin_to_eth_address(op.external_address) token = Token.get(web3, address) network = op.network def gen_error(e: Exception): # Set operation as impossible to complete # Set user readable and technical error explanation op.mark_failed() op.other_data["error"] = "Address did not provide EIP-20 token API:" + address op.other_data["exception"] = str(e) logger.exception(e) try: name = token.contract.call().name() symbol = token.contract.call().symbol() supply = Decimal(token.contract.call().totalSupply()) except eth_abi.exceptions.DecodingError as e: # When we try to access a contract attrib which is not supported by underlying code gen_error(e) return asset = network.create_asset(name=name, symbol=symbol, supply=supply, asset_class=AssetClass.token) asset.external_id = op.external_address # Fill in balances for the addresses we host # TODO: Too much for one transaction for caddress in dbsession.query(CryptoAddress).all(): # Returns 0 for unknown addresses try: amount = token.contract.call().balanceOf(bin_to_eth_address(caddress.address)) except eth_abi.exceptions.DecodingError as e: # Bad contract doesn't define balanceOf() # This leaves badly imported asset gen_error(e) return if amount > 0: account = caddress.get_or_create_account(asset) account.account.do_withdraw_or_deposit(Decimal(amount), "Token contract import") # This operation immediately closes op.mark_performed() op.mark_broadcasted() op.mark_complete()
def perform_tx(): op = dbsession.query(CryptoOperation).get(opid) # Check everyting looks sane assert op.crypto_account.id assert op.crypto_account.account.id asset = op.holding_account.asset assert asset.id # Set information on asset that we have now created and have its smart contract id assert not asset.external_id, "Asset has been already assigned its smart contract id. Recreate error?" address = bin_to_eth_address(op.crypto_account.address.address) # Create Tonex proxy object token = Token.create_token(web3, name=asset.name, symbol=asset.symbol, supply=asset.supply, owner=address) # Call geth RPC API over Populus contract proxy op.txid = txid_to_bin(token.initial_txid) op.block = None op.external_address = eth_address_to_bin(token.address) asset.external_id = op.external_address op.mark_performed() op.mark_broadcasted()
def deposit_address(eth_service, eth_network_id, dbsession, registry) -> str: """Creates an address that has matching account on Geth. Sending ETH to this address should trigger a incoming tx logic. :return: 0x hex presentation """ with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) address = CryptoAddress(network=network) dbsession.flush() # Generate address on the account op = CryptoAddressCreation(address) dbsession.add(op) dbsession.flush() # Creates a hosted wallet success_op_count, failed_op_count = eth_service.run_waiting_operations() assert success_op_count == 1 with transaction.manager: return bin_to_eth_address(dbsession.query(CryptoAddress).one().address)
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 render_sms(self, template_context): op = self.context.op amount = format_asset_amount(op.amount, op.asset.asset_class) template_context.update({ "amount": amount, "asset": op.asset.name, "address": bin_to_eth_address(op.external_address), }) return render("wallet/sms/confirm_withdraw.txt", template_context, request=self.request)
def test_withdraw_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, deposit_address: str, token_asset: str, target_account: str): """"See that we can withdraw token outside to an account.""" with transaction.manager: address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = dbsession.query(Asset).get(token_asset) caccount = address.get_account(asset) op = caccount.withdraw(Decimal(2000), eth_address_to_bin(target_account), "Sending to friend") opid = op.id assert caccount.account.get_balance() == 8000 # Run import token operation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # TX was broadcasted, marked as complete with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert isinstance(op, CryptoAddressWithdraw) asset = dbsession.query(Asset).get(token_asset) assert op.broadcasted_at assert not op.completed_at wait_for_op_confirmations(eth_service, opid) # After tx has been mined the new balances should match with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.broadcasted_at assert op.completed_at asset = dbsession.query(Asset).get(token_asset) # Tokens have been removed on from account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(deposit_address) == 8000 # Tokens have been credited on to account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(target_account) == 2000
def get_monitored_addresses(self) -> Iterable[str]: """Get list of all ETH crtypto deposit addresses.""" addresses = [] for addr in self.dbsession.query( CryptoAddress, CryptoAddress.address).filter( CryptoAddress.network_id == self.network_id, CryptoAddress.address != None): # addr.address is not set if the address is under construction addresses.append(bin_to_eth_address(addr.address)) return addresses
def prepare_withdraw(): # Check everyting looks sane op = dbsession.query(CryptoOperation).get(opid) assert op.crypto_account.id assert op.crypto_account.account.id assert op.holding_account.id assert op.holding_account.get_balance() > 0 assert op.external_address assert op.required_confirmation_count # Should be set by the creator asset = op.holding_account.asset assert asset.asset_class == AssetClass.token from_address = bin_to_eth_address(op.crypto_account.address.address) to_address = bin_to_eth_address(op.external_address) asset_address = bin_to_eth_address(asset.external_id) # How much we are withdrawing amount = op.holding_account.transactions.one().amount op.mark_performed() # Don't try to pick this op automatically again return from_address, to_address, asset_address, amount
def get_monitored_addresses(self) -> Iterable[str]: """Get list of all known token smart contract addresses.""" addresses = [] for asset in self.dbsession.query(Asset, Asset.external_id).filter( Asset.network_id == self.network_id, Asset.external_id != None): addresses.append(bin_to_eth_address(asset.external_id)) return addresses
def test_withdraw_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, deposit_address: str, token_asset: str, target_account: str): """"See that we can withdraw token outside to an account.""" with transaction.manager: address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = dbsession.query(Asset).get(token_asset) caccount = address.get_account(asset) op = caccount.withdraw(Decimal(2000), eth_address_to_bin(target_account), "Sending to friend") opid = op.id assert caccount.account.get_balance() == 8000 # Run import token operation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # TX was broadcasted, marked as complete with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert isinstance(op, CryptoAddressWithdraw) asset = dbsession.query(Asset).get(token_asset) assert op.broadcasted_at assert not op.completed_at wait_for_op_confirmations(eth_service, opid) with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.broadcasted_at assert op.completed_at asset = dbsession.query(Asset).get(token_asset) # Tokens have been removed on from account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(deposit_address) == 8000 # Tokens have been credited on to account token = Token.get(web3, bin_to_eth_address(asset.external_id)) assert token.contract.call().balanceOf(target_account) == 2000
def test_starter_eth(dbsession, registry, eth_network_id, web3: Web3, eth_service: EthereumService, house_address, starter_eth): """Test the user gets some starter ETH when signing up.""" with transaction.manager: user = create_user(dbsession, registry) setup_user_account(user, do_mainnet=True) # Let all the events completed success, fail = eth_service.run_event_cycle() assert success >= 1 assert fail == 0 # When op is confirmed, the user account is correctly credited with transaction.manager: user = dbsession.query(User).first() txid = user.user_data["starter_asset_txs"][0]["eth"] confirm_transaction(web3, txid) # Let the transfer come through eth_service.run_event_cycle() with transaction.manager: user = dbsession.query(User).first() client = get_rpc_client(web3) asset = get_ether_asset(dbsession) ua = user.owned_crypto_addresses.first() address = bin_to_eth_address(ua.address.address) # Sanity check we get events from starter deposit logs = client.get_logs(from_block=0, address=[address]) ops = list(user.owned_crypto_operations) # The event was processed on log level assert len(logs) == 1 # The last user operation is deposit depo = ops[-1] assert isinstance(depo.crypto_operation, CryptoAddressDeposit) opid = depo.crypto_operation.id # Wait deposit to confirm wait_for_op_confirmations(eth_service, opid) with transaction.manager: # User ETH balance is expected asset = get_ether_asset(dbsession) user = dbsession.query(User).first() ua = user.owned_crypto_addresses.first() caccout = ua.address.get_account(asset) assert caccout.account.get_balance() == Decimal("0.1")
def test_starter_eth(dbsession, registry, eth_network_id, web3: Web3, eth_service: EthereumService, house_address, starter_eth): """Test the user gets some starter ETH when signing up.""" with transaction.manager: user = create_user(dbsession, registry) setup_user_account(user) # Let all the events completed success, fail = eth_service.run_event_cycle() assert success >= 1 assert fail == 0 # When op is confirmed, the user account is correctly credited with transaction.manager: user = dbsession.query(User).first() txid = user.user_data["starter_asset_txs"][0]["eth"] confirm_transaction(web3, txid) # Let the transfer come through eth_service.run_event_cycle() with transaction.manager: user = dbsession.query(User).first() client = get_rpc_client(web3) asset = get_ether_asset(dbsession) ua = user.owned_crypto_addresses.first() address = bin_to_eth_address(ua.address.address) # Sanity check we get events from starter deposit logs = client.get_logs(from_block=0, address=[address]) ops = list(user.owned_crypto_operations) # The event was processed on log level assert len(logs) == 1 # The last user operation is deposit depo = ops[-1] assert isinstance(depo.crypto_operation, CryptoAddressDeposit) opid = depo.crypto_operation.id # Wait deposit to confirm wait_for_op_confirmations(eth_service, opid) with transaction.manager: # User ETH balance is expected asset = get_ether_asset(dbsession) user = dbsession.query(User).first() ua = user.owned_crypto_addresses.first() caccout = ua.address.get_account(asset) assert caccout.account.get_balance() == Decimal("0.1")
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 dictify(self, obj: Asset) -> dict: """Serialize SQLAlchemy model instance to nested dictionary appstruct presentation.""" appstruct = dictify(self, obj, excludes=("long_description", "external_id")) # Convert between binary storage and human readable hex presentation appstruct["long_description"] = obj.other_data.pop("long_description", "") if obj.external_id: appstruct["external_id"] = bin_to_eth_address(obj.external_id) else: appstruct["external_id"] = "" return appstruct
class CryptoAddressListing(DefaultListing): """List all crypto addresses on the site.""" table = listing.Table(columns=[ listing.Column("user", "User", getter=find_user_for_address), listing.Column("network", "Network", getter=lambda v, c, address: address.network.name), listing.Column( "address", "Address", getter=lambda v, c, address: bin_to_eth_address(address.address)), listing.Column("eth", "ETH balance", getter=find_eth_balance), listing.ControlsColumn() ])
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 dictify(self, obj: Asset) -> dict: """Serialize SQLAlchemy model instance to nested dictionary appstruct presentation.""" appstruct = dictify(self, obj, excludes=("long_description", "external_id")) # Convert between binary storage and human readable hex presentation appstruct["long_description"] = obj.other_data.pop( "long_description", "") if obj.external_id: appstruct["external_id"] = bin_to_eth_address(obj.external_id) else: appstruct["external_id"] = "" return appstruct
def prepare_withdraw(): # Check everyting looks sane op = dbsession.query(CryptoOperation).get(opid) assert op.crypto_account.id assert op.crypto_account.account.id assert op.holding_account.id assert op.holding_account.get_balance() > 0 assert op.external_address assert op.required_confirmation_count # Should be set by the creator address = bin_to_eth_address(op.crypto_account.address.address) # How much we are withdrawing amount = op.holding_account.transactions.one().amount op.mark_performed() # Don't pick this to action list anymore return address, amount, op.external_address
def withdraw_eth(web3: Web3, dbsession: Session, opid: UUID): """Perform ETH withdraw operation from the wallet.""" @retryable(tm=dbsession.transaction_manager) def prepare_withdraw(): # Check everyting looks sane op = dbsession.query(CryptoOperation).get(opid) assert op.crypto_account.id assert op.crypto_account.account.id assert op.holding_account.id assert op.holding_account.get_balance() > 0 assert op.external_address assert op.required_confirmation_count # Should be set by the creator address = bin_to_eth_address(op.crypto_account.address.address) # How much we are withdrawing amount = op.holding_account.transactions.one().amount op.mark_performed() # Don't pick this to action list anymore gas = op.other_data.get("gas") data = op.other_data.get("data") return address, amount, op.external_address, gas, data @retryable(tm=dbsession.transaction_manager) def close_withdraw(): # Fill in details. # Block number will be filled in later, when confirmation updater picks a transaction receipt for this operation. op = dbsession.query(CryptoOperation).get(opid) op.txid = txid_to_bin(txid) op.block = None op.mark_broadcasted() address, amount, external_address, gas, data = prepare_withdraw() # Do actual network communications outside the transaction, # so we avoid double withdraws in the case transaction is retried wallet = HostedWallet.get(web3, address) txid = wallet.withdraw(bin_to_eth_address(external_address), amount, max_gas=gas, data=data) close_withdraw()
def prepare_withdraw(): # Check everyting looks sane op = dbsession.query(CryptoOperation).get(opid) assert op.crypto_account.id assert op.crypto_account.account.id assert op.holding_account.id assert op.holding_account.get_balance() > 0 assert op.external_address assert op.required_confirmation_count # Should be set by the creator address = bin_to_eth_address(op.crypto_account.address.address) # How much we are withdrawing amount = op.holding_account.transactions.one().amount op.mark_performed() # Don't pick this to action list anymore gas = op.other_data.get("gas") data = op.other_data.get("data") return address, amount, op.external_address, gas, data
class AssetListing(adminbaseviews.Listing): table = listing.Table(columns=[ # listing.Column("id", "Id",), listing.Column("name", "Name", getter=lambda v, c, asset: asset.name), listing.Column("description", "Description", getter=lambda v, c, asset: asset.name), # Convert between binary storage and human readable hex presentation listing.Column("address", "Address", getter=lambda v, c, asset: asset.external_id and bin_to_eth_address(asset.external_id or "")), listing.ControlsColumn() ]) def order_query(self, query: Query): """Sort the query.""" return query.order_by(Asset.name)
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 check_bad_hosted_wallet_events(self, op: CryptoOperation, receipt: dict): bad_events = { # OutOfGasWithdraw "0xb41c321524c6519973481dcf28a48b2c632cc3a7a2bb4ee1b849d68392f044e0": "Out of gas when performing the transaction. Please specify higher gas limit and try again.", # ExceededWithdraw "0x77c51a30b87b909c33ea71d8a231c335e73c443c891c73b0b2f9226dc65ffe9c": "Exceeded the available balance", # NoMatchingFunction "0x1b021189060cad216b135fb489585944f25904395c4ec6fe7d76187f32e3303d": "No matching function", } # Any of HostedWallet failure events # {'transactionIndex': 0, 'contractAddress': None, 'from': '0xaab2a08c0e0d8fa965e0b5167bbc33c145d29416', 'to': '0x50fd35249be4ce4e48f9f97ca8e73dba93c4f45b', 'transactionHash': '0x5b36e438f548a19ed6cdb655965fc51d67a890641a7b227b9366dc0b8d3d6f89', 'logs': [{'address': '0x50fd35249be4ce4e48f9f97ca8e73dba93c4f45b', 'transactionIndex': 0, 'logIndex': 0, 'topics': ['0xb41c321524c6519973481dcf28a48b2c632cc3a7a2bb4ee1b849d68392f044e0'], 'data': '0x000000000000000000000000f77c795e79a02a39100ac0c3f027943c60a588e70000000000000000000000000000000000000000000000000011c37937e08000', 'transactionHash': '0x5b36e438f548a19ed6cdb655965fc51d67a890641a7b227b9366dc0b8d3d6f89', 'blockHash': '0xc6ae4fe252cea45493564ffc60bfc4227af3d32615da29a2df5a503cd150487b', 'blockNumber': 9}], 'blockHash': '0xc6ae4fe252cea45493564ffc60bfc4227af3d32615da29a2df5a503cd150487b', 'cumulativeGasUsed': 34136, 'gasUsed': 34136, 'root': '5edc07ef145e2eaa489bf7cfaccff1f72548fc54b44859889b96b1be2b534141', 'blockNumber': 9} if isinstance(op, CryptoAddressWithdraw): for log in receipt["logs"]: # Make sure event was generated by HostedWallet, not a contract it's calling if log["address"] == bin_to_eth_address(op.get_from_address()): for t in log["topics"]: if t in bad_events: return bad_events[t]
def withdraw_eth(web3: Web3, dbsession: Session, opid: UUID): """Perform ETH withdraw operation from the wallet.""" @retryable(tm=dbsession.transaction_manager) def prepare_withdraw(): # Check everyting looks sane op = dbsession.query(CryptoOperation).get(opid) assert op.crypto_account.id assert op.crypto_account.account.id assert op.holding_account.id assert op.holding_account.get_balance() > 0 assert op.external_address assert op.required_confirmation_count # Should be set by the creator address = bin_to_eth_address(op.crypto_account.address.address) # How much we are withdrawing amount = op.holding_account.transactions.one().amount op.mark_performed() # Don't pick this to action list anymore return address, amount, op.external_address @retryable(tm=dbsession.transaction_manager) def close_withdraw(): # Fill in details. # Block number will be filled in later, when confirmation updater picks a transaction receipt for this operation. op = dbsession.query(CryptoOperation).get(opid) op.txid = txid_to_bin(txid) op.block = None op.mark_broadcasted() address, amount, external_address = prepare_withdraw() # Do actual network communications outside the transaction, # so we avoid double withdraws in the case transaction is retried wallet = HostedWallet.get(web3, address) txid = wallet.withdraw(bin_to_eth_address(external_address), amount) close_withdraw()
def _create_token(web3, dbsession, opid): with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) # Check everyting looks sane assert op.crypto_account.id assert op.crypto_account.account.id asset = op.holding_account.asset assert asset.id # Set information on asset that we have now created and have its smart contract id assert not asset.external_id, "Asset has been already assigned its smart contract id. Recreate error?" address = bin_to_eth_address(op.crypto_account.address.address) # Call geth RPC API over Populus contract proxy op.txid = txid_to_bin(TEST_TOKEN_TXID) op.block = None op.external_address = eth_address_to_bin(TEST_TOKEN_ADDRESS) asset.external_id = op.external_address op.mark_performed() op.mark_broadcasted()
def house_address(dbsession, eth_service, web3, eth_network_id) -> UUID: """Create a network specific house address. :return: Address UUID """ with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) op = create_house_address(network) opid = op.id address_id = op.address.id assert address_id # this runs op eth_service.run_waiting_operations() with transaction.manager: address = dbsession.query(CryptoAddress).get(address_id) addr = bin_to_eth_address(address.address) # Send some funds to the house from coinbase txid = send_balance_to_address(web3, addr, Decimal("0.1")) wait_tx(web3, txid) return address_id
def get_monitored_addresses(self) -> Iterable[str]: """Get list of all known token smart contract addresses.""" with self._get_tm(): for asset in self.dbsession.query(Asset, Asset.external_id).filter(Asset.network_id == self.network_id, Asset.external_id != None): yield bin_to_eth_address(asset.external_id)
def get_monitored_addresses(self) -> Iterable[str]: """Get list of all ETH crtypto deposit addresses.""" with self._get_tm(): for addr in self.dbsession.query(CryptoAddress, CryptoAddress.address).filter(CryptoAddress.network_id == self.network_id, CryptoAddress.address != None): # addr.address is not set if the address is under construction yield bin_to_eth_address(addr.address)
def test_starter_token(dbsession, registry, eth_network_id, web3: Web3, eth_service: EthereumService, house_address, toybox): """See that a fresh user account is supplied with some play assets.""" with transaction.manager: user = create_user(dbsession, registry) setup_user_account(user, do_mainnet=True) # Let all the events completed success, fail = eth_service.run_event_cycle() assert success == 1 assert fail == 0 # We need another event cycle to process the initial asset transfers with transaction.manager: user = dbsession.query(User).first() opid = user.user_data["starter_asset_txs"][0]["toybox"] assert opid wait_for_op_confirmations(eth_service, opid) # Let the transfer come through eth_service.run_event_cycle() # Make sure we confirm the deposit with transaction.manager: user = dbsession.query(User).first() user_depo = user.owned_crypto_operations.join( CryptoOperation).filter_by( operation_type=CryptoOperationType.deposit).first() opid = user_depo.crypto_operation.id wait_for_op_confirmations(eth_service, opid) with transaction.manager: # Sanity check our token contract posts us logs user = dbsession.query(User).first() client = get_rpc_client(web3) asset = dbsession.query(Asset).get(toybox) address = bin_to_eth_address(asset.external_id) network = dbsession.query(AssetNetwork).get(eth_network_id) user_address = UserCryptoAddress.get_default(user, network).address house_address = dbsession.query(CryptoAddress).get(house_address) house = bin_to_eth_address(house_address.address) token = Token.get(web3, address) # Check we correctly resolved low level logs token_logs = client.get_logs(from_block=0, address=[address]) wallet_logs = client.get_logs(from_block=0, address=[house]) assert len(token_logs) > 0 assert len(wallet_logs) > 0 # Check contract state matches assert token.contract.call().balanceOf(house) == 9990 assert token.contract.call().balanceOf( bin_to_eth_address(user_address.address)) == 10 # Check our internal book keeping matches assert house_address.get_account(asset).account.get_balance() == 9990 assert user_address.get_account(asset).account.get_balance() == 10
def __str__(self): if self.address: return bin_to_eth_address(self.address) else: return "<no address assigned yet>"
def address(self) -> str: if self.asset.external_id: return bin_to_eth_address(self.asset.external_id) else: return ""