We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Replace defi protocol icons fetched from Zerion SDK
## Problem Definition

We need to remove the reliance on Zerion SDK's metadata for icons since it is deprecated and it will be removed.

> As for icons -- metadata for protocols has been deprecated and will be removed in the next release, so you should not rely on it (yes, we stopped downloading icons for the latest protocols)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rotkehlchen/chain/ethereum/zerion.py`
Content:
```
1 import logging
2 from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple, Union
3 
4 from eth_utils.address import to_checksum_address
5 from typing_extensions import Literal
6 
7 from rotkehlchen.accounting.structures import Balance
8 from rotkehlchen.assets.asset import Asset
9 from rotkehlchen.chain.ethereum.contracts import EthereumContract
10 from rotkehlchen.chain.ethereum.defi import handle_defi_price_query
11 from rotkehlchen.chain.ethereum.utils import token_normalized_value_decimals
12 from rotkehlchen.constants.assets import A_DAI, A_USDC
13 from rotkehlchen.constants.ethereum import ZERION_ABI
14 from rotkehlchen.constants.misc import ZERO
15 from rotkehlchen.errors import UnknownAsset, UnsupportedAsset
16 from rotkehlchen.fval import FVal
17 from rotkehlchen.inquirer import Inquirer, get_underlying_asset_price
18 from rotkehlchen.serialization.deserialize import deserialize_ethereum_address
19 from rotkehlchen.typing import ChecksumEthAddress, Price
20 from rotkehlchen.user_messages import MessagesAggregator
21 from rotkehlchen.utils.misc import get_chunks
22 
23 if TYPE_CHECKING:
24     from rotkehlchen.chain.ethereum.manager import EthereumManager
25 
26 log = logging.getLogger(__name__)
27 
28 
29 def _is_token_non_standard(symbol: str, address: ChecksumEthAddress) -> bool:
30     """ignore some assets we do not yet support and don't want to spam warnings with"""
31     if symbol in ('UNI-V2', 'swUSD', 'crCRV'):
32         return True
33 
34     if address in ('0xCb2286d9471cc185281c4f763d34A962ED212962',):  # Sushi LP token
35         return True
36 
37     return False
38 
39 
40 class DefiProtocol(NamedTuple):
41     name: str
42     description: str
43     url: str
44     icon_link: str
45     version: int
46 
47     def serialize(self) -> Dict[str, str]:
48         return {'name': self.name, 'icon': self.icon_link}
49 
50 
51 class DefiBalance(NamedTuple):
52     token_address: ChecksumEthAddress
53     token_name: str
54     token_symbol: str
55     balance: Balance
56 
57 
58 class DefiProtocolBalances(NamedTuple):
59     protocol: DefiProtocol
60     balance_type: Literal['Asset', 'Debt']
61     base_balance: DefiBalance
62     underlying_balances: List[DefiBalance]
63 
64 
65 # Type of the argument given to functions that need the defi balances
66 GIVEN_DEFI_BALANCES = Union[
67     Dict[ChecksumEthAddress, List[DefiProtocolBalances]],
68     Callable[[], Dict[ChecksumEthAddress, List[DefiProtocolBalances]]],
69 ]
70 
71 
72 def _handle_pooltogether(normalized_balance: FVal, token_name: str) -> Optional[DefiBalance]:
73     """Special handling for pooltogether
74 
75     https://github.com/rotki/rotki/issues/1429
76     """
77     if 'DAI' in token_name:
78         dai_price = Inquirer.find_usd_price(A_DAI)
79         return DefiBalance(
80             token_address=to_checksum_address('0x49d716DFe60b37379010A75329ae09428f17118d'),
81             token_name='Pool Together DAI token',
82             token_symbol='plDAI',
83             balance=Balance(
84                 amount=normalized_balance,
85                 usd_value=normalized_balance * dai_price,
86             ),
87         )
88     elif 'USDC' in token_name:
89         usdc_price = Inquirer.find_usd_price(A_USDC)
90         return DefiBalance(
91             token_address=to_checksum_address('0xBD87447F48ad729C5c4b8bcb503e1395F62e8B98'),
92             token_name='Pool Together USDC token',
93             token_symbol='plUSDC',
94             balance=Balance(
95                 amount=normalized_balance,
96                 usd_value=normalized_balance * usdc_price,
97             ),
98         )
99 
100     return None
101 
102 
103 # last known zerion adapter address
104 ZERION_ADAPTER_ADDRESS = deserialize_ethereum_address('0x06FE76B2f432fdfEcAEf1a7d4f6C3d41B5861672')
105 
106 
107 def query_zerion_address(
108         ethereum: 'EthereumManager',
109         msg_aggregator: MessagesAggregator,
110 ) -> ChecksumEthAddress:
111     """Queries the zerion contract address. If query fails, then last known
112     address is used"""
113     result = ethereum.ens_lookup('api.zerion.eth')
114     if result is None:
115         msg_aggregator.add_error(
116             'Could not query api.zerion.eth address. Using last known address',
117         )
118         contract_address = ZERION_ADAPTER_ADDRESS
119     else:
120         contract_address = result
121 
122     return contract_address
123 
124 
125 class Zerion():
126     """Adapter for the Zerion DeFi SDK https://github.com/zeriontech/defi-sdk"""
127 
128     def __init__(
129             self,
130             ethereum_manager: 'EthereumManager',
131             msg_aggregator: MessagesAggregator,
132             contract_address: Optional[ChecksumEthAddress] = None,
133     ) -> None:
134         self.ethereum = ethereum_manager
135         self.msg_aggregator = msg_aggregator
136 
137         if contract_address:
138             self.contract_address = contract_address
139             return
140 
141         # else
142         self.contract_address = query_zerion_address(ethereum_manager, msg_aggregator)
143 
144     def all_balances_for_account(self, account: ChecksumEthAddress) -> List[DefiProtocolBalances]:
145         """Calls the contract's getBalances() to get all protocol balances for account
146 
147         https://docs.zerion.io/smart-contracts/adapterregistry-v3#getbalances
148         """
149         result = self.ethereum.call_contract(
150             contract_address=self.contract_address,
151             abi=ZERION_ABI,
152             method_name='getBalances',
153             arguments=[account],
154         )
155         protocol_balances = []
156         for entry in result:
157             protocol = DefiProtocol(
158                 name=entry[0][0],
159                 description=entry[0][1],
160                 url=entry[0][2],
161                 icon_link=entry[0][3],
162                 version=entry[0][4],
163             )
164             for adapter_balance in entry[1]:
165                 balance_type = adapter_balance[0][1]  # can be either 'Asset' or 'Debt'
166                 for balances in adapter_balance[1]:
167                     underlying_balances = []
168                     base_balance = self._get_single_balance(protocol.name, balances[0])
169                     for balance in balances[1]:
170                         defi_balance = self._get_single_balance(protocol.name, balance)
171                         underlying_balances.append(defi_balance)
172 
173                     if base_balance.balance.usd_value == ZERO:
174                         # This can happen. We can't find a price for some assets
175                         # such as combined pool assets. But we can instead use
176                         # the sum of the usd_value of the underlying_balances
177                         usd_sum = sum(x.balance.usd_value for x in underlying_balances)
178                         base_balance.balance.usd_value = usd_sum  # type: ignore
179 
180                     protocol_balances.append(DefiProtocolBalances(
181                         protocol=protocol,
182                         balance_type=balance_type,
183                         base_balance=base_balance,
184                         underlying_balances=underlying_balances,
185                     ))
186 
187         return protocol_balances
188 
189     def _get_single_balance(
190             self,
191             protocol_name: str,
192             entry: Tuple[Tuple[str, str, str, int], int],
193     ) -> DefiBalance:
194         metadata = entry[0]
195         balance_value = entry[1]
196         decimals = metadata[3]
197         normalized_value = token_normalized_value_decimals(balance_value, decimals)
198         token_symbol = metadata[2]
199         token_address = to_checksum_address(metadata[0])
200         token_name = metadata[1]
201 
202         special_handling = self.handle_protocols(
203             protocol_name=protocol_name,
204             token_symbol=token_symbol,
205             normalized_balance=normalized_value,
206             token_address=token_address,
207             token_name=token_name,
208         )
209         if special_handling:
210             return special_handling
211 
212         try:
213             asset = Asset(token_symbol)
214             usd_price = Inquirer().find_usd_price(asset)
215         except (UnknownAsset, UnsupportedAsset):
216             if not _is_token_non_standard(token_symbol, token_address):
217                 self.msg_aggregator.add_warning(
218                     f'Unsupported asset {token_symbol} with address '
219                     f'{token_address} encountered during DeFi protocol queries',
220                 )
221             usd_price = Price(ZERO)
222 
223         usd_value = normalized_value * usd_price
224         defi_balance = DefiBalance(
225             token_address=token_address,
226             token_name=token_name,
227             token_symbol=token_symbol,
228             balance=Balance(amount=normalized_value, usd_value=usd_value),
229         )
230         return defi_balance
231 
232     def handle_protocols(
233             self,
234             protocol_name: str,
235             token_symbol: str,
236             normalized_balance: FVal,
237             token_address: str,
238             token_name: str,
239     ) -> Optional[DefiBalance]:
240         """Special handling for price for token/protocols which are easier to do onchain
241         or need some kind of special treatment.
242         """
243         if protocol_name == 'PoolTogether':
244             result = _handle_pooltogether(normalized_balance, token_name)
245             if result is not None:
246                 return result
247 
248         underlying_asset_price = get_underlying_asset_price(token_symbol)
249         usd_price = handle_defi_price_query(self.ethereum, token_symbol, underlying_asset_price)
250         if usd_price is None:
251             return None
252 
253         return DefiBalance(
254             token_address=to_checksum_address(token_address),
255             token_name=token_name,
256             token_symbol=token_symbol,
257             balance=Balance(amount=normalized_balance, usd_value=normalized_balance * usd_price),
258         )
259 
260 
261 # -- The below should be moved and integrated to the uniswap module
262 
263 class UniLPBalance(NamedTuple):
264     pool: DefiBalance
265     token0: DefiBalance
266     token1: DefiBalance
267 
268 
269 def _decode_token(entry: Tuple) -> DefiBalance:
270     # TODO: This would need work to also calculate the usd value of the balance
271     decimals = entry[0][3]
272     return DefiBalance(
273         token_address=entry[0][0],
274         token_name=entry[0][1],
275         token_symbol=entry[0][2],
276         balance=Balance(
277             amount=token_normalized_value_decimals(entry[1], decimals),
278             usd_value=ZERO,
279         ),
280     )
281 
282 
283 def _decode_result(data: Tuple) -> UniLPBalance:
284     pool_token = _decode_token(data[0])
285     token0 = _decode_token(data[1][0])
286     token1 = _decode_token(data[1][1])
287     return UniLPBalance(pool=pool_token, token0=token0, token1=token1)
288 
289 
290 def uniswap_lp_token_balances(
291         address: ChecksumEthAddress,
292         ethereum: 'EthereumManager',
293         zerion: Zerion,
294         lp_addresses: List[ChecksumEthAddress],
295 ) -> List[UniLPBalance]:
296     """Query uniswap token balances from ethereum
297 
298     The number of addresses to query in one call depends a lot on the node used.
299     With an infura node we saw the following:
300     500 addresses per call took on average 43 seconds for 20450 addresses
301     2000 addresses per call took on average 36 seconds for 20450 addresses
302     4000 addresses per call took on average 32.6 seconds for 20450 addresses
303     5000 addresses timed out a few times
304     """
305     zerion_contract = EthereumContract(
306         address=zerion.contract_address,
307         abi=ZERION_ABI,
308         deployed_block=0,
309     )
310     chunks = list(get_chunks(lp_addresses, n=4000))
311     balances = []
312     for idx, chunk in enumerate(chunks):
313         print(f'Querying univ2 token balances for {address} {idx + 1} / {len(chunks)}')
314         result = zerion_contract.call(
315             ethereum=ethereum,
316             method_name='getAdapterBalance',
317             arguments=[address, '0x4EdBac5c8cb92878DD3fd165e43bBb8472f34c3f', chunk],
318         )
319 
320         for entry in result[1]:
321             balances.append(_decode_result(entry))
322 
323     return balances
324
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/rotkehlchen/chain/ethereum/zerion.py b/rotkehlchen/chain/ethereum/zerion.py
--- a/rotkehlchen/chain/ethereum/zerion.py
+++ b/rotkehlchen/chain/ethereum/zerion.py
@@ -41,11 +41,10 @@
     name: str
     description: str
     url: str
