Exemplo n.º 1
0
 def helper_test_on_send_click(self, app):
     """
     Verifies clicking "Send" Ethers works as expected, refs #63.
     Also checks for the amount field, refs #152.
     """
     controller = app.controller
     # TODO: use dispatch('on_release') on navigation drawer
     controller.load_landing_page()
     send = controller.send
     send_button_id = send.ids.send_button_id
     # verifies clicking send button doesn't crash the application
     send_button_id.dispatch('on_release')
     dialogs = Dialog.dialogs
     # but it would still raise some popups since the form is invalid
     self.assertEqual(len(dialogs), 2)
     self.assertEqual(dialogs[0].title, 'Input error')
     self.assertEqual(dialogs[1].title, 'Invalid form')
     Dialog.dismiss_all_dialogs()
     self.assertEqual(len(dialogs), 0)
     # also checks for the amount field, refs #152
     send_amount_id = send.ids.send_amount_id
     send_amount_id.text = '0.1'
     # the send_amount property should get updated from the input
     self.assertEqual(send.send_amount, 0.1)
     # blank amount shouldn't crash the app, just get ignored
     send_amount_id.text = ''
     self.assertEqual(send.send_amount, 0.1)
Exemplo n.º 2
0
 def helper_test_delete_account_none_selected(self, app):
     """
     Tries to delete account when none are selected, refs #90.
     """
     controller = app.controller
     pywalib = controller.pywalib
     manage_existing = controller.manage_existing
     # makes sure an account is selected
     pywalib.new_account(password="******", security_ratio=1)
     controller.current_account = pywalib.get_account_list()[0]
     # ManageExisting and Controller current_account should be in sync
     self.assertEqual(manage_existing.current_account,
                      controller.current_account)
     # chaning in the Controller, should trigger the change on the other
     self.assertTrue(manage_existing.current_account is not None)
     controller.current_account = None
     self.assertIsNone(manage_existing.current_account)
     # let's try to delete this "None account"
     delete_button_id = manage_existing.ids.delete_button_id
     delete_button_id.dispatch('on_release')
     # an error dialog should pop
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'No account selected.')
     Dialog.dismiss_all_dialogs()
Exemplo n.º 3
0
 def helper_test_delete_last_account(self, app):
     """
     Trying to delete the last account, should not crash the app,
     refs #120.
     """
     controller = app.controller
     pywalib = controller.pywalib
     manage_existing = controller.manage_existing
     # makes sure there's only one account left
     self.assertEqual(len(pywalib.get_account_list()), 1)
     # deletes it
     delete_button_id = manage_existing.ids.delete_button_id
     delete_button_id.dispatch('on_release')
     # a confirmation popup should show
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'Are you sure?')
     # confirm it
     manage_existing.on_delete_account_yes(dialog)
     # account was deleted dialog message
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'Account deleted, redirecting...')
     Dialog.dismiss_all_dialogs()
     self.advance_frames(1)
     # verifies the account was deleted
     self.assertEqual(len(pywalib.get_account_list()), 0)
     # this should be done by the events, but doesn't seem to happen
     # so we have to trigger it manually
     controller.history.current_account = None
     self.advance_frames(1)
