From b45ffc6ff602f5404518f5465abf2b8b86ad9273 Mon Sep 17 00:00:00 2001 From: Arash Fatahzade Date: Thu, 4 Apr 2024 19:26:47 +0330 Subject: [PATCH] Add weth decoder for all supported evm chains --- docs/changelog.rst | 1 + .../chain/ethereum/modules/weth/__init__.py | 0 .../chain/ethereum/modules/weth/constants.py | 3 - .../chain/ethereum/modules/weth/decoder.py | 32 -- rotkehlchen/chain/evm/decoding/decoder.py | 10 + .../chain/evm/decoding/weth/constants.py | 22 + .../chain/evm/decoding/weth/decoder.py | 59 ++- rotkehlchen/constants/assets.py | 1 + rotkehlchen/data/global.db | Bin 6508544 -> 6524928 bytes rotkehlchen/tests/unit/decoders/test_weth.py | 470 +++++++++++++++++- 10 files changed, 557 insertions(+), 41 deletions(-) delete mode 100644 rotkehlchen/chain/ethereum/modules/weth/__init__.py delete mode 100644 rotkehlchen/chain/ethereum/modules/weth/constants.py delete mode 100644 rotkehlchen/chain/ethereum/modules/weth/decoder.py create mode 100644 rotkehlchen/chain/evm/decoding/weth/constants.py 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 8a361c634cacbabc83609238153d2bb396de6036..8f80b103f2355df4a5b3cec44485a2bbb501e29b 100644 GIT binary patch delta 2595 zcmd^>ZEO_B8OL{Lc4u$*Zgzb(J{udGBeA__V{?8vV{9CJemR$L#GDJ*Ex6%qd=a1~ zJse6=MZqVn*EEgE5n?}5tKyBDwn-3(RDo@Z6Rv!tv=o)*LtP~`3YCaNiD}i!l_2eG z57B&RDOErA!|(QHqr_YTlaQmb!L++JKq$-1?S`vAryceQmF!GO+AJ61Z*ic+ z2S(uQqoh>eIDU)W9^+#?EPS|9gzu(FK1{qG2-vG$CPyh)M>cdVQJi%sg7Js=+qe(s zqATbHP?E7ygd$L3kyc1rq>RPFN1a%w_>wLmC8NadWL;xpOJhT*p|L)g7};50+fsX= zu>pF2v9uhAir#^%w`d2Vs`D&{;2+oqD9sMmDYZJxrO6ez{ylaS;klr0557jWyIB^l z9axs|R1PnY8sRvnU4ZIz-!fK_+6o)eeJj|JNwC)+tg&M;ejdYx={^rw7V*I4yUro) z$_AakR>=~6oE@mNO1H0BqY-SWQ<35-1 z2&1?LPl6PtYZ0EbJHm7e=I3F>A}xeA#ixtzjjUEkE${P9`TDEuk}mod6Rs&MZt{1P zI%qw#1PX7^DrZ+8KhlDvLR_b*wh(bu!XDi zo|ea?Yd{H%KgB=6`*A(Gjox&m3n7$%)_Gb6&&|{B|5mz!eDEJ5MG)@I4Iuer2p=aM zNWSSji(&dWVbW#j72t2MqnN(epxdb%bnX8rT}=C3D zW5(k~+1uqp(#5>UvlqE~(nDIZhP;Ci+Vf9G3XUV8_dF_qQJ@?3~eXpGOul9U`CNQ5|^m@>(eOBF72u zbg?OkFN5}yTpw8OTHW(BoBp(2K!*``7fzhq)NHp*g#W_3NOM@=52JNZ-L$zGaVP)L zdL3g%r)EMCGaWY5?Pj{eOm~{;E;G$$y3O?G%=CIQ{p7jy27Vz=JI}2C9;apSWo+VZ zjM01G8jWs)=SHKeok)9}?(9Qw{-tn4bc)6B)>BG8{L%{Du~j2_$}KwE;vn;CD10s* z72=Oo3kNw@K)NaYKpK*klQ}X;wvs&YsyHF87qKua90b=wv<2b|QKyMXA2}&*W75X< z!s{>%9mS9-L#6(&bhCUis`w9=0{T9yE*g=t=T-r!Z;=ADiCYBk#@M>?gGD`z zO^g_$m+^VVX2urARz@GApYa987wv;Z+m48MsniORe_I`rTC+6(tKo$?+r=8Zf?Yrrf==+@ZBW<^~OueEWR)f@TSB8GY+u>MzBXNFb z#IpXg&2mc4&(G%s?x8unq5jC0^W+3)mMI=p!xsHz~tTu_dlJr_E{yw0iZHdQwfOWptiS)9tj#^J~v> x&nA!R{+aum?oQ>NGOHX=n&dn38F`Of>H59vH5iR4ZTH1vyPV|h7Z#$H^mi3sB=Gs zIs1b^C?wVp`3;yEla>qxBWWJ{0%hMhh_DrJsVVT=UatltoMpnmAj^X|6|3LxuJkgl z3WBg1M!)Bk_DPksc7YgXr(paxSE1Zlq(R-s>@pM`X6azX7HNdXM4L1?aN6s&3lFn{ z97b>RbnySmH7DmzoVG|fO2OG4%7PM$m8tX^wP+K~B;({mvH{vHmIIgi>Rr$`$XuXu zpJvw$u^R-`CZCt^(@^}7r@;36TqEj=sU|Sy*X)*4Y(wl+KXD~blld+D*6|4Y04|Q@ zdF`Lpw`M9Z^GlWj%_s8kU20=9q(CfJaqLDT!iU%Utv%7BavZwY!Btes%v!B_^ib+Cyo1WO4MDrESBu$Kk9a}Lrm-eIC=1IO= zfjygAeY&c<^*B9VPtd37iTZRsNl(^O^i(}fPuFMY8G5FkrDy9i^%wM6dd^_3)EqWzwbKIIM(K2BO z6l?Aoxl$IpD4bp8oDQpvR4-J&6C8oapc9ST(URQJzCN|1TTd}IN#5Gh51*;7+V3vvF+SyIrC(`OlGs&th}ANuJR*0?RnlYW zg0u&k?*x5z^Zr&#QKCXcQVj9arj-(*w`bdA5Lsz&n_LCSG}&1KN?ffU_MNCHwzI<- zPQ^wy-w?;r?i`79DZ(VguMCy