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 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 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 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 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 withdraw_token(web3: Web3, dbsession: Session, opid: UUID): """Perform token 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 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 @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() from_address, to_address, asset_address, amount = prepare_withdraw() wallet = HostedWallet.get(web3, from_address) token = Token.get(web3, asset_address) amount = token.validate_transfer_amount(amount) # Perform actual transfer outside retryable transaction # boundaries to avoid double withdrwa txid = wallet.execute(token.contract, "transfer", [to_address, amount]) close_withdraw()
def withdraw_token(web3: Web3, dbsession: Session, opid: UUID): """Perform token 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 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 @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() from_address, to_address, asset_address, amount = prepare_withdraw() wallet = HostedWallet.get(web3, from_address) token = Token.get(web3, asset_address) amount = token.validate_transfer_amount(amount) # Perform actual transfer outside retryable transaction # boundaries to avoid double withdrwa txid = wallet.execute(token.contract, "transfer", [to_address, amount]) close_withdraw()
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