Repository: rotki/rotki

Issue:
Error in uniswap LP PnL calculation
## Problem Definition

Uniswap Liquidity providing PnL calculation shows wrong signs for the total profit.

## Task

Fix it


Discussion:

Resolving patch:
diff --git a/rotkehlchen/chain/ethereum/uniswap/uniswap.py b/rotkehlchen/chain/ethereum/uniswap/uniswap.py
--- a/rotkehlchen/chain/ethereum/uniswap/uniswap.py
+++ b/rotkehlchen/chain/ethereum/uniswap/uniswap.py
@@ -156,13 +156,13 @@ def _calculate_events_balances(
             pool_aggregated_amount[pool].events.append(event)
 
             if event.event_type == EventType.MINT:
-                pool_aggregated_amount[pool].profit_loss0 += event.amount0
-                pool_aggregated_amount[pool].profit_loss1 += event.amount1
-                pool_aggregated_amount[pool].usd_profit_loss += event.usd_price
-            else:  # event_type == EventType.BURN
                 pool_aggregated_amount[pool].profit_loss0 -= event.amount0
                 pool_aggregated_amount[pool].profit_loss1 -= event.amount1
                 pool_aggregated_amount[pool].usd_profit_loss -= event.usd_price
+            else:  # event_type == EventType.BURN
+                pool_aggregated_amount[pool].profit_loss0 += event.amount0
+                pool_aggregated_amount[pool].profit_loss1 += event.amount1
+                pool_aggregated_amount[pool].usd_profit_loss += event.usd_price
 
         # Instantiate `LiquidityPoolEventsBalance` per pool using
         # `pool_aggregated_amount`. If `pool_balance` exists (all events case),
@@ -176,9 +176,9 @@ def _calculate_events_balances(
             if pool in pool_balance:
                 token0 = pool_balance[pool].assets[0].asset
                 token1 = pool_balance[pool].assets[1].asset
-                profit_loss0 -= pool_balance[pool].assets[0].user_balance.amount
-                profit_loss1 -= pool_balance[pool].assets[1].user_balance.amount
-                usd_profit_loss -= pool_balance[pool].user_balance.usd_value
+                profit_loss0 += pool_balance[pool].assets[0].user_balance.amount
+                profit_loss1 += pool_balance[pool].assets[1].user_balance.amount
+                usd_profit_loss += pool_balance[pool].user_balance.usd_value
             else:
                 # NB: get `token0` and `token1` from any pool event
                 token0 = aggregated_amount.events[0].token0


Test patch:
diff --git a/rotkehlchen/tests/api/test_uniswap.py b/rotkehlchen/tests/api/test_uniswap.py
--- a/rotkehlchen/tests/api/test_uniswap.py
+++ b/rotkehlchen/tests/api/test_uniswap.py
@@ -779,9 +779,9 @@ def test_get_uniswap_exotic_history(
                 lp_amount=AssetAmount(FVal('1.220680531244355402')),
             ),
         ],
-        profit_loss0=AssetAmount(FVal('-35.489683548121546956')),
-        profit_loss1=AssetAmount(FVal('0.059966416263997186')),
-        usd_profit_loss=Price(FVal('35.19515540870021242982170811')),
+        profit_loss0=AssetAmount(FVal('35.489683548121546956')),
+        profit_loss1=AssetAmount(FVal('-0.059966416263997186')),
+        usd_profit_loss=Price(FVal('-35.19515540870021242982170811')),
     ),
 ]
 EXPECTED_EVENTS_BALANCES_2 = [
@@ -833,9 +833,9 @@ def test_get_uniswap_exotic_history(
                 lp_amount=AssetAmount(FVal('2.074549918528068811')),
             ),
         ],
-        profit_loss0=AssetAmount(FVal('0.610130605729210250')),
-        profit_loss1=AssetAmount(FVal('-1.971799615456732408')),
-        usd_profit_loss=Price(FVal('352.3903567533354058260380955')),
+        profit_loss0=AssetAmount(FVal('-0.610130605729210250')),
+        profit_loss1=AssetAmount(FVal('1.971799615456732408')),
+        usd_profit_loss=Price(FVal('-352.3903567533354058260380955')),
     ),
     # Response index 3
     UniswapPoolEventsBalance(
@@ -885,9 +885,9 @@ def test_get_uniswap_exotic_history(
                 lp_amount=AssetAmount(FVal('18.297385821424685079')),
             ),
         ],
-        profit_loss0=AssetAmount(FVal('97.450299238691232377')),
-        profit_loss1=AssetAmount(FVal('-0.607335819235337397')),
-        usd_profit_loss=Price(FVal('-497.9538322903351201689338200')),
+        profit_loss0=AssetAmount(FVal('-97.450299238691232377')),
+        profit_loss1=AssetAmount(FVal('0.607335819235337397')),
+        usd_profit_loss=Price(FVal('497.9538322903351201689338200')),
     ),
 ]
 
