コード例 #1
0
ファイル: test_pywalib.py プロジェクト: imouren/PyWallet
class PywalibTestCase(unittest.TestCase):
    """
    Simple test cases, verifying pywalib works as expected.
    """
    def setUp(self):
        self.keystore_dir = mkdtemp()
        self.pywalib = PyWalib(self.keystore_dir)

    def tearDown(self):
        shutil.rmtree(self.keystore_dir, ignore_errors=True)

    def helper_new_account(self, password=PASSWORD, security_ratio=1):
        """
        Helper method for fast account creation.
        """
        account = self.pywalib.new_account(password, security_ratio)
        return account

    def test_new_account(self):
        """
        Simple account creation test case.
        1) verifies the current account list is empty
        2) creates a new account and verify we can retrieve it
        3) tries to unlock the account
        """
        pywalib = self.pywalib
        # 1) verifies the current account list is empty
        account_list = pywalib.get_account_list()
        self.assertEqual(len(account_list), 0)
        # 2) creates a new account and verify we can retrieve it
        password = PASSWORD
        # weak account, but fast creation
        security_ratio = 1
        account = pywalib.new_account(password, security_ratio)
        account_list = pywalib.get_account_list()
        self.assertEqual(len(account_list), 1)
        self.assertEqual(account, pywalib.get_main_account())
        # 3) tries to unlock the account
        # it's unlocked by default after creation
        self.assertFalse(account.locked)
        # let's lock it and unlock it back
        account.lock()
        self.assertTrue(account.locked)
        account.unlock(password)
        self.assertFalse(account.locked)

    def test_update_account_password(self):
        """
        Verifies updating account password works.
        """
        pywalib = self.pywalib
        current_password = "******"
        # weak account, but fast creation
        security_ratio = 1
        account = pywalib.new_account(current_password, security_ratio)
        # first try when the account is already unlocked
        self.assertFalse(account.locked)
        new_password = "******"
        # on unlocked account the current_password is optional
        pywalib.update_account_password(account,
                                        new_password,
                                        current_password=None)
        # verify it worked
        account.lock()
        account.unlock(new_password)
        self.assertFalse(account.locked)
        # now try when the account is first locked
        account.lock()
        current_password = "******"
        with self.assertRaises(ValueError):
            pywalib.update_account_password(account, new_password,
                                            current_password)
        current_password = new_password
        pywalib.update_account_password(account, new_password,
                                        current_password)
        self.assertFalse(account.locked)

    def test_deleted_account_dir(self):
        """
        The deleted_account_dir() helper method should be working
        with and without trailing slash.
        """
        expected_deleted_keystore_dir = '/tmp/keystore-deleted'
        keystore_dirs = [
            # without trailing slash
            '/tmp/keystore',
            # with one trailing slash
            '/tmp/keystore/',
            # with two trailing slashes
            '/tmp/keystore//',
        ]
        for keystore_dir in keystore_dirs:
            self.assertEqual(PyWalib.deleted_account_dir(keystore_dir),
                             expected_deleted_keystore_dir)

    def test_delete_account(self):
        """
        Creates a new account and delete it.
        Then verify we can load the account from the backup/trash location.
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        address = account.address
        self.assertEqual(len(pywalib.get_account_list()), 1)
        # deletes the account and verifies it's not loaded anymore
        pywalib.delete_account(account)
        self.assertEqual(len(pywalib.get_account_list()), 0)
        # even recreating the PyWalib object
        pywalib = PyWalib(self.keystore_dir)
        self.assertEqual(len(pywalib.get_account_list()), 0)
        # tries to reload it from the backup/trash location
        deleted_keystore_dir = PyWalib.deleted_account_dir(self.keystore_dir)
        pywalib = PyWalib(deleted_keystore_dir)
        self.assertEqual(len(pywalib.get_account_list()), 1)
        self.assertEqual(pywalib.get_account_list()[0].address, address)

    def test_delete_account_already_exists(self):
        """
        If the destination (backup/trash) directory where the account is moved
        already exists, it should be handled gracefully.
        This could happens if the account gets deleted, then reimported and
        deleted again, refs:
        https://github.com/AndreMiras/PyWallet/issues/88
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        # creates a file in the backup/trash folder that would conflict
        # with the deleted account
        deleted_keystore_dir = PyWalib.deleted_account_dir(self.keystore_dir)
        os.makedirs(deleted_keystore_dir)
        account_filename = os.path.basename(account.path)
        deleted_account_path = os.path.join(deleted_keystore_dir,
                                            account_filename)
        # create that file
        open(deleted_account_path, 'a').close()
        # then deletes the account and verifies it worked
        self.assertEqual(len(pywalib.get_account_list()), 1)
        pywalib.delete_account(account)
        self.assertEqual(len(pywalib.get_account_list()), 0)

    def test_handle_etherscan_error(self):
        """
        Checks handle_etherscan_error() error handling.
        """
        # no transaction found
        response_json = {
            'message': 'No transactions found',
            'result': [],
            'status': '0'
        }
        with self.assertRaises(NoTransactionFoundException):
            PyWalib.handle_etherscan_error(response_json)
        # unknown error
        response_json = {
            'message': 'Unknown error',
            'result': [],
            'status': '0'
        }
        with self.assertRaises(UnknownEtherscanException):
            PyWalib.handle_etherscan_error(response_json)
        # no error
        response_json = {'message': 'OK', 'result': [], 'status': '1'}
        self.assertEqual(PyWalib.handle_etherscan_error(response_json), None)

    def test_address_hex(self):
        """
        Checks handle_etherscan_error() error handling.
        """
        expected_addresss = ADDRESS
        # no 0x prefix
        address_no_prefix = ADDRESS.lower().strip("0x")
        address = address_no_prefix
        normalized = PyWalib.address_hex(address)
        self.assertEqual(normalized, expected_addresss)
        # uppercase
        address = "0x" + address_no_prefix.upper()
        normalized = PyWalib.address_hex(address)
        self.assertEqual(normalized, expected_addresss)
        # prefix cannot be uppercase
        address = "0X" + address_no_prefix.upper()
        with self.assertRaises(Exception) as context:
            PyWalib.address_hex(address)
        self.assertEqual(context.exception.message,
                         "Invalid address format: '%s'" % (address))

    def test_get_balance(self):
        """
        Checks get_balance() returns a float.
        """
        address = ADDRESS
        balance_eth = PyWalib.get_balance(address)
        self.assertTrue(type(balance_eth), float)

    def helper_get_history(self, transactions):
        """
        Helper method to test history related methods.
        """
        self.assertEqual(type(transactions), list)
        self.assertTrue(len(transactions) > 1)
        # ordered by timeStamp
        self.assertTrue(
            transactions[0]['timeStamp'] < transactions[1]['timeStamp'])
        # and a bunch of other things
        self.assertEqual(
            set(transactions[0].keys()),
            set([
                'nonce', 'contractAddress', 'cumulativeGasUsed', 'hash',
                'blockHash', 'extra_dict', 'timeStamp', 'gas', 'value',
                'blockNumber', 'to', 'confirmations', 'input', 'from',
                'transactionIndex', 'isError', 'gasPrice', 'gasUsed'
            ]))

    def test_get_transaction_history(self):
        """
        Checks get_transaction_history() works as expected.
        """
        address = ADDRESS
        transactions = PyWalib.get_transaction_history(address)
        self.helper_get_history(transactions)
        # value is stored in Wei
        self.assertEqual(transactions[1]['value'], '200000000000000000')
        # but converted to Ether is also accessible
        self.assertEqual(transactions[1]['extra_dict']['value_eth'], 0.2)
        # history contains all send or received transactions
        self.assertEqual(transactions[1]['extra_dict']['sent'], False)
        self.assertEqual(transactions[1]['extra_dict']['received'], True)
        self.assertEqual(transactions[2]['extra_dict']['sent'], True)
        self.assertEqual(transactions[2]['extra_dict']['received'], False)

    def test_get_out_transaction_history(self):
        """
        Checks get_out_transaction_history() works as expected.
        """
        address = ADDRESS
        transactions = PyWalib.get_out_transaction_history(address)
        self.helper_get_history(transactions)
        for i in range(len(transactions)):
            transaction = transactions[i]
            extra_dict = transaction['extra_dict']
            # this is only sent transactions
            self.assertEqual(extra_dict['sent'], True)
            self.assertEqual(extra_dict['received'], False)
            # nonce should be incremented each time
            self.assertEqual(transaction['nonce'], str(i))

    def test_get_nonce(self):
        """
        Checks get_nonce() returns the next nonce, i.e. transaction count.
        """
        address = ADDRESS
        nonce = PyWalib.get_nonce(address)
        transactions = PyWalib.get_out_transaction_history(address)
        last_transaction = transactions[-1]
        last_nonce = int(last_transaction['nonce'])
        self.assertEqual(nonce, last_nonce + 1)

    def test_get_nonce_no_out_transaction(self):
        """
        Makes sure get_nonce() doesn't crash on no out transaction,
        but just returns 0.
        """
        # the VOID_ADDRESS has a lot of in transactions,
        # but no out ones, so the nonce should be 0
        address = VOID_ADDRESS
        nonce = PyWalib.get_nonce(address)
        self.assertEqual(nonce, 0)

    def test_get_nonce_no_transaction(self):
        """
        Makes sure get_nonce() doesn't crash on no transaction,
        but just returns 0.
        """
        # the newly created address has no in or out transaction history
        account = self.helper_new_account()
        address = account.address
        nonce = PyWalib.get_nonce(address)
        self.assertEqual(nonce, 0)

    def test_handle_etherscan_tx_error(self):
        """
        Checks handle_etherscan_tx_error() error handling.
        """
        # no transaction found
        response_json = {
            'jsonrpc': '2.0',
            'id': 1,
            'error': {
                'message':
                'Insufficient funds. '
                'The account you tried to send transaction from does not '
                'have enough funds. Required 10001500000000000000 and'
                'got: 53856999715015294.',
                'code':
                -32010,
                'data':
                None
            }
        }
        with self.assertRaises(InsufficientFundsException):
            PyWalib.handle_etherscan_tx_error(response_json)
        # unknown error
        response_json = {
            'jsonrpc': '2.0',
            'id': 1,
            'error': {
                'message': 'Unknown error',
                'code': 0,
                'data': None
            }
        }
        with self.assertRaises(UnknownEtherscanException):
            PyWalib.handle_etherscan_tx_error(response_json)
        # no error
        response_json = {'jsonrpc': '2.0', 'id': 1}
        self.assertEqual(PyWalib.handle_etherscan_tx_error(response_json),
                         None)

    def test_transact_no_found(self):
        """
        Tries to send a transaction from an address with no found.
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        to = ADDRESS
        sender = account.address
        value_wei = 100
        with self.assertRaises(InsufficientFundsException):
            pywalib.transact(to=to, value=value_wei, sender=sender)

    def test_get_default_keystore_path(self):
        """
        Checks we the default keystore directory exists or create it.
        Verify the path is correct and that we have read/write access to it.
        """
        keystore_dir = PyWalib.get_default_keystore_path()
        if not os.path.exists(keystore_dir):
            os.makedirs(keystore_dir)
        # checks path correctness
        self.assertTrue(keystore_dir.endswith(".config/pyethapp/keystore/"))
        # checks read/write access
        self.assertEqual(os.access(keystore_dir, os.R_OK), True)
        self.assertEqual(os.access(keystore_dir, os.W_OK), True)
コード例 #2
0
ファイル: test_pywalib.py プロジェクト: BBXLIVE/PyWallet
class PywalibTestCase(unittest.TestCase):
    """
    Simple test cases, verifying pywalib works as expected.
    """
    def setUp(self):
        self.keystore_dir = mkdtemp()
        self.pywalib = PyWalib(self.keystore_dir)

    def tearDown(self):
        shutil.rmtree(self.keystore_dir, ignore_errors=True)

    def helper_new_account(self, password=PASSWORD, security_ratio=1):
        """
        Helper method for fast account creation.
        """
        account = self.pywalib.new_account(password, security_ratio)
        return account

    def test_new_account(self):
        """
        Simple account creation test case.
        1) verifies the current account list is empty
        2) creates a new account and verify we can retrieve it
        3) tries to unlock the account
        """
        pywalib = self.pywalib
        # 1) verifies the current account list is empty
        account_list = pywalib.get_account_list()
        self.assertEqual(len(account_list), 0)
        # 2) creates a new account and verify we can retrieve it
        password = PASSWORD
        # weak account, but fast creation
        security_ratio = 1
        account = pywalib.new_account(password, security_ratio)
        account_list = pywalib.get_account_list()
        self.assertEqual(len(account_list), 1)
        self.assertEqual(account, pywalib.get_main_account())
        # 3) tries to unlock the account
        # it's unlocked by default after creation
        self.assertFalse(account.locked)
        # let's lock it and unlock it back
        account.lock()
        self.assertTrue(account.locked)
        account.unlock(password)
        self.assertFalse(account.locked)

    def helper_test_new_account_security_ratio_ok(self, security_ratio):
        """
        Helper method to unit test `new_account()` security_ratio parameter on
        happy scenarios.
        """
        pywalib = self.pywalib
        password = PASSWORD
        with mock.patch.object(pywalib.account_utils,
                               'new_account') as m_new_account:
            self.assertIsNotNone(pywalib.new_account(password, security_ratio))
        self.assertEqual(m_new_account.call_args_list,
                         [mock.call(iterations=mock.ANY, password='******')])

    def helper_test_new_account_security_ratio_error(self, security_ratio):
        """
        Helper method to unit test `new_account()` security_ratio parameter on
        rainy scenarios.
        """
        password = PASSWORD
        with self.assertRaises(ValueError) as ex_info:
            self.pywalib.new_account(password, security_ratio)
        self.assertEqual(ex_info.exception.args[0],
                         'security_ratio must be within 1 and 100')

    def test_new_account_security_ratio(self):
        """
        Checks the security_ratio parameter behave as expected.
        Possible value are:
            - security_ratio == None
            - security_ratio >= 1
            - security_ratio <= 100
        """
        security_ratio = None
        self.helper_test_new_account_security_ratio_ok(security_ratio)
        security_ratio = 1
        self.helper_test_new_account_security_ratio_ok(security_ratio)
        security_ratio = 100
        self.helper_test_new_account_security_ratio_ok(security_ratio)
        # anything else would fail
        security_ratio = 0
        self.helper_test_new_account_security_ratio_error(security_ratio)
        security_ratio = 101
        self.helper_test_new_account_security_ratio_error(security_ratio)

    def test_update_account_password(self):
        """
        Verifies updating account password works.
        """
        pywalib = self.pywalib
        current_password = "******"
        # weak account, but fast creation
        security_ratio = 1
        account = pywalib.new_account(current_password, security_ratio)
        # first try when the account is already unlocked
        self.assertFalse(account.locked)
        new_password = "******"
        # on unlocked account the current_password is optional
        pywalib.update_account_password(account,
                                        new_password,
                                        current_password=None)
        # verify it worked
        account.lock()
        account.unlock(new_password)
        self.assertFalse(account.locked)
        # now try when the account is first locked
        account.lock()
        current_password = "******"
        with self.assertRaises(ValueError):
            pywalib.update_account_password(account, new_password,
                                            current_password)
        current_password = new_password
        pywalib.update_account_password(account, new_password,
                                        current_password)
        self.assertFalse(account.locked)

    def test_deleted_account_dir(self):
        """
        The deleted_account_dir() helper method should be working
        with and without trailing slash.
        """
        expected_deleted_keystore_dir = '/tmp/keystore-deleted'
        keystore_dirs = [
            # without trailing slash
            '/tmp/keystore',
            # with one trailing slash
            '/tmp/keystore/',
            # with two trailing slashes
            '/tmp/keystore//',
        ]
        for keystore_dir in keystore_dirs:
            self.assertEqual(PyWalib.deleted_account_dir(keystore_dir),
                             expected_deleted_keystore_dir)

    def test_delete_account(self):
        """
        Creates a new account and delete it.
        Then verify we can load the account from the backup/trash location.
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        address = account.address
        self.assertEqual(len(pywalib.get_account_list()), 1)
        # deletes the account and verifies it's not loaded anymore
        pywalib.delete_account(account)
        self.assertEqual(len(pywalib.get_account_list()), 0)
        # even recreating the PyWalib object
        pywalib = PyWalib(self.keystore_dir)
        self.assertEqual(len(pywalib.get_account_list()), 0)
        # tries to reload it from the backup/trash location
        deleted_keystore_dir = PyWalib.deleted_account_dir(self.keystore_dir)
        pywalib = PyWalib(deleted_keystore_dir)
        self.assertEqual(len(pywalib.get_account_list()), 1)
        self.assertEqual(pywalib.get_account_list()[0].address, address)

    def test_delete_account_already_exists(self):
        """
        If the destination (backup/trash) directory where the account is moved
        already exists, it should be handled gracefully.
        This could happens if the account gets deleted, then reimported and
        deleted again, refs:
        https://github.com/AndreMiras/PyWallet/issues/88
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        # creates a file in the backup/trash folder that would conflict
        # with the deleted account
        deleted_keystore_dir = PyWalib.deleted_account_dir(self.keystore_dir)
        os.makedirs(deleted_keystore_dir)
        account_filename = os.path.basename(account.path)
        deleted_account_path = os.path.join(deleted_keystore_dir,
                                            account_filename)
        # create that file
        open(deleted_account_path, 'a').close()
        # then deletes the account and verifies it worked
        self.assertEqual(len(pywalib.get_account_list()), 1)
        pywalib.delete_account(account)
        self.assertEqual(len(pywalib.get_account_list()), 0)

    def test_get_balance(self):
        """
        Checks get_balance() returns a float.
        """
        pywalib = self.pywalib
        address = ADDRESS
        with patch_requests_get() as m_get:
            m_get.return_value.status_code = http.HTTPStatus.OK
            m_get.return_value.json.return_value = {
                'status': '1',
                'message': 'OK',
                'result': '350003576885437676061958',
            }
            balance_eth = pywalib.get_balance(address)
        url = mock.ANY
        headers = REQUESTS_HEADERS
        self.assertEqual(m_get.call_args_list,
                         [mock.call(url, headers=headers)])
        self.assertEqual(balance_eth, 350003.577)

    def test_get_balance_web3(self):
        """
        Checks get_balance() returns a float.
        """
        pywalib = self.pywalib
        address = ADDRESS
        with mock.patch('web3.eth.Eth.getBalance') as m_getBalance:
            m_getBalance.return_value = 350003576885437676061958
            balance_eth = pywalib.get_balance_web3(address)
        checksum_address = to_checksum_address(address)
        self.assertEqual(m_getBalance.call_args_list,
                         [mock.call(checksum_address)])
        self.assertTrue(type(balance_eth), float)
        self.assertEqual(balance_eth, 350003.577)

    def helper_test_get_history(self, transactions):
        """
        Helper method to test history related methods.
        """
        self.assertEqual(type(transactions), list)
        self.assertTrue(len(transactions) > 1)
        # ordered by timeStamp
        self.assertTrue(
            transactions[0]['timeStamp'] < transactions[1]['timeStamp'])
        # and a bunch of other things
        self.assertEqual(
            set(transactions[0].keys()),
            set([
                'nonce',
                'contractAddress',
                'cumulativeGasUsed',
                'hash',
                'blockHash',
                'extra_dict',
                'timeStamp',
                'gas',
                'value',
                'blockNumber',
                'to',
                'confirmations',
                'input',
                'from',
                'transactionIndex',
                'isError',
                'gasPrice',
                'gasUsed',
                'txreceipt_status',
            ]))

    def test_get_transaction_history(self):
        """
        Checks get_transaction_history() works as expected.
        """
        address = ADDRESS
        m_transactions = M_TRANSACTIONS
        with patch_requests_get() as m_get:
            m_get.return_value.status_code = http.HTTPStatus.OK
            m_get.return_value.json.return_value = {
                'status': '1',
                'message': 'OK',
                'result': m_transactions,
            }
            transactions = PyWalib.get_transaction_history(address)
        url = mock.ANY
        headers = REQUESTS_HEADERS
        self.assertEqual(m_get.call_args_list,
                         [mock.call(url, headers=headers)])
        self.helper_test_get_history(transactions)
        # value is stored in Wei
        self.assertEqual(transactions[1]['value'], '10000000000000000')
        # but converted to Ether is also accessible
        self.assertEqual(transactions[1]['extra_dict']['value_eth'], 0.01)
        # history contains all send or received transactions
        self.assertEqual(transactions[1]['extra_dict']['sent'], True)
        self.assertEqual(transactions[1]['extra_dict']['received'], False)
        self.assertEqual(transactions[2]['extra_dict']['sent'], True)
        self.assertEqual(transactions[2]['extra_dict']['received'], False)

    def test_get_out_transaction_history(self):
        """
        Checks get_out_transaction_history() works as expected.
        """
        address = ADDRESS
        m_transactions = M_TRANSACTIONS
        with patch_requests_get() as m_get:
            m_get.return_value.status_code = http.HTTPStatus.OK
            m_get.return_value.json.return_value = {
                'status': '1',
                'message': 'OK',
                'result': m_transactions,
            }
            transactions = PyWalib.get_out_transaction_history(address)
        url = mock.ANY
        headers = REQUESTS_HEADERS
        self.assertEqual(m_get.call_args_list,
                         [mock.call(url, headers=headers)])
        self.helper_test_get_history(transactions)
        # 4 transactions including 3 out transactions
        self.assertEquals(len(m_transactions), 4)
        self.assertEquals(len(transactions), 3)
        for i in range(len(transactions)):
            transaction = transactions[i]
            extra_dict = transaction['extra_dict']
            # this is only sent transactions
            self.assertEqual(extra_dict['sent'], True)
            self.assertEqual(extra_dict['received'], False)
            # nonce should be incremented each time
            self.assertEqual(transaction['nonce'], str(i))

    def test_get_nonce(self):
        """
        Checks get_nonce() returns the next nonce, i.e. transaction count.
        """
        address = ADDRESS
        m_transactions = M_TRANSACTIONS
        with patch_requests_get() as m_get:
            m_get.return_value.status_code = http.HTTPStatus.OK
            m_get.return_value.json.return_value = {
                'status': '1',
                'message': 'OK',
                'result': m_transactions,
            }
            nonce = PyWalib.get_nonce(address)
            transactions = PyWalib.get_out_transaction_history(address)
        url = mock.ANY
        headers = REQUESTS_HEADERS
        self.assertEqual(m_get.call_args_list,
                         2 * [mock.call(url, headers=headers)])
        last_transaction = transactions[-1]
        last_nonce = int(last_transaction['nonce'])
        self.assertEqual(nonce, last_nonce + 1)

    def test_get_nonce_no_out_transaction(self):
        """
        Makes sure get_nonce() doesn't crash on no out transaction,
        but just returns 0.
        """
        # the VOID_ADDRESS has a lot of in transactions,
        # but no out ones, so the nonce should be 0
        address = VOID_ADDRESS
        # truncated for readability
        transactions = [{
            'blockHash': ('0x7e5a9336dd82efff0bfe8c25ccb0e8c'
                          'f44b4c6f781b25b3fc3578f004f60b872'),
            'from':
            '0x22f2dcff5ad78c3eb6850b5cb951127b659522e6',
            'timeStamp':
            '1438922865',
            'to':
            '0x0000000000000000000000000000000000000000',
            'value':
            '0'
        }]
        with patch_requests_get() as m_get:
            m_get.return_value.status_code = http.HTTPStatus.OK
            m_get.return_value.json.return_value = {
                'status': '1',
                'message': 'OK',
                'result': transactions,
            }
            nonce = PyWalib.get_nonce(address)
        url = mock.ANY
        headers = REQUESTS_HEADERS
        self.assertEqual(m_get.call_args_list,
                         [mock.call(url, headers=headers)])
        self.assertEqual(nonce, 0)

    def test_get_nonce_no_transaction(self):
        """
        Makes sure get_nonce() doesn't crash on no transaction,
        but just returns 0.
        """
        # the newly created address has no in or out transaction history
        account = self.helper_new_account()
        address = account.address
        with patch_requests_get() as m_get:
            m_get.return_value.status_code = http.HTTPStatus.OK
            m_get.return_value.json.return_value = {
                'status': '0',
                'message': 'No transactions found',
                'result': [],
            }
            nonce = PyWalib.get_nonce(address)
        url = mock.ANY
        headers = REQUESTS_HEADERS
        self.assertEqual(m_get.call_args_list,
                         [mock.call(url, headers=headers)])
        self.assertEqual(nonce, 0)

    def test_handle_web3_exception(self):
        """
        Checks handle_web3_exception() error handling.
        """
        # insufficient funds
        exception = ValueError({
            'code':
            -32000,
            'message':
            'insufficient funds for gas * price + value'
        })
        with self.assertRaises(InsufficientFundsException) as e:
            PyWalib.handle_web3_exception(exception)
        self.assertEqual(e.exception.args[0], exception.args[0])
        # unknown error code
        exception = ValueError({'code': 0, 'message': 'Unknown error'})
        with self.assertRaises(UnknownEtherscanException) as e:
            PyWalib.handle_web3_exception(exception)
        self.assertEqual(e.exception.args[0], exception.args[0])
        # no code
        exception = ValueError({'message': 'Unknown error'})
        with self.assertRaises(UnknownEtherscanException) as e:
            PyWalib.handle_web3_exception(exception)
        self.assertEqual(e.exception.args[0], exception.args[0])

    def test_transact(self):
        """
        Basic transact() test, makes sure web3 sendRawTransaction gets called.
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        to = ADDRESS
        sender = account.address
        value_wei = 100
        with mock.patch('web3.eth.Eth.sendRawTransaction') \
                as m_sendRawTransaction:
            pywalib.transact(to=to, value=value_wei, sender=sender)
        self.assertTrue(m_sendRawTransaction.called)

    def test_transact_no_sender(self):
        """
        The sender parameter should default to the main account.
        Makes sure the transaction is being signed by the available account.
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        to = ADDRESS
        value_wei = 100
        with mock.patch('web3.eth.Eth.sendRawTransaction') \
                as m_sendRawTransaction, \
                mock.patch('web3.eth.Eth.account.signTransaction') \
                as m_signTransaction:
            pywalib.transact(to=to, value=value_wei)
        self.assertTrue(m_sendRawTransaction.called)
        m_signTransaction.call_args_list
        transaction = {
            'chainId': 1,
            'gas': 25000,
            'gasPrice': 4000000000,
            'nonce': 0,
            'to': to_checksum_address(to),
            'value': value_wei,
        }
        expected_call = mock.call(transaction, account.privkey)
        self.assertEqual(m_signTransaction.call_args_list, [expected_call])

    def test_transact_no_funds(self):
        """
        Tries to send a transaction from an address with no funds.
        """
        pywalib = self.pywalib
        account = self.helper_new_account()
        to = ADDRESS
        sender = account.address
        value_wei = 100
        with self.assertRaises(InsufficientFundsException):
            pywalib.transact(to=to, value=value_wei, sender=sender)

    def test_get_default_keystore_path(self):
        """
        Checks we the default keystore directory exists or create it.
        Verify the path is correct and that we have read/write access to it.
        """
        keystore_dir = PyWalib.get_default_keystore_path()
        if not os.path.exists(keystore_dir):
            os.makedirs(keystore_dir)
        # checks path correctness
        self.assertTrue(keystore_dir.endswith(".config/pyethapp/keystore/"))
        # checks read/write access
        self.assertEqual(os.access(keystore_dir, os.R_OK), True)
        self.assertEqual(os.access(keystore_dir, os.W_OK), True)