Repository: rotki/rotki

Issue:
DivisionByZero when generating Profit and Loss Report
## Problem Definition

<!-- Provide a description of what is the current problem and why you are raising this issue.
If it's a bug please describe what was the unexpected thing that occured and what was the
expected behaviour. -->

Generating a Profit and Loss report results in the following error:

```
An unexpected response was received while generating the profit and loss report. This might indicate an issue with the generation mechanism. Please check the logs for any error messages (you might have to enable debug logging first) and create an issue.

The backend query task died unexpectedly: [<class 'decimal.DivisionByZero'>]
```
## Logs

<!-- Rotki also logs debug information to the `rotkehlchen.log` file. Please attach it to the
issue as it may help us find the source of the issue faster. You can find the logs location by going
to the Help menu on the top and then choosing "Logs directory" -->

```
[30/01/2021 14:58:00 CET] DEBUG rotkehlchen.accounting.events: Processing sell trade of ETH for EUR at 1587032943
[30/01/2021 14:58:00 CET] ERROR rotkehlchen.api.rest: Greenlet for task 8 dies with exception: [<class 'decimal.DivisionByZero'>].
Exception Name: <class 'decimal.DivisionByZero'>
Exception Info: [<class 'decimal.DivisionByZero'>]
Traceback:
   File "src/gevent/greenlet.py", line 906, in gevent._gevent_cgreenlet.Greenlet.run
  File "rotkehlchen/api/rest.py", line 268, in _do_query_async
  File "rotkehlchen/api/rest.py", line 1300, in _process_history
  File "rotkehlchen/rotkehlchen.py", line 571, in process_history
  File "rotkehlchen/accounting/accountant.py", line 332, in process_history
  File "rotkehlchen/accounting/accountant.py", line 597, in _process_action
  File "rotkehlchen/accounting/accountant.py", line 241, in trade_add_to_sell_events
  File "rotkehlchen/accounting/events.py", line 442, in add_sell_and_corresponding_buy
  File "rotkehlchen/accounting/events.py", line 509, in add_sell
  File "rotkehlchen/fval.py", line 88, in __truediv__
```

### System Description


Operating system: Fedora Linux 33
Rotki version: 1.13.0 appimage



Discussion:
Hey @jenkadit thank a lot for the issue. This is unexpected and super interesting! Never seen that before in either production or in our tests.

Looking at the code around the place where the exception occurs:

```python
            SellEvent(
                amount=selling_amount,
                timestamp=timestamp,
                rate=rate_in_profit_currency,
                fee_rate=total_fee_in_profit_currency / selling_amount,  # <--- here is the culprit
                gain=gain_in_profit_currency,
            ),
```

The division is with the selling amount. So it seems you somehow got a sell of 0 of something in there. Can you look at the logs around a bit more and see more information of what is being processed? Selling 0 of something makes no sense.

We should naturally handle it better, and we will thanks to you finding this, but I want to see where did that come in from.
Looking at the code what I can I can imagine is that somehow a zero price was returned for the asset at the given time and that it was a crypto to crypto trade that also generated a virtual sell. And the sell amount there is created from the price and the amount ...  and if price is 0 the sell amount ends up being zero.

In that case what we should do is that it should be skipped and an error logged in the logs.
In the logs I see

`{"entry": {"fee_currency": "ETH", "amount": "0.00", "trade_type": "sell", "timestamp": 1587032943, "fee": "0.01000", "location": "bitstamp", "notes": "", "pair": "ETH_EUR", "rate": "156.65", "link": "110769053", "trade_id": "d0e91edaf3f96a57e0d632192b49681410cfe6eee4f79eef8a86c0626dc6cb61"}, "ignored_in_accounting": false}`

Seems like it's due to rounding somewhere, in the Bitstamp UI I see a corresponding transaction for 0.00000798ETH (remeinder of a partially fulfilled order)
Interesting :thinking: 
We do not do any rounding on rotki in the calculations so a zero amount should never reach there due to that. One option is that bittrex itself returned a zero amount there.

Let's see then. Can you go to your trades history tab and find the trade there and see the amount? By default in the frontend they are displayed with 2 decimal points of accuracy but if you hover over the number the whole thing should be shown.

P.S.: For faster back + forth you can also chat with me or the other devs in our discord: https://discord.gg/aGCxHG7

