def test_get_trade_with_1_token_pool( rotkehlchen_api_server, ethereum_accounts, # pylint: disable=unused-argument rotki_premium_credentials, # pylint: disable=unused-argument start_with_valid_premium, # pylint: disable=unused-argument ): """ Test the special case of a swap within an 1 token pool. This can probably happen if the controller has since removed tokens from the pool. """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen setup = setup_balances( rotki, ethereum_accounts=ethereum_accounts, btc_accounts=None, original_queries=['zerion', 'logs', 'blocknobytime'], ) with ExitStack() as stack: # patch ethereum/etherscan to not autodetect tokens setup.enter_ethereum_patches(stack) response = requests.get( api_url_for(rotkehlchen_api_server, 'balancertradeshistoryresource'), json={ 'from_timestamp': 1621358338, 'to_timestamp': 1621358340, }, ) result = assert_proper_response_with_result(response) db_trades = rotki.data.db.get_amm_swaps() assert len(db_trades) == 29 address_trades = result[BALANCER_TEST_ADDR4] assert len(address_trades) == 1 assert address_trades[0] == AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_WBTC, amount=AssetAmount(FVal('0.205421420618533148')), rate=Price(FVal('0.07606382992071615428519015532')), trade_index=0, swaps=[ AMMSwap( tx_hash='0x4f9e0d8aa660a5d3db276a1ade038f7027f29838dd22d5276571d2e4ea7131ae', # noqa: E501 log_index=84, address=string_to_ethereum_address(BALANCER_TEST_ADDR4), # noqa: E501 from_address=string_to_ethereum_address('0xFD3dFB524B2dA40c8a6D703c62BE36b5D8540626'), # noqa: E501 to_address=string_to_ethereum_address('0x582818356331877553F3E9Cf9557b48e5DdbD54a'), # noqa: E501 timestamp=Timestamp(1621358339), location=Location.BALANCER, token0=A_WBTC, token1=A_WETH, amount0_in=AssetAmount(FVal('0.01562514')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('0.205421420618533148')), ), ], ).serialize()
def get_balancer_test_addr2_expected_trades(): """In a function since the new(unknown) assets needs to have been loaded in the DB""" A_WCRES = EthereumToken.initialize( # noqa: N806 address=string_to_ethereum_address( '0xa0afAA285Ce85974c3C881256cB7F225e3A1178a'), decimals=18, symbol='wCRES', ) return [ AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_AAVE, amount=AssetAmount(FVal('1.616934038985744521')), rate=Price(FVal('6.963972908793392530935439799')), trade_index=1, swaps=[ AMMSwap( tx_hash= '0x3c457da9b541ae39a7dc781ab04a03938b98b5649512aec2a2d32635c9bbf589', # noqa: E501 log_index=24, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x7c90a3cd7Ec80dd2F633ed562480AbbEEd3bE546' ), # noqa: E501 timestamp=Timestamp(1607008178), location=Location.BALANCER, token0=A_AAVE, token1=A_WETH, amount0_in=AssetAmount(FVal('11.260284842802604032')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('1.616934038985744521')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_AAVE, quote_asset=A_WETH, amount=AssetAmount(FVal('11.260286362820602094')), rate=Price(FVal('0.1416068599966922676173010716')), trade_index=0, swaps=[ AMMSwap( tx_hash= '0x3c457da9b541ae39a7dc781ab04a03938b98b5649512aec2a2d32635c9bbf589', # noqa: E501 log_index=18, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x70985E557aE0CD6dC88189a532e54FbC61927BAd' ), # noqa: E501 timestamp=Timestamp(1607008178), location=Location.BALANCER, token0=A_WETH, token1=A_AAVE, amount0_in=AssetAmount(FVal('1.594533794502600192')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('11.260286362820602094')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_SYN, amount=AssetAmount(FVal('1.352902561458047718')), rate=Price(FVal('724.4303350385182691258363763')), trade_index=0, swaps=[ AMMSwap( tx_hash= '0x5e235216cb03e4eb234014f5ccf3efbfddd40c4576424e2a8204f1d12b96ed35', # noqa: E501 log_index=143, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x8982E9bBf7AC6A49c434aD81D2fF8e16895318e5' ), # noqa: E501 timestamp=Timestamp(1607008218), location=Location.BALANCER, token0=A_SYN, token1=A_WETH, amount0_in=AssetAmount(FVal('980.08365587152306176')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('1.352902561458047718')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_WCRES, amount=AssetAmount(FVal('0.205709519074945018')), rate=Price(FVal('232.7409943164679514496089589')), trade_index=0, swaps=[ AMMSwap( tx_hash= '0xf54be824b4619777f1db0e3da91b0cd52f6dba730c95a75644e2b085e6ab9824', # noqa: E501 log_index=300, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x10996eC4f3E7A1b314EbD966Fa8b1ad0fE0f8307' ), # noqa: E501 timestamp=Timestamp(1607009877), location=Location.BALANCER, token0=A_WCRES, token1=A_WETH, amount0_in=AssetAmount(FVal('47.87703800986513408')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('0.205709519074945018')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_API3, quote_asset=A_WETH, amount=AssetAmount(FVal('295.881648100500428692')), rate=Price(FVal('0.003346787723157288562491614498')), trade_index=0, swaps=[ AMMSwap( tx_hash= '0xfed4e15051e3ce4dc0d2816f719701e5920e40bf41614b5feaa3c5a6a0186c03', # noqa: E501 log_index=22, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x997c0fc9578a8194EFDdE2E0cD7aa6A69cFCD7c1' ), # noqa: E501 timestamp=Timestamp(1607010888), location=Location.BALANCER, token0=A_WETH, token1=A_API3, amount0_in=AssetAmount(FVal('0.990253067370299904')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('295.881648100500428692')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_MFT, amount=AssetAmount(FVal('0.686544199299304057')), rate=Price(FVal('243775.0324093115004367119900')), trade_index=0, swaps=[ AMMSwap( tx_hash= '0xf0147c4b81098676c08ae20ae5bf8f8b60d0ad79eec484f3f93ac6ab49a3c51c', # noqa: E501 log_index=97, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x2Eb6CfbFFC8785Cd0D9f2d233d0a617bF4269eeF' ), # noqa: E501 timestamp=Timestamp(1607015059), location=Location.BALANCER, token0=A_MFT, token1=A_WETH, amount0_in=AssetAmount(FVal('167362.334434612660404224')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('0.686544199299304057')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_WETH, quote_asset=A_AAVE, amount=AssetAmount(FVal('3.055412574642681758')), rate=Price(FVal('6.916116208273240607778771150')), trade_index=1, swaps=[ AMMSwap( tx_hash= '0x67c0e9a0fdd002d0b9d1cca0c8e4ca4d30435bbf57bbf0091396275efaea414b', # noqa: E501 log_index=37, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x0E552307659E70bF61f918f96AA880Cdec40d7E2' ), # noqa: E501 timestamp=Timestamp(1607015339), location=Location.BALANCER, token0=A_AAVE, token1=A_WETH, amount0_in=AssetAmount(FVal('21.131588430448123904')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('3.055412574642681758')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_AAVE, quote_asset=A_WETH, amount=AssetAmount(FVal('21.131588567541018817')), rate=Price(FVal('0.1435213742524287826717337545')), trade_index=0, swaps=[ AMMSwap( tx_hash= '0x67c0e9a0fdd002d0b9d1cca0c8e4ca4d30435bbf57bbf0091396275efaea414b', # noqa: E501 log_index=31, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x7c90a3cd7Ec80dd2F633ed562480AbbEEd3bE546' ), # noqa: E501 timestamp=Timestamp(1607015339), location=Location.BALANCER, token0=A_WETH, token1=A_AAVE, amount0_in=AssetAmount(FVal('3.0328346313504')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('21.131588567541018817')), ), ], ), ]
from rotkehlchen.chain.ethereum.trades import AMMSwap, AMMTrade from rotkehlchen.constants.misc import ZERO from rotkehlchen.fval import FVal from rotkehlchen.serialization.deserialize import deserialize_ethereum_address from rotkehlchen.tests.api.test_balancer import BALANCER_TEST_ADDR2_EXPECTED_TRADES from rotkehlchen.typing import AssetAmount, Location, Price, Timestamp, TradeType TEST_SWAPS_TX_1 = [ AMMSwap( tx_hash='0x1b0d3525964d8e5fbcc0dcdeebcced4bec9017f648e97c3c9761fda1ca6e7b22', log_index=254, address=deserialize_ethereum_address('0x8e670b4d6651C4051e65B21AA4a575F3f99b8B83'), from_address=deserialize_ethereum_address('0x65003947dC16956AfC4400008606001500940000'), to_address=deserialize_ethereum_address('0x7860E28ebFB8Ae052Bfe279c07aC5d94c9cD2937'), timestamp=Timestamp(1614094145), location=Location.BALANCER, token0=EthereumToken('USDC'), token1=EthereumToken('AMPL'), amount0_in=AssetAmount(FVal('12401.224639')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('14285.153512382')), ), ] TEST_SWAPS_TX_2 = [ AMMSwap( tx_hash='0x1b0d3525964d8e5fbcc0dcdeebcced4bec9017f648e97c3c9761fda1ca6e7b22', log_index=254, address=deserialize_ethereum_address('0x8e670b4d6651C4051e65B21AA4a575F3f99b8B83'), from_address=deserialize_ethereum_address('0x65003947dC16956AfC4400008606001500940000'), to_address=deserialize_ethereum_address('0x7860E28ebFB8Ae052Bfe279c07aC5d94c9cD2937'),
def _get_trades_graph_for_address( self, address: ChecksumEthAddress, start_ts: Timestamp, end_ts: Timestamp, ) -> List[AMMTrade]: """Get the address' trades data querying the Uniswap subgraph Each trade (swap) instantiates an <AMMTrade>. The trade pair (i.e. BASE_QUOTE) is determined by `reserve0_reserve1`. Translated to Uniswap lingo: Trade type BUY: - `asset1In` (QUOTE, reserve1) is gt 0. - `asset0Out` (BASE, reserve0) is gt 0. Trade type SELL: - `asset0In` (BASE, reserve0) is gt 0. - `asset1Out` (QUOTE, reserve1) is gt 0. """ trades: List[AMMTrade] = [] param_types = { '$limit': 'Int!', '$offset': 'Int!', '$address': 'Bytes!', '$start_ts': 'BigInt!', '$end_ts': 'BigInt!', } param_values = { 'limit': GRAPH_QUERY_LIMIT, 'offset': 0, 'address': address.lower(), 'start_ts': str(start_ts), 'end_ts': str(end_ts), } querystr = format_query_indentation(SWAPS_QUERY.format()) while True: result = self.graph.query( # type: ignore # caller already checks querystr=querystr, param_types=param_types, param_values=param_values, ) result_data = result['swaps'] for entry in result_data: swaps = [] for swap in entry['transaction']['swaps']: timestamp = swap['timestamp'] swap_token0 = swap['pair']['token0'] swap_token1 = swap['pair']['token1'] token0 = get_ethereum_token( symbol=swap_token0['symbol'], ethereum_address=to_checksum_address(swap_token0['id']), name=swap_token0['name'], decimals=swap_token0['decimals'], ) token1 = get_ethereum_token( symbol=swap_token1['symbol'], ethereum_address=to_checksum_address(swap_token1['id']), name=swap_token1['name'], decimals=int(swap_token1['decimals']), ) amount0_in = FVal(swap['amount0In']) amount1_in = FVal(swap['amount1In']) amount0_out = FVal(swap['amount0Out']) amount1_out = FVal(swap['amount1Out']) swaps.append(AMMSwap( tx_hash=swap['id'].split('-')[0], log_index=int(swap['logIndex']), address=address, from_address=to_checksum_address(swap['sender']), to_address=to_checksum_address(swap['to']), timestamp=Timestamp(int(timestamp)), location=Location.UNISWAP, token0=token0, token1=token1, amount0_in=AssetAmount(amount0_in), amount1_in=AssetAmount(amount1_in), amount0_out=AssetAmount(amount0_out), amount1_out=AssetAmount(amount1_out), )) # Now that we got all swaps for a transaction, create the trade object trades.extend(self._tx_swaps_to_trades(swaps)) # Check whether an extra request is needed if len(result_data) < GRAPH_QUERY_LIMIT: break # Update pagination step param_values = { **param_values, 'offset': param_values['offset'] + GRAPH_QUERY_LIMIT, # type: ignore } return trades
def test_query_trades_including_ammswaps(data_dir, username): """Test that querying trades succesfully queries from both the trades and ammswaps""" msg_aggregator = MessagesAggregator() data = DataHandler(data_dir, msg_aggregator) data.unlock(username, '123', create_new=True) trades = [ Trade( timestamp=1, location=Location.EXTERNAL, base_asset=A_ETH, quote_asset=A_USDC, trade_type=TradeType.BUY, amount=FVal(1), rate=Price(FVal(1.5)), fee=None, fee_currency=None, link='', notes=None, ), Trade( timestamp=2, location=Location.KRAKEN, base_asset=A_BTC, quote_asset=A_EUR, trade_type=TradeType.SELL, amount=FVal(1), rate=Price(FVal(1.5)), fee=None, fee_currency=None, link='', notes=None, ), Trade( timestamp=3, location=Location.POLONIEX, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=FVal(2), rate=Price(FVal(1.5)), fee=None, fee_currency=None, link='', notes=None, ), ] swaps = [ AMMSwap( tx_hash='0x1', log_index=1, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=2, location=Location.UNISWAP, token0=A_DAI, token1=A_USDC, amount0_in=FVal(1), amount1_in=ZERO, amount0_out=ZERO, amount1_out=FVal(2), ), AMMSwap( tx_hash='0x2', log_index=2, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=3, location=Location.BALANCER, token0=A_UNI, token1=A_USDC, amount0_in=FVal(1), amount1_in=ZERO, amount0_out=ZERO, amount1_out=FVal(1.5), ), AMMSwap( tx_hash='0x2', log_index=3, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=3, location=Location.BALANCER, token0=A_USDC, token1=A_DAI, amount0_in=FVal(1.5), amount1_in=ZERO, amount0_out=ZERO, amount1_out=FVal(3.5), ), AMMSwap( tx_hash='0x3', log_index=1, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=4, location=Location.SUSHISWAP, token0=A_UNI, token1=A_DAI, amount0_in=ZERO, amount1_in=FVal(2), amount0_out=FVal(5), amount1_out=ZERO, ), AMMSwap( tx_hash='0x3', log_index=2, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=4, location=Location.SUSHISWAP, token0=A_DAI, token1=A_USDC, amount0_in=FVal(5), amount1_in=ZERO, amount0_out=ZERO, amount1_out=FVal(4.95), ), AMMSwap( tx_hash='0x3', log_index=3, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=4, location=Location.SUSHISWAP, token0=A_GNO, token1=A_USDC, amount0_in=ZERO, amount1_in=FVal(4.95), amount0_out=FVal(8.2), amount1_out=ZERO, ), AMMSwap( tx_hash='0x4', log_index=5, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=11, location=Location.UNISWAP, token0=A_GNO, token1=A_USDC, amount0_in=FVal(5), amount1_in=FVal(6), amount0_out=ZERO, amount1_out=FVal(4.95), ), AMMSwap( tx_hash='0x4', log_index=10, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=11, location=Location.UNISWAP, token0=A_USDC, token1=A_UNI, amount0_in=FVal(4.95), amount1_in=ZERO, amount0_out=ZERO, amount1_out=FVal(5.4), ), AMMSwap( tx_hash='0x5', log_index=1, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=14, location=Location.SUSHISWAP, token0=A_UNI, token1=A_USDC, amount0_in=FVal(4.5), amount1_in=FVal(6), amount0_out=ZERO, amount1_out=FVal(3.2), ), AMMSwap( tx_hash='0x5', log_index=3, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=14, location=Location.SUSHISWAP, token0=A_USDC, token1=A_GNO, amount0_in=FVal(5.55), amount1_in=ZERO, amount0_out=FVal(2.15), amount1_out=FVal(5.4), ), ] data.db.add_trades(trades) data.db.add_amm_swaps(swaps) swap1_trade = Trade( timestamp=swaps[0].timestamp, location=swaps[0].location, base_asset=swaps[0].token1, quote_asset=swaps[0].token0, trade_type=TradeType.BUY, amount=swaps[0].amount1_out, rate=swaps[0].amount0_in / swaps[0].amount1_out, fee=None, fee_currency=None, link=swaps[0].tx_hash, notes=None, ) swap2_trade = Trade( timestamp=swaps[1].timestamp, location=swaps[1].location, base_asset=swaps[2].token1, quote_asset=swaps[1].token0, trade_type=TradeType.BUY, amount=swaps[2].amount1_out, rate=swaps[1].amount0_in / swaps[2].amount1_out, fee=None, fee_currency=None, link=swaps[1].tx_hash, notes=None, ) swap3_trade = Trade( timestamp=swaps[3].timestamp, location=swaps[3].location, base_asset=swaps[5].token0, quote_asset=swaps[3].token1, trade_type=TradeType.BUY, amount=swaps[5].amount0_out, rate=swaps[3].amount1_in / swaps[5].amount0_out, fee=None, fee_currency=None, link=swaps[3].tx_hash, notes=None, ) swap4_trade1 = Trade( timestamp=swaps[6].timestamp, location=swaps[6].location, base_asset=swaps[7].token1, quote_asset=swaps[6].token0, trade_type=TradeType.BUY, amount=swaps[7].amount1_out / 2, rate=swaps[6].amount0_in / (swaps[7].amount1_out / 2), fee=None, fee_currency=None, link=swaps[6].tx_hash, notes=None, ) swap4_trade2 = Trade( timestamp=swaps[6].timestamp, location=swaps[6].location, base_asset=swaps[7].token1, quote_asset=swaps[6].token1, trade_type=TradeType.BUY, amount=swaps[7].amount1_out / 2, rate=swaps[6].amount1_in / (swaps[7].amount1_out / 2), fee=None, fee_currency=None, link=swaps[6].tx_hash, notes=None, ) swap5_trade1 = Trade( timestamp=swaps[8].timestamp, location=swaps[8].location, base_asset=swaps[9].token1, quote_asset=swaps[8].token1, trade_type=TradeType.BUY, amount=swaps[9].amount1_out, rate=swaps[8].amount1_in / swaps[9].amount1_out, fee=None, fee_currency=None, link=swaps[8].tx_hash, notes=None, ) swap5_trade2 = Trade( timestamp=swaps[8].timestamp, location=swaps[8].location, base_asset=swaps[9].token0, quote_asset=swaps[8].token0, trade_type=TradeType.BUY, amount=swaps[9].amount0_out, rate=swaps[8].amount0_in / swaps[9].amount0_out, fee=None, fee_currency=None, link=swaps[8].tx_hash, notes=None, ) # Get all trades returned_trades = data.db.get_trades(filter_query=TradesFilterQuery.make(), has_premium=True) assert len(returned_trades) == 10 assert returned_trades[0] == trades[0] assert returned_trades[2] == trades[1] assert returned_trades[4] == trades[2] assert_trades_equal(returned_trades[1], swap1_trade) assert_trades_equal(returned_trades[3], swap2_trade) assert_trades_equal(returned_trades[5], swap3_trade) assert_trades_equal(returned_trades[6], swap4_trade2) assert_trades_equal(returned_trades[7], swap4_trade1) assert_trades_equal(returned_trades[8], swap5_trade1) assert_trades_equal(returned_trades[9], swap5_trade2) # Get last 5 trades returned_trades = data.db.get_trades( filter_query=TradesFilterQuery.make(limit=5, offset=5), has_premium=True, ) assert len(returned_trades) == 5 assert_trades_equal(returned_trades[0], swap3_trade) assert_trades_equal(returned_trades[1], swap4_trade2) assert_trades_equal(returned_trades[2], swap4_trade1) assert_trades_equal(returned_trades[3], swap5_trade1) assert_trades_equal(returned_trades[4], swap5_trade2) # Get first 5 trades that are in uniswap and that buy USDC returned_trades = data.db.get_trades( filter_query=TradesFilterQuery.make( limit=5, offset=0, location=Location.UNISWAP, base_asset=A_USDC, ), has_premium=True, ) assert len(returned_trades) == 1 assert_trades_equal(returned_trades[0], swap1_trade) # Get all trades with quote asset USDC returned_trades = data.db.get_trades( filter_query=TradesFilterQuery.make(quote_asset=A_USDC), has_premium=True, ) assert len(returned_trades) == 3 assert_trades_equal(returned_trades[0], trades[0]) assert_trades_equal(returned_trades[1], swap4_trade2) assert_trades_equal(returned_trades[2], swap5_trade1) # Get all trades as non premium user with 2 free trades as limit limit_patch = patch( target='rotkehlchen.db.dbhandler.FREE_TRADES_LIMIT', new=2, ) with limit_patch: returned_trades, total_found = data.db.get_trades_and_limit_info( filter_query=TradesFilterQuery.make(), has_premium=False, ) # trades should be the latest 2 assert total_found == 3 # the 3 normal trades -- free users don't see swaps assert len(returned_trades) == 2 assert_trades_equal(returned_trades[0], trades[1]) assert_trades_equal(returned_trades[1], trades[2]) # Get filtered trades as non premium user with 2 free trades as limit limit_patch = patch( target='rotkehlchen.db.dbhandler.FREE_TRADES_LIMIT', new=2, ) with limit_patch: returned_trades, total_found = data.db.get_trades_and_limit_info( filter_query=TradesFilterQuery.make(from_ts=1, to_ts=2), has_premium=False, ) # trades should be the second one since the free limit includes the last 2 only assert total_found == 2, 'total found for filter should be 2' assert len(returned_trades) == 1 assert_trades_equal(returned_trades[0], trades[1])
def deserialize_swap(raw_swap: Dict[str, Any]) -> AMMSwap: """May raise DeserializationError""" try: tx_hash, log_index = deserialize_transaction_id(raw_swap['id']) timestamp = deserialize_timestamp(raw_swap['timestamp']) raw_tokens = raw_swap['poolAddress']['tokens'] token0_symbol = raw_swap['tokenInSym'] token1_symbol = raw_swap['tokenOutSym'] amount0_in = deserialize_asset_amount(raw_swap['tokenAmountIn']) amount1_out = deserialize_asset_amount(raw_swap['tokenAmountOut']) raw_user_address = raw_swap['userAddress']['id'] # address raw_caller_address = raw_swap['caller'] # from_address raw_pool_address = raw_swap['poolAddress']['id'] # to_address raw_token_in_address = raw_swap['tokenIn'] # token0_address raw_token_out_address = raw_swap['tokenOut'] # token1_address except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e if amount0_in == ZERO: # Prevent a division by zero error when creating the trade raise DeserializationError('TokenAmountIn balance is zero.') # Checksum addresses try: user_address = to_checksum_address(raw_user_address) except ValueError as e: raise DeserializationError( f'Invalid ethereum address: {raw_user_address} in userAddress.id.', ) from e try: caller_address = to_checksum_address(raw_caller_address) except ValueError as e: raise DeserializationError( f'Invalid ethereum address: {raw_caller_address} in caller.', ) from e try: pool_address = to_checksum_address(raw_pool_address) except ValueError as e: raise DeserializationError( f'Invalid ethereum address: {raw_pool_address} in poolAddress.id.', ) from e try: token_in_address = to_checksum_address(raw_token_in_address) except ValueError as e: raise DeserializationError( f'Invalid ethereum address: {raw_token_in_address} in tokenIn.', ) from e try: token_out_address = to_checksum_address(raw_token_out_address) except ValueError as e: raise DeserializationError( f'Invalid ethereum address: {raw_token_out_address} in tokenOut.', ) from e # Get token0 and token1 # When the controller removes all the tokens from a pool, `raw_tokens` will # be an empty list. Therefore it won't be possible to get their names and # decimals. In case of having to instantiate an UnknownEthereumToken both # params will be None. if len(raw_tokens) != 0: try: raw_address_tokens = { raw_token['address']: raw_token for raw_token in raw_tokens } raw_token0 = raw_address_tokens[raw_token_in_address] raw_token1 = raw_address_tokens[raw_token_out_address] token0_name = raw_token0['name'] token0_decimals = raw_token0['decimals'] token1_name = raw_token1['name'] token1_decimals = raw_token1['decimals'] except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e else: token0_name = None token0_decimals = None token1_name = None token1_decimals = None token0 = get_ethereum_token( symbol=token0_symbol, ethereum_address=token_in_address, name=token0_name, decimals=token0_decimals, ) token1 = get_ethereum_token( symbol=token1_symbol, ethereum_address=token_out_address, name=token1_name, decimals=token1_decimals, ) amm_swap = AMMSwap( tx_hash=tx_hash, log_index=log_index, address=user_address, from_address=caller_address, to_address=pool_address, timestamp=timestamp, location=Location.BALANCER, token0=token0, token1=token1, amount0_in=amount0_in, amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=amount1_out, ) return amm_swap
def test_measure_trades_api_query(rotkehlchen_api_server, start_with_valid_premium): """Measures the response time of the combined trades view API query. This is required since it's quite a complicated query and takes a lot of time to process so we can use this test to measure any potential optimizations. """ trades = [ Trade( timestamp=x, location=Location.EXTERNAL, base_asset=A_WETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('1')), rate=Price(FVal('320')), fee=Fee(ZERO), fee_currency=A_EUR, link='', notes='', ) for x in range(1, 10000) ] rotki = rotkehlchen_api_server.rest_api.rotkehlchen rotki.data.db.add_trades(trades) swaps = [ AMMSwap( tx_hash='0x' + str(x), log_index=x + i, address='0xfoo', from_address='0xfrom', to_address='0xto', timestamp=11 + x, location=Location.UNISWAP, token0=A_WETH, token1=A_EUR, amount0_in=FVal(5), amount1_in=ZERO, amount0_out=ZERO, amount1_out=FVal(4.95), ) for x in range(2000) for i in range(2) ] rotki.data.db.add_amm_swaps(swaps) start = time.time() requests.get( api_url_for( rotkehlchen_api_server, 'tradesresource', ), json={'only_cache': True}, ) end = time.time() test_warnings.warn( UserWarning( f'Premium: {start_with_valid_premium}. Full Query Time: {end - start}', )) start = time.time() requests.get( api_url_for( rotkehlchen_api_server, 'tradesresource', ), json={ 'only_cache': True, 'offset': 200, 'limit': 10 }, ) end = time.time() test_warnings.warn( UserWarning( f'Premium: {start_with_valid_premium}. First Page Query Time: {end - start}', )) start = time.time() requests.get( api_url_for( rotkehlchen_api_server, 'tradesresource', ), json={ 'only_cache': True, 'offset': 210, 'limit': 10 }, ) end = time.time() test_warnings.warn( UserWarning( f'Premium: {start_with_valid_premium}. Second Page Query Time: {end - start}', )) start = time.time() requests.get( api_url_for( rotkehlchen_api_server, 'tradesresource', ), json={ 'only_cache': True, 'offset': 220, 'limit': 10 }, ) end = time.time() test_warnings.warn( UserWarning( f'Premium: {start_with_valid_premium}. Third Page Query Time: {end - start}', ))
def _read_subgraph_trades( self, address: ChecksumEthAddress, start_ts: Timestamp, end_ts: Timestamp, ) -> List[AMMTrade]: """Get the address' trades data querying the AMM subgraph Each trade (swap) instantiates an <AMMTrade>. The trade pair (i.e. BASE_QUOTE) is determined by `reserve0_reserve1`. Translated to AMM lingo: Trade type BUY: - `asset1In` (QUOTE, reserve1) is gt 0. - `asset0Out` (BASE, reserve0) is gt 0. Trade type SELL: - `asset0In` (BASE, reserve0) is gt 0. - `asset1Out` (QUOTE, reserve1) is gt 0. May raise - RemoteError """ trades: List[AMMTrade] = [] query_id = '0' query_offset = 0 param_types = { '$limit': 'Int!', '$offset': 'Int!', '$address': 'Bytes!', '$start_ts': 'BigInt!', '$end_ts': 'BigInt!', '$id': 'ID!', } param_values = { 'limit': GRAPH_QUERY_LIMIT, 'offset': 0, 'address': address.lower(), 'start_ts': str(start_ts), 'end_ts': str(end_ts), 'id': query_id, } querystr = format_query_indentation(self.swaps_query.format()) while True: try: result = self.graph.query( querystr=querystr, param_types=param_types, param_values=param_values, ) except RemoteError as e: self.msg_aggregator.add_error( SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e), location=self.location), ) raise for entry in result['swaps']: swaps = [] try: for swap in entry['transaction']['swaps']: timestamp = swap['timestamp'] swap_token0 = swap['pair']['token0'] swap_token1 = swap['pair']['token1'] try: token0_deserialized = deserialize_ethereum_address( swap_token0['id']) token1_deserialized = deserialize_ethereum_address( swap_token1['id']) from_address_deserialized = deserialize_ethereum_address( swap['sender']) # noqa to_address_deserialized = deserialize_ethereum_address( swap['to']) except DeserializationError: msg = ( f'Failed to deserialize addresses in trade from {self.location} graph' # noqa f' with token 0: {swap_token0["id"]}, token 1: {swap_token1["id"]}, ' # noqa f'swap sender: {swap["sender"]}, swap receiver {swap["to"]}' ) log.error(msg) continue token0 = get_or_create_ethereum_token( userdb=self.database, symbol=swap_token0['symbol'], ethereum_address=token0_deserialized, name=swap_token0['name'], decimals=swap_token0['decimals'], ) token1 = get_or_create_ethereum_token( userdb=self.database, symbol=swap_token1['symbol'], ethereum_address=token1_deserialized, name=swap_token1['name'], decimals=int(swap_token1['decimals']), ) try: amount0_in = FVal(swap['amount0In']) amount1_in = FVal(swap['amount1In']) amount0_out = FVal(swap['amount0Out']) amount1_out = FVal(swap['amount1Out']) except ValueError as e: log.error( f'Failed to read amounts in {self.location} swap {str(swap)}. ' f'{str(e)}.', ) continue swaps.append( AMMSwap( tx_hash=swap['id'].split('-')[0], log_index=int(swap['logIndex']), address=address, from_address=from_address_deserialized, to_address=to_address_deserialized, timestamp=Timestamp(int(timestamp)), location=self.location, token0=token0, token1=token1, amount0_in=AssetAmount(amount0_in), amount1_in=AssetAmount(amount1_in), amount0_out=AssetAmount(amount0_out), amount1_out=AssetAmount(amount1_out), )) query_id = entry['id'] except KeyError as e: log.error( f'Failed to read trade in {self.location} swap {str(entry)}. ' f'{str(e)}.', ) continue # with the new logic the list of swaps can be empty, in that case don't try # to make trades from the swaps if len(swaps) == 0: continue # Now that we got all swaps for a transaction, create the trade object trades.extend(self._tx_swaps_to_trades(swaps)) # Check whether an extra request is needed if len(result['swaps']) < GRAPH_QUERY_LIMIT: break # Update pagination step if query_offset == GRAPH_QUERY_SKIP_LIMIT: query_offset = 0 new_query_id = query_id else: query_offset += GRAPH_QUERY_LIMIT new_query_id = '0' param_values = { **param_values, 'id': new_query_id, 'offset': query_offset, } return trades
def get_expected_trades(): """Function so no price (unknown) assets can be resolved only when existing in the DB""" address = string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121') return [AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), quote_asset=EthereumToken('0x4b4D2e899658FB59b1D518b68fe836B100ee8958'), amount=AssetAmount(FVal('796.857811')), rate=Price(FVal('0.0008323741932057006980885326353')), trade_index=0, swaps=[AMMSwap( tx_hash='0x962d904d75c751fbff316f7a2ed280bd93241d5088d747a4f26fe7437813512f', log_index=141, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'), timestamp=Timestamp(1609308616), location=Location.SUSHISWAP, token0=EthereumToken('0x4b4D2e899658FB59b1D518b68fe836B100ee8958'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(FVal('0.663283877530785731')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('796.857811')), )], ), AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), quote_asset=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), amount=AssetAmount(FVal('2.223721994593087248')), rate=Price(FVal('1124.241252314216598775470692')), trade_index=0, swaps=[AMMSwap( tx_hash='0x90f68af0ebbbb8d4938a4fbd07a70862e806124abd907d1225f25a10afda0180', log_index=26, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x06da0fd433C1A5d7a4faa01111c044910A184553'), timestamp=Timestamp(1609303966), location=Location.SUSHISWAP, token0=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), amount0_in=AssetAmount(FVal('2500')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('3.410623314913014194')), ), AMMSwap( tx_hash='0x90f68af0ebbbb8d4938a4fbd07a70862e806124abd907d1225f25a10afda0180', log_index=29, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'), timestamp=Timestamp(1609303966), location=Location.SUSHISWAP, token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(FVal('3.410623314913014194')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('2474.601464')), ), AMMSwap( tx_hash='0x90f68af0ebbbb8d4938a4fbd07a70862e806124abd907d1225f25a10afda0180', log_index=32, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'), timestamp=Timestamp(1609303966), location=Location.SUSHISWAP, token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(ZERO), amount1_in=AssetAmount(FVal('2474.601464')), amount0_out=AssetAmount(FVal('2.223721994593087248')), amount1_out=AssetAmount(ZERO), )], ), AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), quote_asset=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), amount=AssetAmount(FVal('1.925851508322134245')), rate=Price(FVal('1012.539124420295894184748806')), trade_index=0, swaps=[AMMSwap( tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2', log_index=205, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x06da0fd433C1A5d7a4faa01111c044910A184553'), timestamp=Timestamp(1609301469), location=Location.SUSHISWAP, token0=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), amount0_in=AssetAmount(FVal('1950')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('2.6455727132446468')), ), AMMSwap( tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2', log_index=208, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'), timestamp=Timestamp(1609301469), location=Location.SUSHISWAP, token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(FVal('2.6455727132446468')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('1936.810111')), ), AMMSwap( tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2', log_index=211, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'), timestamp=Timestamp(1609301469), location=Location.SUSHISWAP, token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(ZERO), amount1_in=AssetAmount(FVal('1936.810111')), amount0_out=AssetAmount(FVal('1.925851508322134245')), amount1_out=AssetAmount(ZERO), )], ), AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), quote_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), amount=AssetAmount(FVal('951.611472')), rate=Price(FVal('0.001050849038104071952592013309')), trade_index=0, swaps=[AMMSwap( tx_hash='0xb3cea8179e8bc349661f265937187403ae4914c108d118889d026bac1fbec4e9', log_index=9, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'), timestamp=Timestamp(1609296759), location=Location.SUSHISWAP, token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(FVal('1')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('951.611472')), )], ), AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), quote_asset=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), amount=AssetAmount(FVal('1')), rate=Price(FVal('511.20342')), trade_index=0, swaps=[AMMSwap( tx_hash='0x0ccec8fd15c8d3ab923ee4a2406778f22769e74da20b19a35e614bfead6bab8d', log_index=242, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x06da0fd433C1A5d7a4faa01111c044910A184553'), timestamp=Timestamp(1609294923), location=Location.SUSHISWAP, token0=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), amount0_in=AssetAmount(FVal('511.20342')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('0.690577933591789501')), ), AMMSwap( tx_hash='0x0ccec8fd15c8d3ab923ee4a2406778f22769e74da20b19a35e614bfead6bab8d', log_index=245, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'), timestamp=Timestamp(1609294923), location=Location.SUSHISWAP, token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(FVal('0.690577933591789501')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('506.399839')), ), AMMSwap( tx_hash='0x0ccec8fd15c8d3ab923ee4a2406778f22769e74da20b19a35e614bfead6bab8d', log_index=248, address=address, from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'), timestamp=Timestamp(1609294923), location=Location.SUSHISWAP, token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(ZERO), amount1_in=AssetAmount(FVal('506.399839')), amount0_out=AssetAmount(FVal('1')), amount1_out=AssetAmount(ZERO), )], )]
def test_associated_locations(database): """Test that locations imported in different places are correctly stored in database""" # Add trades from different locations trades = [Trade( timestamp=Timestamp(1595833195), location=Location.CRYPTOCOM, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('1.0')), rate=Price(FVal('281.14')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1587825824), location=Location.CRYPTOCOM, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('50.0')), rate=Price(FVal('3.521')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1596014214), location=Location.BLOCKFI, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('50.0')), rate=Price(FVal('3.521')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1565888464), location=Location.NEXO, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('50.0')), rate=Price(FVal('3.521')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1596014214), location=Location.NEXO, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('50.0')), rate=Price(FVal('3.521')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1612051199), location=Location.BLOCKFI, base_asset=symbol_to_asset_or_token('USDC'), quote_asset=symbol_to_asset_or_token('LTC'), trade_type=TradeType.BUY, amount=AssetAmount(FVal('6404.6')), rate=Price(FVal('151.6283999982779809352223797')), fee=None, fee_currency=None, link='', notes='One Time', ), Trade( timestamp=Timestamp(1595833195), location=Location.POLONIEX, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('1.0')), rate=Price(FVal('281.14')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1596429934), location=Location.COINBASE, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('0.00061475')), rate=Price(FVal('309.0687271248474989833265555')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', ), Trade( timestamp=Timestamp(1596429934), location=Location.EXTERNAL, base_asset=A_ETH, quote_asset=A_EUR, trade_type=TradeType.BUY, amount=AssetAmount(FVal('1')), rate=Price(FVal('320')), fee=Fee(ZERO), fee_currency=A_USD, link='', notes='', )] # Add multiple entries for same exchange + connected exchange database.add_trades(trades) kraken_api_key1 = ApiKey('kraken_api_key') kraken_api_secret1 = ApiSecret(b'kraken_api_secret') kraken_api_key2 = ApiKey('kraken_api_key2') kraken_api_secret2 = ApiSecret(b'kraken_api_secret2') binance_api_key = ApiKey('binance_api_key') binance_api_secret = ApiSecret(b'binance_api_secret') # add mock kraken and binance database.add_exchange('kraken1', Location.KRAKEN, kraken_api_key1, kraken_api_secret1) database.add_exchange('kraken2', Location.KRAKEN, kraken_api_key2, kraken_api_secret2) database.add_exchange('binance', Location.BINANCE, binance_api_key, binance_api_secret) # Add uniswap and sushiswap events database.add_amm_events([ LiquidityPoolEvent( tx_hash='0x47ea26957ce09e84a51b51dfdab6a4ac1c3672a372eef77b15ef7677174ac847', log_index=23, address=ChecksumEthAddress('0x3163Bb273E8D9960Ce003fD542bF26b4C529f515'), timestamp=Timestamp(1590011534), event_type=EventType.MINT_SUSHISWAP, pool_address=ChecksumEthAddress('0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974'), token0=EthereumToken('0x514910771AF9Ca656af840dff83E8264EcF986CA'), token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), amount0=FVal('3.313676003468974932'), amount1=FVal('0.064189269269768657'), usd_price=FVal('26.94433946158740371839009166230438'), lp_amount=FVal('0.460858304063739927'), ), ]) database.add_amm_swaps([ AMMSwap( tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2', log_index=208, address=ChecksumEthAddress('0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974'), from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'), to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'), timestamp=Timestamp(1609301469), location=Location.UNISWAP, token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'), amount0_in=AssetAmount(FVal('2.6455727132446468')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('1936.810111')), ), ]) database.add_balancer_events([ BalancerEvent( tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b3', log_index=23, address=ChecksumEthAddress('0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974'), timestamp=Timestamp(1609301469), event_type=BalancerBPTEventType.MINT, pool_address_token=EthereumToken('0x514910771AF9Ca656af840dff83E8264EcF986CA'), lp_balance=Balance(amount=FVal(2), usd_value=FVal(3)), amounts=[ AssetAmount(FVal(1)), AssetAmount(FVal(2)), ], ), ]) expected_locations = { Location.KRAKEN, Location.BINANCE, Location.BLOCKFI, Location.NEXO, Location.CRYPTOCOM, Location.POLONIEX, Location.COINBASE, Location.EXTERNAL, Location.SUSHISWAP, Location.UNISWAP, Location.BALANCER, } assert set(database.get_associated_locations()) == expected_locations
EXPECTED_TRADES = [AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('USDT'), quote_asset=EthereumToken('WETH'), amount=AssetAmount(FVal('20632.012923')), rate=Price(FVal('0.002665760253508154513092246380')), trade_index=0, swaps=[AMMSwap( tx_hash='0x13723c8b286ec56e95b00e091557e6a76f723d20a52503d2e08df5867d942b51', log_index=319, address=deserialize_ethereum_address('0x21d05071cA08593e13cd3aFD0b4869537e015C92'), from_address=deserialize_ethereum_address('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'), to_address=deserialize_ethereum_address('0x21d05071cA08593e13cd3aFD0b4869537e015C92'), timestamp=Timestamp(1603180607), location=Location.UNISWAP, token0=EthereumToken('WETH'), token1=EthereumToken('USDT'), amount0_in=AssetAmount(FVal(55)), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('20632.012923')), )], ), AMMTrade( trade_type=TradeType.BUY, base_asset=EthereumToken('DAI'), quote_asset=EthereumToken('WETH'), amount=AssetAmount(FVal('1411.453463704718081611')), rate=Price(FVal('0.002692260210992670573731568435')), trade_index=0, swaps=[AMMSwap(
def deserialize_swap(userdb: 'DBHandler', raw_swap: Dict[str, Any]) -> AMMSwap: """May raise DeserializationError""" try: tx_hash, log_index = deserialize_transaction_id(raw_swap['id']) timestamp = deserialize_timestamp(raw_swap['timestamp']) raw_tokens = raw_swap['poolAddress']['tokens'] token0_symbol = raw_swap['tokenInSym'] token1_symbol = raw_swap['tokenOutSym'] amount0_in = deserialize_asset_amount(raw_swap['tokenAmountIn']) amount1_out = deserialize_asset_amount(raw_swap['tokenAmountOut']) raw_user_address = raw_swap['userAddress']['id'] # address raw_caller_address = raw_swap['caller'] # from_address raw_pool_address = raw_swap['poolAddress']['id'] # to_address raw_token_in_address = raw_swap['tokenIn'] # token0_address raw_token_out_address = raw_swap['tokenOut'] # token1_address except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e if amount0_in == ZERO: # Prevent a division by zero error when creating the trade raise DeserializationError('TokenAmountIn balance is zero.') # Checksum addresses user_address = deserialize_ethereum_address(raw_user_address) caller_address = deserialize_ethereum_address(raw_caller_address) pool_address = deserialize_ethereum_address(raw_pool_address) token_in_address = deserialize_ethereum_address(raw_token_in_address) token_out_address = deserialize_ethereum_address(raw_token_out_address) # Get token0 and token1 # When the controller removes all the tokens from a pool, `raw_tokens` will # be an empty list. Therefore it won't be possible to get their names and # decimals. if len(raw_tokens) != 0: try: raw_address_tokens = {raw_token['address']: raw_token for raw_token in raw_tokens} raw_token0 = raw_address_tokens.get(raw_token_in_address) raw_token1 = raw_address_tokens.get(raw_token_out_address) if raw_token0 is not None: token0_name = raw_token0['name'] token0_decimals = raw_token0['decimals'] else: token0_name = None token0_decimals = None if raw_token1 is not None: token1_name = raw_token1['name'] token1_decimals = raw_token1['decimals'] else: token1_name = None token1_decimals = None except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e else: token0_name = None token0_decimals = None token1_name = None token1_decimals = None token0 = get_or_create_ethereum_token( userdb=userdb, symbol=token0_symbol, ethereum_address=token_in_address, name=token0_name, decimals=token0_decimals, ) token1 = get_or_create_ethereum_token( userdb=userdb, symbol=token1_symbol, ethereum_address=token_out_address, name=token1_name, decimals=token1_decimals, ) amm_swap = AMMSwap( tx_hash=tx_hash, log_index=log_index, address=user_address, from_address=caller_address, to_address=pool_address, timestamp=timestamp, location=Location.BALANCER, token0=token0, token1=token1, amount0_in=amount0_in, amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=amount1_out, ) return amm_swap
def _get_trades_graph_v3_for_address( self, address: ChecksumEthAddress, start_ts: Timestamp, end_ts: Timestamp, ) -> List[AMMTrade]: """Get the address' trades data querying the Uniswap subgraph Each trade (swap) instantiates an <AMMTrade>. The trade pair (i.e. BASE_QUOTE) is determined by `reserve0_reserve1`. Translated to Uniswap lingo: Trade type BUY: - `amount1` (QUOTE, reserve1) is gt 0. - `amount0` (BASE, reserve0) is lt 0. Trade type SELL: - `amount0` (BASE, reserve0) is gt 0. - `amount1` (QUOTE, reserve1) is lt 0. May raise: - RemoteError """ trades: List[AMMTrade] = [] param_types = { '$limit': 'Int!', '$offset': 'Int!', '$address': 'Bytes!', '$start_ts': 'BigInt!', '$end_ts': 'BigInt!', } param_values = { 'limit': GRAPH_QUERY_LIMIT, 'offset': 0, 'address': address.lower(), 'start_ts': str(start_ts), 'end_ts': str(end_ts), } querystr = format_query_indentation(V3_SWAPS_QUERY.format()) while True: try: result = self.graph_v3.query( querystr=querystr, param_types=param_types, param_values=param_values, ) except RemoteError as e: self.msg_aggregator.add_error(SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e))) raise result_data = result['swaps'] for entry in result_data: swaps = [] for swap in entry['transaction']['swaps']: timestamp = swap['timestamp'] swap_token0 = swap['token0'] swap_token1 = swap['token1'] try: token0_deserialized = deserialize_ethereum_address(swap_token0['id']) token1_deserialized = deserialize_ethereum_address(swap_token1['id']) from_address_deserialized = deserialize_ethereum_address(swap['sender']) to_address_deserialized = deserialize_ethereum_address(swap['recipient']) except DeserializationError: msg = ( f'Failed to deserialize addresses in trade from uniswap graph with ' f'token 0: {swap_token0["id"]}, token 1: {swap_token1["id"]}, ' f'swap sender: {swap["sender"]}, swap receiver {swap["to"]}' ) log.error(msg) continue token0 = get_or_create_ethereum_token( userdb=self.database, symbol=swap_token0['symbol'], ethereum_address=token0_deserialized, name=swap_token0['name'], decimals=swap_token0['decimals'], ) token1 = get_or_create_ethereum_token( userdb=self.database, symbol=swap_token1['symbol'], ethereum_address=token1_deserialized, name=swap_token1['name'], decimals=int(swap_token1['decimals']), ) try: if swap['amount0'].startswith('-'): amount0_in = AssetAmount(FVal(ZERO)) amount0_out = deserialize_asset_amount_force_positive(swap['amount0']) amount1_in = deserialize_asset_amount_force_positive(swap['amount1']) amount1_out = AssetAmount(FVal(ZERO)) else: amount0_in = deserialize_asset_amount_force_positive(swap['amount0']) amount0_out = AssetAmount(FVal(ZERO)) amount1_in = AssetAmount(FVal(ZERO)) amount1_out = deserialize_asset_amount_force_positive(swap['amount1']) except ValueError as e: log.error( f'Failed to read amounts in Uniswap V3 swap {str(swap)}. ' f'{str(e)}.', ) continue swaps.append(AMMSwap( tx_hash=swap['id'].split('#')[0], log_index=int(swap['logIndex']), address=address, from_address=from_address_deserialized, to_address=to_address_deserialized, timestamp=Timestamp(int(timestamp)), location=Location.UNISWAP, token0=token0, token1=token1, amount0_in=amount0_in, amount1_in=amount1_in, amount0_out=amount0_out, amount1_out=amount1_out, )) # with the new logic the list of swaps can be empty, in that case don't try # to make trades from the swaps if len(swaps) == 0: continue # Now that we got all swaps for a transaction, create the trade object trades.extend(self._tx_swaps_to_trades(swaps)) # Check whether an extra request is needed if len(result_data) < GRAPH_QUERY_LIMIT: break # Update pagination step param_values = { **param_values, 'offset': param_values['offset'] + GRAPH_QUERY_LIMIT, # type: ignore } return trades
quote_asset=A_AAVE, amount=AssetAmount(FVal('1.616934038985744521')), rate=Price(FVal('6.963972908793392530935439799')), trade_index=1, swaps=[ AMMSwap( tx_hash= '0x3c457da9b541ae39a7dc781ab04a03938b98b5649512aec2a2d32635c9bbf589', # noqa: E501 log_index=24, address=string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b' ), # noqa: E501 from_address=string_to_ethereum_address( '0x0000000000007F150Bd6f54c40A34d7C3d5e9f56' ), # noqa: E501 to_address=string_to_ethereum_address( '0x7c90a3cd7Ec80dd2F633ed562480AbbEEd3bE546' ), # noqa: E501 timestamp=Timestamp(1607008178), location=Location.BALANCER, token0=A_AAVE, token1=A_WETH, amount0_in=AssetAmount(FVal('11.260284842802604032')), amount1_in=AssetAmount(ZERO), amount0_out=AssetAmount(ZERO), amount1_out=AssetAmount(FVal('1.616934038985744521')), ), ], ), AMMTrade( trade_type=TradeType.BUY, base_asset=A_AAVE,