Repository: rotki/rotki

Issue:
Decode deposits to Curve.fi: (Deposit USDN)
Deposit to curve pools should decode and I guess this is a pool not a gauge (which is still WIP).

Deposit to Curve.fi: Deposit USDN are not decoded.
https://etherscan.io/address/0x094d12e5b541784701FD8d65F11fc0598FBC6332

Request: Decode it.

![image](https://user-images.githubusercontent.com/36508484/221869535-7da90605-0b1d-49ea-b390-ea46619f198b.png)



Discussion:
Curve pool deposits **should** be decoded. So I would take this as a bug if it does not. Can you give me a transaction hash as the link you gave is for a contract.

Resolving patch:
diff --git a/rotkehlchen/chain/ethereum/modules/curve/decoder.py b/rotkehlchen/chain/ethereum/modules/curve/decoder.py
--- a/rotkehlchen/chain/ethereum/modules/curve/decoder.py
+++ b/rotkehlchen/chain/ethereum/modules/curve/decoder.py
@@ -1,6 +1,6 @@
 import logging
 from collections.abc import Mapping
-from typing import TYPE_CHECKING, Any, Callable, Optional
+from typing import TYPE_CHECKING, Any, Callable, Literal, Optional
 from rotkehlchen.accounting.structures.evm_event import EvmProduct
 
 from rotkehlchen.accounting.structures.types import HistoryEventSubType, HistoryEventType
@@ -36,15 +36,59 @@
     from rotkehlchen.user_messages import MessagesAggregator
 
 
-ADD_LIQUIDITY = b'B?d\x95\xa0\x8f\xc6RB\\\xf4\xed\r\x1f\x9e7\xe5q\xd9\xb9R\x9b\x1c\x1c#\xcc\xe7\x80\xb2\xe7\xdf\r'  # noqa: E501
-ADD_LIQUIDITY_2_ASSETS = b'&\xf5Z\x85\x08\x1d$\x97N\x85\xc6\xc0\x00E\xd0\xf0E9\x91\xe9Xs\xf5+\xff\r!\xaf@y\xa7h'  # noqa: E501
-ADD_LIQUIDITY_4_ASSETS = b'?\x19\x15w^\x0c\x9a8\xa5z{\xb7\xf1\xf9\x00_Ho\xb9\x04\xe1\xf8J\xa2\x156MVs\x19\xa5\x8d'  # noqa: E501
-REMOVE_LIQUIDITY = b"Z\xd0V\xf2\xe2\x8a\x8c\xec# \x15@k\x846h\xc1\xe3l\xdaY\x81'\xec;\x8cY\xb8\xc7's\xa0"  # noqa: E501
-REMOVE_ONE = b'\x9e\x96\xdd;\x99z*%~\xecM\xf9\xbbn\xafbn m\xf5\xf5C\xbd\x966\x82\xd1C0\x0b\xe3\x10'  # noqa: E501
-REMOVE_LIQUIDITY_3_ASSETS = b'\xa4\x9dL\xf0&V\xae\xbf\x8cw\x1fZ\x85\x85c\x8a*\x15\xeel\x97\xcfr\x05\xd4 \x8e\xd7\xc1\xdf%-'  # noqa: E501
-REMOVE_LIQUIDITY_4_ASSETS = b'\x98x\xca7^\x10o*C\xc3\xb5\x99\xfcbEh\x13\x1cL\x9aK\xa6j\x14V7\x15v;\xe9\xd5\x9d'  # noqa: E501
+ADD_LIQUIDITY_EVENTS = {
+    b'B?d\x95\xa0\x8f\xc6RB\\\xf4\xed\r\x1f\x9e7\xe5q\xd9\xb9R\x9b\x1c\x1c#\xcc\xe7\x80\xb2\xe7\xdf\r',  # ADD_LIQUIDITY  # noqa: E501
+    b'&\xf5Z\x85\x08\x1d$\x97N\x85\xc6\xc0\x00E\xd0\xf0E9\x91\xe9Xs\xf5+\xff\r!\xaf@y\xa7h',  # ADD_LIQUIDITY_2_ASSETS  # noqa: E501
+    b'?\x19\x15w^\x0c\x9a8\xa5z{\xb7\xf1\xf9\x00_Ho\xb9\x04\xe1\xf8J\xa2\x156MVs\x19\xa5\x8d',  # ADD_LIQUIDITY_4_ASSETS  # noqa: E501
+}
 REMOVE_LIQUIDITY_IMBALANCE = b'\xb9d\xb7/s\xf5\xef[\xf0\xfd\xc5Y\xb2\xfa\xb9\xa7\xb1*9\xe4x\x17\xa5G\xf1\xf0\xae\xe4\x7f\xeb\xd6\x02'  # noqa: E501
-CURVE_Y_DEPOSIT = string_to_evm_address('0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3')
+REMOVE_LIQUIDITY_EVENTS = {
+    REMOVE_LIQUIDITY_IMBALANCE,
+    b'|68T\xcc\xf7\x96#A\x1f\x89\x95\xb3b\xbc\xe5\xed\xdf\xf1\x8c\x92~\xdco]\xbb\xb5\xe0X\x19\xa8,',  # REMOVE_LIQUIDITY  # noqa: E501,
+    b'\x9e\x96\xdd;\x99z*%~\xecM\xf9\xbbn\xafbn m\xf5\xf5C\xbd\x966\x82\xd1C0\x0b\xe3\x10',  # REMOVE_ONE  # noqa: E501
+    b"Z\xd0V\xf2\xe2\x8a\x8c\xec# \x15@k\x846h\xc1\xe3l\xdaY\x81'\xec;\x8cY\xb8\xc7's\xa0",  # REMOVE_LIQUIDITY  # noqa: E501,
+    b'\xa4\x9dL\xf0&V\xae\xbf\x8cw\x1fZ\x85\x85c\x8a*\x15\xeel\x97\xcfr\x05\xd4 \x8e\xd7\xc1\xdf%-',  # REMOVE_LIQUIDITY_3_ASSETS  # noqa: E501
+    b'\x98x\xca7^\x10o*C\xc3\xb5\x99\xfcbEh\x13\x1cL\x9aK\xa6j\x14V7\x15v;\xe9\xd5\x9d',  # REMOVE_LIQUIDITY_4_ASSETS  # noqa: E501
+}
+# Deposit contracts are retrieved from the links below Deposit<pool>:
+# https://curve.readthedocs.io/ref-addresses.html#base-pools
+# https://curve.readthedocs.io/ref-addresses.html#metapools
+#
+# The duplicates were found via Etherscan:
+# https://etherscan.io/find-similar-contracts?a=0x35796DAc54f144DFBAD1441Ec7C32313A7c29F39
+CURVE_DEPOSIT_CONTRACTS = {
+    string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),  # curve usdn
+    string_to_evm_address('0x35796DAc54f144DFBAD1441Ec7C32313A7c29F39'),  # curve usdn duplicate
+    string_to_evm_address('0xB0a0716841F2Fc03fbA72A891B8Bb13584F52F2d'),  # curve ust
+    string_to_evm_address('0x3c8cAee4E09296800f8D29A68Fa3837e2dae4940'),  # curve usdp
+    string_to_evm_address('0xF1f85a74AD6c64315F85af52d3d46bF715236ADc'),  # curve usdk
+    string_to_evm_address('0x6600e98b71dabfD4A8Cac03b302B0189Adb86Afb'),  # curve usdk duplicate
+    string_to_evm_address('0xBE175115BF33E12348ff77CcfEE4726866A0Fbd5'),  # curve rsv
+    string_to_evm_address('0x803A2B40c5a9BB2B86DD630B274Fa2A9202874C2'),  # curve musd
+    string_to_evm_address('0x78CF256256C8089d68Cde634Cf7cDEFb39286470'),  # curve musd duplicate
+    string_to_evm_address('0x1de7f0866e2c4adAC7b457c58Cc25c8688CDa1f2'),  # curve linkusd
+    string_to_evm_address('0xF6bDc2619FFDA72c537Cd9605e0A274Dc48cB1C9'),  # curve linkusd duplicate  # noqa: E501
+    string_to_evm_address('0x09672362833d8f703D5395ef3252D4Bfa51c15ca'),  # curve husd
+    string_to_evm_address('0x0a53FaDa2d943057C47A301D25a4D9b3B8e01e8E'),  # curve husd duplicate
+    string_to_evm_address('0x64448B78561690B70E17CBE8029a3e5c1bB7136e'),  # curve gusd
+    string_to_evm_address('0x0aE274c98c0415C0651AF8cF52b010136E4a0082'),  # curve gusd duplicate
+    string_to_evm_address('0x61E10659fe3aa93d036d099405224E4Ac24996d0'),  # curve dusd
+    string_to_evm_address('0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3'),  # curve y
+    string_to_evm_address('0xb6c057591E073249F2D9D88Ba59a46CFC9B59EdB'),  # curve busd
+    string_to_evm_address('0xeB21209ae4C2c9FF2a86ACA31E123764A3B6Bc06'),  # curve compound
+    string_to_evm_address('0xA50cCc70b6a011CffDdf45057E39679379187287'),  # curve pax
+    string_to_evm_address('0xFCBa3E75865d2d561BE8D220616520c171F12851'),  # curve susd v2
+    string_to_evm_address('0x331aF2E331bd619DefAa5DAc6c038f53FCF9F785'),  # curve zap
+    string_to_evm_address('0xac795D2c97e60DF6a99ff1c814727302fD747a80'),  # curve usdt
+    string_to_evm_address('0xC45b2EEe6e09cA176Ca3bB5f7eEe7C47bF93c756'),  # curve bbtc
+    string_to_evm_address('0xd5BCf53e2C81e1991570f33Fa881c49EEa570C8D'),  # curve obtc
+    string_to_evm_address('0x11F419AdAbbFF8d595E7d5b223eee3863Bb3902C'),  # curve pbtc
+    string_to_evm_address('0xaa82ca713D94bBA7A89CEAB55314F9EfFEdDc78c'),  # curve tbtc
+    string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),  # curve 3pool
+    string_to_evm_address('0x08780fb7E580e492c1935bEe4fA5920b94AA95Da'),  # curve fraxusdc
+    string_to_evm_address('0x7abdbaf29929e7f8621b757d2a7c04d78d633834'),  # curve sbtc
+    string_to_evm_address('0xA2d40Edbf76C6C0701BA8899e2d059798eBa628e'),  # curve sbtc2
+}
 
 GAUGE_DEPOSIT = b'\xe1\xff\xfc\xc4\x92=\x04\xb5Y\xf4\xd2\x9a\x8b\xfcl\xda\x04\xeb[\r<F\x07Q\xc2@,\\\\\xc9\x10\x9c'  # noqa: E501
 GAUGE_WITHDRAW = b'\x88N\xda\xd9\xceo\xa2D\r\x8aT\xcc\x124\x90\xeb\x96\xd2v\x84y\xd4\x9f\xf9\xc76a%\xa9BCd'  # noqa: E501
