Repository: rotki/rotki

Issue:
The ycrvRenWSBTC did not show up in Dashboard and showed up with wrong $ value in Defi Overview
## Problem Definition

A user detected that their `ycrvRenWSBTC` token:

(1) Did not show up in the Dashboard
(2) showed up with a mistaken (very low) $ value in the DeFi Overview.

## Task

- [x] Fix it
- [x] Check the price calculations for the other vaults too


Discussion:

Resolving patch:
diff --git a/rotkehlchen/chain/ethereum/defi.py b/rotkehlchen/chain/ethereum/defi.py
--- a/rotkehlchen/chain/ethereum/defi.py
+++ b/rotkehlchen/chain/ethereum/defi.py
@@ -1,6 +1,8 @@
 from typing import TYPE_CHECKING, Optional
 
+from rotkehlchen.assets.asset import EthereumToken
 from rotkehlchen.constants.ethereum import EthereumConstants, EthereumContract
+from rotkehlchen.constants.misc import ONE
 from rotkehlchen.fval import FVal
 from rotkehlchen.typing import Price
 
@@ -31,7 +33,26 @@ def _handle_yearn_curve_vault(
         ethereum: 'EthereumManager',
         curve_contract: EthereumContract,
         yearn_contract: EthereumContract,
+        div_decimals: int,
+        asset_price: FVal,
 ) -> FVal:
