Repository: rotki/rotki

Issue:
Cannot import custom asset 
For the import asset functionality, we cannot do the import if I have custom asset in the csv
endpoint:  `<PUT> /api/1/assets/user`
errors: 

```Provided file does not have the expected format. {'assets': {0: {'asset_type': ['Missing data for required field.'], 'cryptocompare': ['Missing data for required field.'], 'forked': ['Missing data for required field.'], 'coingecko': ['Missing data for required field.'], 'symbol': ['Missing data for required field.'], 'swapped_for': ['Missing data for required field.'], 'started': ['Missing data for required field.'], 'custom_asset_type': ['Unknown field.'], 'notes': ['Unknown field.']}}}```
Example of CSV
[assets.zip](https://github.com/rotki/rotki/files/11474689/assets.zip)




Discussion:

Resolving patch:
diff --git a/rotkehlchen/globaldb/assets_management.py b/rotkehlchen/globaldb/assets_management.py
--- a/rotkehlchen/globaldb/assets_management.py
+++ b/rotkehlchen/globaldb/assets_management.py
@@ -7,6 +7,7 @@
 
 from rotkehlchen.assets.asset import Asset
 from rotkehlchen.assets.types import AssetData, AssetType
+from rotkehlchen.db.custom_assets import DBCustomAssets
 from rotkehlchen.errors.asset import UnknownAsset
 from rotkehlchen.errors.misc import InputError
 from rotkehlchen.globaldb.handler import GlobalDBHandler
@@ -53,26 +54,31 @@ def import_assets_from_file(
         # for ethereum tokens comparing by identifier. The edge case of a non-ethereum token
         # with same name and symbol will make this fail.
         asset_type = asset_data['asset_type']
-        asset_ref: Union[Optional[list[str]], Optional[AssetData]]
-        if asset_type == AssetType.EVM_TOKEN:
-            asset_ref = globaldb.get_asset_data(
-                identifier=asset_data['identifier'],
-                form_with_incomplete_data=True,
-            )
-        else:
-            asset_ref = globaldb.check_asset_exists(
-                asset_type=asset_type,
-                name=asset_data['name'],
-                symbol=asset_data['symbol'],
-            )
-        if asset_ref is not None:
-            msg_aggregator.add_warning(
-                f'Tried to import existing asset {asset_data["identifier"]} with '
-                f'name {asset_data["name"]}',
-            )
-            continue
-
         try:
+            if asset_type == AssetType.CUSTOM_ASSET:
+                custom_assets_db = DBCustomAssets(db_handler)
+                custom_assets_db.add_custom_asset(asset_data['extra_information'])
+                continue
+
+            asset_ref: Union[Optional[list[str]], Optional[AssetData]]
+            if asset_type == AssetType.EVM_TOKEN:
+                asset_ref = globaldb.get_asset_data(
+                    identifier=asset_data['identifier'],
+                    form_with_incomplete_data=True,
+                )
+            else:
+                asset_ref = globaldb.check_asset_exists(
+                    asset_type=asset_type,
+                    name=asset_data['name'],
+                    symbol=asset_data['symbol'],
+                )
+            if asset_ref is not None:
+                msg_aggregator.add_warning(
+                    f'Tried to import existing asset {asset_data["identifier"]} with '
+                    f'name {asset_data["name"]}',
+                )
+                continue
+
             globaldb.add_asset(
                 asset_id=asset_data['identifier'],
                 asset_type=asset_type,
diff --git a/rotkehlchen/serialization/schemas.py b/rotkehlchen/serialization/schemas.py
--- a/rotkehlchen/serialization/schemas.py
+++ b/rotkehlchen/serialization/schemas.py
@@ -4,15 +4,18 @@
 
 from rotkehlchen.api.v1.fields import AssetField, AssetTypeField, TimestampField
 from rotkehlchen.api.v1.schemas import OptionalEvmTokenInformationSchema, UnderlyingTokenInfoSchema
-from rotkehlchen.assets.asset import CryptoAsset, EvmToken, UnderlyingToken
+from rotkehlchen.assets.asset import CryptoAsset, CustomAsset, EvmToken, UnderlyingToken
 from rotkehlchen.assets.types import AssetType
 
 
-class AssetDataSchema(OptionalEvmTokenInformationSchema):
+class AssetWithOraclesSchema(OptionalEvmTokenInformationSchema):
+    def __init__(self, asset_type: AssetType) -> None:
+        super().__init__()
+        self.asset_type = asset_type
+
     identifier = fields.String(required=True)
     name = fields.String(required=True)
     symbol = fields.String(required=True)
-    asset_type = AssetTypeField(required=True)
     forked = AssetField(expected_type=CryptoAsset, required=True, allow_none=True)
     swapped_for = AssetField(expected_type=CryptoAsset, required=True, allow_none=True)
     cryptocompare = fields.String(required=True, allow_none=True)
@@ -46,13 +49,12 @@ def transform_data(
                     token_kind=data['token_kind'],
                 ))
 
-        asset_type = data['asset_type']
         extra_information: Union[dict[str, Any], EvmToken]
         swapped_for, swapped_for_ident = data.pop('swapped_for'), None
         if swapped_for is not None:
             swapped_for_ident = swapped_for.identifier
 
-        if asset_type == AssetType.EVM_TOKEN:
+        if self.asset_type == AssetType.EVM_TOKEN:
             extra_information = EvmToken.initialize(
                 address=data.pop('address'),
                 chain_id=data.pop('evm_chain'),
@@ -82,11 +84,54 @@ def transform_data(
             }
 
         data['underlying_tokens'] = underlying_tokens
-        data['asset_type'] = asset_type
+        data['asset_type'] = self.asset_type
         data['extra_information'] = extra_information
         return data
 
 
+class CustomAssetSchema(Schema):
+    identifier = fields.String(required=True)
+    name = fields.String(required=True)
+    notes = fields.String(load_default=None)
+    custom_asset_type = fields.String(required=True)
+
+    @post_load
+    def make_custom_asset(
+            self,
+            data: dict[str, Any],
+            **_kwargs: Any,
+    ) -> dict[str, CustomAsset]:
+        custom_asset = CustomAsset.initialize(
+            identifier=str(data['identifier']),
+            name=data['name'],
+            notes=data['notes'],
+            custom_asset_type=data['custom_asset_type'],
+        )
+        data['asset_type'] = AssetType.CUSTOM_ASSET
+        data['extra_information'] = custom_asset
+        return data
+
+
+class AssetDataSchema(Schema):
+    identifier = fields.String(required=True)
+    asset_type = AssetTypeField(required=True)
+
+    class Meta:
+        unknown = 'include'  # Needed to accept extra arguments depending on asset type
+
+    @post_load
+    def transform_data(
+            self,
+            data: dict[str, Any],
+            **_kwargs: Any,
+    ) -> dict[str, Any]:
+        asset_type = data.pop('asset_type')
+        if asset_type == AssetType.CUSTOM_ASSET:
+            return CustomAssetSchema().load(data)
+
+        return AssetWithOraclesSchema(asset_type=asset_type).load(data)
+
+
 class ExportedAssetsSchema(Schema):
     version = fields.String(required=True)
     assets = fields.List(fields.Nested(AssetDataSchema), load_default=None)


Test patch:
diff --git a/rotkehlchen/tests/data/exported_assets.json b/rotkehlchen/tests/data/exported_assets.json
--- a/rotkehlchen/tests/data/exported_assets.json
+++ b/rotkehlchen/tests/data/exported_assets.json
@@ -1 +1 @@
-{ "version": "3", "assets": [ { "identifier": "eip155:1/erc20:0xFEEf77d3f69374f66429C91d732A244f074bdf74", "name": "Convex FXS", "symbol": "cvxFXS", "asset_type": "evm token", "started": null, "forked": null, "swapped_for": null, "cryptocompare": "", "coingecko": null, "address": "0xFEEf77d3f69374f66429C91d732A244f074bdf74", "evm_chain": "ethereum", "token_kind": "erc20", "decimals": 18, "protocol": null, "underlying_tokens": [ { "address": "0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0", "weight": "100", "token_kind": "erc20" } ] }, { "identifier": "b3a59173-7275-42e2-b3b1-4f5e4abd7878", "name": "MIMATIC", "symbol": "MAI", "asset_type": "other", "started": null, "forked": null, "swapped_for": null, "cryptocompare": "", "coingecko": "mimatic" }, { "identifier": "eip155:1/erc20:0xaC14864ce5A98aF3248Ffbf549441b04421247D3", "name": "Staked SDT", "symbol": "xSDT", "asset_type": "evm token", "started": null, "forked": null, "swapped_for": null, "cryptocompare": "", "coingecko": "stake-dao", "address": "0xaC14864ce5A98aF3248Ffbf549441b04421247D3", "evm_chain": "ethereum", "token_kind": "erc20", "decimals": 18, "protocol": null, "underlying_tokens": null }, { "identifier": "eip155:1/erc20:0xA0446D8804611944F1B527eCD37d7dcbE442caba", "name": "1INCH Token (Staked)", "symbol": "st1INCH", "asset_type": "evm token", "started": null, "forked": null, "swapped_for": null, "cryptocompare": "", "coingecko": null, "address": "0xA0446D8804611944F1B527eCD37d7dcbE442caba", "evm_chain": "ethereum", "token_kind": "erc20", "decimals": 18, "protocol": null, "underlying_tokens": [ { "address": "0x111111111117dC0aa78b770fA6A738034120C302", "weight": "100", "token_kind": "erc20" } ] } ] }
+{"version":"3","assets":[{"identifier":"e7fa04fa-2473-49c1-898c-09332d8ee3e9","asset_type":"custom asset","name":"AAPL","custom_asset_type":"Stock","notes":"Apple Stock"},{"identifier":"eip155:1/erc20:0xFEEf77d3f69374f66429C91d732A244f074bdf74","name":"Convex FXS","symbol":"cvxFXS","asset_type":"evm token","started":null,"forked":null,"swapped_for":null,"cryptocompare":"","coingecko":null,"address":"0xFEEf77d3f69374f66429C91d732A244f074bdf74","evm_chain":"ethereum","token_kind":"erc20","decimals":18,"protocol":null,"underlying_tokens":[{"address":"0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0","weight":"100","token_kind":"erc20"}]},{"identifier":"b3a59173-7275-42e2-b3b1-4f5e4abd7878","name":"MIMATIC","symbol":"MAI","asset_type":"other","started":null,"forked":null,"swapped_for":null,"cryptocompare":"","coingecko":"mimatic"},{"identifier":"eip155:1/erc20:0xaC14864ce5A98aF3248Ffbf549441b04421247D3","name":"Staked SDT","symbol":"xSDT","asset_type":"evm token","started":null,"forked":null,"swapped_for":null,"cryptocompare":"","coingecko":"stake-dao","address":"0xaC14864ce5A98aF3248Ffbf549441b04421247D3","evm_chain":"ethereum","token_kind":"erc20","decimals":18,"protocol":null,"underlying_tokens":null},{"identifier":"eip155:1/erc20:0xA0446D8804611944F1B527eCD37d7dcbE442caba","name":"1INCH Token (Staked)","symbol":"st1INCH","asset_type":"evm token","started":null,"forked":null,"swapped_for":null,"cryptocompare":"","coingecko":null,"address":"0xA0446D8804611944F1B527eCD37d7dcbE442caba","evm_chain":"ethereum","token_kind":"erc20","decimals":18,"protocol":null,"underlying_tokens":[{"address":"0x111111111117dC0aa78b770fA6A738034120C302","weight":"100","token_kind":"erc20"}]}]}
\ No newline at end of file
diff --git a/rotkehlchen/tests/data/exported_assets.zip b/rotkehlchen/tests/data/exported_assets.zip
Binary files a/rotkehlchen/tests/data/exported_assets.zip and b/rotkehlchen/tests/data/exported_assets.zip differ