Exemplo n.º 4
0
 def helper_test_delete_account(self, app):
     """
     Deletes account from the UI.
     """
     controller = app.controller
     pywalib = controller.pywalib
     # makes sure we have an account to play with
     self.assertEqual(len(pywalib.get_account_list()), 1)
     # makes sure the account appears in the switch account view
     switch_account = self.helper_load_switch_account(app)
     account_list_id = switch_account.ids.account_list_id
     children = account_list_id.children
     self.assertEqual(len(children), 1)
     item = children[0]
     self.assertEqual(type(item), kivymd.list.OneLineListItem)
     self.assertEqual(item.account, pywalib.get_account_list()[0])
     # go to the manage account screen
     # TODO: use dispatch('on_release') on navigation drawer
     controller.load_manage_keystores()
     # TODO: broken in #124
     # self.advance_frames(1)
     self.advance_frames(30)
     self.assertEqual('Manage existing', app.controller.toolbar.title)
     # verifies an account is showing
     manage_existing = controller.manage_existing
     account_address_id = manage_existing.ids.account_address_id
     account = pywalib.get_account_list()[0]
     account_address = '0x' + account.address.hex()
     self.assertEqual(account_address_id.text, account_address)
     # clicks delete
     delete_button_id = manage_existing.ids.delete_button_id
     delete_button_id.dispatch('on_release')
     # a confirmation popup should show
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'Are you sure?')
     # confirm it
     # TODO: click on the dialog action button itself
     manage_existing.on_delete_account_yes(dialog)
     # the dialog should be replaced by another one
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'Account deleted, redirecting...')
     Dialog.dismiss_all_dialogs()
     # and the account deleted
     self.assertEqual(len(pywalib.get_account_list()), 0)
     # makes sure the account was also cleared from the selection view
     switch_account = self.helper_load_switch_account(app)
     account_list_id = switch_account.ids.account_list_id
     # TODO: broken in #124
     self.advance_frames(30)
     self.assertEqual(len(account_list_id.children), 0)
Exemplo n.º 5
0
 def show_redirect_dialog(self):
     title = "Account deleted, redirecting..."
     body = ""
     body += "Your account was deleted, "
     body += "you will be redirected to the overview."
     dialog = Dialog.create_dialog(title, body)
     dialog.open()
Exemplo n.º 6
0
 def verify_amount_field(self):
     title = "Input error"
     body = "Invalid amount field"
     if self.send_amount == 0:
         dialog = Dialog.create_dialog(title, body)
         dialog.open()
         return False
     return True
Exemplo n.º 7
0
 def update_password(self):
     """
     Update account password with new password provided.
     """
     if not self.verify_fields():
         Dialog.show_invalid_form_dialog()
         return
     Dialog.snackbar_message("Verifying current password...")
     if not self.verify_current_password_field():
         Dialog.snackbar_message("Wrong account password")
         return
     pywalib = self.controller.pywalib
     account = self.current_account
     new_password = self.new_password1
     Dialog.snackbar_message("Updating account...")
     pywalib.update_account_password(account, new_password=new_password)
     Dialog.snackbar_message("Updated!")
Exemplo n.º 8
0
 def show_storage_permissions_required_dialog(self):
     title = "External storage permissions required"
     body = ""
     body += "In order to save your keystore, PyWallet requires access "
     body += "to your device storage. "
     body += "Please allow PyWallet to access it when prompted."
     dialog = Dialog.create_dialog(title, body)
     dialog.open()
     return dialog
Exemplo n.º 9
0
 def prompt_no_account_error(self):
     """
     Prompts an error since no account are selected for deletion, refs:
     https://github.com/AndreMiras/PyWallet/issues/90
     """
     title = "No account selected."
     body = "No account selected for deletion."
     dialog = Dialog.create_dialog(title, body)
     dialog.open()
Exemplo n.º 10
0
 def helper_test_on_send_click(self, app):
     """
     Verifies clicking "Send" Ethers works as expected, refs #63.
     Also checks for the amount field, refs #152.
     """
     controller = app.controller
     # TODO: use dispatch('on_release') on navigation drawer
     controller.load_landing_page()
     send = controller.send
     send_button_id = send.ids.send_button_id
     # verifies clicking send button doesn't crash the application
     send_button_id.dispatch('on_release')
     dialogs = Dialog.dialogs
     # but it would still raise a popup since the form is invalid
     self.assertEqual(len(dialogs), 1)
     self.assertEqual(dialogs[0].title, 'Input error')
     Dialog.dismiss_all_dialogs()
     self.assertEqual(len(dialogs), 0)
Exemplo n.º 11
0
 def setup(self):
     self.controller = App.get_running_app().controller
     self.keystore_path = Settings.get_keystore_path()
     accounts = self.controller.pywalib.get_account_list()
     if len(accounts) == 0:
         title = "No keystore found."
         body = "Import or create one."
         dialog = Dialog.create_dialog(title, body)
         dialog.open()
Exemplo n.º 12
0
 def verify_to_address_field(self):
     title = "Input error"
     body = "Invalid address field"
     try:
         to_checksum_address(self.send_to_address)
     except ValueError:
         dialog = Dialog.create_dialog(title, body)
         dialog.open()
         return False
     return True
