Repository: rotki/rotki

Issue:
Add the assset's symbol in the Pnl report CSV export
## Abstract

The CAIP format is extensive and contains all the information anyone would need for an EVM token `eip155:1/erc20:0xdAAAAA` but is not the most user friendly/readable thing.

So it would be nice to also export the symbol of the asset (or empty string if not existing) to help users understand what the row works with.


## Specification

Add a new column in the PnL export to contain the asset symbol



Discussion:

Resolving patch:
diff --git a/rotkehlchen/accounting/export/csv.py b/rotkehlchen/accounting/export/csv.py
--- a/rotkehlchen/accounting/export/csv.py
+++ b/rotkehlchen/accounting/export/csv.py
@@ -206,6 +206,7 @@ def _maybe_add_summary(self, events: list[dict[str, Any]], pnls: PnlTotals) -> N
             'location': '',
             'timestamp': '',
             'asset': '',
+            'asset_identifier': '',
             'free_amount': '',
             'taxable_amount': '',
             'price': '',
diff --git a/rotkehlchen/accounting/structures/processed_event.py b/rotkehlchen/accounting/structures/processed_event.py
--- a/rotkehlchen/accounting/structures/processed_event.py
+++ b/rotkehlchen/accounting/structures/processed_event.py
@@ -96,7 +96,7 @@ def to_exported_dict(
             'notes': self.notes,
             'location': str(self.location),
             'timestamp': self.timestamp,
-            'asset': self.asset.identifier,
+            'asset_identifier': self.asset.identifier,
             'free_amount': str(self.free_amount),
             'taxable_amount': str(self.taxable_amount),
             'price': str(self.price),
@@ -110,7 +110,11 @@ def to_exported_dict(
                 taxable_basis, free_basis = self.cost_basis.to_string(ts_converter)
             exported_dict['cost_basis_taxable'] = taxable_basis
             exported_dict['cost_basis_free'] = free_basis
-            exported_dict['asset'] = str(self.asset)
+            exported_dict['asset_identifier'] = str(self.asset)
+            try:
+                exported_dict['asset'] = self.asset.symbol_or_name()
+            except UnknownAsset:
+                exported_dict['asset'] = ''
             if tx_hash is not None:
                 exported_dict['notes'] = f'{evm_explorer}{tx_hash}  ->  {self.notes}'
         else:  # for the other types of export we include the cost basis information
@@ -216,7 +220,7 @@ def deserialize_from_db(cls: builtins.type[T], timestamp: Timestamp, stringified
                 notes=data['notes'],
                 location=Location.deserialize(data['location']),
                 timestamp=timestamp,
-                asset=Asset(data['asset']).check_existence(),
+                asset=Asset(data['asset_identifier']).check_existence(),
                 free_amount=deserialize_fval(data['free_amount'], name='free_amount', location='processed event decoding'),  # noqa: E501
                 taxable_amount=deserialize_fval(data['taxable_amount'], name='taxable_amount', location='processed event decoding'),  # noqa: E501
                 price=deserialize_price(data['price']),


Test patch:
diff --git a/rotkehlchen/tests/api/test_pnl_csv.py b/rotkehlchen/tests/api/test_pnl_csv.py
--- a/rotkehlchen/tests/api/test_pnl_csv.py
+++ b/rotkehlchen/tests/api/test_pnl_csv.py
@@ -15,20 +15,25 @@
 from rotkehlchen.accounting.export.csv import FILENAME_ALL_CSV
 from rotkehlchen.accounting.mixins.event import AccountingEventType
 from rotkehlchen.accounting.structures.balance import Balance
+from rotkehlchen.assets.asset import CustomAsset
 from rotkehlchen.chain.evm.constants import GENESIS_HASH
 from rotkehlchen.constants.assets import A_ETH
+from rotkehlchen.db.custom_assets import DBCustomAssets
 from rotkehlchen.fval import FVal
+from rotkehlchen.history.events.structures.base import HistoryEvent
 from rotkehlchen.history.events.structures.evm_event import EvmEvent
 from rotkehlchen.history.events.structures.types import HistoryEventSubType, HistoryEventType
+from rotkehlchen.tests.utils.accounting import accounting_history_process
 from rotkehlchen.tests.utils.api import api_url_for, assert_error_response, assert_proper_response
 from rotkehlchen.tests.utils.constants import ETH_ADDRESS1, ETH_ADDRESS2, ETH_ADDRESS3
+from rotkehlchen.tests.utils.factories import make_evm_tx_hash
 from rotkehlchen.tests.utils.history import prepare_rotki_for_history_processing_test, prices
 from rotkehlchen.tests.utils.pnl_report import query_api_create_and_get_report
-from rotkehlchen.types import Location, TimestampMS
+from rotkehlchen.types import Location, Timestamp, TimestampMS
 from rotkehlchen.utils.misc import create_timestamp
 
 
-def assert_csv_export_response(response, csv_dir, is_download=False):
+def assert_csv_export_response(response, csv_dir, is_download=False, expected_num_of_events=37):
     if is_download:
         assert response.status_code == HTTPStatus.OK
     else:
@@ -45,7 +50,7 @@ def assert_csv_export_response(response, csv_dir, is_download=False):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
-            assert len(row) == 12
+            assert len(row) == 13
             assert row['location'] in {
                 'kraken',
                 'bittrex',
@@ -65,6 +70,7 @@ def assert_csv_export_response(response, csv_dir, is_download=False):
             assert create_timestamp(row['timestamp'], '%d/%m/%Y %H:%M:%S %Z') > 0
             assert row['notes'] is not None
             assert row['asset'] is not None
+            assert row['asset_identifier'] is not None
             assert row['taxable_amount'] is not None
             assert row['free_amount'] is not None
             assert row['price'] is not None
@@ -75,7 +81,7 @@ def assert_csv_export_response(response, csv_dir, is_download=False):
             count += 1
             rows.append(row)
 
-    assert count == 37
+    assert count == expected_num_of_events
 
 
 @pytest.mark.parametrize('have_decoders', [True])
@@ -157,6 +163,34 @@ def test_history_export_download_csv(
         with zipfile.ZipFile(tempzipfile, 'r') as zip_ref:
             zip_ref.extractall(extractdir)
         assert_csv_export_response(response, extractdir, is_download=True)
+    # check if the report works when using assets that don't have a symbol
+    custom_asset = CustomAsset.initialize(
+        identifier='XYZ',
+        name='custom name',
+        custom_asset_type='custom type',
+    )
+    db_custom_assets = DBCustomAssets(rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen.data.db)  # noqa: E501
+    db_custom_assets.add_custom_asset(custom_asset)
+    accounting_history_process(
+        rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen.accountant,
+        start_ts=Timestamp(0),
+        end_ts=Timestamp(1640493376),
+        history_list=[HistoryEvent(
+            event_identifier=str(make_evm_tx_hash()),
+            sequence_index=0,
+            timestamp=TimestampMS(1601040360000),
+            location=Location.ETHEREUM,
+            asset=custom_asset,
+            balance=Balance(amount=FVal('1.5')),
+            event_type=HistoryEventType.RECEIVE,
+            event_subtype=HistoryEventSubType.NONE,
+        )],
+    )
+    response = requests.get(
+        api_url_for(rotkehlchen_api_server_with_exchanges, 'historyexportingresource'),
+        json={'directory_path': csv_dir},
+    )
+    assert_csv_export_response(response, csv_dir, expected_num_of_events=1)
 
 
 @pytest.mark.parametrize('mocked_price_queries', [{'ETH': {'EUR': {1569924574: 1}}}])
diff --git a/rotkehlchen/tests/utils/history.py b/rotkehlchen/tests/utils/history.py
--- a/rotkehlchen/tests/utils/history.py
+++ b/rotkehlchen/tests/utils/history.py
@@ -266,6 +266,11 @@
             1628994441: FVal(0.9),
         },
     },
+    'XYZ': {  # custom asset used in test 'test_history_export_download_csv'
+        'EUR': {
+            1601040360: ONE,
+        },
+    },
 }