Resolving patch:
diff --git a/rotkehlchen/accounting/events.py b/rotkehlchen/accounting/events.py
--- a/rotkehlchen/accounting/events.py
+++ b/rotkehlchen/accounting/events.py
@@ -270,6 +270,12 @@ def add_buy_and_corresponding_sell(
             gain_in_profit_currency = with_bought_asset_gain
 
         sold_amount = trade_rate * bought_amount
+        if sold_amount == ZERO:
+            logger.error(
+                f'Not adding a virtual sell event. Could not calculate it from '
+                f'trade_rate * bought_amount = {trade_rate} * {bought_amount}',
+            )
+            return
 
         sold_asset_rate_in_profit_currency = self.get_rate_in_profit_currency(
             paid_with_asset,
@@ -493,6 +499,14 @@ def add_sell(
         if skip_trade:
             return
 
+        if selling_amount == ZERO:
+            logger.error(
+                f'Skipping sell trade of {selling_asset.identifier} for '
+                f'{receiving_asset.identifier if receiving_asset else "nothing"} at {timestamp}'
+                f' since the selling amount is 0',
+            )
+            return
+
         logger.debug(
             f'Processing sell trade of {selling_asset.identifier} for '
             f'{receiving_asset.identifier if receiving_asset else "nothing"} at {timestamp}',
diff --git a/rotkehlchen/chain/ethereum/adex/adex.py b/rotkehlchen/chain/ethereum/adex/adex.py
--- a/rotkehlchen/chain/ethereum/adex/adex.py
+++ b/rotkehlchen/chain/ethereum/adex/adex.py
@@ -902,7 +902,14 @@ def _update_channel_withdraw_events_token(
 
         # Update token property for each channel withdraw event
         for channel_withdraw in channel_withdraws:
-            channel_withdraw.token = channel_id_token.get(channel_withdraw.channel_id, None)
+            token = channel_id_token.get(channel_withdraw.channel_id, None)
+            if token is None:
+                log.error(
+                    f'Could not find a channel in the channel id to token mapping.'
+                    f'Channel withdraw with tx_hash {channel_withdraw.tx_hash} '
+                    f'will have no token and usd value',
+                )
+            channel_withdraw.token = token
 
         return None
 
diff --git a/rotkehlchen/exchanges/bitstamp.py b/rotkehlchen/exchanges/bitstamp.py
--- a/rotkehlchen/exchanges/bitstamp.py
+++ b/rotkehlchen/exchanges/bitstamp.py
@@ -449,6 +449,7 @@ def _api_query_paginated(
                         is_result_timesamp_gt_end_ts = True  # prevent extra request
                         break
 
+                    log.debug(f'Attempting to deserialize bitstamp {case_pretty}: {raw_result}')
                     result = deserialization_method(raw_result)
 
                 except DeserializationError as e:


Test patch:
diff --git a/rotkehlchen/tests/api/test_adex.py b/rotkehlchen/tests/api/test_adex.py
--- a/rotkehlchen/tests/api/test_adex.py
+++ b/rotkehlchen/tests/api/test_adex.py
@@ -17,7 +17,6 @@
     assert_simple_ok_response,
     wait_for_async_task,
 )
-from rotkehlchen.constants.assets import A_ADX
 from rotkehlchen.accounting.structures import Balance
 from rotkehlchen.tests.utils.rotkehlchen import setup_balances
 from rotkehlchen.chain.ethereum.adex.typing import Bond, ChannelWithdraw, Unbond
@@ -144,6 +143,7 @@ def test_get_events(
     identity_address = '0x2a6c38D16BFdc7b4a20f1F982c058F07BDCe9204'
     tom_pool_id = '0x2ce0c96383fb229d9776f33846e983a956a7d95844fac57b180ed0071d93bb28'
     bond_id = '0x540cab9883923c01e657d5da4ca5674b6e4626b4a148224635495502d674c7c5'
+    channel_id = '0x30d87bab0ef1e7f8b4c3b894ca2beed41bbd54c481f31e5791c1e855c9dbf4ba'
     result = result[ADEX_TEST_ADDR]
     expected_events = [Bond(
         tx_hash='0x9989f47c6c0a761f98f910ac24e2438d858be96c12124a13be4bb4b3150c55ea',
@@ -160,10 +160,10 @@ def test_get_events(
         address=ADEX_TEST_ADDR,
         identity_address=identity_address,
         timestamp=1607453764,
-        channel_id='',
+        channel_id=channel_id,
         pool_id=tom_pool_id,
-        value=Balance(FVal('5056.894263641728544592'), FVal('10113.788527283457089184')),
-        token=A_ADX,
+        value=Balance(FVal('5056.894263641728544592'), FVal('0')),
+        token=None,
     ), Unbond(
         tx_hash='0xa9ee91af823c0173fc5ada908ff9fe3f4d7c84a2c9da795f0889b3f4ace75b13',
         address=ADEX_TEST_ADDR,