Exemplo n.º 13
0
 def helper_confirm_account_deletion(self, app):
     """
     Helper method for confirming account deletion popups.
     """
     controller = app.controller
     manage_existing = controller.manage_existing
     # a confirmation popup should show
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'Are you sure?')
     # confirm it
     # TODO: click on the dialog action button itself
     manage_existing.on_delete_account_yes(dialog)
     # the dialog should be replaced by another one
     dialogs = Dialog.dialogs
     self.assertEqual(len(dialogs), 1)
     dialog = dialogs[0]
     self.assertEqual(dialog.title, 'Account deleted, redirecting...')
     Dialog.dismiss_all_dialogs()
     self.assertEqual(len(dialogs), 0)
Exemplo n.º 14
0
 def create_account(self):
     """
     Creates an account from provided form.
     Verify we can unlock it.
     Disables widgets during the process, so the user doesn't try
     to create another account during the process.
     """
     # circular ref
     from pywallet.controller import Controller
     self.toggle_widgets(False)
     if not self.verify_fields():
         Dialog.show_invalid_form_dialog()
         self.toggle_widgets(True)
         return
     pywalib = self.controller.pywalib
     password = self.new_password1
     security_ratio = self.security_slider_value
     # dividing again by 10, because otherwise it's
     # too slow on smart devices
     security_ratio /= 10.0
     Dialog.snackbar_message("Creating account...")
     account = pywalib.new_account(password=password,
                                   security_ratio=security_ratio)
     Dialog.snackbar_message("Created!")
     self.toggle_widgets(True)
     Controller.set_account_alias(account, self.alias)
     self.on_account_created(account)
     CreateNewAccount.try_unlock(account, password)
     self.show_redirect_dialog()
     self.controller.load_landing_page()
     return account
Exemplo n.º 15
0
 def fetch_balance(self):
     """
     Fetches the new balance & sets accounts_balance property.
     """
     if self.current_account is None:
         return
     address = '0x' + self.current_account.address.hex()
     chain_id = Settings.get_stored_network()
     try:
         balance = PyWalib.get_balance(address, chain_id)
     except ConnectionError:
         Dialog.on_balance_connection_error()
         Logger.warning('ConnectionError', exc_info=True)
         return
     except ValueError:
         # most likely the JSON object could not be decoded, refs #91
         # currently logged as an error, because we want more insight
         # in order to eventually handle it more specifically
         Dialog.on_balance_value_error()
         Logger.error('ValueError', exc_info=True)
         return
     except UnknownEtherscanException:
         # also handles uknown errors, refs #112
         Dialog.on_balance_unknown_error()
         Logger.error('UnknownEtherscanException', exc_info=True)
         return
     # triggers accounts_balance observers update
     self.accounts_balance[address] = balance
Exemplo n.º 16
0
 def fetch_history(self):
     if self.current_account is None:
         return
     chain_id = Settings.get_stored_network()
     address = '0x' + self.current_account.address.hex()
     try:
         transactions = PyWalib.get_transaction_history(address, chain_id)
     except ConnectionError:
         Dialog.on_history_connection_error()
         Logger.warning('ConnectionError', exc_info=True)
         return
     except NoTransactionFoundException:
         transactions = []
     except ValueError:
         # most likely the JSON object could not be decoded, refs #91
         Dialog.on_history_value_error()
         # currently logged as an error, because we want more insight
         # in order to eventually handle it more specifically
         Logger.error('ValueError', exc_info=True)
         return
     # triggers accounts_history observers update
     self.controller.accounts_history[address] = transactions
Exemplo n.º 17
0
 def prompt_password_dialog(self):
     """
     Prompt the password dialog.
     """
     title = "Enter your password"
     content = PasswordForm()
     dialog = Dialog.create_dialog_content_helper(title=title,
                                                  content=content)
     # workaround for MDDialog container size (too small by default)
     dialog.ids.container.size_hint_y = 1
     dialog.add_action_button(
         "Unlock",
         action=lambda *x: self.on_unlock_clicked(dialog, content.password))
     return dialog