@@ -102,7 +146,7 @@ def _decode_curve_remove_events(
             transaction: EvmTransaction,
             tx_log: EvmTxReceiptLog,
             decoded_events: list['EvmEvent'],
-            user_address: ChecksumEvmAddress,
+            user_or_contract_address: ChecksumEvmAddress,
     ) -> DecodingOutput:
         """Decode information related to withdrawing assets from curve pools"""
         withdrawal_events: list['EvmEvent'] = []
@@ -118,7 +162,7 @@ def _decode_curve_remove_events(
                 event.event_type == HistoryEventType.RECEIVE and
                 event.event_subtype == HistoryEventSubType.NONE and
                 event.asset == A_ETH and
-                event.location_label == user_address
+                event.location_label == user_or_contract_address
             ):
                 event.event_type = HistoryEventType.WITHDRAWAL
                 event.event_subtype = HistoryEventSubType.REMOVE_ASSET
@@ -130,7 +174,8 @@ def _decode_curve_remove_events(
                 event.event_subtype == HistoryEventSubType.NONE and
                 event.location_label == transaction.from_address and
                 (
-                    user_address == event.location_label or
+                    user_or_contract_address == event.location_label or
+                    user_or_contract_address in CURVE_DEPOSIT_CONTRACTS or
                     tx_log.topics[0] == REMOVE_LIQUIDITY_IMBALANCE
                 )
             ):