@@ -1029,27 +1029,19 @@ def test_get_events_history_filtering_by_timestamp_case2(
     assert EXPECTED_EVENTS_BALANCES_2[1].serialize() == events_balances[3]
 
 
-@pytest.mark.parametrize('ethereum_accounts', [[AAVE_TEST_ACC_1]])
+PNL_TEST_ACC = '0x1F9fbD2F6a8754Cd56D4F56ED35338A63C5Bfd1f'
+
+
+@pytest.mark.parametrize('ethereum_accounts', [[PNL_TEST_ACC]])
 @pytest.mark.parametrize('ethereum_modules', [['uniswap']])
 @pytest.mark.parametrize('start_with_valid_premium', [True])
-def test_get_events_history_get_all_events(
+def test_events_pnl(
         rotkehlchen_api_server,
         ethereum_accounts,  # pylint: disable=unused-argument
         rotki_premium_credentials,  # pylint: disable=unused-argument
         start_with_valid_premium,  # pylint: disable=unused-argument
 ):
-    """Test get all the events balances
-
-    LPs involved by the address: minimum 6
-
-    By calling the endpoint without a specific time range:
-      - All the events are queried.
-      - The events balances factorise in the current balances in the
-      protocol (do not assert amounts).
-
-    As the structures are already tested in the tests above, only the response
-    length is tested.
-    """
+    """Test the uniswap events history profit and loss calculation"""
     async_query = random.choice([False, True])
     rotki = rotkehlchen_api_server.rest_api.rotkehlchen
 
@@ -1068,12 +1060,17 @@ def test_get_events_history_get_all_events(
         btc_accounts=None,
         original_queries=['zerion', 'logs', 'blocknobytime'],
     )
+    json_data = {
+        'async_query': async_query,
+        'from_timestamp': 0,
+        'to_timestamp': 1609282296,  # time until which pnl was checked
+    }
     with ExitStack() as stack:
         # patch ethereum/etherscan to not autodetect tokens
         setup.enter_ethereum_patches(stack)
         response = requests.get(api_url_for(
             rotkehlchen_api_server, 'uniswapeventshistoryresource'),
-            json={'async_query': async_query},
+            json=json_data,
         )
         if async_query:
             task_id = assert_ok_async_response(response)
@@ -1083,9 +1080,22 @@ def test_get_events_history_get_all_events(
         else:
             result = assert_proper_response_with_result(response)
 
-    events_balances = result[AAVE_TEST_ACC_1]
-
-    assert len(events_balances) >= 6
+    events_balances = result[PNL_TEST_ACC]
+    assert len(events_balances) == 3
+    # Assert some event details
+    assert events_balances[0]['events'][0]['amount0'] == '42.247740079122297434'
+    assert events_balances[0]['events'][0]['amount1'] == '0.453409755976350622'
+    assert events_balances[0]['events'][0]['event_type'] == 'mint'
+    assert events_balances[0]['events'][1]['amount0'] == '64.052160560025177012'
+    assert events_balances[0]['events'][1]['amount1'] == '0.455952395125600548'
+    assert events_balances[0]['events'][1]['event_type'] == 'burn'
+    # Most importantly assert the profit loss
+    assert FVal(events_balances[0]['profit_loss0']) >= FVal('21.8')
+    assert FVal(events_balances[0]['profit_loss1']) >= FVal('0.00254')
+    assert FVal(events_balances[1]['profit_loss0']) >= FVal('-0.0013')
+    assert FVal(events_balances[1]['profit_loss1']) >= FVal('0.054')
+    assert FVal(events_balances[2]['profit_loss0']) >= FVal('50.684')
+    assert FVal(events_balances[2]['profit_loss1']) >= FVal('-0.04')
 
 
 @pytest.mark.parametrize('ethereum_accounts', [[TEST_ADDRESS_FACTORY_CONTRACT]])
diff --git a/rotkehlchen/tests/unit/uniswap/utils.py b/rotkehlchen/tests/unit/uniswap/utils.py
--- a/rotkehlchen/tests/unit/uniswap/utils.py
+++ b/rotkehlchen/tests/unit/uniswap/utils.py
@@ -358,9 +358,9 @@
         token0=TOKEN_BASED,
         token1=ASSET_WETH,
         events=LP_1_EVENTS,
-        profit_loss0=AssetAmount(FVal('-35.489683548121546956')),
-        profit_loss1=AssetAmount(FVal('0.059966416263997186')),
-        usd_profit_loss=Price(FVal('35.19515540870021242982170811')),
+        profit_loss0=AssetAmount(FVal('35.489683548121546956')),
+        profit_loss1=AssetAmount(FVal('-0.059966416263997186')),
+        usd_profit_loss=Price(FVal('-35.19515540870021242982170811')),
     )
 )
 LP_2_EVENTS_BALANCE = (
@@ -370,9 +370,9 @@
         token0=ASSET_WETH,
         token1=TOKEN_DICE,
         events=LP_2_EVENTS,
-        profit_loss0=AssetAmount(FVal('0.610130605729210250')),
-        profit_loss1=AssetAmount(FVal('-1.971799615456732408')),
-        usd_profit_loss=Price(FVal('352.3903567533354058260380955')),
+        profit_loss0=AssetAmount(FVal('-0.610130605729210250')),
+        profit_loss1=AssetAmount(FVal('1.971799615456732408')),
+        usd_profit_loss=Price(FVal('-352.3903567533354058260380955')),
     )
 )
 LP_3_EVENTS = [
@@ -442,9 +442,9 @@
         token0=TOKEN_BASED,
         token1=ASSET_WETH,
         events=LP_3_EVENTS,
-        profit_loss0=AssetAmount(FVal('0.773209925184996494')),
-        profit_loss1=AssetAmount(FVal('0.056631443395672732')),
-        usd_profit_loss=Price(FVal('36.2389300619698095220125311')),
+        profit_loss0=AssetAmount(FVal('-0.773209925184996494')),
+        profit_loss1=AssetAmount(FVal('-0.056631443395672732')),
+        usd_profit_loss=Price(FVal('-36.2389300619698095220125311')),
     )
 )