-    icon_link: str
     version: int
 
     def serialize(self) -> Dict[str, str]:
-        return {'name': self.name, 'icon': self.icon_link}
+        return {'name': self.name}
 
 
 class DefiBalance(NamedTuple):
@@ -158,7 +157,6 @@
                 name=entry[0][0],
                 description=entry[0][1],
                 url=entry[0][2],
-                icon_link=entry[0][3],
                 version=entry[0][4],
             )
             for adapter_balance in entry[1]:

{"golden_diff": "diff --git a/rotkehlchen/chain/ethereum/zerion.py b/rotkehlchen/chain/ethereum/zerion.py\n--- a/rotkehlchen/chain/ethereum/zerion.py\n+++ b/rotkehlchen/chain/ethereum/zerion.py\n@@ -41,11 +41,10 @@\n     name: str\n     description: str\n     url: str\n-    icon_link: str\n     version: int\n \n     def serialize(self) -> Dict[str, str]:\n-        return {'name': self.name, 'icon': self.icon_link}\n+        return {'name': self.name}\n \n \n class DefiBalance(NamedTuple):\n@@ -158,7 +157,6 @@\n                 name=entry[0][0],\n                 description=entry[0][1],\n                 url=entry[0][2],\n-                icon_link=entry[0][3],\n                 version=entry[0][4],\n             )\n             for adapter_balance in entry[1]:\n", "issue": "Replace defi protocol icons fetched from Zerion SDK\n## Problem Definition\r\n\r\nWe need to remove the reliance on Zerion SDK's metadata for icons since it is deprecated and it will be removed.\r\n\r\n> As for icons -- metadata for protocols has been deprecated and will be removed in the next release, so you should not rely on it (yes, we stopped downloading icons for the latest protocols)\r\n\n", "before_files": [{"content": "import logging\nfrom typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple, Union\n\nfrom eth_utils.address import to_checksum_address\nfrom typing_extensions import Literal\n\nfrom rotkehlchen.accounting.structures import Balance\nfrom rotkehlchen.assets.asset import Asset\nfrom rotkehlchen.chain.ethereum.contracts import EthereumContract\nfrom rotkehlchen.chain.ethereum.defi import handle_defi_price_query\nfrom rotkehlchen.chain.ethereum.utils import token_normalized_value_decimals\nfrom rotkehlchen.constants.assets import A_DAI, A_USDC\nfrom rotkehlchen.constants.ethereum import ZERION_ABI\nfrom rotkehlchen.constants.misc import ZERO\nfrom rotkehlchen.errors import UnknownAsset, UnsupportedAsset\nfrom rotkehlchen.fval import FVal\nfrom rotkehlchen.inquirer import Inquirer, get_underlying_asset_price\nfrom rotkehlchen.serialization.deserialize import deserialize_ethereum_address\nfrom rotkehlchen.typing import ChecksumEthAddress, Price\nfrom rotkehlchen.user_messages import MessagesAggregator\nfrom rotkehlchen.utils.misc import get_chunks\n\nif TYPE_CHECKING:\n    from rotkehlchen.chain.ethereum.manager import EthereumManager\n\nlog = logging.getLogger(__name__)\n\n\ndef _is_token_non_standard(symbol: str, address: ChecksumEthAddress) -> bool:\n    \"\"\"ignore some assets we do not yet support and don't want to spam warnings with\"\"\"\n    if symbol in ('UNI-V2', 'swUSD', 'crCRV'):\n        return True\n\n    if address in ('0xCb2286d9471cc185281c4f763d34A962ED212962',):  # Sushi LP token\n        return True\n\n    return False\n\n\nclass DefiProtocol(NamedTuple):\n    name: str\n    description: str\n    url: str\n    icon_link: str\n    version: int\n\n    def serialize(self) -> Dict[str, str]:\n        return {'name': self.name, 'icon': self.icon_link}\n\n\nclass DefiBalance(NamedTuple):\n    token_address: ChecksumEthAddress\n    token_name: str\n    token_symbol: str\n    balance: Balance\n\n\nclass DefiProtocolBalances(NamedTuple):\n    protocol: DefiProtocol\n    balance_type: Literal['Asset', 'Debt']\n    base_balance: DefiBalance\n    underlying_balances: List[DefiBalance]\n\n\n# Type of the argument given to functions that need the defi balances\nGIVEN_DEFI_BALANCES = Union[\n    Dict[ChecksumEthAddress, List[DefiProtocolBalances]],\n    Callable[[], Dict[ChecksumEthAddress, List[DefiProtocolBalances]]],\n]\n\n\ndef _handle_pooltogether(normalized_balance: FVal, token_name: str) -> Optional[DefiBalance]:\n    \"\"\"Special handling for pooltogether\n\n    https://github.com/rotki/rotki/issues/1429\n    \"\"\"\n    if 'DAI' in token_name:\n        dai_price = Inquirer.find_usd_price(A_DAI)\n        return DefiBalance(\n            token_address=to_checksum_address('0x49d716DFe60b37379010A75329ae09428f17118d'),\n            token_name='Pool Together DAI token',\n            token_symbol='plDAI',\n            balance=Balance(\n                amount=normalized_balance,\n                usd_value=normalized_balance * dai_price,\n            ),\n        )\n    elif 'USDC' in token_name:\n        usdc_price = Inquirer.find_usd_price(A_USDC)\n        return DefiBalance(\n            token_address=to_checksum_address('0xBD87447F48ad729C5c4b8bcb503e1395F62e8B98'),\n            token_name='Pool Together USDC token',\n            token_symbol='plUSDC',\n            balance=Balance(\n                amount=normalized_balance,\n                usd_value=normalized_balance * usdc_price,\n            ),\n        )\n\n    return None\n\n\n# last known zerion adapter address\nZERION_ADAPTER_ADDRESS = deserialize_ethereum_address('0x06FE76B2f432fdfEcAEf1a7d4f6C3d41B5861672')\n\n\ndef query_zerion_address(\n        ethereum: 'EthereumManager',\n        msg_aggregator: MessagesAggregator,\n) -> ChecksumEthAddress:\n    \"\"\"Queries the zerion contract address. If query fails, then last known\n    address is used\"\"\"\n    result = ethereum.ens_lookup('api.zerion.eth')\n    if result is None:\n        msg_aggregator.add_error(\n            'Could not query api.zerion.eth address. Using last known address',\n        )\n        contract_address = ZERION_ADAPTER_ADDRESS\n    else:\n        contract_address = result\n\n    return contract_address\n\n\nclass Zerion():\n    \"\"\"Adapter for the Zerion DeFi SDK https://github.com/zeriontech/defi-sdk\"\"\"\n\n    def __init__(\n            self,\n            ethereum_manager: 'EthereumManager',\n            msg_aggregator: MessagesAggregator,\n            contract_address: Optional[ChecksumEthAddress] = None,\n    ) -> None:\n        self.ethereum = ethereum_manager\n        self.msg_aggregator = msg_aggregator\n\n        if contract_address:\n            self.contract_address = contract_address\n            return\n\n        # else\n        self.contract_address = query_zerion_address(ethereum_manager, msg_aggregator)\n\n    def all_balances_for_account(self, account: ChecksumEthAddress) -> List[DefiProtocolBalances]:\n        \"\"\"Calls the contract's getBalances() to get all protocol balances for account\n\n        https://docs.zerion.io/smart-contracts/adapterregistry-v3#getbalances\n        \"\"\"\n        result = self.ethereum.call_contract(\n            contract_address=self.contract_address,\n            abi=ZERION_ABI,\n            method_name='getBalances',\n            arguments=[account],\n        )\n        protocol_balances = []\n        for entry in result:\n            protocol = DefiProtocol(\n                name=entry[0][0],\n                description=entry[0][1],\n                url=entry[0][2],\n                icon_link=entry[0][3],\n                version=entry[0][4],\n            )\n            for adapter_balance in entry[1]:\n                balance_type = adapter_balance[0][1]  # can be either 'Asset' or 'Debt'\n                for balances in adapter_balance[1]:\n                    underlying_balances = []\n                    base_balance = self._get_single_balance(protocol.name, balances[0])\n                    for balance in balances[1]:\n                        defi_balance = self._get_single_balance(protocol.name, balance)\n                        underlying_balances.append(defi_balance)\n\n                    if base_balance.balance.usd_value == ZERO:\n                        # This can happen. We can't find a price for some assets\n                        # such as combined pool assets. But we can instead use\n                        # the sum of the usd_value of the underlying_balances\n                        usd_sum = sum(x.balance.usd_value for x in underlying_balances)\n                        base_balance.balance.usd_value = usd_sum  # type: ignore\n\n                    protocol_balances.append(DefiProtocolBalances(\n                        protocol=protocol,\n                        balance_type=balance_type,\n                        base_balance=base_balance,\n                        underlying_balances=underlying_balances,\n                    ))\n\n        return protocol_balances\n\n    def _get_single_balance(\n            self,\n            protocol_name: str,\n            entry: Tuple[Tuple[str, str, str, int], int],\n    ) -> DefiBalance:\n        metadata = entry[0]\n        balance_value = entry[1]\n        decimals = metadata[3]\n        normalized_value = token_normalized_value_decimals(balance_value, decimals)\n        token_symbol = metadata[2]\n        token_address = to_checksum_address(metadata[0])\n        token_name = metadata[1]\n\n        special_handling = self.handle_protocols(\n            protocol_name=protocol_name,\n            token_symbol=token_symbol,\n            normalized_balance=normalized_value,\n            token_address=token_address,\n            token_name=token_name,\n        )\n        if special_handling:\n            return special_handling\n\n        try:\n            asset = Asset(token_symbol)\n            usd_price = Inquirer().find_usd_price(asset)\n        except (UnknownAsset, UnsupportedAsset):\n            if not _is_token_non_standard(token_symbol, token_address):\n                self.msg_aggregator.add_warning(\n                    f'Unsupported asset {token_symbol} with address '\n                    f'{token_address} encountered during DeFi protocol queries',\n                )\n            usd_price = Price(ZERO)\n\n        usd_value = normalized_value * usd_price\n        defi_balance = DefiBalance(\n            token_address=token_address,\n            token_name=token_name,\n            token_symbol=token_symbol,\n            balance=Balance(amount=normalized_value, usd_value=usd_value),\n        )\n        return defi_balance\n\n    def handle_protocols(\n            self,\n            protocol_name: str,\n            token_symbol: str,\n            normalized_balance: FVal,\n            token_address: str,\n            token_name: str,\n    ) -> Optional[DefiBalance]:\n        \"\"\"Special handling for price for token/protocols which are easier to do onchain\n        or need some kind of special treatment.\n        \"\"\"\n        if protocol_name == 'PoolTogether':\n            result = _handle_pooltogether(normalized_balance, token_name)\n            if result is not None:\n                return result\n\n        underlying_asset_price = get_underlying_asset_price(token_symbol)\n        usd_price = handle_defi_price_query(self.ethereum, token_symbol, underlying_asset_price)\n        if usd_price is None:\n            return None\n\n        return DefiBalance(\n            token_address=to_checksum_address(token_address),\n            token_name=token_name,\n            token_symbol=token_symbol,\n            balance=Balance(amount=normalized_balance, usd_value=normalized_balance * usd_price),\n        )\n\n\n# -- The below should be moved and integrated to the uniswap module\n\nclass UniLPBalance(NamedTuple):\n    pool: DefiBalance\n    token0: DefiBalance\n    token1: DefiBalance\n\n\ndef _decode_token(entry: Tuple) -> DefiBalance:\n    # TODO: This would need work to also calculate the usd value of the balance\n    decimals = entry[0][3]\n    return DefiBalance(\n        token_address=entry[0][0],\n        token_name=entry[0][1],\n        token_symbol=entry[0][2],\n        balance=Balance(\n            amount=token_normalized_value_decimals(entry[1], decimals),\n            usd_value=ZERO,\n        ),\n    )\n\n\ndef _decode_result(data: Tuple) -> UniLPBalance:\n    pool_token = _decode_token(data[0])\n    token0 = _decode_token(data[1][0])\n    token1 = _decode_token(data[1][1])\n    return UniLPBalance(pool=pool_token, token0=token0, token1=token1)\n\n\ndef uniswap_lp_token_balances(\n        address: ChecksumEthAddress,\n        ethereum: 'EthereumManager',\n        zerion: Zerion,\n        lp_addresses: List[ChecksumEthAddress],\n) -> List[UniLPBalance]:\n    \"\"\"Query uniswap token balances from ethereum\n\n    The number of addresses to query in one call depends a lot on the node used.\n    With an infura node we saw the following:\n    500 addresses per call took on average 43 seconds for 20450 addresses\n    2000 addresses per call took on average 36 seconds for 20450 addresses\n    4000 addresses per call took on average 32.6 seconds for 20450 addresses\n    5000 addresses timed out a few times\n    \"\"\"\n    zerion_contract = EthereumContract(\n        address=zerion.contract_address,\n        abi=ZERION_ABI,\n        deployed_block=0,\n    )\n    chunks = list(get_chunks(lp_addresses, n=4000))\n    balances = []\n    for idx, chunk in enumerate(chunks):\n        print(f'Querying univ2 token balances for {address} {idx + 1} / {len(chunks)}')\n        result = zerion_contract.call(\n            ethereum=ethereum,\n            method_name='getAdapterBalance',\n            arguments=[address, '0x4EdBac5c8cb92878DD3fd165e43bBb8472f34c3f', chunk],\n        )\n\n        for entry in result[1]:\n            balances.append(_decode_result(entry))\n\n    return balances\n", "path": "rotkehlchen/chain/ethereum/zerion.py"}], "after_files": [{"content": "import logging\nfrom typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple, Union\n\nfrom eth_utils.address import to_checksum_address\nfrom typing_extensions import Literal\n\nfrom rotkehlchen.accounting.structures import Balance\nfrom rotkehlchen.assets.asset import Asset\nfrom rotkehlchen.chain.ethereum.contracts import EthereumContract\nfrom rotkehlchen.chain.ethereum.defi import handle_defi_price_query\nfrom rotkehlchen.chain.ethereum.utils import token_normalized_value_decimals\nfrom rotkehlchen.constants.assets import A_DAI, A_USDC\nfrom rotkehlchen.constants.ethereum import ZERION_ABI\nfrom rotkehlchen.constants.misc import ZERO\nfrom rotkehlchen.errors import UnknownAsset, UnsupportedAsset\nfrom rotkehlchen.fval import FVal\nfrom rotkehlchen.inquirer import Inquirer, get_underlying_asset_price\nfrom rotkehlchen.serialization.deserialize import deserialize_ethereum_address\nfrom rotkehlchen.typing import ChecksumEthAddress, Price\nfrom rotkehlchen.user_messages import MessagesAggregator\nfrom rotkehlchen.utils.misc import get_chunks\n\nif TYPE_CHECKING:\n    from rotkehlchen.chain.ethereum.manager import EthereumManager\n\nlog = logging.getLogger(__name__)\n\n\ndef _is_token_non_standard(symbol: str, address: ChecksumEthAddress) -> bool:\n    \"\"\"ignore some assets we do not yet support and don't want to spam warnings with\"\"\"\n    if symbol in ('UNI-V2', 'swUSD', 'crCRV'):\n        return True\n\n    if address in ('0xCb2286d9471cc185281c4f763d34A962ED212962',):  # Sushi LP token\n        return True\n\n    return False\n\n\nclass DefiProtocol(NamedTuple):\n    name: str\n    description: str\n    url: str\n    version: int\n\n    def serialize(self) -> Dict[str, str]:\n        return {'name': self.name}\n\n\nclass DefiBalance(NamedTuple):\n    token_address: ChecksumEthAddress\n    token_name: str\n    token_symbol: str\n    balance: Balance\n\n\nclass DefiProtocolBalances(NamedTuple):\n    protocol: DefiProtocol\n    balance_type: Literal['Asset', 'Debt']\n    base_balance: DefiBalance\n    underlying_balances: List[DefiBalance]\n\n\n# Type of the argument given to functions that need the defi balances\nGIVEN_DEFI_BALANCES = Union[\n    Dict[ChecksumEthAddress, List[DefiProtocolBalances]],\n    Callable[[], Dict[ChecksumEthAddress, List[DefiProtocolBalances]]],\n]\n\n\ndef _handle_pooltogether(normalized_balance: FVal, token_name: str) -> Optional[DefiBalance]:\n    \"\"\"Special handling for pooltogether\n\n    https://github.com/rotki/rotki/issues/1429\n    \"\"\"\n    if 'DAI' in token_name:\n        dai_price = Inquirer.find_usd_price(A_DAI)\n        return DefiBalance(\n            token_address=to_checksum_address('0x49d716DFe60b37379010A75329ae09428f17118d'),\n            token_name='Pool Together DAI token',\n            token_symbol='plDAI',\n            balance=Balance(\n                amount=normalized_balance,\n                usd_value=normalized_balance * dai_price,\n            ),\n        )\n    elif 'USDC' in token_name:\n        usdc_price = Inquirer.find_usd_price(A_USDC)\n        return DefiBalance(\n            token_address=to_checksum_address('0xBD87447F48ad729C5c4b8bcb503e1395F62e8B98'),\n            token_name='Pool Together USDC token',\n            token_symbol='plUSDC',\n            balance=Balance(\n                amount=normalized_balance,\n                usd_value=normalized_balance * usdc_price,\n            ),\n        )\n\n    return None\n\n\n# last known zerion adapter address\nZERION_ADAPTER_ADDRESS = deserialize_ethereum_address('0x06FE76B2f432fdfEcAEf1a7d4f6C3d41B5861672')\n\n\ndef query_zerion_address(\n        ethereum: 'EthereumManager',\n        msg_aggregator: MessagesAggregator,\n) -> ChecksumEthAddress:\n    \"\"\"Queries the zerion contract address. If query fails, then last known\n    address is used\"\"\"\n    result = ethereum.ens_lookup('api.zerion.eth')\n    if result is None:\n        msg_aggregator.add_error(\n            'Could not query api.zerion.eth address. Using last known address',\n        )\n        contract_address = ZERION_ADAPTER_ADDRESS\n    else:\n        contract_address = result\n\n    return contract_address\n\n\nclass Zerion():\n    \"\"\"Adapter for the Zerion DeFi SDK https://github.com/zeriontech/defi-sdk\"\"\"\n\n    def __init__(\n            self,\n            ethereum_manager: 'EthereumManager',\n            msg_aggregator: MessagesAggregator,\n            contract_address: Optional[ChecksumEthAddress] = None,\n    ) -> None:\n        self.ethereum = ethereum_manager\n        self.msg_aggregator = msg_aggregator\n\n        if contract_address:\n            self.contract_address = contract_address\n            return\n\n        # else\n        self.contract_address = query_zerion_address(ethereum_manager, msg_aggregator)\n\n    def all_balances_for_account(self, account: ChecksumEthAddress) -> List[DefiProtocolBalances]:\n        \"\"\"Calls the contract's getBalances() to get all protocol balances for account\n\n        https://docs.zerion.io/smart-contracts/adapterregistry-v3#getbalances\n        \"\"\"\n        result = self.ethereum.call_contract(\n            contract_address=self.contract_address,\n            abi=ZERION_ABI,\n            method_name='getBalances',\n            arguments=[account],\n        )\n        protocol_balances = []\n        for entry in result:\n            protocol = DefiProtocol(\n                name=entry[0][0],\n                description=entry[0][1],\n                url=entry[0][2],\n                version=entry[0][4],\n            )\n            for adapter_balance in entry[1]:\n                balance_type = adapter_balance[0][1]  # can be either 'Asset' or 'Debt'\n                for balances in adapter_balance[1]:\n                    underlying_balances = []\n                    base_balance = self._get_single_balance(protocol.name, balances[0])\n                    for balance in balances[1]:\n                        defi_balance = self._get_single_balance(protocol.name, balance)\n                        underlying_balances.append(defi_balance)\n\n                    if base_balance.balance.usd_value == ZERO:\n                        # This can happen. We can't find a price for some assets\n                        # such as combined pool assets. But we can instead use\n                        # the sum of the usd_value of the underlying_balances\n                        usd_sum = sum(x.balance.usd_value for x in underlying_balances)\n                        base_balance.balance.usd_value = usd_sum  # type: ignore\n\n                    protocol_balances.append(DefiProtocolBalances(\n                        protocol=protocol,\n                        balance_type=balance_type,\n                        base_balance=base_balance,\n                        underlying_balances=underlying_balances,\n                    ))\n\n        return protocol_balances\n\n    def _get_single_balance(\n            self,\n            protocol_name: str,\n            entry: Tuple[Tuple[str, str, str, int], int],\n    ) -> DefiBalance:\n        metadata = entry[0]\n        balance_value = entry[1]\n        decimals = metadata[3]\n        normalized_value = token_normalized_value_decimals(balance_value, decimals)\n        token_symbol = metadata[2]\n        token_address = to_checksum_address(metadata[0])\n        token_name = metadata[1]\n\n        special_handling = self.handle_protocols(\n            protocol_name=protocol_name,\n            token_symbol=token_symbol,\n            normalized_balance=normalized_value,\n            token_address=token_address,\n            token_name=token_name,\n        )\n        if special_handling:\n            return special_handling\n\n        try:\n            asset = Asset(token_symbol)\n            usd_price = Inquirer().find_usd_price(asset)\n        except (UnknownAsset, UnsupportedAsset):\n            if not _is_token_non_standard(token_symbol, token_address):\n                self.msg_aggregator.add_warning(\n                    f'Unsupported asset {token_symbol} with address '\n                    f'{token_address} encountered during DeFi protocol queries',\n                )\n            usd_price = Price(ZERO)\n\n        usd_value = normalized_value * usd_price\n        defi_balance = DefiBalance(\n            token_address=token_address,\n            token_name=token_name,\n            token_symbol=token_symbol,\n            balance=Balance(amount=normalized_value, usd_value=usd_value),\n        )\n        return defi_balance\n\n    def handle_protocols(\n            self,\n            protocol_name: str,\n            token_symbol: str,\n            normalized_balance: FVal,\n            token_address: str,\n            token_name: str,\n    ) -> Optional[DefiBalance]:\n        \"\"\"Special handling for price for token/protocols which are easier to do onchain\n        or need some kind of special treatment.\n        \"\"\"\n        if protocol_name == 'PoolTogether':\n            result = _handle_pooltogether(normalized_balance, token_name)\n            if result is not None:\n                return result\n\n        underlying_asset_price = get_underlying_asset_price(token_symbol)\n        usd_price = handle_defi_price_query(self.ethereum, token_symbol, underlying_asset_price)\n        if usd_price is None:\n            return None\n\n        return DefiBalance(\n            token_address=to_checksum_address(token_address),\n            token_name=token_name,\n            token_symbol=token_symbol,\n            balance=Balance(amount=normalized_balance, usd_value=normalized_balance * usd_price),\n        )\n\n\n# -- The below should be moved and integrated to the uniswap module\n\nclass UniLPBalance(NamedTuple):\n    pool: DefiBalance\n    token0: DefiBalance\n    token1: DefiBalance\n\n\ndef _decode_token(entry: Tuple) -> DefiBalance:\n    # TODO: This would need work to also calculate the usd value of the balance\n    decimals = entry[0][3]\n    return DefiBalance(\n        token_address=entry[0][0],\n        token_name=entry[0][1],\n        token_symbol=entry[0][2],\n        balance=Balance(\n            amount=token_normalized_value_decimals(entry[1], decimals),\n            usd_value=ZERO,\n        ),\n    )\n\n\ndef _decode_result(data: Tuple) -> UniLPBalance:\n    pool_token = _decode_token(data[0])\n    token0 = _decode_token(data[1][0])\n    token1 = _decode_token(data[1][1])\n    return UniLPBalance(pool=pool_token, token0=token0, token1=token1)\n\n\ndef uniswap_lp_token_balances(\n        address: ChecksumEthAddress,\n        ethereum: 'EthereumManager',\n        zerion: Zerion,\n        lp_addresses: List[ChecksumEthAddress],\n) -> List[UniLPBalance]:\n    \"\"\"Query uniswap token balances from ethereum\n\n    The number of addresses to query in one call depends a lot on the node used.\n    With an infura node we saw the following:\n    500 addresses per call took on average 43 seconds for 20450 addresses\n    2000 addresses per call took on average 36 seconds for 20450 addresses\n    4000 addresses per call took on average 32.6 seconds for 20450 addresses\n    5000 addresses timed out a few times\n    \"\"\"\n    zerion_contract = EthereumContract(\n        address=zerion.contract_address,\n        abi=ZERION_ABI,\n        deployed_block=0,\n    )\n    chunks = list(get_chunks(lp_addresses, n=4000))\n    balances = []\n    for idx, chunk in enumerate(chunks):\n        print(f'Querying univ2 token balances for {address} {idx + 1} / {len(chunks)}')\n        result = zerion_contract.call(\n            ethereum=ethereum,\n            method_name='getAdapterBalance',\n            arguments=[address, '0x4EdBac5c8cb92878DD3fd165e43bBb8472f34c3f', chunk],\n        )\n\n        for entry in result[1]:\n            balances.append(_decode_result(entry))\n\n    return balances\n", "path": "rotkehlchen/chain/ethereum/zerion.py"}]}