Exemplo n.º 18
0
 def helper_test_delete_account_twice(self, app):
     """
     Trying to delete the same account twice, shoult not crash the app,
     refs #51.
     """
     controller = app.controller
     pywalib = controller.pywalib
     manage_existing = controller.manage_existing
     # makes sure an account is selected
     pywalib.new_account(password="******", security_ratio=None)
     controller.current_account = pywalib.get_account_list()[0]
     self.assertTrue(manage_existing.current_account is not None)
     account_count_before = len(pywalib.get_account_list())
     # let's try to delete this account once
     delete_button_id = manage_existing.ids.delete_button_id
     delete_button_id.dispatch('on_release')
     self.helper_confirm_account_deletion(app)
     # the account should be deleted
     self.assertEqual(len(pywalib.get_account_list()),
                      account_count_before - 1)
     # makes sure the account was also cleared from the selection view
     switch_account = self.helper_load_switch_account(app)
     account_list_id = switch_account.ids.account_list_id
     self.assertEqual(len(account_list_id.children),
                      len(pywalib.get_account_list()))
     # TODO: the selected account should now be None
     # self.assertIsNone(manage_existing.current_account)
     # self.assertIsNone(controller.current_account)
     # let's try to delete this account a second time
     delete_button_id = manage_existing.ids.delete_button_id
     delete_button_id.dispatch('on_release')
     # TODO: the second time an error dialog should pop
     # dialogs = Dialog.dialogs
     # self.assertEqual(len(dialogs), 1)
     # dialog = dialogs[0]
     # self.assertEqual(dialog.title, 'No account selected.')
     Dialog.dismiss_all_dialogs()
Exemplo n.º 19
0
 def prompt_delete_account_dialog(self):
     """
     Prompt a confirmation dialog before deleting the account.
     """
     if self.current_account is None:
         self.prompt_no_account_error()
         return
     title = "Are you sure?"
     body = ""
     body += "This action cannot be undone.\n"
     body += "Are you sure you want to delete this account?\n"
     dialog = Dialog.create_dialog_helper(title, body)
     dialog.add_action_button("No", action=lambda *x: dialog.dismiss())
     dialog.add_action_button(
         "Yes", action=lambda *x: self.on_delete_account_yes(dialog))
     dialog.open()
Exemplo n.º 20
0
 def try_unlock(account, password):
     """
     Just as a security measure, verifies we can unlock
     the newly created account with provided password.
     """
     # making sure it's locked first
     account.lock()
     try:
         account.unlock(password)
     except ValueError:
         title = "Unlock error"
         body = ""
         body += "Couldn't unlock your account.\n"
         body += "The issue should be reported."
         dialog = Dialog.create_dialog(title, body)
         dialog.open()
         return
Exemplo n.º 21
0
 def helper_test_dismiss_dialog_twice(self, app):
     """
     If by some choice the dismiss event of a dialog created with
     Controller.create_dialog_helper() is fired twice, it should be
     handled gracefully, refs #89.
     """
     title = "title"
     body = "body"
     # makes sure the controller has no dialog
     self.assertEqual(Dialog.dialogs, [])
     # creates one and verifies it was created
     dialog = Dialog.create_dialog_helper(title, body)
     self.assertEqual(len(Dialog.dialogs), 1)
     # dimisses it once and verifies it was handled
     dialog.dispatch('on_dismiss')
     self.assertEqual(Dialog.dialogs, [])
     # then a second time and it should not crash
     dialog.dispatch('on_dismiss')
     self.assertEqual(Dialog.dialogs, [])