+    """The price of a yearn curve vault is
+    asset_price * (pool.get_virtual_price / 10 ^ pool_decimals) *
+    (vault.getPricePerFullShare / 10 ^ vault_decimals)
+
+    asset_price is the asset price of the pool token.
+    Normally for pools of stablecoins such as ycrv pool one should take
+    (dai_weight * dai_price +
+    usdc_weight * usdc_price +
+    usdt_weight * usdt_price +
+    tusd_weight * tusd_price)
+
+    And weights change all the time.
+
+    It's safe to assume value of 1 for such pools at the moment. For a BTC
+    pool you should follow the same approach with the weights and average or
+    just take the current price of BTC. Same for other assets.
+    """
     virtual_price = ethereum.call_contract(
         contract_address=curve_contract.address,
         abi=curve_contract.abi,
@@ -44,33 +65,58 @@ def _handle_yearn_curve_vault(
         method_name='getPricePerFullShare',
         arguments=[],
     )
-    usd_value = FVal(virtual_price * price_per_full_share) / 10 ** 36
-    return usd_value
+    usd_value = FVal(virtual_price * price_per_full_share) / 10 ** div_decimals
+    return usd_value * asset_price
+
+
+def _handle_curvepool_price(
+        ethereum: 'EthereumManager',
+        contract: EthereumContract,
+        div_decimals: int,
+        asset_price: FVal,
+) -> FVal:
+    """The price of a curve pool token is
+    asset_price * (pool.get_virtual_price / 10 ^ pool_decimals)
 
+    asset_price is the asset price of the pool token.
+    Normally for pools of stablecoins such as ycrv pool one should take
+    (dai_weight * dai_price +
+    usdc_weight * usdc_price +
+    usdt_weight * usdt_price +
+    tusd_weight * tusd_price)
 
-def _handle_curvepool_price(ethereum: 'EthereumManager', contract: EthereumContract) -> FVal:
+    And weights change all the time.
+
+    It's safe to assume value of 1 for such pools at the moment. For a BTC
+    pool you should follow the same approach with the weights and average or
+    just take the current price of BTC. Same for other assets.
+    """
     virtual_price = ethereum.call_contract(
         contract_address=contract.address,
         abi=contract.abi,
         method_name='get_virtual_price',
         arguments=[],
     )
-    usd_value = FVal(virtual_price) / (10 ** 18)
+    usd_value = (asset_price * FVal(virtual_price)) / (10 ** div_decimals)
     return usd_value
 
 
 def handle_underlying_price_yearn_vault(
         ethereum: 'EthereumManager',
-        underlying_asset_price: Price,
         contract: EthereumContract,
+        div_decimals: int,
+        asset_price: Price,
 ) -> FVal:
+    # TODO This needs to change. Either make it constant for all vaults of this type
+    # or understand why yUSDC and yUSDT which have 6 decimals don't work correctly
+    div_decimals = 18
     price_per_full_share = ethereum.call_contract(
         contract_address=contract.address,
         abi=contract.abi,
         method_name='getPricePerFullShare',
         arguments=[],
     )
-    usd_value = FVal(underlying_asset_price * price_per_full_share) / 10 ** 18
+    usd_value = FVal(asset_price * price_per_full_share) / 10 ** div_decimals
     return usd_value
 
 
@@ -91,79 +137,118 @@ def handle_defi_price_query(
             ethereum=ethereum,
             curve_contract=CURVEFI_YSWAP,
             yearn_contract=YEARN_YCRV_VAULT,
+            div_decimals=36,
+            asset_price=ONE,  # assuming price of $1 for all stablecoins in pool
         )
     elif token_symbol == 'yyDAI+yUSDC+yUSDT+yBUSD':
         usd_value = _handle_yearn_curve_vault(
             ethereum=ethereum,
             curve_contract=CURVEFI_BUSDSWAP,
             yearn_contract=YEARN_BCURVE_VAULT,
+            div_decimals=36,
+            asset_price=ONE,  # assuming price of $1 for all stablecoins in pool
         )
     elif token_symbol == 'ycrvRenWSBTC':
+        assert underlying_asset_price
         usd_value = _handle_yearn_curve_vault(
             ethereum=ethereum,
             curve_contract=CURVEFI_SRENSWAP,
             yearn_contract=YEARN_SRENCURVE_VAULT,
+            div_decimals=36,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yDAI+yUSDC+yUSDT+yTUSD':
-        usd_value = _handle_curvepool_price(ethereum, CURVEFI_YSWAP)
+        token = EthereumToken(token_symbol)
+        usd_value = _handle_curvepool_price(ethereum, CURVEFI_YSWAP, token.decimals, ONE)
     elif token_symbol == 'ypaxCrv':
-        usd_value = _handle_curvepool_price(ethereum, CURVEFI_PAXSWAP)
+        token = EthereumToken(token_symbol)
+        usd_value = _handle_curvepool_price(ethereum, CURVEFI_PAXSWAP, token.decimals, ONE)
     elif token_symbol == 'crvRenWBTC':
-        usd_value = _handle_curvepool_price(ethereum, CURVEFI_RENSWAP)
+        assert underlying_asset_price
+        token = EthereumToken(token_symbol)
+        usd_value = _handle_curvepool_price(
+            ethereum=ethereum,
+            contract=CURVEFI_RENSWAP,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
+        )
     elif token_symbol == 'crvRenWSBTC':
-        usd_value = _handle_curvepool_price(ethereum, CURVEFI_SRENSWAP)
+        assert underlying_asset_price
+        token = EthereumToken(token_symbol)
+        usd_value = _handle_curvepool_price(
+            ethereum=ethereum,
+            contract=CURVEFI_SRENSWAP,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
+        )
     elif token_symbol == 'crvPlain3andSUSD':
-        usd_value = _handle_curvepool_price(ethereum, CURVEFI_SUSDV2SWAP)
+        token = EthereumToken(token_symbol)
+        usd_value = _handle_curvepool_price(ethereum, CURVEFI_SUSDV2SWAP, token.decimals, ONE)
     elif token_symbol == 'yDAI+yUSDC+yUSDT+yBUSD':
-        usd_value = _handle_curvepool_price(ethereum, CURVEFI_BUSDSWAP)
+        token = EthereumToken(token_symbol)
+        usd_value = _handle_curvepool_price(ethereum, CURVEFI_BUSDSWAP, token.decimals, ONE)
     elif token_symbol == 'yaLINK':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_ALINK_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yDAI':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_DAI_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yWETH':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_WETH_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yYFI':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_YFI_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yUSDT':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_USDT_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yUSDC':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_USDC_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     elif token_symbol == 'yTUSD':
         assert underlying_asset_price
+        token = EthereumToken(token_symbol)
         usd_value = handle_underlying_price_yearn_vault(
             ethereum=ethereum,
-            underlying_asset_price=underlying_asset_price,
             contract=YEARN_TUSD_VAULT,
+            div_decimals=token.decimals,
+            asset_price=underlying_asset_price,
         )
     else:
         return None
diff --git a/rotkehlchen/constants/misc.py b/rotkehlchen/constants/misc.py
--- a/rotkehlchen/constants/misc.py
+++ b/rotkehlchen/constants/misc.py
@@ -14,6 +14,7 @@
 EV_DEFI = EventType('defi_event')
 
 ZERO = FVal(0)
+ONE = FVal(1)
 
 
 # API URLS
diff --git a/rotkehlchen/inquirer.py b/rotkehlchen/inquirer.py
--- a/rotkehlchen/inquirer.py
+++ b/rotkehlchen/inquirer.py
@@ -1,4 +1,4 @@
-from __future__ import unicode_literals
+from __future__ import unicode_literals  # isort:skip
 
 import logging
 from json.decoder import JSONDecodeError
@@ -12,6 +12,7 @@
 from rotkehlchen.constants import ZERO
 from rotkehlchen.constants.assets import (
     A_ALINK,
+    A_BTC,
     A_DAI,
     A_ETH,
     A_TUSD,
@@ -41,7 +42,7 @@
     'yyDAI+yUSDC+yUSDT+yTUSD',
     'yDAI+yUSDC+yUSDT+yBUSD',
     'yDAI+yUSDC+yUSDT+yTUSD',
-    'ycrvRenWSBTC'
+    'ycrvRenWSBTC',
     'ypaxCrv',
     'crvRenWBTC',
     'crvRenWSBTC',
@@ -78,6 +79,8 @@ def get_underlying_asset_price(token_symbol: str) -> Optional[Price]:
         price = Inquirer().find_usd_price(A_USDC)
     elif token_symbol == 'yTUSD':
         price = Inquirer().find_usd_price(A_TUSD)
+    elif token_symbol in ('ycrvRenWSBTC', 'crvRenWBTC', 'crvRenWSBTC'):
+        price = Inquirer().find_usd_price(A_BTC)
 
     return price
 


Test patch:
diff --git a/rotkehlchen/tests/unit/test_assets.py b/rotkehlchen/tests/unit/test_assets.py
--- a/rotkehlchen/tests/unit/test_assets.py
+++ b/rotkehlchen/tests/unit/test_assets.py
@@ -133,7 +133,7 @@ def test_coingecko_identifiers_are_reachable():
 def test_assets_json_meta():
     """Test that all_assets.json md5 matches and that if md5 changes since last
     time then version is also bumped"""
-    last_meta = {'md5': '54a3d4ddac60932806255527105864a2', 'version': 11}
+    last_meta = {'md5': 'eb7528aa0824182b9096c5cba1dd8d12', 'version': 12}
     data_dir = Path(__file__).resolve().parent.parent.parent / 'data'
     data_md5 = file_md5(data_dir / 'all_assets.json')