def token_asset(dbsession, eth_network_id, deposit_address, eth_service: EthereumService) -> UUID: """Database asset referring to the token contract. deposit_address will hold 10000 tokens :return: Asset id """ with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) asset = network.create_asset(name="MyToken", symbol="MY", supply=Decimal(10000), asset_class=AssetClass.token) address = CryptoAddress.get_network_address( network, eth_address_to_bin(deposit_address)) op = address.create_token(asset) opid = op.id aid = asset.id # This gives op a txid success, fails = eth_service.run_waiting_operations() assert success == 1 wait_for_op_confirmations(eth_service, opid) with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) asset = dbsession.query(Asset).get(aid) address = CryptoAddress.get_network_address( network, eth_address_to_bin(deposit_address)) account = address.get_account(asset) assert account.account.get_balance() > 0 return aid
def token_asset(dbsession, eth_network_id, deposit_address, eth_service: EthereumService) -> UUID: """Database asset referring to the token contract. deposit_address will hold 10000 tokens :return: Asset id """ with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) asset = network.create_asset(name="MyToken", symbol="MY", supply=Decimal(10000), asset_class=AssetClass.token) address = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) op = address.create_token(asset) opid = op.id aid = asset.id # This gives op a txid success, fails = eth_service.run_waiting_operations() assert success == 1 wait_for_op_confirmations(eth_service, opid) with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) asset = dbsession.query(Asset).get(aid) address = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) account = address.get_account(asset) assert account.account.get_balance() > 0 return aid
def handle_event(self, event_name: str, contract_address: str, log_data: dict, log_entry: dict): """Map incoming EVM log to database entry.""" opid = self.get_unique_transaction_id(log_entry) existing_op = self.get_existing_op(opid, CryptoOperationType.deposit) if existing_op: # Already in the database, all we need to do is to call blocknumber updater now return False network = self.dbsession.query(AssetNetwork).get(self.network_id) asset = self.dbsession.query(Asset).filter_by( network=network, external_id=eth_address_to_bin(contract_address)).one() if event_name == "Transfer": to_address = eth_address_to_bin(log_data["to"]) from_address = eth_address_to_bin(log_data["from"]) value = Decimal(log_data["value"]) self.logger.debug("Incoming transfer event %s %s %s", from_address, to_address, value) # Get destination address entry address = self.dbsession.query(CryptoAddress).filter_by( address=to_address).one_or_none() if not address: # Address not in our system return False # Create operation op = CryptoAddressDeposit(network=network) op.opid = opid op.txid = txid_to_bin(log_entry["transactionHash"]) op.external_address = from_address op.block = int(log_entry["blockNumber"], 16) op.required_confirmation_count = self.confirmation_count op.crypto_account = address.get_or_create_account(asset) # Create holding account that keeps the value until we receive N amount of confirmations acc = Account(asset=asset) self.dbsession.add(acc) self.dbsession.flush() acc.do_withdraw_or_deposit( value, "Token {} deposit from {} in tx {}".format( asset.symbol, log_data["from"], log_entry["transactionHash"])) op.holding_account = acc self.dbsession.add(op) self.notify_deposit(op) return True else: # Unmonitored event return False
def test_deposit_eth(dbsession, eth_network_id, web3, eth_service, coinbase, deposit_address): """Accept incoming deposit.""" # Do a transaction over ETH network txid = send_balance_to_address(web3, deposit_address, TEST_VALUE) confirm_transaction(web3, txid) success_op_count, failed_op_count = eth_service.run_listener_operations() assert success_op_count == 1 assert failed_op_count == 0 # We get a operation, which is not resolved yet due to block confirmation numbers with transaction.manager: # Account not yet updated address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() eth_asset = get_ether_asset(dbsession) assert address.get_account(eth_asset).account.get_balance() == 0 # We have one ETH account on this address assert address.crypto_address_accounts.count() == 1 # We have one pending operation ops = list(dbsession.query(CryptoOperation).all()) assert len(ops) == 2 # Create + deposit op = ops[-1] assert isinstance(op, CryptoAddressDeposit) assert op.holding_account.get_balance() == TEST_VALUE assert op.completed_at is None opid = op.id # Wait that we reach critical confirmation count wait_for_op_confirmations(eth_service, opid) # Now account shoult have been settled with transaction.manager: # We have one complete operation ops = list(dbsession.query(CryptoOperation).all()) assert len(ops) == 2 # Create + deposit op = ops[-1] assert isinstance(op, CryptoAddressDeposit) assert op.holding_account.get_balance() == 0 assert op.completed_at is not None address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() # We have one ETH account on this address assert address.crypto_address_accounts.count() == 1 # We have one credited account eth_asset = get_ether_asset(dbsession) caccount = address.get_account(eth_asset) assert caccount.account.get_balance() == TEST_VALUE assert op.state == CryptoOperationState.success
def test_create_token(dbsession, eth_network_id, eth_service, coinbase, deposit_address): """Test user initiated token creation.""" # Initiate token creation operation with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) asset = network.create_asset(name="MyToken", symbol="MY", supply=Decimal(10000), asset_class=AssetClass.token) address = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) op = address.create_token(asset) opid = op.id aid = asset.id assert op.completed_at is None # Check asset is intact assert asset.symbol == "MY" assert asset.supply == 10000 assert asset.name == "MyToken" # This gives op a txid when smart contract creation tx is posted to geth success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # Check that initial asset data is in place with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) network = dbsession.query(AssetNetwork).get(eth_network_id) address = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) asset = network.get_asset(aid) assert op.txid assert not op.block assert op.broadcasted_at is not None assert op.completed_at is None # Initial balance doesn't hit us until tx has been confirmed assert address.get_account(asset).account.get_balance() == 0 # Asset has received its smart contract address assert asset.external_id # Wait that the smart contract creation is confirmed wait_for_op_confirmations(eth_service, opid) # Initial balance doesn't hit us until op has enough confirmations with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) network = dbsession.query(AssetNetwork).get(eth_network_id) address = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) asset = network.get_asset(aid) assert op.broadcasted_at is not None assert op.completed_at is not None assert address.get_account(asset).account.get_balance() == 10000
def test_double_address(dbsession, eth_network_id): """Cannot create Address object under the same network twice.""" with pytest.raises(sqlalchemy.exc.IntegrityError): with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) address = CryptoAddress(network=network, address=eth_address_to_bin(TEST_ADDRESS)) dbsession.add(address) dbsession.flush() address = CryptoAddress(network=network, address=eth_address_to_bin(TEST_ADDRESS)) dbsession.add(address)
def handle_event(self, event_name: str, contract_address: str, log_data: dict, log_entry: dict): """Map incoming EVM log to database entry.""" with self._get_tm(): opid = self.get_unique_transaction_id(log_entry) existing_op = self.get_existing_op(opid, CryptoOperationType.deposit) if existing_op: # Already in the database, all we need to do is to call blocknumber updater now return False network = self.dbsession.query(AssetNetwork).get(self.network_id) asset = self.dbsession.query(Asset).filter_by(network=network, external_id=eth_address_to_bin(contract_address)).one() if event_name == "Transfer": to_address = eth_address_to_bin(log_data["to"]) from_address = eth_address_to_bin(log_data["from"]) value = Decimal(log_data["value"]) # Get destination address entry address = self.dbsession.query(CryptoAddress).filter_by(address=to_address).one_or_none() if not address: # Address not in our system return False # Create operation op = CryptoAddressDeposit(network=network) op.opid = opid op.txid = txid_to_bin(log_entry["transactionHash"]) op.external_address = from_address op.block = int(log_entry["blockNumber"], 16) op.required_confirmation_count = self.confirmation_count op.crypto_account = address.get_or_create_account(asset) # Create holding account that keeps the value until we receive N amount of confirmations acc = Account(asset=asset) self.dbsession.add(acc) self.dbsession.flush() acc.do_withdraw_or_deposit(value, "Token {} deposit from {} in tx {}".format(asset.symbol, log_data["from"], log_entry["transactionHash"])) op.holding_account = acc self.dbsession.add(op) self.notify_deposit(op) return True else: # Unmonitored event return False
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 test_create_wallet(hosted_wallet): """Deploy a wallet contract on a testnet chain. """ # Check we get somewhat valid ids assert hosted_wallet.version == 2 assert eth_address_to_bin(hosted_wallet.address)
def test_create_address_accounts(dbsession, eth_network_id, eth_service, eth_faux_address, eth_asset_id): """Check that we can cerate different accounts under an account.""" # Create ETH account with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) address = CryptoAddress(network=network, address=eth_address_to_bin(TEST_ADDRESS)) asset = dbsession.query(Asset).get(eth_asset_id) dbsession.flush() account = address.get_or_create_account(asset) account_id = account.id # We get it reflected back second time with transaction.manager: address = dbsession.query(CryptoAddress).one() asset = dbsession.query(Asset).get(eth_asset_id) account = address.get_or_create_account(asset) assert account.id == account_id # We cannot create double account for the same asset with pytest.raises(MultipleAssetAccountsPerAddress): with transaction.manager: address = dbsession.query(CryptoAddress).one() asset = dbsession.query(Asset).get(eth_asset_id) address.create_account(asset)
def _create_address(service, dbsession, opid): with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert isinstance(op.address, CryptoAddress) op.address.address = eth_address_to_bin(address) op.mark_performed() op.mark_complete()
def on_deposit(self, address: CryptoAddress, opid, log_data, log_entry) -> CryptoAddressDeposit: """Handle Hosted Wallet Deposit event. Create incoming holding account holding the ETH assets until we receive enough confirmations. """ op = CryptoAddressDeposit(address.network) # Get or create final account where we deposit the transaction asset = get_ether_asset(self.dbsession, network=address.network) crypto_account = address.get_or_create_account(asset) op.crypto_account = crypto_account op.external_address = eth_address_to_bin(log_data["from"]) # Create holding account that keeps the value until we receive N amount of confirmations acc = Account(asset=asset) self.dbsession.add(acc) self.dbsession.flush() value = wei_to_eth(log_data["value"]) acc.do_withdraw_or_deposit( value, "ETH deposit from {} in tx {}".format( log_data["from"], log_entry["transactionHash"])) op.holding_account = acc return op
def handle_event(self, event_name: str, contract_address: str, log_data: dict, log_entry: dict): """Map incoming EVM log to database entry.""" with self._get_tm(): opid = self.get_unique_transaction_id(log_entry) existing_op = self.get_existing_op(opid, CryptoOperationType.deposit) if existing_op: # Already in the database, all we need to do is to call blocknumber updater now return False network = self.dbsession.query(AssetNetwork).get(self.network_id) address = self.dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(contract_address), network=network).one() op = self.create_op(event_name, address, opid, log_data, log_entry) if not op: # This was an event we don't care about return False op.opid = opid op.txid = txid_to_bin(log_entry["transactionHash"]) op.block = int(log_entry["blockNumber"], 16) op.required_confirmation_count = self.confirmation_count self.dbsession.add(op) self.notify_deposit(op) return True
def handle_event(self, event_name: str, contract_address: str, log_data: dict, log_entry: dict): """Map incoming EVM log to database entry.""" opid = self.get_unique_transaction_id(log_entry) existing_op = self.get_existing_op(opid, CryptoOperationType.deposit) if existing_op: # Already in the database, all we need to do is to call blocknumber updater now return False network = self.dbsession.query(AssetNetwork).get(self.network_id) address = self.dbsession.query(CryptoAddress).filter_by( address=eth_address_to_bin(contract_address), network=network).one() op = self.create_op(event_name, address, opid, log_data, log_entry) if not op: # This was an event we don't care about return False op.opid = opid op.txid = txid_to_bin(log_entry["transactionHash"]) op.block = int(log_entry["blockNumber"], 16) op.required_confirmation_count = self.confirmation_count self.dbsession.add(op) self.notify_deposit(op) return True
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 on_deposit(self, address: CryptoAddress, opid, log_data, log_entry) -> CryptoAddressDeposit: """Handle Hosted Wallet Deposit event. Create incoming holding account holding the ETH assets until we receive enough confirmations. """ op = CryptoAddressDeposit(address.network) # Get or create final account where we deposit the transaction asset = get_ether_asset(self.dbsession, network=address.network) crypto_account = address.get_or_create_account(asset) op.crypto_account = crypto_account op.external_address = eth_address_to_bin(log_data["from"]) # Create holding account that keeps the value until we receive N amount of confirmations acc = Account(asset=asset) self.dbsession.add(acc) self.dbsession.flush() value = wei_to_eth(log_data["value"]) acc.do_withdraw_or_deposit(value, "ETH deposit from {} in tx {}".format(log_data["from"], log_entry["transactionHash"])) op.holding_account = acc return op
def test_deposit_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, coinbase: str, deposit_address: str, token: Token): """"See that we can deposit tokens to accounts.""" # Import a contract where coinbase has all balance with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) import_token(network, eth_address_to_bin(token.address)) # Run import token operation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # Coinbase transfers token balance to deposit address txid = token.transfer(deposit_address, Decimal(4000)) confirm_transaction(web3, txid) # We should pick up incoming deposit success_count, failure_count = eth_service.run_listener_operations() assert success_count == 1 assert failure_count == 0 # Check that data is setup correctly on incoming transaction with transaction.manager: op = dbsession.query(CryptoOperation).all()[-1] opid = op.id assert not op.completed_at address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = op.holding_account.asset assert op.holding_account.get_balance() == 4000 assert op.completed_at is None assert address.get_account(asset).account.get_balance() == 0 # Not credited until confirmations reached assert address.crypto_address_accounts.count() == 1 # Wait the token transaction to get enough confirmations wait_for_op_confirmations(eth_service, opid) # Check that the transaction is not final with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at assert op.completed_at address = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() asset = op.holding_account.asset assert op.holding_account.get_balance() == 0 assert address.get_account(asset).account.get_balance() == 4000 assert op.state == CryptoOperationState.success
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 do_faux_withdraw(user_address: UserCryptoAddress, target_address, asset_id, amount) -> UserCryptoOperation: """Simulate user withdrawing assets from one of his addresses.""" dbsession = Session.object_session(user_address) asset = dbsession.query(Asset).get(asset_id) op = user_address.withdraw(asset, Decimal(amount), eth_address_to_bin(target_address), "Simulated withraw", required_confirmation_count=1) return op
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 withdraw(user_asset: UserAddressAsset, request): """Ask user for the withdraw details.""" title = "Withdraw" wallet = user_asset.wallet asset_resource = user_asset network_resource = get_network_resource(request, asset_resource.asset.network) balance = format_asset_amount(user_asset.balance, user_asset.asset.asset_class) address_resource = asset_resource.address account = user_asset.account schema = WithdrawSchema().bind(request=request, account=account) b = deform.Button(name='process', title="Verify with SMS", css_class="btn-block btn-lg") form = deform.Form(schema, buttons=(b,)) # User submitted this form if request.method == "POST": if 'process' in request.POST: try: appstruct = form.validate(request.POST.items()) # Save form data from appstruct amount = appstruct["amount"] address = eth_address_to_bin(appstruct["address"]) note = appstruct["note"] confirmations = get_required_confirmation_count(request.registry, user_asset.account.asset.network, CryptoOperationType.withdraw) user_crypto_address = asset_resource.address.address # Create the withdraw user_withdraw = user_crypto_address.withdraw(asset_resource.asset, amount, address, note, confirmations) # Mark it as pending for confirmation UserWithdrawConfirmation.require_confirmation(user_withdraw) # Redirect user to the confirmation page user_crypto_operation_resource = get_user_crypto_operation_resource(request, user_withdraw) return HTTPFound(request.resource_url(user_crypto_operation_resource, "confirm-withdraw")) except deform.ValidationFailure as e: # Render a form version where errors are visible next to the fields, # and the submitted values are posted back rendered_form = e.render() else: # We don't know which control caused form submission raise HTTPInternalServerError("Unknown form button pressed") else: # Render a form with initial values rendered_form = form.render() return locals()
def test_import_token(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, coinbase: str, deposit_address: str, token: Token): """Import an existing smart contract token to system.""" # Make sure we have an address that holds some of the tokens so it is cleared up during import txid = token.transfer(deposit_address, Decimal(4000)) confirm_transaction(web3, txid) with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) op = import_token(network, eth_address_to_bin(token.address)) opid = op.id # Let's create another address that doesn't hold tokens # and see that import doesn't fail for it addr = CryptoAddress(network=network, address=eth_address_to_bin("0x2f70d3d26829e412a602e83fe8eebf80255aeea5")) dbsession.add(addr) success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # Check that we created a new asset and its imports where fulfilled with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at # We got account with tokens on it network = dbsession.query(AssetNetwork).get(eth_network_id) caddress = CryptoAddress.get_network_address(network, eth_address_to_bin(deposit_address)) asset = network.assets.filter_by(external_id=eth_address_to_bin(token.address)).one() assert asset.name == "Mootoken" assert asset.symbol == "MOO" assert asset.supply == 10000 caccount = caddress.get_account_by_address(eth_address_to_bin(token.address)) assert caccount.account.get_balance() == 4000
def finish_op(): op = dbsession.query(CryptoOperation).get(opid) txid = wallet.initial_txid receipt = web3.eth.getTransactionReceipt(txid) op.txid = txid_to_bin(txid) op.block = receipt["blockNumber"] op.address.address = eth_address_to_bin(wallet.address) op.external_address = op.address.address # There is no broadcast wait, so we can close this right away op.mark_performed() op.mark_broadcasted() op.mark_complete()
def objectify(self, appstruct: dict, obj: Asset): """Store the dictionary data from the form submission on the object.""" objectify(self, appstruct, obj, excludes=("long_description", "external_id")) if not obj.other_data: # When creating the object JSON value may be None # instead of empty dict obj.other_data = {} # Special case of field stored inside JSON bag obj.other_data["long_description"] = appstruct["long_description"] # Convert between binary storage and human readable hex presentation if appstruct["external_id"]: obj.external_id = eth_address_to_bin(appstruct["external_id"])
def test_confirm_user_withdraw_timeout(dbsession, eth_network_id, eth_asset_id, user_id, topped_up_user): """User did not reply to withdraw confirmation within the timeout.""" with transaction.manager: uca = dbsession.query(UserCryptoAddress).first() asset = dbsession.query(Asset).get(eth_asset_id) withdraw_op = uca.withdraw(asset, Decimal(5), eth_address_to_bin(TEST_ADDRESS), "Foobar", 1) UserWithdrawConfirmation.require_confirmation(withdraw_op) with transaction.manager: ManualConfirmation.run_timeout_checks(dbsession, now() + timedelta(hours=12)) with transaction.manager: confirmation = dbsession.query(UserWithdrawConfirmation).first() assert confirmation.action_taken_at assert confirmation.state == ManualConfirmationState.timed_out assert confirmation.user_crypto_operation.crypto_operation.state == CryptoOperationState.cancelled assert "error" in confirmation.user_crypto_operation.crypto_operation.other_data
def test_import_no_address_token(dbsession: Session, eth_network_id, web3: Web3, eth_service: EthereumService): """Import should fail for address that doesn't exist.""" with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) # A random address op = import_token(network, eth_address_to_bin("0x2f70d3d26829e412a602e83fe8eebf80255aeea5")) opid = op.id # Success count here means the operation passed, but might be marked as failure success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.failed_at assert op.other_data["error"].startswith("Address did not provide EIP-20 token API:")
def _create_address(web3, dbsession, opid): with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) txid = TEST_ADDRESS_INITIAL_TXID # Deterministically pull faux addresses from pool network = op.network address_pool = network.other_data["test_address_pool"] address = address_pool.pop() op.txid = txid_to_bin(txid) op.block = 666 op.address.address = eth_address_to_bin(address) op.external_address = op.address.address op.mark_performed() op.mark_broadcasted()
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 toycrowd(dbsession, web3, eth_service, eth_network_id, token) -> Callable: """Set up a Toycrowd asset. :return: Crowdsale callable """ data = get_crowdsale_data(token) with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) asset = network.create_asset(name=data["name"], symbol=data["symbol"], supply=data["supply"], asset_class=AssetClass.token) bin_address = eth_address_to_bin(token.address) asset.external_id = bin_address asset.other_data["contract"] = "CrowdfundToken" dbsession.add(asset) dbsession.flush() asset_id = asset.id return lambda: dbsession.query(Asset).get(asset_id)
def test_confirm_user_withdraw_success(dbsession, eth_network_id, eth_asset_id, user_id, topped_up_user): """SMS confirmation success.""" with transaction.manager: uca = dbsession.query(UserCryptoAddress).first() asset = dbsession.query(Asset).get(eth_asset_id) withdraw_op = uca.withdraw(asset, Decimal(5), eth_address_to_bin(TEST_ADDRESS), "Foobar", 1) confirmation = UserWithdrawConfirmation.require_confirmation(withdraw_op) assert confirmation.created_at assert confirmation.confirmation_type == ManualConfirmationType.sms assert confirmation.state == ManualConfirmationState.pending with transaction.manager: confirmation = dbsession.query(UserWithdrawConfirmation).first() code = confirmation.other_data["sms_code"] confirmation.resolve_sms(code, None) assert confirmation.action_taken_at assert confirmation.state == ManualConfirmationState.resolved assert confirmation.user_crypto_operation.crypto_operation.state == CryptoOperationState.waiting
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 test_confirm_user_withdraw_cancelled(dbsession, eth_network_id, eth_asset_id, user_id, topped_up_user): """User cancels the withdraw confirmation.""" with transaction.manager: uca = dbsession.query(UserCryptoAddress).first() asset = dbsession.query(Asset).get(eth_asset_id) original_balance = uca.get_crypto_account(asset).account.get_balance() withdraw_op = uca.withdraw(asset, Decimal(7), eth_address_to_bin(TEST_ADDRESS), "Foobar", 1) UserWithdrawConfirmation.require_confirmation(withdraw_op) with transaction.manager: confirmation = dbsession.query(UserWithdrawConfirmation).first() confirmation.cancel() assert confirmation.action_taken_at assert confirmation.state == ManualConfirmationState.cancelled assert confirmation.user_crypto_operation.crypto_operation.state == CryptoOperationState.cancelled assert "error" in confirmation.user_crypto_operation.crypto_operation.other_data # The balance was restored uca = dbsession.query(UserCryptoAddress).first() asset = dbsession.query(Asset).get(eth_asset_id) assert uca.get_crypto_account(asset).account.get_balance() == original_balance
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 withdraw(user_asset: UserAddressAsset, request): """Ask user for the withdraw details.""" title = "Withdraw" wallet = user_asset.wallet asset_resource = user_asset network_resource = get_network_resource(request, asset_resource.asset.network) balance = format_asset_amount(user_asset.balance, user_asset.asset.asset_class) address_resource = asset_resource.address account = user_asset.account schema = WithdrawSchema().bind(request=request, account=account) withdraw = deform.Button(name='process', title="Verify with SMS", css_class="btn-primary btn-block btn-lg") cancel = deform.Button(name='cancel', title="Cancel", css_class="btn-primary btn-block btn-lg") form = deform.Form(schema, buttons=(withdraw, cancel), resource_registry=ResourceRegistry(request)) # User submitted this form if request.method == "POST": if 'process' in request.POST: try: appstruct = form.validate(request.POST.items()) # Save form data from appstruct amount = appstruct["amount"] address = eth_address_to_bin(appstruct["address"]) note = appstruct["note"] confirmations = get_required_confirmation_count(request.registry, user_asset.account.asset.network, CryptoOperationType.withdraw) user_crypto_address = asset_resource.address.address # Create the withdraw user_withdraw = user_crypto_address.withdraw(asset_resource.asset, amount, address, note, confirmations) # Ethereum special parameters user_withdraw.crypto_operation.other_data["gas"] = appstruct["advanced"]["gas"] data = appstruct["advanced"]["data"] if data: user_withdraw.crypto_operation.other_data["data"] = data # Mark it as pending for confirmation UserWithdrawConfirmation.require_confirmation(user_withdraw) # Redirect user to the confirmation page user_crypto_operation_resource = get_user_crypto_operation_resource(request, user_withdraw) return HTTPFound(request.resource_url(user_crypto_operation_resource, "confirm-withdraw")) except deform.ValidationFailure as e: # Render a form version where errors are visible next to the fields, # and the submitted values are posted back rendered_form = e.render() elif "cancel" in request.POST: return HTTPFound(request.resource_url(wallet)) else: # We don't know which control caused form submission raise HTTPInternalServerError("Unknown form button pressed") else: # Render a form with initial values rendered_form = form.render() # This loads widgets specific CSS/JavaScript in HTML code, # if form widgets specify any static assets. form.resource_registry.pull_in_resources(request, form) return locals()
def test_withdraw_eth_account(dbsession, eth_service, eth_network_id, eth_asset_id): """Withdraw ETHs to an address.""" # 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.flush() assert address.id assert address.address asset = dbsession.query(Asset).get(eth_asset_id) # Create an account of ETH tokens on that address ca_account = address.create_account(asset) # It should have zero balance by default with transaction.manager: ca_account = dbsession.query(CryptoAddressAccount).one_or_none() assert ca_account.account.asset_id == eth_asset_id assert ca_account.account.get_balance() == Decimal(0) # Faux top up so we have value to withdraw with transaction.manager: ca_account = dbsession.query(CryptoAddressAccount).one_or_none() assert ca_account.account.do_withdraw_or_deposit( Decimal("+10"), "Faux top up") # Create withdraw operations withdraw_address = eth_address_to_bin(TEST_ADDRESS) with transaction.manager: ca_account = dbsession.query(CryptoAddressAccount).one_or_none() op = ca_account.withdraw(Decimal("10"), withdraw_address, "Bailing out") # We withdraw 10 ETHs assert op.holding_account.get_balance() == Decimal("10") assert op.holding_account.asset == dbsession.query(Asset).get( eth_asset_id) assert op.holding_account.transactions.count() == 1 assert op.holding_account.transactions.first().message == "Bailing out" # Check all looks good on sending account assert ca_account.account.transactions.count() == 2 assert ca_account.account.transactions.all( )[0].message == "Faux top up" assert ca_account.account.transactions.all( )[1].message == "Bailing out" assert ca_account.account.get_balance() == 0 def _withdraw_eth(service, dbsession, opid): # Mocked withdraw op that always success with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) op.txid = txid_to_bin(TEST_TXID) op.mark_complete() with mock.patch("websauna.wallet.ethereum.ops.withdraw_eth", new=_withdraw_eth): success_op_count, failed_op_count = eth_service.run_waiting_operations( ) # Check that operations have been marked as success with transaction.manager: ops = list(dbsession.query(CryptoOperation).all()) assert len(ops) == 1 assert isinstance(ops[0], CryptoAddressWithdraw) assert ops[0].state == CryptoOperationState.success assert ops[0].txid == txid_to_bin(TEST_TXID)
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 get_title(self): if not self.get_object().address: return "-" return eth_address_to_bin(self.get_object().address)
def test_transfer_tokens_between_accounts(dbsession, eth_network_id, web3: Web3, eth_service: EthereumService, deposit_address: str, token_asset: str, coinbase: str): """Transfer tokens between two internal accounts. Do transfers in two batches to make sure subsequent events top up correctly. """ # Initiate a target address creatin with transaction.manager: network = dbsession.query(AssetNetwork).get(eth_network_id) opid = CryptoAddress.create_address(network).id # Run address creation success_count, failure_count = eth_service.run_waiting_operations() assert success_count == 1 assert failure_count == 0 # We resolved address creation operation. # Get the fresh address for the future withdraw targets. with transaction.manager: op = dbsession.query(CryptoAddressCreation).get(opid) addr = op.address.address assert addr # Move tokens between accounts 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), addr, "Sending to friend") opid = op.id # Resolve the 1st transaction eth_service.run_event_cycle() wait_for_op_confirmations(eth_service, opid) # See that our internal balances match with transaction.manager: # Withdraw operation is complete op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at assert op.completed_at, "Op not confirmed {}".format(op) # We should have received a Transfer operation targetting target account op = dbsession.query(CryptoOperation).join(CryptoAddressAccount).join(CryptoAddress).filter_by(address=addr).one() opid = op.id # Confirm incoming Transfer wait_for_op_confirmations(eth_service, opid, timeout=180) # Check Transfer looks valid with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at assert op.completed_at, "Op not confirmed {}".format(op) asset = dbsession.query(Asset).get(token_asset) source = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() target = dbsession.query(CryptoAddress).filter_by(address=addr).one() assert source.get_account(asset).account.get_balance() == 8000 assert target.get_account(asset).account.get_balance() == 2000 # Add some ETH on deposit_address txid = send_balance_to_address(web3, deposit_address, TEST_VALUE) confirm_transaction(web3, txid) eth_service.run_event_cycle() # Wait for confirmations to have ETH deposit credired with transaction.manager: op = dbsession.query(CryptoOperation).filter_by(txid=txid_to_bin(txid)).one() opid = op.id confirmed = op.completed_at if not confirmed: wait_for_op_confirmations(eth_service, opid) # Send some more tokens + ether, so we see account can't get mixed up 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(4000), addr, "Sending tokens to friend äää") eth_asset = get_ether_asset(dbsession) caccount = address.get_account(eth_asset) op = caccount.withdraw(TEST_VALUE, addr, "Sending ETH to friend äää") opid = op.id # Resolve second transaction eth_service.run_event_cycle() wait_for_op_confirmations(eth_service, opid) # See that our internal balances match with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at assert op.completed_at asset = dbsession.query(Asset).get(token_asset) source = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() target = dbsession.query(CryptoAddress).filter_by(address=addr).one() eth_asset = get_ether_asset(dbsession) assert source.get_account(asset).account.get_balance() == 4000 assert target.get_account(asset).account.get_balance() == 6000 assert target.get_account(eth_asset).account.get_balance() == TEST_VALUE # Transfer 3: # Send everything back plus some ether with transaction.manager: address = dbsession.query(CryptoAddress).filter_by(address=addr).one() asset = dbsession.query(Asset).get(token_asset) caccount = address.get_account(asset) op = caccount.withdraw(Decimal(6000), eth_address_to_bin(deposit_address), "Sending everything back to friend äää") opid = op.id # Resolve third transaction wait_for_op_confirmations(eth_service, opid) eth_service.run_event_cycle() # See that our internal balances match with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) assert op.completed_at assert op.completed_at asset = dbsession.query(Asset).get(token_asset) source = dbsession.query(CryptoAddress).filter_by(address=eth_address_to_bin(deposit_address)).one() target = dbsession.query(CryptoAddress).filter_by(address=addr).one() assert source.get_account(asset).account.get_balance() == 10000 assert target.get_account(asset).account.get_balance() == 0 eth_asset = get_ether_asset(dbsession) assert source.get_account(eth_asset).account.get_balance() == 0 assert target.get_account(eth_asset).account.get_balance() == TEST_VALUE
def test_withdraw_eth_account(dbsession, eth_service, eth_network_id, eth_asset_id): """Withdraw ETHs to an address.""" # 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.flush() assert address.id assert address.address asset = dbsession.query(Asset).get(eth_asset_id) # Create an account of ETH tokens on that address ca_account = address.create_account(asset) # It should have zero balance by default with transaction.manager: ca_account = dbsession.query(CryptoAddressAccount).one_or_none() assert ca_account.account.asset_id == eth_asset_id assert ca_account.account.get_balance() == Decimal(0) # Faux top up so we have value to withdraw with transaction.manager: ca_account = dbsession.query(CryptoAddressAccount).one_or_none() assert ca_account.account.do_withdraw_or_deposit(Decimal("+10"), "Faux top up") # Create withdraw operations withdraw_address = eth_address_to_bin(TEST_ADDRESS) with transaction.manager: ca_account = dbsession.query(CryptoAddressAccount).one_or_none() op = ca_account.withdraw(Decimal("10"), withdraw_address, "Bailing out") # We withdraw 10 ETHs assert op.holding_account.get_balance() == Decimal("10") assert op.holding_account.asset == dbsession.query(Asset).get(eth_asset_id) assert op.holding_account.transactions.count() == 1 assert op.holding_account.transactions.first().message == "Bailing out" # Check all looks good on sending account assert ca_account.account.transactions.count() == 2 assert ca_account.account.transactions.all()[0].message == "Faux top up" assert ca_account.account.transactions.all()[1].message == "Bailing out" assert ca_account.account.get_balance() == 0 def _withdraw_eth(service, dbsession, opid): # Mocked withdraw op that always success with transaction.manager: op = dbsession.query(CryptoOperation).get(opid) op.txid = txid_to_bin(TEST_TXID) op.mark_complete() with mock.patch("websauna.wallet.ethereum.ops.withdraw_eth", new=_withdraw_eth): success_op_count, failed_op_count = eth_service.run_waiting_operations() # Check that operations have been marked as success with transaction.manager: ops = list(dbsession.query(CryptoOperation).all()) assert len(ops) == 1 assert isinstance(ops[0], CryptoAddressWithdraw) assert ops[0].state == CryptoOperationState.success assert ops[0].txid == txid_to_bin(TEST_TXID)