@@ -143,13 +188,13 @@ def _decode_curve_remove_events(
                 event.event_type == HistoryEventType.RECEIVE and
                 event.event_subtype == HistoryEventSubType.NONE and
                 event.location_label == transaction.from_address and
-                user_address == event.location_label and
+                user_or_contract_address == event.location_label and
                 tx_log.address in self.curve_pools
             ):
                 event.event_type = HistoryEventType.WITHDRAWAL
                 event.event_subtype = HistoryEventSubType.REMOVE_ASSET
                 event.counterparty = CPT_CURVE
-                event.notes = f'Remove {event.balance.amount} {crypto_asset.symbol} from the curve pool {tx_log.address}'  # noqa: E501
+                event.notes = f'Remove {event.balance.amount} {crypto_asset.symbol} from {tx_log.address} curve pool'  # noqa: E501
                 withdrawal_events.append(event)
 
         # Make sure that the order is the following:
@@ -158,18 +203,24 @@ def _decode_curve_remove_events(
         # 3. Withdrawal 2
         # etc.
         if return_event is None or len(withdrawal_events) == 0:
+            # for deposit zap contracts, this is handled during post decoding
+            if user_or_contract_address in CURVE_DEPOSIT_CONTRACTS and return_event is not None:
+                return DecodingOutput(matched_counterparty=CPT_CURVE)
+
             log.debug(
-                f'Expected to see a receive pool token event and deposit events for a curve pool, '
-                f'but have not found them. Tx_hash: {transaction.tx_hash.hex()} '
-                f'User address: {user_address}',
+                f'Expected to see a return pool token event and '
+                f'withdrawal events for a curve pool, but have not found them. '
+                f'Tx_hash: {transaction.tx_hash.hex()} '
+                f'User address: {user_or_contract_address}',
             )
             return DEFAULT_DECODING_OUTPUT
 
-        return_event.extra_data = {'withdrawal_events_num': len(withdrawal_events)}  # for accounting  # noqa: E501
-        previous_event = return_event
-        for event in withdrawal_events:
-            maybe_reshuffle_events(previous_event, event, decoded_events)
-            previous_event = event
+        self._sort_events(
+            action_type='removal',
+            return_or_receive_event=return_event,
+            withdrawal_or_deposit_events=withdrawal_events,
+            all_events=decoded_events,
+        )
         return DEFAULT_DECODING_OUTPUT
 
     def _decode_curve_deposit_events(
@@ -177,7 +228,7 @@ def _decode_curve_deposit_events(
             transaction: EvmTransaction,
             tx_log: EvmTxReceiptLog,
             decoded_events: list['EvmEvent'],
-            user_address: ChecksumEvmAddress,
+            user_or_contract_address: ChecksumEvmAddress,
     ) -> DecodingOutput:
         """Decode information related to depositing assets in curve pools"""
         deposit_events: list['EvmEvent'] = []
@@ -193,37 +244,17 @@ def _decode_curve_deposit_events(
                 event.event_type == HistoryEventType.SPEND and
                 event.event_subtype == HistoryEventSubType.NONE and
                 event.asset == A_ETH and
-                event.location_label == user_address
+                event.location_label == user_or_contract_address
             ):
                 event.event_type = HistoryEventType.DEPOSIT
                 event.event_subtype = HistoryEventSubType.DEPOSIT_ASSET
                 event.counterparty = CPT_CURVE
                 event.notes = f'Deposit {event.balance.amount} {crypto_asset.symbol} in curve pool'  # noqa: E501
                 deposit_events.append(event)
-            elif (  # deposit give asset
-                (
-                    event.event_type == HistoryEventType.SPEND and
-                    event.event_subtype == HistoryEventSubType.NONE and
-                    event.location_label == user_address and
-                    tx_log.address in self.curve_pools
-                ) or
-                (
-                    tx_log.topics[0] == ADD_LIQUIDITY_4_ASSETS and
-                    user_address == CURVE_Y_DEPOSIT and
-                    event.asset != A_ETH
-                )
-            ):
-                event.event_type = HistoryEventType.DEPOSIT
-                event.event_subtype = HistoryEventSubType.DEPOSIT_ASSET
-                event.counterparty = CPT_CURVE
-                event.notes = f'Deposit {event.balance.amount} {crypto_asset.symbol} in curve pool'  # noqa: E501
-                if tx_log.address in self.curve_pools:
-                    event.notes += f' {tx_log.address}'
-                deposit_events.append(event)
             elif (  # Deposit receive pool token
                 event.event_type == HistoryEventType.RECEIVE and
                 event.event_subtype == HistoryEventSubType.NONE and
-                event.location_label == user_address and
+                event.location_label == user_or_contract_address and
                 tx_log.address in self.curve_pools
             ):
                 event.event_type = HistoryEventType.RECEIVE
@@ -234,7 +265,10 @@ def _decode_curve_deposit_events(
             elif (  # deposit give asset
                 event.event_type == HistoryEventType.SPEND and
                 event.event_subtype == HistoryEventSubType.NONE and
-                event.location_label == user_address and
+                (
+                    event.location_label == user_or_contract_address or
+                    user_or_contract_address in CURVE_DEPOSIT_CONTRACTS
+                ) and
                 tx_log.address in self.curve_pools
             ):
                 event.event_type = HistoryEventType.DEPOSIT
@@ -249,18 +283,24 @@ def _decode_curve_deposit_events(
         # 3. Deposit 2
         # etc.
         if receive_event is None or len(deposit_events) == 0:
+            # for deposit zap contracts, this is handled during post decoding
+            if user_or_contract_address in CURVE_DEPOSIT_CONTRACTS and len(deposit_events) > 0:
+                return DecodingOutput(matched_counterparty=CPT_CURVE)
+
             log.debug(
-                f'Expected to see a receive pool token event and deposit events for a curve pool, '
-                f'but have not found them. Tx_hash: {transaction.tx_hash.hex()} '
-                f'User address: {user_address}',
+                f'Expected to see a receive pool token event and deposit '
+                f'events for a curve pool, but have not found them. '
+                f'Tx_hash: {transaction.tx_hash.hex()} '
+                f'User address: {user_or_contract_address}',
             )
             return DEFAULT_DECODING_OUTPUT
 
-        receive_event.extra_data = {'deposit_events_num': len(deposit_events)}  # for accounting
-        previous_event = receive_event
-        for event in deposit_events:
-            maybe_reshuffle_events(previous_event, event, decoded_events)
-            previous_event = event
+        self._sort_events(
+            action_type='addition',
+            return_or_receive_event=receive_event,
+            withdrawal_or_deposit_events=deposit_events,
+            all_events=decoded_events,
+        )
         return DEFAULT_DECODING_OUTPUT
 
     def _decode_curve_trades(self, context: DecoderContext) -> DecodingOutput:
@@ -367,31 +407,23 @@ def _decode_curve_trades(self, context: DecoderContext) -> DecodingOutput:
         return DEFAULT_DECODING_OUTPUT
 
     def _decode_curve_events(self, context: DecoderContext) -> DecodingOutput:
-        if context.tx_log.topics[0] in (
-            REMOVE_LIQUIDITY,
-            REMOVE_ONE,
-            REMOVE_LIQUIDITY_IMBALANCE,
-            REMOVE_LIQUIDITY_3_ASSETS,
-            REMOVE_LIQUIDITY_4_ASSETS,
-        ):
-            user_address = hex_or_bytes_to_address(context.tx_log.topics[1])
+        if context.tx_log.topics[0] in REMOVE_LIQUIDITY_EVENTS:
+            # it can either be the user or a deposit zap contract
+            user_or_contract_address = hex_or_bytes_to_address(context.tx_log.topics[1])
             return self._decode_curve_remove_events(
                 tx_log=context.tx_log,
                 transaction=context.transaction,
                 decoded_events=context.decoded_events,
-                user_address=user_address,
+                user_or_contract_address=user_or_contract_address,
             )
-        if context.tx_log.topics[0] in (
-            ADD_LIQUIDITY,
-            ADD_LIQUIDITY_2_ASSETS,
-            ADD_LIQUIDITY_4_ASSETS,
-        ):
-            user_address = hex_or_bytes_to_address(context.tx_log.topics[1])
+        if context.tx_log.topics[0] in ADD_LIQUIDITY_EVENTS:
+            # it can either be the user or a deposit zap contract
+            user_or_contract_address = hex_or_bytes_to_address(context.tx_log.topics[1])
             return self._decode_curve_deposit_events(
                 transaction=context.transaction,
                 tx_log=context.tx_log,
                 decoded_events=context.decoded_events,
-                user_address=user_address,
+                user_or_contract_address=user_or_contract_address,
             )
 
         if context.tx_log.topics[0] in (
@@ -430,6 +462,136 @@ def _decode_curve_gauge_events(self, context: DecoderContext) -> DecodingOutput:
 
         return DEFAULT_DECODING_OUTPUT
 
+    def _handle_post_decoding(
+            self,
+            transaction: EvmTransaction,
+            decoded_events: list['EvmEvent'],
+            all_logs: list[EvmTxReceiptLog],  # pylint: disable=unused-argument
+    ) -> list['EvmEvent']:
+        """This method handles post decoding for curve events made through the deposit contracts.
+
+        This is required because certain events are not available when `AddLiquidity` or `RemoveLiquidity` occurs.
+        An example of such is when liquidity is removed, the transfer event of the token removed comes after the
+        `RemoveLiquidity` event.
+        """  # noqa: E501
+        for tx_log in all_logs:
+            if (
+                tx_log.topics[0] in ADD_LIQUIDITY_EVENTS and
+                hex_or_bytes_to_address(tx_log.topics[1]) in CURVE_DEPOSIT_CONTRACTS
+            ):
+                self._handle_zap_contracts_liquidity_addition(
+                    transaction=transaction,
+                    decoded_events=decoded_events,
+                )
+            if (
+                tx_log.topics[0] in REMOVE_LIQUIDITY_EVENTS and
+                hex_or_bytes_to_address(tx_log.topics[1]) in CURVE_DEPOSIT_CONTRACTS
+            ):
+                self._handle_zap_contracts_liquidity_removal(
+                    pool_address=tx_log.address,
+                    transaction=transaction,
+                    decoded_events=decoded_events,
+                )
+
+        return decoded_events  # noop. just return the decoded events.
+
+    def _handle_zap_contracts_liquidity_addition(
+            self,
+            transaction: EvmTransaction,
+            decoded_events: list['EvmEvent'],
+    ) -> None:
+        """
+        This method completes the decoding of liquidity addition made through
+        Curve zap contracts such as e.g. Curve Deposit USDN, etc.
+        """
+        receive_event_idx, deposit_events = None, []
+        for idx, event in enumerate(decoded_events):
+            if (
+                event.event_type == HistoryEventType.DEPOSIT and
+                event.event_subtype == HistoryEventSubType.DEPOSIT_ASSET and
+                event.counterparty == CPT_CURVE
+            ):
+                deposit_events.append(event)
+
+            # search for the event of the wrapped token received after deposit
+            elif (
+                event.event_type == HistoryEventType.RECEIVE and
+                event.event_subtype == HistoryEventSubType.NONE and
+                event.location_label == transaction.from_address and
+                (
+                    event.address in CURVE_DEPOSIT_CONTRACTS or
+                    event.address == ZERO_ADDRESS
+                )
+            ):
+                receive_event_idx = idx
+                crypto_asset = event.asset.resolve_to_crypto_asset()
+                event.event_type = HistoryEventType.RECEIVE
+                event.event_subtype = HistoryEventSubType.RECEIVE_WRAPPED
+                event.counterparty = CPT_CURVE
+                event.notes = f'Receive {event.balance.amount} {crypto_asset.symbol} after depositing in a curve pool'  # noqa: E501
+
+        if receive_event_idx is not None and len(deposit_events) > 0:
+            self._sort_events(
+                action_type='addition',
+                return_or_receive_event=decoded_events[receive_event_idx],
+                withdrawal_or_deposit_events=deposit_events,
+                all_events=decoded_events,
+            )
+            return
+
+        log.debug(
+            f'Expected to see a receive pool token event and deposit events for a curve pool, '
+            f'but have not found them. Tx_hash: {transaction.tx_hash.hex()}',
+        )
+
+    def _handle_zap_contracts_liquidity_removal(
+            self,
+            pool_address: ChecksumEvmAddress,
+            transaction: EvmTransaction,
+            decoded_events: list['EvmEvent'],
+    ) -> None:
+        """
+        This method completes the decoding of liquidity removal made through
+        Curve zap contracts such as e.g. Curve Deposit USDN, etc.
+        """
+        return_event_idx, withdrawal_events = None, []
+        for idx, event in enumerate(decoded_events):
+            if (
+                event.event_type == HistoryEventType.SPEND and
+                event.event_subtype == HistoryEventSubType.RETURN_WRAPPED and
+                event.counterparty == CPT_CURVE and
+                event.extra_data is None
+            ):
+                return_event_idx = idx
+
+            elif (  # find receive events from the deposit contract.
+                event.event_type == HistoryEventType.RECEIVE and
+                event.event_subtype == HistoryEventSubType.NONE and
+                event.location_label == transaction.from_address and
+                event.address in CURVE_DEPOSIT_CONTRACTS
+            ):
+                crypto_asset = event.asset.resolve_to_crypto_asset()
+                event.event_type = HistoryEventType.WITHDRAWAL
+                event.event_subtype = HistoryEventSubType.REMOVE_ASSET
+                event.counterparty = CPT_CURVE
+                event.notes = f'Remove {event.balance.amount} {crypto_asset.symbol} from {pool_address} curve pool'  # noqa: E501
+                withdrawal_events.append(event)
+
+        if return_event_idx is not None and len(withdrawal_events) > 0:
+            self._sort_events(
+                action_type='removal',
+                return_or_receive_event=decoded_events[return_event_idx],
+                withdrawal_or_deposit_events=withdrawal_events,
+                all_events=decoded_events,
+            )
+            return
+
+        log.debug(
+            f'Expected to see a return pool token event and withdrawal events '
+            f'for a curve pool, but have not found them. '
+            f'Tx_hash: {transaction.tx_hash.hex()}',
+        )
+
     def _maybe_enrich_curve_transfers(self, context: EnricherContext) -> TransferEnrichmentOutput:
         """
         May raise:
@@ -437,19 +599,6 @@ def _maybe_enrich_curve_transfers(self, context: EnricherContext) -> TransferEnr
         - WrongAssetType
         """
         source_address = hex_or_bytes_to_address(context.tx_log.topics[1])
-        to_address = hex_or_bytes_to_address(context.tx_log.topics[2])
-        if (  # deposit give asset
-            context.event.event_type == HistoryEventType.RECEIVE and
-            context.event.event_subtype == HistoryEventSubType.NONE and
-            source_address == CURVE_Y_DEPOSIT and
-            context.transaction.from_address == to_address
-        ):
-            crypto_asset = context.event.asset.resolve_to_crypto_asset()
-            context.event.event_type = HistoryEventType.WITHDRAWAL
-            context.event.event_subtype = HistoryEventSubType.REMOVE_ASSET
-            context.event.counterparty = CPT_CURVE
-            context.event.notes = f'Receive {context.event.balance.amount} {crypto_asset.symbol} from the curve pool {CURVE_Y_DEPOSIT}'  # noqa: E501
-            return DEFAULT_ENRICHMENT_OUTPUT
         if (
             context.event.event_type == HistoryEventType.RECEIVE and
             context.event.event_subtype == HistoryEventSubType.NONE and
@@ -461,6 +610,32 @@ def _maybe_enrich_curve_transfers(self, context: EnricherContext) -> TransferEnr
             context.event.counterparty = CPT_CURVE
         return DEFAULT_ENRICHMENT_OUTPUT
 
+    @staticmethod
+    def _sort_events(
+            action_type: Literal['addition', 'removal'],
+            return_or_receive_event: 'EvmEvent',
+            withdrawal_or_deposit_events: list['EvmEvent'],
+            all_events: list['EvmEvent'],
+    ) -> list['EvmEvent']:
+        """
+        This method sorts the events in the expected order and adds
+        extra info to the in/out event depending on the action_type.
+        """
+        if action_type == 'addition':
+            return_or_receive_event.extra_data = {
+                'deposit_events_num': len(withdrawal_or_deposit_events),
+            }
+        else:  # can only be removal
+            return_or_receive_event.extra_data = {
+                'withdrawal_events_num': len(withdrawal_or_deposit_events),
+            }
+        previous_event = return_or_receive_event
+        for event in withdrawal_or_deposit_events:
+            maybe_reshuffle_events(previous_event, event, all_events)
+            previous_event = event
+
+        return all_events
+
     # -- DecoderInterface methods
 
     def addresses_to_decoders(self) -> dict[ChecksumEvmAddress, tuple[Any, ...]]:
@@ -475,6 +650,9 @@ def addresses_to_decoders(self) -> dict[ChecksumEvmAddress, tuple[Any, ...]]:
         mapping[CURVE_SWAP_ROUTER] = (self._decode_curve_events,)
         return mapping
 
+    def post_decoding_rules(self) -> dict[str, list[tuple[int, Callable]]]:
+        return {CPT_CURVE: [(0, self._handle_post_decoding)]}
+
     def enricher_rules(self) -> list[Callable]:
         return [
             self._maybe_enrich_curve_transfers,


Test patch:
diff --git a/rotkehlchen/tests/unit/decoders/test_curve.py b/rotkehlchen/tests/unit/decoders/test_curve.py
--- a/rotkehlchen/tests/unit/decoders/test_curve.py
+++ b/rotkehlchen/tests/unit/decoders/test_curve.py
@@ -443,7 +443,7 @@ def test_curve_remove_liquidity(database, ethereum_transaction_decoder):
             asset=A_LINK,
             balance=Balance(amount=FVal('3120.992481448818559504'), usd_value=ZERO),
             location_label=location_label,
-            notes='Remove 3120.992481448818559504 LINK from the curve pool 0xF178C0b5Bb7e7aBF4e12A4838C7b7c5bA2C623c0',  # noqa: E501
+            notes='Remove 3120.992481448818559504 LINK from 0xF178C0b5Bb7e7aBF4e12A4838C7b7c5bA2C623c0 curve pool',  # noqa: E501
             counterparty=CPT_CURVE,
             address=string_to_evm_address('0xF178C0b5Bb7e7aBF4e12A4838C7b7c5bA2C623c0'),
         )]
@@ -715,10 +715,11 @@ def test_curve_remove_imbalanced(database, ethereum_transaction_decoder):
             event_type=HistoryEventType.SPEND,
             event_subtype=HistoryEventSubType.RETURN_WRAPPED,
             asset=EvmToken('eip155:1/erc20:0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8'),
-            balance=Balance(amount=FVal('584.093916507047953183'), usd_value=ZERO),
+            balance=Balance(FVal('584.093916507047953183')),
             location_label=location_label,
             notes='Return 584.093916507047953183 yDAI+yUSDC+yUSDT+yTUSD',
             counterparty=CPT_CURVE,
+            extra_data={'withdrawal_events_num': 1},
             address=string_to_evm_address('0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3'),
         ), EvmEvent(
             event_identifier=evmhash,
@@ -730,7 +731,7 @@ def test_curve_remove_imbalanced(database, ethereum_transaction_decoder):
             asset=A_USDC,
             balance=Balance(amount=FVal('665.267705'), usd_value=ZERO),
             location_label=location_label,
-            notes='Receive 665.267705 USDC from the curve pool 0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3',  # noqa: E501
+            notes='Remove 665.267705 USDC from 0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51 curve pool',  # noqa: E501
             counterparty=CPT_CURVE,
             address=string_to_evm_address('0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3'),
         )]
@@ -1150,3 +1151,313 @@ def test_curve_swap_router(ethereum_transaction_decoder, ethereum_accounts):
         ),
     ]
     assert events == expected_events
+
+
+@pytest.mark.vcr()
+@pytest.mark.parametrize('ethereum_accounts', [['0xdE206bC0Fde2eF5C8BB6A1d552a64F82A2407Be4']])
+def test_curve_usdn_add_liquidity(ethereum_transaction_decoder, ethereum_accounts):
+    """Check that adding liquidity to a curve pool using the USDN contract is properly decoded."""
+    tx_hex = deserialize_evm_tx_hash('0x6c28df56ae4a7f784577273f72402a9b6640024327ee952fdde72c9cfdf08da5')  # noqa: E501
+    evmhash = deserialize_evm_tx_hash(tx_hex)
+    user_address = ethereum_accounts[0]
+    events, _ = get_decoded_events_of_transaction(
+        evm_inquirer=ethereum_transaction_decoder.evm_inquirer,
+        database=ethereum_transaction_decoder.database,
+        tx_hash=tx_hex,
+    )
+    timestamp = TimestampMS(Timestamp(1674470159000))
+    expected_events = [
+        EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=0,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.SPEND,
+            event_subtype=HistoryEventSubType.FEE,
+            asset=A_ETH,
+            balance=Balance(FVal('0.005672980418415474')),
+            location_label=user_address,
+            counterparty=CPT_GAS,
+            notes='Burned 0.005672980418415474 ETH for gas',
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=78,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.RECEIVE,
+            event_subtype=HistoryEventSubType.RECEIVE_WRAPPED,
+            asset=Asset('eip155:1/erc20:0x4f3E8F405CF5aFC05D68142F3783bDfE13811522'),
+            balance=Balance(FVal('2676.003172633078929284')),
+            location_label=user_address,
+            counterparty=CPT_CURVE,
+            notes='Receive 2676.003172633078929284 usdn3CRV after depositing in a curve pool',
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+            extra_data={'deposit_events_num': 1},
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=85,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.DEPOSIT,
+            event_subtype=HistoryEventSubType.DEPOSIT_ASSET,
+            asset=A_USDC,
+            balance=Balance(FVal('761.396655')),
+            location_label=user_address,
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+            notes='Deposit 761.396655 USDC in curve pool 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7',  # noqa: E501
+        ),
+    ]
+    assert events == expected_events
+
+
+@pytest.mark.vcr()
+@pytest.mark.parametrize('ethereum_accounts', [['0x6d84264A7bD2Cffa4A117BA2350403b3A9866949']])
+def test_curve_usdn_remove_liquidity(ethereum_transaction_decoder, ethereum_accounts):
+    """Check that removing liquidity from a curve pool using the USDN contract is properly decoded."""  # noqa: E501
+    tx_hex = deserialize_evm_tx_hash('0x4d77fba437b9dee6679dbb0f238b123f01b7b1bdd41bf46e35b00ce016cf8ab2')  # noqa: E501
+    evmhash = deserialize_evm_tx_hash(tx_hex)
+    user_address = ethereum_accounts[0]
+    events, _ = get_decoded_events_of_transaction(
+        evm_inquirer=ethereum_transaction_decoder.evm_inquirer,
+        database=ethereum_transaction_decoder.database,
+        tx_hash=tx_hex,
+    )
+    timestamp = TimestampMS(Timestamp(1676708639000))
+    expected_events = [
+        EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=0,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.SPEND,
+            event_subtype=HistoryEventSubType.FEE,
+            asset=A_ETH,
+            balance=Balance(FVal('0.009847222')),
+            location_label=user_address,
+            notes='Burned 0.009847222 ETH for gas',
+            counterparty=CPT_GAS,
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=224,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.SPEND,
+            event_subtype=HistoryEventSubType.RETURN_WRAPPED,
+            asset=Asset('eip155:1/erc20:0x4f3E8F405CF5aFC05D68142F3783bDfE13811522'),
+            balance=Balance(FVal('22876.332126821513165437')),
+            location_label=user_address,
+            notes='Return 22876.332126821513165437 usdn3CRV',
+            counterparty=CPT_CURVE,
+            extra_data={'withdrawal_events_num': 4},
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=234,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=Asset('eip155:1/erc20:0x674C6Ad92Fd080e4004b2312b45f796a192D27a0'),
+            balance=Balance(FVal('25186.906071469663867559')),
+            location_label=user_address,
+            notes='Remove 25186.906071469663867559 USDN from 0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=235,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=A_DAI,
+            balance=Balance(FVal('93.260120972251887648')),
+            location_label=user_address,
+            notes='Remove 93.260120972251887648 DAI from 0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=236,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=A_USDC,
+            balance=Balance(FVal('96.486847')),
+            location_label=user_address,
+            notes='Remove 96.486847 USDC from 0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=237,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=A_USDT,
+            balance=Balance(FVal('70.225535')),
+            location_label=user_address,
+            notes='Remove 70.225535 USDT from 0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0x094d12e5b541784701FD8d65F11fc0598FBC6332'),
+        ),
+    ]
+    assert events == expected_events
+
+
+@pytest.mark.vcr()
+@pytest.mark.parametrize('ethereum_accounts', [['0xcbE942516AE7687d80a5fF94F8f9A203Be800713']])
+def test_3pool_add_liquidity(ethereum_transaction_decoder, ethereum_accounts):
+    """Check that adding liquidity to a curve pool using the 3Pool zap contract is properly decoded."""  # noqa: E501
+    tx_hex = deserialize_evm_tx_hash('0xf7c6764b832069785eeee22a078f4cb3c92149c25eb0bdc6bba36ebd1598c255')  # noqa: E501
+    evmhash = deserialize_evm_tx_hash(tx_hex)
+    user_address = ethereum_accounts[0]
+    events, _ = get_decoded_events_of_transaction(
+        evm_inquirer=ethereum_transaction_decoder.evm_inquirer,
+        database=ethereum_transaction_decoder.database,
+        tx_hash=tx_hex,
+    )
+    timestamp = TimestampMS(1680503171000)
+    expected_events = [
+        EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=0,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.SPEND,
+            event_subtype=HistoryEventSubType.FEE,
+            asset=A_ETH,
+            balance=Balance(FVal('0.006158572854866488')),
+            location_label=user_address,
+            counterparty=CPT_GAS,
+            notes='Burned 0.006158572854866488 ETH for gas',
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=66,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.RECEIVE,
+            event_subtype=HistoryEventSubType.RECEIVE_WRAPPED,
+            asset=Asset('eip155:1/erc20:0xc270b3B858c335B6BA5D5b10e2Da8a09976005ad'),
+            balance=Balance(FVal('195463.032550265720355345')),
+            location_label=user_address,
+            counterparty=CPT_CURVE,
+            notes='Receive 195463.032550265720355345 pax-usdp3CRV-f after depositing in a curve pool',  # noqa: E501
+            address=string_to_evm_address('0x0000000000000000000000000000000000000000'),
+            extra_data={'deposit_events_num': 1},
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=71,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.DEPOSIT,
+            event_subtype=HistoryEventSubType.DEPOSIT_ASSET,
+            asset=A_USDT,
+            balance=Balance(FVal('200000')),
+            location_label=user_address,
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),
+            notes='Deposit 200000 USDT in curve pool 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7',  # noqa: E501
+        ),
+    ]
+    assert events == expected_events
+
+
+@pytest.mark.vcr()
+@pytest.mark.parametrize('ethereum_accounts', [['0xC7BFb2ED20D14407C78cc1FC4a4Abe39f1964964']])
+def test_3pool_remove_liquidity(ethereum_transaction_decoder, ethereum_accounts):
+    """Check that removing liquidity from a curve pool using the 3Pool zap contract is properly decoded."""  # noqa: E501
+    tx_hex = deserialize_evm_tx_hash('0xbf4a445d0452e2f1e046c3ab3d10018c801e2acae89051c98332e9264f36d7f7')  # noqa: E501
+    evmhash = deserialize_evm_tx_hash(tx_hex)
+    user_address = ethereum_accounts[0]
+    events, _ = get_decoded_events_of_transaction(
+        evm_inquirer=ethereum_transaction_decoder.evm_inquirer,
+        database=ethereum_transaction_decoder.database,
+        tx_hash=tx_hex,
+    )
+    timestamp = TimestampMS(1680390095000)
+    expected_events = [
+        EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=0,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.SPEND,
+            event_subtype=HistoryEventSubType.FEE,
+            asset=A_ETH,
+            balance=Balance(FVal('0.007508781310937599')),
+            location_label=user_address,
+            notes='Burned 0.007508781310937599 ETH for gas',
+            counterparty=CPT_GAS,
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=39,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.SPEND,
+            event_subtype=HistoryEventSubType.RETURN_WRAPPED,
+            asset=Asset('eip155:1/erc20:0xe9123CBC5d1EA65301D417193c40A72Ac8D53501'),
+            balance=Balance(FVal('95093.489007941270920107')),
+            location_label=user_address,
+            notes='Return 95093.489007941270920107 3CRVlvUSD3CRV-f',
+            counterparty=CPT_CURVE,
+            extra_data={'withdrawal_events_num': 4},
+            address=string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=49,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=Asset('eip155:1/erc20:0x94A18d9FE00bab617fAD8B49b11e9F1f64Db6b36'),
+            balance=Balance(FVal('56718.010997734097866656')),
+            location_label=user_address,
+            notes='Remove 56718.010997734097866656 lvUSD from 0xe9123CBC5d1EA65301D417193c40A72Ac8D53501 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=50,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=A_DAI,
+            balance=Balance(FVal('14119.166521016875857287')),
+            location_label=user_address,
+            notes='Remove 14119.166521016875857287 DAI from 0xe9123CBC5d1EA65301D417193c40A72Ac8D53501 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=51,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=A_USDC,
+            balance=Balance(FVal('16072.772093')),
+            location_label=user_address,
+            notes='Remove 16072.772093 USDC from 0xe9123CBC5d1EA65301D417193c40A72Ac8D53501 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),
+        ), EvmEvent(
+            event_identifier=evmhash,
+            sequence_index=52,
+            timestamp=timestamp,
+            location=Location.ETHEREUM,
+            event_type=HistoryEventType.WITHDRAWAL,
+            event_subtype=HistoryEventSubType.REMOVE_ASSET,
+            asset=A_USDT,
+            balance=Balance(FVal('8326.299501')),
+            location_label=user_address,
+            notes='Remove 8326.299501 USDT from 0xe9123CBC5d1EA65301D417193c40A72Ac8D53501 curve pool',  # noqa: E501
+            counterparty=CPT_CURVE,
+            address=string_to_evm_address('0xA79828DF1850E8a3A3064576f380D90aECDD3359'),
+        ),
+    ]
+    assert events == expected_events