Exemplo n.º 22
0
 def helper_test_controller_fetch_balance(self, app):
     """
     Verifies Controller.fetch_balance() works in most common cases.
     1) simple case, library PyWalib.get_balance() gets called
     2) ConnectionError should be handled
     3) handles 503 "service is unavailable", refs #91
     4) UnknownEtherscanException should be handled
     """
     controller = app.controller
     account = controller.current_account
     balance = 42
     # 1) simple case, library PyWalib.get_balance() gets called
     with mock.patch('pywalib.PyWalib.get_balance') as mock_get_balance, \
             patch_get_store_path(self.temp_path):
         mock_get_balance.return_value = balance
         thread = controller.fetch_balance()
         thread.join()
     address = '0x' + account.address.hex()
     mock_get_balance.assert_called_with(address, pywalib.ChainID.MAINNET)
     # and the balance updated
     self.assertEqual(controller.accounts_balance[address], balance)
     # 2) ConnectionError should be handled
     self.assertEqual(len(Dialog.dialogs), 0)
     with mock.patch('pywalib.PyWalib.get_balance') as mock_get_balance, \
             mock.patch('pywallet.controller.Logger') as mock_logger:
         mock_get_balance.side_effect = requests.exceptions.ConnectionError
         thread = controller.fetch_balance()
         thread.join()
     self.assertEqual(len(Dialog.dialogs), 1)
     dialog = Dialog.dialogs[0]
     self.assertEqual(dialog.title, 'Network error')
     Dialog.dismiss_all_dialogs()
     # the error should be logged
     mock_logger.warning.assert_called_with('ConnectionError',
                                            exc_info=True)
     # 3) handles 503 "service is unavailable", refs #91
     self.assertEqual(len(Dialog.dialogs), 0)
     response = requests.Response()
     response.status_code = 503
     response.raw = io.BytesIO(b'The service is unavailable.')
     with mock.patch('requests.get') as mock_requests_get, \
             mock.patch('pywallet.controller.Logger') as mock_logger:
         mock_requests_get.return_value = response
         thread = controller.fetch_balance()
         thread.join()
     self.assertEqual(len(Dialog.dialogs), 1)
     dialog = Dialog.dialogs[0]
     self.assertEqual(dialog.title, 'Unknown error')
     Dialog.dismiss_all_dialogs()
     # the error should be logged
     mock_logger.error.assert_called_with('UnknownEtherscanException',
                                          exc_info=True)
     # 4) UnknownEtherscanException should be handled
     self.assertEqual(len(Dialog.dialogs), 0)
     with mock.patch('pywalib.PyWalib.get_balance') as mock_get_balance, \
             mock.patch('pywallet.controller.Logger') as mock_logger:
         mock_get_balance.side_effect = pywalib.UnknownEtherscanException
         thread = controller.fetch_balance()
         thread.join()
     self.assertEqual(len(Dialog.dialogs), 1)
     dialog = Dialog.dialogs[0]
     self.assertEqual(dialog.title, 'Unknown error')
     Dialog.dismiss_all_dialogs()
     # the error should be logged
     mock_logger.error.assert_called_with('UnknownEtherscanException',
                                          exc_info=True)
Exemplo n.º 23
0
 def on_send_click(self):
     if not self.verify_fields():
         Dialog.show_invalid_form_dialog()
         return
     dialog = self.prompt_password_dialog()
     dialog.open()
Exemplo n.º 24
0
    def unlock_send_transaction(self):
        """
        Unlocks the account with password in order to sign and publish the
        transaction.
        """
        controller = App.get_running_app().controller
        pywalib = controller.pywalib
        address = to_checksum_address(self.send_to_address)
        amount_eth = round(self.send_amount, ROUND_DIGITS)
        amount_wei = int(amount_eth * pow(10, 18))
        gas_price_gwei = Settings.get_stored_gas_price()
        gas_price_wei = int(gas_price_gwei * (10 ** 9))
        # TODO: not the main account, but the current account
        account = controller.current_account
        Dialog.snackbar_message("Unlocking account...")
        try:
            account.unlock(self.password)
        except ValueError:
            Dialog.snackbar_message("Could not unlock account")
            return

        Dialog.snackbar_message("Unlocked! Sending transaction...")
        sender = account.address
        try:
            pywalib.transact(
                address, value=amount_wei, data='', sender=sender,
                gasprice=gas_price_wei)
        except InsufficientFundsException:
            Dialog.snackbar_message("Insufficient funds")
            return
        except UnknownEtherscanException:
            Dialog.snackbar_message("Unknown error")
            Logger.error('UnknownEtherscanException', exc_info=True)
            return
        # TODO: handle ConnectionError
        Dialog.snackbar_message("Sent!")