diff --git a/docs/changelog.rst b/docs/changelog.rst index 72a7caabfa..63d0202fc6 100755 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :feature:`7708` rotki now properly decode WETH transactions on all supported EVM chains with native ETH * :feature:`6636` Aave v3 positions and liabilities will now be properly shown in the dashboard. * :feature:`7423` Users will be able to sort address book entries by displayed name and address. diff --git a/rotkehlchen/chain/ethereum/modules/weth/__init__.py b/rotkehlchen/chain/ethereum/modules/weth/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/rotkehlchen/chain/ethereum/modules/weth/constants.py b/rotkehlchen/chain/ethereum/modules/weth/constants.py deleted file mode 100644 index 3822624cd2..0000000000 --- a/rotkehlchen/chain/ethereum/modules/weth/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -from typing import Final - -CPT_WETH: Final = 'weth' diff --git a/rotkehlchen/chain/ethereum/modules/weth/decoder.py b/rotkehlchen/chain/ethereum/modules/weth/decoder.py deleted file mode 100644 index 96de05b509..0000000000 --- a/rotkehlchen/chain/ethereum/modules/weth/decoder.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import TYPE_CHECKING - -from rotkehlchen.chain.ethereum.modules.weth.constants import CPT_WETH -from rotkehlchen.chain.evm.decoding.types import CounterpartyDetails -from rotkehlchen.chain.evm.decoding.weth.decoder import WethDecoderBase -from rotkehlchen.constants.assets import A_ETH, A_WETH - -if TYPE_CHECKING: - from rotkehlchen.chain.ethereum.node_inquirer import EthereumInquirer - from rotkehlchen.chain.evm.decoding.base import BaseDecoderTools - from rotkehlchen.user_messages import MessagesAggregator - - -class WethDecoder(WethDecoderBase): - def __init__( - self, - ethereum_inquirer: 'EthereumInquirer', - base_tools: 'BaseDecoderTools', - msg_aggregator: 'MessagesAggregator', - ) -> None: - super().__init__( - evm_inquirer=ethereum_inquirer, - base_tools=base_tools, - msg_aggregator=msg_aggregator, - base_asset=A_ETH.resolve_to_crypto_asset(), - wrapped_token=A_WETH.resolve_to_evm_token(), - counterparty=CPT_WETH, - ) - - @staticmethod - def counterparties() -> tuple[CounterpartyDetails, ...]: - return (CounterpartyDetails(identifier=CPT_WETH, label='WETH', image='weth.svg'),) diff --git a/rotkehlchen/chain/evm/decoding/decoder.py b/rotkehlchen/chain/evm/decoding/decoder.py index 9ea26b9416..46b801ec93 100644 --- a/rotkehlchen/chain/evm/decoding/decoder.py +++ b/rotkehlchen/chain/evm/decoding/decoder.py @@ -21,6 +21,8 @@ from rotkehlchen.chain.evm.decoding.safe.decoder import SafemultisigDecoder from rotkehlchen.chain.evm.decoding.socket_bridge.decoder import SocketBridgeDecoder from rotkehlchen.chain.evm.decoding.types import CounterpartyDetails +from rotkehlchen.chain.evm.decoding.weth.constants import CHAINS_WITHOUT_NATIVE_ETH +from rotkehlchen.chain.evm.decoding.weth.decoder import WethDecoder from rotkehlchen.chain.evm.structures import EvmTxReceipt, EvmTxReceiptLog from rotkehlchen.constants import ZERO from rotkehlchen.db.constants import HISTORY_MAPPING_STATE_DECODED @@ -184,6 +186,14 @@ def _add_builtin_decoders(self, rules: DecodingRules) -> None: self._add_single_decoder(class_name='Oneinchv5', decoder_class=Oneinchv5Decoder, rules=rules) # noqa: E501 self._add_single_decoder(class_name='SocketBridgeDecoder', decoder_class=SocketBridgeDecoder, rules=rules) # noqa: E501 + # Excluding Gnosis and Polygon PoS because they dont have ETH as native token + if self.evm_inquirer.chain_id not in CHAINS_WITHOUT_NATIVE_ETH: + self._add_single_decoder( + class_name='Weth', + decoder_class=WethDecoder, + rules=rules, + ) + def _add_single_decoder( self, class_name: str, diff --git a/rotkehlchen/chain/evm/decoding/weth/constants.py b/rotkehlchen/chain/evm/decoding/weth/constants.py new file mode 100644 index 0000000000..f6cb5c6c91 --- /dev/null +++ b/rotkehlchen/chain/evm/decoding/weth/constants.py @@ -0,0 +1,22 @@ +import typing +from typing import Final + +from rotkehlchen.constants.assets import A_WETH, A_WETH_ARB, A_WETH_BASE, A_WETH_OPT, A_WETH_SCROLL +from rotkehlchen.types import SUPPORTED_CHAIN_IDS, ChainID + +CPT_WETH: Final = 'weth' +CHAINS_WITHOUT_NATIVE_ETH: Final = {ChainID.GNOSIS, ChainID.POLYGON_POS} +CHAIN_ID_TO_WETH_MAPPING: Final = { + # A mapping from chain id to WETH asset for every supported evm chain except Gnosis + ChainID.ETHEREUM: A_WETH, + ChainID.ARBITRUM_ONE: A_WETH_ARB, + ChainID.OPTIMISM: A_WETH_OPT, + ChainID.SCROLL: A_WETH_SCROLL, + ChainID.BASE: A_WETH_BASE, +} + +# Make sure the CHAIN_ID_TO_WETH_MAPPING has all supported EVM chains with native ETH +assert ( + set(CHAIN_ID_TO_WETH_MAPPING.keys()) == + set(typing.get_args(SUPPORTED_CHAIN_IDS)) - CHAINS_WITHOUT_NATIVE_ETH +) diff --git a/rotkehlchen/chain/evm/decoding/weth/decoder.py b/rotkehlchen/chain/evm/decoding/weth/decoder.py index 50114f746b..b9b942d0e4 100644 --- a/rotkehlchen/chain/evm/decoding/weth/decoder.py +++ b/rotkehlchen/chain/evm/decoding/weth/decoder.py @@ -3,7 +3,6 @@ from rotkehlchen.accounting.structures.balance import Balance from rotkehlchen.assets.asset import CryptoAsset, EvmToken -from rotkehlchen.chain.ethereum.modules.weth.constants import CPT_WETH from rotkehlchen.chain.ethereum.utils import asset_normalized_value from rotkehlchen.chain.evm.decoding.interfaces import DecoderInterface from rotkehlchen.chain.evm.decoding.structures import ( @@ -13,8 +12,10 @@ ) from rotkehlchen.chain.evm.decoding.types import CounterpartyDetails from rotkehlchen.chain.evm.decoding.utils import maybe_reshuffle_events +from rotkehlchen.chain.evm.decoding.weth.constants import CHAIN_ID_TO_WETH_MAPPING, CPT_WETH +from rotkehlchen.constants.assets import A_ETH, A_WETH_SCROLL from rotkehlchen.history.events.structures.types import HistoryEventSubType, HistoryEventType -from rotkehlchen.types import ChecksumEvmAddress +from rotkehlchen.types import ChainID, ChecksumEvmAddress from rotkehlchen.utils.misc import hex_or_bytes_to_address, hex_or_bytes_to_int if TYPE_CHECKING: @@ -25,6 +26,8 @@ WETH_DEPOSIT_TOPIC = b'\xe1\xff\xfc\xc4\x92=\x04\xb5Y\xf4\xd2\x9a\x8b\xfcl\xda\x04\xeb[\r DecodingOutput: - if context.tx_log.topics[0] == WETH_DEPOSIT_TOPIC: + input_data = context.transaction.input_data + + # WETH on Arbitrum is deployed as proxy, we need to check the method + # On the scroll, the contract is different, So we need to check the method. + if context.tx_log.topics[0] == WETH_DEPOSIT_TOPIC or ( + self.evm_inquirer.chain_id in {ChainID.ARBITRUM_ONE, ChainID.SCROLL} and + input_data.startswith(WETH_DEPOSIT_METHOD) + ): return self._decode_deposit_event(context) - if context.tx_log.topics[0] == WETH_WITHDRAW_TOPIC: + if context.tx_log.topics[0] == WETH_WITHDRAW_TOPIC or ( + self.evm_inquirer.chain_id in {ChainID.ARBITRUM_ONE, ChainID.SCROLL} and + input_data.startswith(WETH_WITHDRAW_METHOD) + ): return self._decode_withdrawal_event(context) return DEFAULT_DECODING_OUTPUT @@ -105,6 +118,22 @@ def _decode_withdrawal_event(self, context: DecoderContext) -> DecodingOutput: asset=self.base_asset, ) + # This only for withdraw tx on Scroll, Because the unwrap method on WETH on scroll + # generates an additional log (Transfer) which not exists on other chains. + # So because of this additional log, this method will be called twice instead of one, + # therefore we need to skip the second call to avoid creating extra events. + # Check this for example: + # A withdraw on Scroll: https://scrollscan.com/tx/0x88f49633073a7667f93eb888ec2151c26f449cc10afca565a15f8df68ee20f82#eventlog + # A withdraw on Ethereum: https://etherscan.io/tx/0xe5d6fa9c60f595a624e1dd71cb3b28ba9260e90cf3097c5b285b901a97b6246d#eventlog + if self.evm_inquirer.chain_id == ChainID.SCROLL: + for event in context.decoded_events: + if ( + event.counterparty == CPT_WETH and + event.asset == A_WETH_SCROLL and + event.balance.amount == withdrawn_amount + ): + return DEFAULT_DECODING_OUTPUT # we have already decoded this event + in_event = None for event in context.decoded_events: if ( @@ -149,3 +178,25 @@ def addresses_to_decoders(self) -> dict[ChecksumEvmAddress, tuple[Any, ...]]: @staticmethod def counterparties() -> tuple[CounterpartyDetails, ...]: return (CounterpartyDetails(identifier=CPT_WETH, label='WETH', image='weth.svg'),) + + +class WethDecoder(WethDecoderBase): + """ + Weth Decoder for all supported EVM chains except Gnosis and Polygon Pos + because of different counterparty and not having ETH as native token. + """ + + def __init__( + self, + evm_inquirer: 'EvmNodeInquirer', + base_tools: 'BaseDecoderTools', + msg_aggregator: 'MessagesAggregator', + ) -> None: + super().__init__( + evm_inquirer=evm_inquirer, + base_tools=base_tools, + msg_aggregator=msg_aggregator, + base_asset=A_ETH.resolve_to_crypto_asset(), + wrapped_token=CHAIN_ID_TO_WETH_MAPPING[evm_inquirer.chain_id].resolve_to_evm_token(), + counterparty=CPT_WETH, + ) diff --git a/rotkehlchen/constants/assets.py b/rotkehlchen/constants/assets.py index 6c607af74b..4185affed0 100644 --- a/rotkehlchen/constants/assets.py +++ b/rotkehlchen/constants/assets.py @@ -277,5 +277,6 @@ A_WETH_ARB = Asset('eip155:42161/erc20:0x82af49447d8a07e3bd95bd0d56f35241523fbab1') A_WETH_BASE = Asset('eip155:8453/erc20:0x4200000000000000000000000000000000000006') A_WETH_POLYGON = Asset('eip155:137/erc20:0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619') +A_WETH_SCROLL = Asset('eip155:534352/erc20:0x5300000000000000000000000000000000000004') CONSTANT_ASSETS = {var_data for var_name, var_data in locals().items() if var_name.startswith('A_')} # noqa: E501 diff --git a/rotkehlchen/data/global.db b/rotkehlchen/data/global.db index 8a361c634c..8f80b103f2 100644 Binary files a/rotkehlchen/data/global.db and b/rotkehlchen/data/global.db differ diff --git a/rotkehlchen/tests/unit/decoders/test_weth.py b/rotkehlchen/tests/unit/decoders/test_weth.py index ced1b8863f..719358c512 100644 --- a/rotkehlchen/tests/unit/decoders/test_weth.py +++ b/rotkehlchen/tests/unit/decoders/test_weth.py @@ -2,14 +2,24 @@ from rotkehlchen.accounting.structures.balance import Balance from rotkehlchen.assets.utils import get_or_create_evm_token -from rotkehlchen.chain.ethereum.modules.weth.constants import CPT_WETH from rotkehlchen.chain.evm.constants import ZERO_ADDRESS from rotkehlchen.chain.evm.decoding.constants import CPT_GAS from rotkehlchen.chain.evm.decoding.uniswap.constants import CPT_UNISWAP_V3 +from rotkehlchen.chain.evm.decoding.weth.constants import CPT_WETH from rotkehlchen.chain.evm.types import string_to_evm_address from rotkehlchen.chain.gnosis.modules.wxdai.constants import CPT_WXDAI from rotkehlchen.constants import ONE, ZERO -from rotkehlchen.constants.assets import A_ETH, A_USDC, A_WETH, A_WXDAI, A_XDAI +from rotkehlchen.constants.assets import ( + A_ETH, + A_USDC, + A_WETH, + A_WETH_ARB, + A_WETH_BASE, + A_WETH_OPT, + A_WETH_SCROLL, + A_WXDAI, + A_XDAI, +) from rotkehlchen.fval import FVal from rotkehlchen.history.events.structures.evm_event import EvmEvent from rotkehlchen.history.events.structures.types import HistoryEventSubType, HistoryEventType @@ -465,3 +475,459 @@ def test_wxdai_wrap(database, gnosis_inquirer, gnosis_accounts): ] assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('arbitrum_one_accounts', [['0xBE6660FBE96B61B72Bf35FFaB40eB2CA886A7f85']]) +def test_weth_withdraw_arbitrum_one(database, arbitrum_one_inquirer): + evmhash = deserialize_evm_tx_hash('0xc19c7e1e0af7819b1922a287d034540e8f8dba4e065317d6483d48ac27e727e9') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=arbitrum_one_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712238368000) + amount, gas_fees = '0.00052', '0.00000108547' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.ARBITRUM_ONE, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0xBE6660FBE96B61B72Bf35FFaB40eB2CA886A7f85', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.ARBITRUM_ONE, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.RETURN_WRAPPED, + asset=A_WETH_ARB, + balance=Balance(amount=FVal(amount)), + location_label='0xBE6660FBE96B61B72Bf35FFaB40eB2CA886A7f85', + notes=f'Unwrap {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.ARBITRUM_ONE, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.NONE, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0xBE6660FBE96B61B72Bf35FFaB40eB2CA886A7f85', + notes=f'Receive {amount} ETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('arbitrum_one_accounts', [['0x7aBAee8F04EFd689961115f7A28bAA2E73Be6703']]) +def test_weth_deposit_arbitrum_one(database, arbitrum_one_inquirer): + evmhash = deserialize_evm_tx_hash('0x57cc837c6f3d84c8fa3db8a7405f7244f11d32152159edf5ba79f5a7c34919b8') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=arbitrum_one_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712328694000) + amount, gas_fees = '0.007767825959188763', '0.000001382891214' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.ARBITRUM_ONE, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0x7aBAee8F04EFd689961115f7A28bAA2E73Be6703', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.ARBITRUM_ONE, + event_type=HistoryEventType.DEPOSIT, + event_subtype=HistoryEventSubType.DEPOSIT_ASSET, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0x7aBAee8F04EFd689961115f7A28bAA2E73Be6703', + notes=f'Wrap {amount} ETH in WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.ARBITRUM_ONE, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.RECEIVE_WRAPPED, + asset=A_WETH_ARB, + balance=Balance(amount=FVal(amount)), + location_label='0x0000000000000000000000000000000000000000', + notes=f'Receive {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('optimism_accounts', [['0x81aa5101D4c376cd6DC031EA62D7b64A9BAE10a0']]) +def test_weth_withdraw_optimism(database, optimism_inquirer): + evmhash = deserialize_evm_tx_hash('0x4a6b47e1f622a8ad059bd0723c53f2c71f12e7b105d2ef2ff4dff07ac1f185c0') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=optimism_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712240095000) + amount, gas_fees = '0.000518962654328944', '0.000001897927938075' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.OPTIMISM, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0x81aa5101D4c376cd6DC031EA62D7b64A9BAE10a0', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.OPTIMISM, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.RETURN_WRAPPED, + asset=A_WETH_OPT, + balance=Balance(amount=FVal(amount)), + location_label='0x81aa5101D4c376cd6DC031EA62D7b64A9BAE10a0', + notes=f'Unwrap {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.OPTIMISM, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.NONE, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0x81aa5101D4c376cd6DC031EA62D7b64A9BAE10a0', + notes=f'Receive {amount} ETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('optimism_accounts', [['0xD6f30247e6a8B8656a8B02Ea37247f5eb939c626']]) +def test_weth_deposit_optimism(database, optimism_inquirer): + evmhash = deserialize_evm_tx_hash('0x42074e2228be1716f84888f1993fa62443f591945b21dfbf159a64ae467990c4') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=optimism_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712241853000) + amount, gas_fees = '0.0345', '0.000002820767318933' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.OPTIMISM, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0xD6f30247e6a8B8656a8B02Ea37247f5eb939c626', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.OPTIMISM, + event_type=HistoryEventType.DEPOSIT, + event_subtype=HistoryEventSubType.DEPOSIT_ASSET, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0xD6f30247e6a8B8656a8B02Ea37247f5eb939c626', + notes=f'Wrap {amount} ETH in WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.OPTIMISM, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.RECEIVE_WRAPPED, + asset=A_WETH_OPT, + balance=Balance(amount=FVal(amount)), + location_label='0xD6f30247e6a8B8656a8B02Ea37247f5eb939c626', + notes=f'Receive {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('scroll_accounts', [['0x6247666Ea4C80083035214780978E9EBa4AA6Cf4']]) +def test_weth_withdraw_scroll(database, scroll_inquirer): + evmhash = deserialize_evm_tx_hash('0x88f49633073a7667f93eb888ec2151c26f449cc10afca565a15f8df68ee20f82') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=scroll_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712239879000) + amount, gas_fees = '0.00211824', '0.0000204875' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.SCROLL, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0x6247666Ea4C80083035214780978E9EBa4AA6Cf4', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.SCROLL, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.RETURN_WRAPPED, + asset=A_WETH_SCROLL, + balance=Balance(amount=FVal(amount)), + location_label='0x6247666Ea4C80083035214780978E9EBa4AA6Cf4', + notes=f'Unwrap {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x5300000000000000000000000000000000000004'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.SCROLL, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.NONE, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0x6247666Ea4C80083035214780978E9EBa4AA6Cf4', + notes=f'Receive {amount} ETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x5300000000000000000000000000000000000004'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('scroll_accounts', [['0xdFd21F8aA81c5787160F9a4B39357F5FE1c743DC']]) +def test_weth_deposit_scroll(database, scroll_inquirer): + evmhash = deserialize_evm_tx_hash('0x1fa6d87801891fcea66a9be2d4fce1c52569c5ce30579fbe7de37eb05bd247f8') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=scroll_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712239897000) + amount, gas_fees = '0.135', '0.0000285406' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.SCROLL, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0xdFd21F8aA81c5787160F9a4B39357F5FE1c743DC', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.SCROLL, + event_type=HistoryEventType.DEPOSIT, + event_subtype=HistoryEventSubType.DEPOSIT_ASSET, + asset=A_ETH, + balance=Balance(amount=FVal(0.135)), + location_label='0xdFd21F8aA81c5787160F9a4B39357F5FE1c743DC', + notes=f'Wrap {amount} ETH in WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x5300000000000000000000000000000000000004'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.SCROLL, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.RECEIVE_WRAPPED, + asset=A_WETH_SCROLL, + balance=Balance(amount=FVal(0.135)), + location_label='0x0000000000000000000000000000000000000000', + notes=f'Receive {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x5300000000000000000000000000000000000004'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('base_accounts', [['0x44f29ebE386c409376C66ad268F9Ae595c8C3e76']]) +def test_weth_withdraw_base(database, base_inquirer): + evmhash = deserialize_evm_tx_hash('0x8d54608c2f684d880ad40a16cf9b82525c51520798ae8875d543d3338327ddad') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=base_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712239837000) + amount, gas_fees = '0.00022448658511341', '0.000000533995613184' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.BASE, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0x44f29ebE386c409376C66ad268F9Ae595c8C3e76', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.BASE, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.RETURN_WRAPPED, + asset=A_WETH_BASE, + balance=Balance(amount=FVal(amount)), + location_label='0x44f29ebE386c409376C66ad268F9Ae595c8C3e76', + notes=f'Unwrap {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.BASE, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.NONE, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0x44f29ebE386c409376C66ad268F9Ae595c8C3e76', + notes=f'Receive {amount} ETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + ] + assert events == expected_events + + +@pytest.mark.vcr() +@pytest.mark.parametrize('base_accounts', [['0xf396e7dbb20489D47F2daBfDA013163223B892a0']]) +def test_weth_deposit_base(database, base_inquirer): + evmhash = deserialize_evm_tx_hash('0x0d418e4a858ca5faf00c36b685561ca0fdac52ebd10364bf2cb6d7b5969e84e5') # noqa: E501 + events, _ = get_decoded_events_of_transaction( + evm_inquirer=base_inquirer, + database=database, + tx_hash=evmhash, + ) + timestamp = TimestampMS(1712239899000) + amount, gas_fees = '1.2', '0.000000775794575663' + expected_events = [ + EvmEvent( + tx_hash=evmhash, + sequence_index=0, + timestamp=timestamp, + location=Location.BASE, + event_type=HistoryEventType.SPEND, + event_subtype=HistoryEventSubType.FEE, + asset=A_ETH, + balance=Balance(amount=FVal(gas_fees)), + location_label='0xf396e7dbb20489D47F2daBfDA013163223B892a0', + notes=f'Burned {gas_fees} ETH for gas', + counterparty=CPT_GAS, + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=1, + timestamp=timestamp, + location=Location.BASE, + event_type=HistoryEventType.DEPOSIT, + event_subtype=HistoryEventSubType.DEPOSIT_ASSET, + asset=A_ETH, + balance=Balance(amount=FVal(amount)), + location_label='0xf396e7dbb20489D47F2daBfDA013163223B892a0', + notes=f'Wrap {amount} ETH in WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + EvmEvent( + tx_hash=evmhash, + sequence_index=2, + timestamp=timestamp, + location=Location.BASE, + event_type=HistoryEventType.RECEIVE, + event_subtype=HistoryEventSubType.RECEIVE_WRAPPED, + asset=A_WETH_BASE, + balance=Balance(amount=FVal(amount)), + location_label='0xf396e7dbb20489D47F2daBfDA013163223B892a0', + notes=f'Receive {amount} WETH', + counterparty=CPT_WETH, + address=string_to_evm_address('0x4200000000000000000000000000000000000006'), + ), + ] + assert events == expected_events