def test_add_edit_token_with_wrong_swapped_for(globaldb): """Test that giving a non-existing swapped_for token in the DB raises InputError This can only be unit tested since via the API, marshmallow checks for Asset existence already """ # To unit test it we need to even hack it a bit. Make a new token, add it in the DB # then delete it and then try to add a new one referencing the old one. Since we # need to obtain a valid EthereumToken object address_to_delete = make_ethereum_address() token_to_delete = EthereumToken.initialize( address=address_to_delete, decimals=18, name='willdell', symbol='DELME', ) token_to_delete_id = 'DELMEID1' globaldb.add_asset( asset_id=token_to_delete_id, asset_type=AssetType.ETHEREUM_TOKEN, data=token_to_delete, ) asset_to_delete = Asset(token_to_delete_id) assert globaldb.delete_ethereum_token( address_to_delete) == token_to_delete_id # now try to add a new token with swapped_for pointing to a non existing token in the DB with pytest.raises(InputError): globaldb.add_asset( asset_id='NEWID', asset_type=AssetType.ETHEREUM_TOKEN, data=EthereumToken.initialize( address=make_ethereum_address(), swapped_for=asset_to_delete, ), ) # now edit a new token with swapped_for pointing to a non existing token in the DB bat_custom = globaldb.get_ethereum_token(A_BAT.ethereum_address) bat_custom = EthereumToken.initialize( address=A_BAT.ethereum_address, decimals=A_BAT.decimals, name=A_BAT.name, symbol=A_BAT.symbol, started=A_BAT.started, swapped_for=asset_to_delete, coingecko=A_BAT.coingecko, cryptocompare=A_BAT.cryptocompare, protocol=None, underlying_tokens=None, ) with pytest.raises(InputError): globaldb.edit_ethereum_token(bat_custom)
def test_price_underlying_tokens(inquirer, globaldb): aave_weight, link_weight, crv_weight = FVal('0.6'), FVal('0.2'), FVal( '0.2') address = make_ethereum_address() token = EthereumToken.initialize( address=address, decimals=18, name='Test', symbol='YAB', underlying_tokens=[ UnderlyingToken(address=A_AAVE.ethereum_address, weight=aave_weight), UnderlyingToken(address=A_LINK.ethereum_address, weight=link_weight), UnderlyingToken(address=A_CRV.ethereum_address, weight=crv_weight), ], ) globaldb.add_asset( asset_id=ethaddress_to_identifier(address), asset_type=AssetType.ETHEREUM_TOKEN, data=token, ) price = inquirer.find_price(EthereumToken(address), A_USD) assert price == FVal(67)
def transform_data( # pylint: disable=no-self-use self, data: Dict[str, Any], **_kwargs: Any, ) -> Dict[str, Any]: """Returns the a dictionary with: - The identifier - extra_information used by the globaldb handler - name - symbol - asset_type as instance of AssetType """ given_underlying_tokens = data.pop('underlying_tokens', None) underlying_tokens = None if given_underlying_tokens is not None: underlying_tokens = [] for entry in given_underlying_tokens: underlying_tokens.append(UnderlyingToken( address=entry['address'], weight=entry['weight'], )) asset_type = data['asset_type'] extra_information: Union[Dict[str, Any], EthereumToken] swapped_for, swapped_for_ident = data.pop('swapped_for'), None if swapped_for is not None: swapped_for_ident = swapped_for.identifier if asset_type == AssetType.ETHEREUM_TOKEN: extra_information = EthereumToken.initialize( address=data.pop('ethereum_address'), name=data.get('name'), symbol=data.get('symbol'), decimals=data.pop('decimals'), started=data.pop('started'), swapped_for=swapped_for_ident, coingecko=data.pop('coingecko'), cryptocompare=data.pop('cryptocompare'), underlying_tokens=underlying_tokens, ) else: forked, forked_ident = data.pop('forked'), None if forked is not None: forked_ident = forked.identifier extra_information = { 'name': data.get('name'), 'symbol': data.get('symbol'), 'started': data.pop('started'), 'forked': forked_ident, 'swapper_for': swapped_for_ident, 'coingecko': data.pop('coingecko'), 'cryptocompare': data.pop('cryptocompare'), } data['underlying_tokens'] = underlying_tokens data['asset_type'] = asset_type data['extra_information'] = extra_information return data
def test_find_uniswap_v2_lp_token_price(inquirer, globaldb, ethereum_manager): addess = '0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974' inquirer.inject_ethereum(ethereum_manager) token = EthereumToken.initialize( address=addess, decimals=18, name='Uniswap LINK/ETH', symbol='UNI-V2', protocol='UNI-V2', ) globaldb.add_asset( asset_id=ethaddress_to_identifier(addess), asset_type=AssetType.ETHEREUM_TOKEN, data=token, ) price = inquirer.find_uniswap_v2_lp_price(EthereumToken(addess)) assert price is not None
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')), ), ], ), ]
# Top holder of WBTC-WETH pool (0x1eff8af5d577060ba4ac8a29a13525bb0ee2a3d5) BALANCER_TEST_ADDR1 = string_to_ethereum_address( '0x49a2DcC237a65Cc1F412ed47E0594602f6141936') BALANCER_TEST_ADDR2 = string_to_ethereum_address( '0x029f388aC4D5C8BfF490550ce0853221030E822b') BALANCER_TEST_ADDR3 = string_to_ethereum_address( '0x7716a99194d758c8537F056825b75Dd0C8FDD89f') BALANCER_TEST_ADDR4 = string_to_ethereum_address( '0x231DC6af3C66741f6Cf618884B953DF0e83C1A2A') BALANCER_TEST_ADDR3_POOL1 = EthereumToken.initialize( address=string_to_ethereum_address( '0x59A19D8c652FA0284f44113D0ff9aBa70bd46fB4'), symbol='BPT', protocol='balancer', underlying_tokens=[ UnderlyingToken(address=string_to_ethereum_address( '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), weight=FVal(0.2)), # noqa: E501 # WETH UnderlyingToken(address=string_to_ethereum_address( '0xba100000625a3754423978a60c9317c58a424e3D'), weight=FVal(0.8)), # noqa: E501 # BAL ], ) BALANCER_TEST_ADDR3_POOL2 = EthereumToken.initialize( address=string_to_ethereum_address( '0x574FdB861a0247401B317a3E68a83aDEAF758cf6'), symbol='BPT', protocol='balancer', underlying_tokens=[ UnderlyingToken(address=string_to_ethereum_address( '0x0D8775F648430679A709E98d2b0Cb6250d2887EF'), weight=FVal(0.1)), # noqa: E501 # BAT
def test_global_db_reset(globaldb): """ Check that the user can recreate assets information from the packaged database with rotki (soft reset). The test adds a new asset, restores the database and checks that the added tokens are still in the database. In addition a token is edited and we check that was correctly restored. """ # Add a custom eth token address_to_delete = make_ethereum_address() token_to_delete = EthereumToken.initialize( address=address_to_delete, decimals=18, name='willdell', symbol='DELME', ) globaldb.add_asset( asset_id='DELMEID1', asset_type=AssetType.ETHEREUM_TOKEN, data=token_to_delete, ) # Add a token with underlying token with_underlying_address = make_ethereum_address() with_underlying = EthereumToken.initialize( address=with_underlying_address, decimals=18, name="Not a scam", symbol="NSCM", started=0, underlying_tokens=[ UnderlyingToken( address=address_to_delete, weight=1, ) ], ) globaldb.add_asset( asset_id='xDELMEID1', asset_type=AssetType.ETHEREUM_TOKEN, data=with_underlying, ) # Add asset that is not a token globaldb.add_asset( asset_id='1', asset_type=AssetType.OWN_CHAIN, data={ 'name': 'Lolcoin', 'symbol': 'LOLZ', 'started': 0, }, ) # Edit one token one_inch_update = EthereumToken.initialize( address='0x111111111117dC0aa78b770fA6A738034120C302', name='1inch boi', ) GlobalDBHandler().edit_ethereum_token(one_inch_update) status, _ = GlobalDBHandler().soft_reset_assets_list() assert status cursor = globaldb._conn.cursor() query = f'SELECT COUNT(*) FROM ethereum_tokens where address == "{address_to_delete}";' r = cursor.execute(query) assert r.fetchone() == ( 1, ), 'Custom ethereum tokens should not been deleted' query = f'SELECT COUNT(*) FROM assets where details_reference == "{address_to_delete}";' r = cursor.execute(query) assert r.fetchone() == (1, ) query = f'SELECT COUNT(*) FROM ethereum_tokens where address == "{with_underlying_address}";' r = cursor.execute(query) assert r.fetchone() == ( 1, ), 'Ethereum token with underlying token should not be deleted' query = f'SELECT COUNT(*) FROM assets where details_reference == "{with_underlying_address}";' r = cursor.execute(query) assert r.fetchone() == (1, ) query = f'SELECT COUNT(*) FROM underlying_tokens_list where address == "{address_to_delete}";' r = cursor.execute(query) assert r.fetchone() == (1, ) query = 'SELECT COUNT(*) FROM assets where identifier == "1";' r = cursor.execute(query) assert r.fetchone() == ( 1, ), 'Non ethereum token added should be in the db' # Check that the 1inch token was correctly fixed assert EthereumToken( '0x111111111117dC0aa78b770fA6A738034120C302').name != '1inch boi' # Check that the number of assets is the expected root_dir = Path(__file__).resolve().parent.parent.parent builtin_database = root_dir / 'data' / 'global.db' conn = sqlite3.connect(builtin_database) cursor_clean_db = conn.cursor() tokens_expected = cursor_clean_db.execute('SELECT COUNT(*) FROM assets;') tokens_local = cursor.execute('SELECT COUNT(*) FROM assets;') assert tokens_expected.fetchone()[0] + 3 == tokens_local.fetchone()[0] conn.close()
def test_global_db_restore(globaldb, database): """ Check that the user can recreate assets information from the packaged database with rotki (hard reset). The test adds a new asset, restores the database and checks that the added token is not in there and that the amount of assets is the expected """ # Add a custom eth token address_to_delete = make_ethereum_address() token_to_delete = EthereumToken.initialize( address=address_to_delete, decimals=18, name='willdell', symbol='DELME', ) globaldb.add_asset( asset_id='DELMEID1', asset_type=AssetType.ETHEREUM_TOKEN, data=token_to_delete, ) # Add a token with underlying token with_underlying_address = make_ethereum_address() with_underlying = EthereumToken.initialize( address=with_underlying_address, decimals=18, name="Not a scam", symbol="NSCM", started=0, underlying_tokens=[ UnderlyingToken( address=address_to_delete, weight=1, ) ], ) globaldb.add_asset( asset_id='xDELMEID1', asset_type=AssetType.ETHEREUM_TOKEN, data=with_underlying, ) # Add asset that is not a token globaldb.add_asset( asset_id='1', asset_type=AssetType.OWN_CHAIN, data={ 'name': 'Lolcoin', 'symbol': 'LOLZ', 'started': 0, }, ) # Add asset that is not a token globaldb.add_asset( asset_id='2', asset_type=AssetType.OWN_CHAIN, data={ 'name': 'Lolcoin2', 'symbol': 'LOLZ2', 'started': 0, }, ) database.add_asset_identifiers('1') database.add_asset_identifiers('2') # Try to reset DB it if we have a trade that uses a custom asset buy_asset = symbol_to_asset_or_token('LOLZ2') buy_amount = deserialize_asset_amount(1) sold_asset = symbol_to_asset_or_token('LOLZ') sold_amount = deserialize_asset_amount(2) rate = Price(buy_amount / sold_amount) trade = Trade( timestamp=Timestamp(12312312), location=Location.BLOCKFI, base_asset=buy_asset, quote_asset=sold_asset, trade_type=TradeType.BUY, amount=buy_amount, rate=rate, fee=None, fee_currency=None, link='', notes="", ) database.add_trades([trade]) status, _ = GlobalDBHandler().hard_reset_assets_list(database) assert status is False # Now do it without the trade database.delete_trade(trade.identifier) status, msg = GlobalDBHandler().hard_reset_assets_list(database, True) assert status, msg cursor = globaldb._conn.cursor() query = f'SELECT COUNT(*) FROM ethereum_tokens where address == "{address_to_delete}";' r = cursor.execute(query) assert r.fetchone() == (0, ), 'Ethereum token should have been deleted' query = f'SELECT COUNT(*) FROM assets where details_reference == "{address_to_delete}";' r = cursor.execute(query) assert r.fetchone() == ( 0, ), 'Ethereum token should have been deleted from assets' query = f'SELECT COUNT(*) FROM ethereum_tokens where address == "{with_underlying_address}";' r = cursor.execute(query) assert r.fetchone() == ( 0, ), 'Token with underlying token should have been deleted from assets' query = f'SELECT COUNT(*) FROM assets where details_reference == "{with_underlying_address}";' r = cursor.execute(query) assert r.fetchone() == (0, ) query = f'SELECT COUNT(*) FROM underlying_tokens_list where address == "{address_to_delete}";' r = cursor.execute(query) assert r.fetchone() == (0, ) query = 'SELECT COUNT(*) FROM assets where identifier == "1";' r = cursor.execute(query) assert r.fetchone() == (0, ), 'Non ethereum token should be deleted' # Check that the user database is correctly updated query = 'SELECT identifier from assets' r = cursor.execute(query) user_db_cursor = database.conn.cursor() user_db_cursor.execute(query) assert r.fetchall() == user_db_cursor.fetchall() # Check that the number of assets is the expected root_dir = Path(__file__).resolve().parent.parent.parent builtin_database = root_dir / 'data' / 'global.db' conn = sqlite3.connect(builtin_database) cursor_clean_db = conn.cursor() tokens_expected = cursor_clean_db.execute('SELECT COUNT(*) FROM assets;') tokens_local = cursor.execute('SELECT COUNT(*) FROM assets;') assert tokens_expected.fetchone() == tokens_local.fetchone() cursor.execute('SELECT asset_id FROM user_owned_assets') msg = 'asset id in trade should not be in the owned table' assert "'2'" not in [entry[0] for entry in cursor.fetchall()], msg conn.close()
def test_update_conflicts(rotkehlchen_api_server, globaldb): """Test that conflicts in an asset update are handled properly""" async_query = random.choice([False, True]) rotki = rotkehlchen_api_server.rest_api.rotkehlchen update_1 = """INSERT INTO assets(identifier,type,name,symbol,started, swapped_for, coingecko, cryptocompare, details_reference) VALUES("121-ada-FADS-as", "F","A name","SYMBOL",NULL, NULL,"", "", "121-ada-FADS-as");INSERT INTO common_asset_details(asset_id, forked) VALUES("121-ada-FADS-as", "BTC"); * INSERT INTO ethereum_tokens(address, decimals, protocol) VALUES("0x6B175474E89094C44Da98b954EedeAC495271d0F", 8, "maker");INSERT INTO assets(identifier,type, name, symbol,started, swapped_for, coingecko, cryptocompare, details_reference) VALUES("_ceth_0x6B175474E89094C44Da98b954EedeAC495271d0F", "C", "New Multi Collateral DAI", "NDAI", 1573672677, NULL, "dai", NULL, "0x6B175474E89094C44Da98b954EedeAC495271d0F"); * INSERT INTO assets(identifier,type,name,symbol,started, swapped_for, coingecko, cryptocompare, details_reference) VALUES("DASH", "B","Dash","DASH",1337, NULL, "dash-coingecko", NULL, "DASH");INSERT INTO common_asset_details(asset_id, forked) VALUES("DASH", "BTC"); * INSERT INTO ethereum_tokens(address, decimals, protocol) VALUES("0x1B175474E89094C44Da98b954EedeAC495271d0F", 18, NULL); INSERT INTO assets(identifier,type, name, symbol,started, swapped_for, coingecko, cryptocompare, details_reference) VALUES("_ceth_0x1B175474E89094C44Da98b954EedeAC495271d0F", "C", "Conflicting token", "CTK", 1573672677, NULL, "ctk", NULL, "0x1B175474E89094C44Da98b954EedeAC495271d0F"); * """ # noqa: E501 globaldb.add_asset( # add a conflicting token asset_id='_ceth_0x1B175474E89094C44Da98b954EedeAC495271d0F', asset_type=AssetType.ETHEREUM_TOKEN, data=EthereumToken.initialize( address=ChecksumEthAddress('0x1B175474E89094C44Da98b954EedeAC495271d0F'), decimals=12, name='Conflicting token', symbol='CTK', started=None, swapped_for=None, coingecko='ctk', cryptocompare=None, protocol=None, underlying_tokens=None, ), ) globaldb.add_user_owned_assets([Asset('_ceth_0x1B175474E89094C44Da98b954EedeAC495271d0F')]) update_patch = mock_asset_updates( original_requests_get=requests.get, latest=999999991, updates={"999999991": { "changes": 3, "min_schema_version": GLOBAL_DB_VERSION, "max_schema_version": GLOBAL_DB_VERSION, }}, sql_actions={"999999991": update_1}, ) globaldb.add_setting_value(ASSETS_VERSION_KEY, 999999990) start_assets_num = len(globaldb.get_all_asset_data(mapping=False)) with update_patch: response = requests.get( api_url_for( rotkehlchen_api_server, 'assetupdatesresource', ), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server, task_id, ) result = outcome['result'] assert outcome['message'] == '' else: result = assert_proper_response_with_result(response) assert result['local'] == 999999990 assert result['remote'] == 999999991 assert result['new_changes'] == 3 response = requests.post( api_url_for( rotkehlchen_api_server, 'assetupdatesresource', ), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server, task_id, ) assert outcome['message'] == 'Found conflicts during assets upgrade' result = outcome['result'] else: result = assert_proper_response_with_result( response, message='Found conflicts during assets upgrade', status_code=HTTPStatus.CONFLICT, ) # Make sure that nothing was committed assert globaldb.get_setting_value(ASSETS_VERSION_KEY, None) == 999999990 assert len(globaldb.get_all_asset_data(mapping=False)) == start_assets_num with pytest.raises(UnknownAsset): Asset('121-ada-FADS-as') errors = rotki.msg_aggregator.consume_errors() warnings = rotki.msg_aggregator.consume_warnings() assert len(errors) == 0, f'Found errors: {errors}' assert len(warnings) == 0, f'Found warnings: {warnings}' # See that we get 3 conflicts expected_result = [{ 'identifier': '_ceth_0x6B175474E89094C44Da98b954EedeAC495271d0F', 'local': { 'name': 'Multi Collateral Dai', 'symbol': 'DAI', 'asset_type': 'ethereum token', 'started': 1573672677, 'forked': None, 'swapped_for': None, 'ethereum_address': '0x6B175474E89094C44Da98b954EedeAC495271d0F', 'decimals': 18, 'cryptocompare': None, 'coingecko': 'dai', 'protocol': None, }, 'remote': { 'name': 'New Multi Collateral DAI', 'symbol': 'NDAI', 'asset_type': 'ethereum token', 'started': 1573672677, 'forked': None, 'swapped_for': None, 'ethereum_address': '0x6B175474E89094C44Da98b954EedeAC495271d0F', 'decimals': 8, 'cryptocompare': None, 'coingecko': 'dai', 'protocol': 'maker', }, }, { 'identifier': 'DASH', 'local': { 'name': 'Dash', 'symbol': 'DASH', 'asset_type': 'own chain', 'started': 1390095618, 'forked': None, 'swapped_for': None, 'ethereum_address': None, 'decimals': None, 'cryptocompare': None, 'coingecko': 'dash', 'protocol': None, }, 'remote': { 'name': 'Dash', 'symbol': 'DASH', 'asset_type': 'own chain', 'started': 1337, 'forked': 'BTC', 'swapped_for': None, 'ethereum_address': None, 'decimals': None, 'cryptocompare': None, 'coingecko': 'dash-coingecko', 'protocol': None, }, }, { 'identifier': '_ceth_0x1B175474E89094C44Da98b954EedeAC495271d0F', 'local': { 'asset_type': 'ethereum token', 'coingecko': 'ctk', 'cryptocompare': None, 'decimals': 12, 'ethereum_address': '0x1B175474E89094C44Da98b954EedeAC495271d0F', 'forked': None, 'name': 'Conflicting token', 'protocol': None, 'started': None, 'swapped_for': None, 'symbol': 'CTK', }, 'remote': { 'asset_type': 'ethereum token', 'coingecko': 'ctk', 'cryptocompare': None, 'decimals': 18, 'ethereum_address': '0x1b175474E89094C44DA98B954EeDEAC495271d0f', 'forked': None, 'name': 'Conflicting token', 'protocol': None, 'started': 1573672677, 'swapped_for': None, 'symbol': 'CTK', }, }] assert result == expected_result # now try the update again but specify the conflicts resolution conflicts = {'_ceth_0x6B175474E89094C44Da98b954EedeAC495271d0F': 'remote', 'DASH': 'local', '_ceth_0x1B175474E89094C44Da98b954EedeAC495271d0F': 'remote'} # noqa: E501 response = requests.post( api_url_for( rotkehlchen_api_server, 'assetupdatesresource', ), json={'async_query': async_query, 'conflicts': conflicts}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server, task_id, ) assert outcome['message'] == '' result = outcome['result'] else: result = assert_proper_response_with_result( response, message='', status_code=HTTPStatus.OK, ) cursor = globaldb._conn.cursor() # check conflicts were solved as per the given choices and new asset also added assert result is True assert globaldb.get_setting_value(ASSETS_VERSION_KEY, None) == 999999991 errors = rotki.msg_aggregator.consume_errors() warnings = rotki.msg_aggregator.consume_warnings() assert len(errors) == 0, f'Found errors: {errors}' assert len(warnings) == 0, f'Found warnings: {warnings}' dai = EthereumToken('0x6B175474E89094C44Da98b954EedeAC495271d0F') assert dai.identifier == strethaddress_to_identifier('0x6B175474E89094C44Da98b954EedeAC495271d0F') # noqa: E501 assert dai.name == 'New Multi Collateral DAI' assert dai.symbol == 'NDAI' assert dai.asset_type == AssetType.ETHEREUM_TOKEN assert dai.started == 1573672677 assert dai.forked is None assert dai.swapped_for is None assert dai.coingecko == 'dai' assert dai.cryptocompare is None assert dai.ethereum_address == '0x6B175474E89094C44Da98b954EedeAC495271d0F' assert dai.decimals == 8 assert dai.protocol == 'maker' # make sure data is in both tables assert cursor.execute('SELECT COUNT(*) from ethereum_tokens WHERE address="0x6B175474E89094C44Da98b954EedeAC495271d0F";').fetchone()[0] == 1 # noqa: E501 assert cursor.execute('SELECT COUNT(*) from assets WHERE identifier="_ceth_0x6B175474E89094C44Da98b954EedeAC495271d0F";').fetchone()[0] == 1 # noqa: E501 dash = Asset('DASH') assert dash.identifier == 'DASH' assert dash.name == 'Dash' assert dash.symbol == 'DASH' assert dash.asset_type == AssetType.OWN_CHAIN assert dash.started == 1390095618 assert dash.forked is None assert dash.swapped_for is None assert dash.coingecko == 'dash' assert dash.cryptocompare is None assert cursor.execute('SELECT COUNT(*) from common_asset_details WHERE asset_id="DASH";').fetchone()[0] == 1 # noqa: E501 assert cursor.execute('SELECT COUNT(*) from assets WHERE identifier="DASH";').fetchone()[0] == 1 # noqa: E501 new_asset = Asset('121-ada-FADS-as') assert new_asset.identifier == '121-ada-FADS-as' assert new_asset.name == 'A name' assert new_asset.symbol == 'SYMBOL' assert new_asset.asset_type == AssetType.COUNTERPARTY_TOKEN assert new_asset.started is None assert new_asset.forked == 'BTC' assert new_asset.swapped_for is None assert new_asset.coingecko == '' assert new_asset.cryptocompare == '' assert cursor.execute('SELECT COUNT(*) from common_asset_details WHERE asset_id="121-ada-FADS-as";').fetchone()[0] == 1 # noqa: E501 assert cursor.execute('SELECT COUNT(*) from assets WHERE identifier="121-ada-FADS-as";').fetchone()[0] == 1 # noqa: E501 ctk = EthereumToken('0x1B175474E89094C44Da98b954EedeAC495271d0F') assert ctk.name == 'Conflicting token' assert ctk.symbol == 'CTK' assert ctk.asset_type == AssetType.ETHEREUM_TOKEN assert ctk.started == 1573672677 assert ctk.forked is None assert ctk.swapped_for is None assert ctk.coingecko == 'ctk' assert ctk.cryptocompare is None assert ctk.ethereum_address == '0x1B175474E89094C44Da98b954EedeAC495271d0F' assert ctk.decimals == 18 assert ctk.protocol is None assert cursor.execute('SELECT COUNT(*) from ethereum_tokens WHERE address="0x1B175474E89094C44Da98b954EedeAC495271d0F";').fetchone()[0] == 1 # noqa: E501 assert cursor.execute('SELECT COUNT(*) from assets WHERE identifier="_ceth_0x1B175474E89094C44Da98b954EedeAC495271d0F";').fetchone()[0] == 1 # noqa: E501
underlying_address1 = make_ethereum_address() underlying_address2 = make_ethereum_address() underlying_address3 = make_ethereum_address() custom_address1 = make_ethereum_address() custom_address2 = make_ethereum_address() INITIAL_TOKENS = [ EthereumToken.initialize( address=custom_address1, decimals=4, name='Custom 1', symbol='CST1', started=Timestamp(0), swapped_for=A_MKR, coingecko='foo', cryptocompare='boo', protocol='uniswap', underlying_tokens=[ UnderlyingToken(address=underlying_address1, weight=FVal('0.5055')), UnderlyingToken(address=underlying_address2, weight=FVal('0.1545')), UnderlyingToken(address=underlying_address3, weight=FVal('0.34')), ], ), EthereumToken.initialize( address=custom_address2, decimals=18, name='Custom 2', symbol='CST2', ), ]
def test_adding_custom_tokens(rotkehlchen_api_server): """Test that the endpoint for adding a custom ethereum token works""" serialized_token = CUSTOM_TOKEN3.serialize_all_info() del serialized_token['identifier'] response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': serialized_token}, ) result = assert_proper_response_with_result(response) assert result == {'identifier': ETHEREUM_DIRECTIVE + CUSTOM_TOKEN3.ethereum_address} response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), ) result = assert_proper_response_with_result(response) expected_tokens = INITIAL_EXPECTED_TOKENS.copy() + [ CUSTOM_TOKEN3, EthereumToken.initialize(address=underlying_address4), ] expected_result = [x.serialize_all_info() for x in expected_tokens] assert_token_entry_exists_in_result(result, expected_result) # test that adding an already existing address is handled properly serialized_token = INITIAL_TOKENS[1].serialize_all_info() del serialized_token['identifier'] response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': serialized_token}, ) expected_msg = ( f'Ethereum token with address {INITIAL_TOKENS[1].ethereum_address} already ' f'exists in the DB', ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) # also test that the addition of underlying tokens has created proper asset entires for them cursor = GlobalDBHandler()._conn.cursor() result = cursor.execute( 'SELECT COUNT(*) from assets WHERE identifier IN (?, ?, ?, ?)', [ETHEREUM_DIRECTIVE + x for x in [underlying_address1, underlying_address2, underlying_address3, underlying_address4]], # noqa: E501 ).fetchone()[0] assert result == 4 result = cursor.execute( 'SELECT COUNT(*) from ethereum_tokens WHERE address IN (?, ?, ?, ?)', (underlying_address1, underlying_address2, underlying_address3, underlying_address4), # noqa: E501 ).fetchone()[0] assert result == 4 # now test that adding a token with underlying tokens adding up to more than 100% is caught bad_token = EthereumToken.initialize( address=make_ethereum_address(), decimals=18, name='foo', symbol='BBB', underlying_tokens=[ UnderlyingToken(address=make_ethereum_address(), weight=FVal('0.5055')), UnderlyingToken(address=make_ethereum_address(), weight=FVal('0.7055')), ], ) serialized_token = bad_token.serialize_all_info() del serialized_token['identifier'] response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': serialized_token}, ) expected_msg = ( f'The sum of underlying token weights for {bad_token.ethereum_address} is ' f'121.1000 and exceeds 100%' ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.BAD_REQUEST, ) # and test that adding a token with underlying tokens adding up to less than 100% is caught bad_token = EthereumToken.initialize( address=make_ethereum_address(), decimals=18, name='foo', symbol='BBB', underlying_tokens=[ UnderlyingToken(address=make_ethereum_address(), weight=FVal('0.1055')), UnderlyingToken(address=make_ethereum_address(), weight=FVal('0.2055')), ], ) serialized_token = bad_token.serialize_all_info() del serialized_token['identifier'] response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': serialized_token}, ) expected_msg = ( f'The sum of underlying token weights for {bad_token.ethereum_address} is ' f'31.1000 and does not add up to 100%' ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.BAD_REQUEST, ) # and test that adding a token with empty list of underlying tokens and not null is an error bad_token = EthereumToken.initialize( address=make_ethereum_address(), decimals=18, name='foo', symbol='BBB', underlying_tokens=[], ) serialized_bad_token = bad_token.serialize_all_info() del serialized_bad_token['identifier'] serialized_bad_token['underlying_tokens'] = [] response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': serialized_bad_token}, ) expected_msg = ( f'Gave an empty list for underlying tokens of {bad_token.ethereum_address}' ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.BAD_REQUEST, ) # test that adding invalid coingecko fails bad_identifier = 'INVALIDID' bad_token = { 'address': make_ethereum_address(), 'decimals': 18, 'name': 'Bad token', 'symbol': 'NAUGHTY', 'coingecko': bad_identifier, } response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': bad_token}, ) assert_error_response( response=response, contained_in_msg=f'Given coingecko identifier {bad_identifier} is not valid', status_code=HTTPStatus.BAD_REQUEST, ) # test that adding invalid cryptocompare fails bad_token['cryptocompare'] = bad_identifier bad_token['coingecko'] = None response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': bad_token}, ) assert_error_response( response=response, contained_in_msg=f'Given cryptocompare identifier {bad_identifier} isnt valid', status_code=HTTPStatus.BAD_REQUEST, )
def test_exporting_custom_assets_list(rotkehlchen_api_server, globaldb, with_custom_path): """Test that the endpoint for exporting custom assets works correctly""" eth_address = make_ethereum_address() identifier = ethaddress_to_identifier(eth_address) globaldb.add_asset( asset_id=identifier, asset_type=AssetType.ETHEREUM_TOKEN, data=EthereumToken.initialize( address=eth_address, decimals=18, name='yabirtoken', symbol='YAB', coingecko='YAB', cryptocompare='YAB', ), ) with tempfile.TemporaryDirectory() as path: if with_custom_path: response = requests.put( api_url_for( rotkehlchen_api_server, 'userassetsresource', ), json={ 'action': 'download', 'destination': path }, ) else: response = requests.put( api_url_for( rotkehlchen_api_server, 'userassetsresource', ), json={'action': 'download'}, ) if with_custom_path: result = assert_proper_response_with_result(response) if with_custom_path: assert path in result['file'] zip_file = ZipFile(result['file']) data = json.loads(zip_file.read('assets.json')) assert int(data['version']) == GLOBAL_DB_VERSION assert len(data['assets']) == 1 assert data['assets'][0] == { 'identifier': identifier, 'name': 'yabirtoken', 'decimals': 18, 'symbol': 'YAB', 'asset_type': 'ethereum token', 'started': None, 'forked': None, 'swapped_for': None, 'cryptocompare': 'YAB', 'coingecko': 'YAB', 'protocol': None, 'underlying_tokens': None, 'ethereum_address': eth_address, } else: assert response.status_code == HTTPStatus.OK assert response.headers['Content-Type'] == 'application/zip' # try to download again to see if the database is properly detached response = requests.put( api_url_for( rotkehlchen_api_server, 'userassetsresource', ), json={ 'action': 'download', 'destination': path }, ) result = assert_proper_response_with_result(response)