Repository: rotki/rotki

Issue:
Unnecessary checksum check
I wanted to add an ETH address.
When I clicked "add" nothing (obvious) happend.
So the first request would be a error message directly near the add button. (and not just a hard to see notification in the top)

The second request: I see now reason why addresses that do not follow the check sum format should be rejected. This is nothing security critical and if the address exists (contains ETH/tokens) it should simply be added?

![Screen Shot 2020-04-04 at 14 13 39](https://user-images.githubusercontent.com/2594089/78450443-e0eff300-767e-11ea-91ac-c185e53ff570.png)

Not sure if/how related to: https://github.com/rotki/rotki/issues/69


Discussion:
Hey @koeppelmann thanks for the issue!

@kelsos About the eror message here how can we improve in general on errors seen when running background tasks for a user? Related issue: https://github.com/rotki/rotki/issues/751

@koeppelmann For the checksum I am not sure. At the moment we enforce checksums everywhere in case we start managing user funds in the future, I think it's a good practise. All addresses are saved checksummed in the DB too.

We could just forgive and turn to checksummed address anyway in fields like this since no on-chain action is taken. So you would prefer if we were not enforcing this in the address additions?
The problem I currently see, and we should definitely improve, is that all the validation at the moment happens on the backend. We should implement input field validation providing direct feedback to the user in the input field. 

Since the call adding an account is an asynchronous task, this is the reason the message appears on the notification, the frontend only gets the result when the task resolved, which is not a good experience indeed.

We should evaluate which inputs have specific rules, not only the addresses and we should include a first validation layer in the frontend itself. This provides the benefit of the user being directly notified about any invalid input directly on the fields itself.

In regards to the checksum, we don't have to change anything on the backend, the conversion can happen on the front-end itself and it can still provide the checksummed address to the API. It would certainly make the experience of using Rotki easier. And since Rotki is not a wallet there is no danger. 
After being added the address will be still displayed in checksummed format.
> The problem I currently see, and we should definitely improve, is that all the validation at the moment happens on the backend. We should implement input field validation providing direct feedback to the user in the input field.

The problem with such an approach is that the backend is the source of truth. You need to be able to handle any kind of error from the backend and show it properly. If we change the backend then you would have to re-implement all the checks in the front-end and it makes no sense in the long run.

> And since Rotki is not a wallet there is no danger.
After being added the address will be still displayed in checksummed format.

Hmm that's a good idea. Perhaps you can consider doing it as the solution to this issue? We can keep #751 for the showing errors to the user problem.
> In regards to the checksum, we don't have to change anything on the backend, the conversion can happen on the front-end itself and it can still provide the checksummed address to the API. It would certainly make the experience of using Rotki easier. And since Rotki is not a wallet there is no danger.
> After being added the address will be still displayed in checksummed format.

Is there any danger that auto-converting a non-checksummed address into a checksummed one would possibly lead to an incorrect address? If not, then even in the case where rotki will do more than just querying, what would the risk be?

EDIT:
In this case we're just talking about the uppercasing of some of the letters right?
> In this case we're just talking about the uppercasing of some of the letters right?

Yes this is how [EIP55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md) implements checksumming.
@LefterisJP 
Having input validation on the frontend doesn't mean we remove the validation from the backend, that would not make sense. But transforming the error messages from the backend to UI errors it not easy. 

It would be easier to evaluate the most important rules from the backend and integrate them in the front-end. Sure it's double effort, but it provides better user experience.
> > In this case we're just talking about the uppercasing of some of the letters right?
> 
> Yes this is how [EIP55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md) implements checksumming.

Don't see any risk in autoconverting it then, and if it's going to autoconvert, shouldn't it just happen in the backend? 
> But transforming the error messages from the backend to UI errors it not easy.

Hm yes yes. Sorry I think I may have not expressed myself correctly.
Definitely somehow autodetecting that the error that popped up from the backend is for a specific field is going to be hard. For that reason yes it would make sense to have field checks in the frontend.

But the problem I am referring to and I think that @koeppelmann also referred to is that it's hard to realize that the error that the user sees in the notifications is due to a particular task. Perhaps we should make a new issue for it? Or discuss in #751 ?
Sure, we had a discussion before with @isidorosp about displaying some kind of toast or snackbar message when there is a notification to ensure better visibility to the user. We should create an issue and include it in the next release.
>  We should create an issue and include it in the next release.

@kelsos sounds good. Can you create such an issue? 
Sure I will do in a moment 

Resolving patch:
diff --git a/rotkehlchen/api/server.py b/rotkehlchen/api/server.py
--- a/rotkehlchen/api/server.py
+++ b/rotkehlchen/api/server.py
@@ -1,3 +1,4 @@
+import json
 import logging
 from http import HTTPStatus
 from typing import Any, Dict, List, Optional, Tuple, Union
@@ -159,7 +160,15 @@ def handle_request_parsing_error(
 ) -> None:
     """ This handles request parsing errors generated for example by schema
     field validation failing."""
-    abort(HTTPStatus.BAD_REQUEST, result=None, message=str(err))
+    msg = str(err)
+    if isinstance(err.messages, dict):
+        # first key is just the location. Ignore
+        key = list(err.messages.keys())[0]
+        msg = json.dumps(err.messages[key])
+    elif isinstance(err.messages, list):
+        msg = ','.join(err.messages)
+
+    abort(HTTPStatus.BAD_REQUEST, result=None, message=msg)
 
 
 class APIServer():
diff --git a/rotkehlchen/api/v1/encoding.py b/rotkehlchen/api/v1/encoding.py
--- a/rotkehlchen/api/v1/encoding.py
+++ b/rotkehlchen/api/v1/encoding.py
@@ -3,7 +3,7 @@
 
 import marshmallow
 import webargs
-from eth_utils import is_checksum_address
+from eth_utils import to_checksum_address
 from marshmallow import Schema, fields, post_load, validates_schema
 from marshmallow.exceptions import ValidationError
 from webargs.compat import MARSHMALLOW_VERSION_INFO
@@ -822,13 +822,18 @@ def _validate_blockchain_account_schemas(
     if data['blockchain'] == SupportedBlockchain.ETHEREUM:
         for account_data in data['accounts']:
             address = address_getter(account_data)
-            if not is_checksum_address(address):
+            # Make sure that given value is an ethereum address
+            try:
+                address = to_checksum_address(address)
+            except (ValueError, TypeError):
                 raise ValidationError(
-                    f'Given value {address} is not a checksummed ethereum address',
+                    f'Given value {address} is not an ethereum address',
+                    field_name='address',
                 )
             if address in given_addresses:
                 raise ValidationError(
                     f'Address {address} appears multiple times in the request data',
+                    field_name='address',
                 )
             given_addresses.add(address)
 
@@ -839,10 +844,12 @@ def _validate_blockchain_account_schemas(
             if not is_valid_btc_address(address):
                 raise ValidationError(
                     f'Given value {address} is not a valid bitcoin address',
+                    field_name='address',
                 )
             if address in given_addresses:
                 raise ValidationError(
                     f'Address {address} appears multiple times in the request data',
+                    field_name='address',
                 )
             given_addresses.add(address)
 
@@ -859,6 +866,17 @@ def validate_schema(  # pylint: disable=no-self-use
     ) -> None:
         _validate_blockchain_account_schemas(data, lambda x: x['address'])
 
+    @post_load  # type: ignore
+    def transform_data(  # pylint: disable=no-self-use
+            self,
+            data: Dict[str, Any],
+            **_kwargs: Any,
+    ) -> Any:
+        if data['blockchain'] == SupportedBlockchain.ETHEREUM:
+            for idx, account in enumerate(data['accounts']):
+                data['accounts'][idx]['address'] = to_checksum_address(account['address'])
+        return data
+
 
 class BlockchainAccountsPutSchema(BlockchainAccountsPatchSchema):
     async_query = fields.Boolean(missing=False)
@@ -870,13 +888,23 @@ class BlockchainAccountsDeleteSchema(Schema):
     async_query = fields.Boolean(missing=False)
 
     @validates_schema  # type: ignore
-    def validate_blockchain_accounts_patch_schema(  # pylint: disable=no-self-use
+    def validate_blockchain_accounts_delete_schema(  # pylint: disable=no-self-use
             self,
             data: Dict[str, Any],
             **_kwargs: Any,
     ) -> None:
         _validate_blockchain_account_schemas(data, lambda x: x)
 
+    @post_load  # type: ignore
+    def transform_data(  # pylint: disable=no-self-use
+            self,
+            data: Dict[str, Any],
+            **_kwargs: Any,
+    ) -> Any:
+        if data['blockchain'] == SupportedBlockchain.ETHEREUM:
+            data['accounts'] = [to_checksum_address(x) for x in data['accounts']]
+        return data
+
 
 class IgnoredAssetsSchema(Schema):
     assets = fields.List(AssetField(), required=True)


Test patch:
diff --git a/rotkehlchen/tests/api/test_assets.py b/rotkehlchen/tests/api/test_assets.py
--- a/rotkehlchen/tests/api/test_assets.py
+++ b/rotkehlchen/tests/api/test_assets.py
@@ -136,7 +136,7 @@ def test_ignored_assets_endpoint_errors(rotkehlchen_api_server_with_exchanges, m
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'assets': ['Missing data for required field",
+        contained_in_msg='"assets": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -149,7 +149,7 @@ def test_ignored_assets_endpoint_errors(rotkehlchen_api_server_with_exchanges, m
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'assets': ['Not a valid list.'",
+        contained_in_msg='"assets": ["Not a valid list."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_blockchain.py b/rotkehlchen/tests/api/test_blockchain.py
--- a/rotkehlchen/tests/api/test_blockchain.py
+++ b/rotkehlchen/tests/api/test_blockchain.py
@@ -5,6 +5,7 @@
 
 import pytest
 import requests
+from eth_utils import to_checksum_address
 
 from rotkehlchen.fval import FVal
 from rotkehlchen.logging import RotkehlchenLogsAdapter
@@ -713,13 +714,11 @@ def test_add_blockchain_accounts_alethio(
 
 
 @pytest.mark.parametrize('number_of_eth_accounts', [0])
-def test_addding_non_checksummed_eth_account_is_error(
-        rotkehlchen_api_server,
-):
-    """Test that adding a non checksummed eth account leads to an error"""
+def test_addding_non_checksummed_eth_account_works(rotkehlchen_api_server):
+    """Test that adding a non checksummed eth account can be handled properly"""
     rotki = rotkehlchen_api_server.rest_api.rotkehlchen
     account = '0x7bd904a3db59fa3879bd4c246303e6ef3ac3a4c6'
-    new_eth_accounts = [account]
+    new_eth_accounts = [to_checksum_address(account)]
     eth_balances = ['1000000']
     setup = setup_balances(
         rotki,
@@ -735,11 +734,7 @@ def test_addding_non_checksummed_eth_account_is_error(
             "blockchainsaccountsresource",
             blockchain='ETH',
         ), json=request_data)
-    assert_error_response(
-        response=response,
-        contained_in_msg=f'Given value {account} is not a checksummed ethereum address',
-        status_code=HTTPStatus.BAD_REQUEST,
-    )
+    assert_proper_response(response)
 
 
 @pytest.mark.parametrize('method', ['PUT', 'DELETE'])
@@ -798,9 +793,9 @@ def test_blockchain_accounts_endpoint_errors(rotkehlchen_api_server, api_port, m
     if method == 'GET':
         message = "'accounts': ['Not a valid list.'"
     elif method == 'DELETE':
-        message = 'Given value foo is not a checksummed ethereum address'
+        message = 'Given value foo is not an ethereum address'
     else:
-        message = "'accounts': {0: {'_schema': ['Invalid input type.'"
+        message = '"accounts": {"0": {"_schema": ["Invalid input type.'
     assert_error_response(
         response=response,
         contained_in_msg=message,
@@ -822,7 +817,7 @@ def test_blockchain_accounts_endpoint_errors(rotkehlchen_api_server, api_port, m
 
     # Provide invalid ETH account (more bytes)
     invalid_eth_account = '0x554FFc77f4251a9fB3c0E3590a6a205f8d4e067d01'
-    msg = f'Given value {invalid_eth_account} is not a checksummed ethereum address'
+    msg = f'Given value {invalid_eth_account} is not an ethereum address'
     if method == 'PUT':
         data = {'accounts': [{'address': invalid_eth_account}]}
     else:
@@ -883,7 +878,7 @@ def test_blockchain_accounts_endpoint_errors(rotkehlchen_api_server, api_port, m
 
     # Provide list with one valid and one invalid account and make sure that nothing
     # is added / removed and the valid one is skipped
-    msg = 'Given value 142 is not a checksummed ethereum address'
+    msg = 'Given value 142 is not an ethereum address'
     if method == 'DELETE':
         # Account should be an existing account
         account = rotki.chain_manager.accounts.eth[0]
@@ -1172,7 +1167,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg="accounts': ['Missing data for required field",
+        contained_in_msg='"accounts": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -1201,7 +1196,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg="address': ['Missing data for required field",
+        contained_in_msg='address": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -1218,7 +1213,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg="address': ['Not a valid string",
+        contained_in_msg='address": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -1235,7 +1230,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg='Given value dsadsd is not a checksummed ethereum address',
+        contained_in_msg='Given value dsadsd is not an ethereum address',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -1252,7 +1247,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg="label': ['Not a valid string",
+        contained_in_msg='label": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -1269,7 +1264,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg="tags': ['Not a valid list",
+        contained_in_msg='tags": ["Not a valid list',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -1286,7 +1281,7 @@ def test_edit_blockchain_account_errors(
     ), json=request_data)
     assert_error_response(
         response=response,
-        contained_in_msg="tags': {0: ['Not a valid string",
+        contained_in_msg='tags": {"0": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_data_import.py b/rotkehlchen/tests/api/test_data_import.py
--- a/rotkehlchen/tests/api/test_data_import.py
+++ b/rotkehlchen/tests/api/test_data_import.py
@@ -43,7 +43,7 @@ def test_data_import_errors(rotkehlchen_api_server, tmpdir_factory):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="filepath': ['Missing data for required field",
+        contained_in_msg='filepath": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -57,7 +57,7 @@ def test_data_import_errors(rotkehlchen_api_server, tmpdir_factory):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="source': ['Missing data for required field",
+        contained_in_msg='source": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -71,7 +71,7 @@ def test_data_import_errors(rotkehlchen_api_server, tmpdir_factory):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="source': ['Not a valid string.'",
+        contained_in_msg='source": ["Not a valid string."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -85,7 +85,7 @@ def test_data_import_errors(rotkehlchen_api_server, tmpdir_factory):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="source': ['Must be one of: cointracking.info",
+        contained_in_msg='source": ["Must be one of: cointracking.info',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_eth_tokens.py b/rotkehlchen/tests/api/test_eth_tokens.py
--- a/rotkehlchen/tests/api/test_eth_tokens.py
+++ b/rotkehlchen/tests/api/test_eth_tokens.py
@@ -151,7 +151,7 @@ def check_modifying_eth_tokens_error_responses(
     ), json={})
     assert_error_response(
         response=response,
-        contained_in_msg="eth_tokens': ['Missing data for required field",
+        contained_in_msg='eth_tokens": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
     # See that providing invalid type for eth_tokens is an error
@@ -161,7 +161,7 @@ def check_modifying_eth_tokens_error_responses(
     ), json={'eth_tokens': {}})
     assert_error_response(
         response=response,
-        contained_in_msg="'eth_tokens': ['Not a valid list.'",
+        contained_in_msg='"eth_tokens": ["Not a valid list."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
     # See that providing invalid type for eth_tokens is an error
@@ -171,7 +171,7 @@ def check_modifying_eth_tokens_error_responses(
     ), json={'eth_tokens': {}})
     assert_error_response(
         response=response,
-        contained_in_msg="'eth_tokens': ['Not a valid list.'",
+        contained_in_msg='"eth_tokens": ["Not a valid list."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
     # See that providing invalid type for a single eth token is an error
diff --git a/rotkehlchen/tests/api/test_external_services.py b/rotkehlchen/tests/api/test_external_services.py
--- a/rotkehlchen/tests/api/test_external_services.py
+++ b/rotkehlchen/tests/api/test_external_services.py
@@ -140,7 +140,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="services': ['Missing data for required field.",
+        contained_in_msg='services": ["Missing data for required field.',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -152,7 +152,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'services': ['Not a valid list.'",
+        contained_in_msg='"services": ["Not a valid list."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -164,7 +164,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="services': {0: {'_schema': ['Invalid input type",
+        contained_in_msg='services": {"0": {"_schema": ["Invalid input type',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -176,7 +176,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'api_key': ['Missing data for required field.'",
+        contained_in_msg='"api_key": ["Missing data for required field."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -188,7 +188,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'name': ['Missing data for required field.'",
+        contained_in_msg='"name": ["Missing data for required field."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -200,7 +200,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'name': ['External service unknown is not known'",
+        contained_in_msg='"name": ["External service unknown is not known"',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -224,7 +224,7 @@ def test_add_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'api_key': ['Not a valid string.'",
+        contained_in_msg='"api_key": ["Not a valid string."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -237,7 +237,7 @@ def test_remove_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="services': ['Missing data for required field.",
+        contained_in_msg='services": ["Missing data for required field.',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -249,7 +249,7 @@ def test_remove_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'services': ['Not a valid list.'",
+        contained_in_msg='"services": ["Not a valid list.',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -261,7 +261,7 @@ def test_remove_external_services_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'services': {0: ['External service name should be a string']",
+        contained_in_msg='"services": {"0": ["External service name should be a string"]',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_history.py b/rotkehlchen/tests/api/test_history.py
--- a/rotkehlchen/tests/api/test_history.py
+++ b/rotkehlchen/tests/api/test_history.py
@@ -296,7 +296,7 @@ def test_query_history_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="async_query': ['Not a valid boolean",
+        contained_in_msg='async_query": ["Not a valid boolean',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -536,7 +536,7 @@ def test_history_export_csv_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'directory_path': ['Given path /idont/exist/for/sure/ does not exist",
+        contained_in_msg='"directory_path": ["Given path /idont/exist/for/sure/ does not exist',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_manually_tracked_balances.py b/rotkehlchen/tests/api/test_manually_tracked_balances.py
--- a/rotkehlchen/tests/api/test_manually_tracked_balances.py
+++ b/rotkehlchen/tests/api/test_manually_tracked_balances.py
@@ -287,7 +287,7 @@ def test_add_edit_manually_tracked_balances_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="balances': ['Missing data for required field",
+        contained_in_msg='balances": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -301,7 +301,7 @@ def test_add_edit_manually_tracked_balances_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="balances': ['Not a valid list",
+        contained_in_msg='balances": ["Not a valid list',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -381,7 +381,7 @@ def test_add_edit_manually_tracked_balances_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="label': ['Not a valid string",
+        contained_in_msg='label": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -474,7 +474,7 @@ def test_add_edit_manually_tracked_balances_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="tags': ['Not a valid list",
+        contained_in_msg='tags": ["Not a valid list',
         status_code=HTTPStatus.BAD_REQUEST,
     )
     # wrong type in list of tags
@@ -489,7 +489,7 @@ def test_add_edit_manually_tracked_balances_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'tags': {1: ['Not a valid string.'",
+        contained_in_msg='"tags": {"1": ["Not a valid string."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -597,7 +597,7 @@ def test_delete_manually_tracked_balances_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="labels': ['Missing data for required field",
+        contained_in_msg='labels": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -610,7 +610,7 @@ def test_delete_manually_tracked_balances_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="labels': ['Not a valid list",
+        contained_in_msg='"labels": ["Not a valid list',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -623,7 +623,7 @@ def test_delete_manually_tracked_balances_errors(rotkehlchen_api_server):
     )
     assert_error_response(
         response=response,
-        contained_in_msg="'labels': {1: ['Not a valid string.'",
+        contained_in_msg='"labels": {"1": ["Not a valid string."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_settings.py b/rotkehlchen/tests/api/test_settings.py
--- a/rotkehlchen/tests/api/test_settings.py
+++ b/rotkehlchen/tests/api/test_settings.py
@@ -208,7 +208,7 @@ def test_set_unknown_settings(rotkehlchen_api_server):
     response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data)
     assert_error_response(
         response=response,
-        contained_in_msg="{'invalid_setting': ['Unknown field.'",
+        contained_in_msg='{"invalid_setting": ["Unknown field."',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_tags.py b/rotkehlchen/tests/api/test_tags.py
--- a/rotkehlchen/tests/api/test_tags.py
+++ b/rotkehlchen/tests/api/test_tags.py
@@ -161,7 +161,7 @@ def test_add_edit_tag_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="name': ['Missing data for required field",
+        contained_in_msg='name": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -181,7 +181,7 @@ def test_add_edit_tag_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="name': ['Not a valid string",
+        contained_in_msg='name": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
     # Invalid type for description
@@ -200,7 +200,7 @@ def test_add_edit_tag_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="description': ['Not a valid string",
+        contained_in_msg='description": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
@@ -225,7 +225,7 @@ def test_add_edit_tag_errors(
             )
             assert_error_response(
                 response=response,
-                contained_in_msg=f"{field}': ['Missing data for required field",
+                contained_in_msg=f'"{field}": ["Missing data for required field',
                 status_code=HTTPStatus.BAD_REQUEST,
             )
         # Invalid color type
@@ -240,7 +240,7 @@ def test_add_edit_tag_errors(
         )
         assert_error_response(
             response=response,
-            contained_in_msg=f"{field}': ['Failed to deserialize color code from int",
+            contained_in_msg=f'"{field}": ["Failed to deserialize color code from int',
             status_code=HTTPStatus.BAD_REQUEST,
         )
         # Wrong kind of string
@@ -256,8 +256,8 @@ def test_add_edit_tag_errors(
         assert_error_response(
             response=response,
             contained_in_msg=(
-                f"{field}': ['The given color code value \"went\" could "
-                f"not be processed as a hex color value"
+                f'"{field}": ["The given color code value \\"went\\" could '
+                f'not be processed as a hex color value'
             ),
             status_code=HTTPStatus.BAD_REQUEST,
         )
@@ -274,8 +274,8 @@ def test_add_edit_tag_errors(
         assert_error_response(
             response=response,
             contained_in_msg=(
-                f"{field}': ['The given color code value \"ffef01ff\" is out "
-                f"of range for a normal color field"
+                f'"{field}": ["The given color code value \\"ffef01ff\\" is out '
+                f'of range for a normal color field'
             ),
             status_code=HTTPStatus.BAD_REQUEST,
         )
@@ -292,8 +292,8 @@ def test_add_edit_tag_errors(
         assert_error_response(
             response=response,
             contained_in_msg=(
-                f"{field}': ['The given color code value \"ff\" does not "
-                f"have 6 hexadecimal digits"
+                f'"{field}": ["The given color code value \\"ff\\" does not '
+                f'have 6 hexadecimal digits'
             ),
             status_code=HTTPStatus.BAD_REQUEST,
         )
@@ -572,7 +572,7 @@ def test_delete_tag_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="name': ['Missing data for required field",
+        contained_in_msg='name": ["Missing data for required field',
         status_code=HTTPStatus.BAD_REQUEST,
     )
     # Invalid type for name
@@ -585,7 +585,7 @@ def test_delete_tag_errors(
     )
     assert_error_response(
         response=response,
-        contained_in_msg="name': ['Not a valid string",
+        contained_in_msg='name": ["Not a valid string',
         status_code=HTTPStatus.BAD_REQUEST,
     )
 
diff --git a/rotkehlchen/tests/api/test_trades.py b/rotkehlchen/tests/api/test_trades.py
--- a/rotkehlchen/tests/api/test_trades.py
+++ b/rotkehlchen/tests/api/test_trades.py
@@ -212,7 +212,7 @@ def assert_all_missing_fields_are_handled(correct_trade, server):
         )
         assert_error_response(
             response=response,
-            contained_in_msg=f"{field}': ['Missing data for required field",
+            contained_in_msg=f'"{field}": ["Missing data for required field',
             status_code=HTTPStatus.BAD_REQUEST,
         )