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 fetch_underlying_tokens( address: ChecksumEthAddress, ) -> Optional[List[UnderlyingToken]]: """Fetch underlying tokens for a token address if they exist""" cursor = GlobalDBHandler()._conn.cursor() query = cursor.execute( 'SELECT address, weight from underlying_tokens_list WHERE parent_token_entry=?;', (address, ), ) results = query.fetchall() underlying_tokens = None if len(results) != 0: underlying_tokens = [ UnderlyingToken.deserialize_from_db(x) for x in results ] return underlying_tokens
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()
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', ), ] INITIAL_EXPECTED_TOKENS = [INITIAL_TOKENS[0]] + [ EthereumToken.initialize(underlying_address1),
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 deserialize_bpt_event( userdb: 'DBHandler', raw_event: Dict[str, Any], event_type: Literal[BalancerBPTEventType.MINT, BalancerBPTEventType.BURN], ) -> BalancerBPTEvent: """May raise DeserializationError""" try: tx_hash, log_index = deserialize_transaction_id(raw_event['id']) raw_user_address = raw_event['user']['id'] amount = deserialize_asset_amount(raw_event['amount']) raw_pool = raw_event['pool'] raw_pool_address = raw_pool['id'] raw_pool_tokens = raw_pool['tokens'] total_weight = deserialize_asset_amount(raw_pool['totalWeight']) except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e if total_weight == ZERO: raise DeserializationError('Pool weight is zero.') user_address = deserialize_ethereum_address(raw_user_address) pool_address = deserialize_ethereum_address(raw_pool_address) underlying_tokens = [] for raw_token in raw_pool_tokens: try: raw_token_address = raw_token['address'] token_symbol = raw_token['symbol'] token_name = raw_token['name'] token_decimals = raw_token['decimals'] token_weight = deserialize_asset_amount(raw_token['denormWeight']) except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e token_address = deserialize_ethereum_address(raw_token_address) token = get_or_create_ethereum_token( userdb=userdb, symbol=token_symbol, ethereum_address=token_address, name=token_name, decimals=token_decimals, ) underlying_tokens.append(UnderlyingToken( address=token.ethereum_address, weight=token_weight / total_weight, )) underlying_tokens.sort(key=lambda x: x.address) pool_address_token = get_or_create_ethereum_token( userdb=userdb, ethereum_address=pool_address, symbol='BPT', protocol='balancer', decimals=18, # all BPT tokens have 18 decimals underlying_tokens=underlying_tokens, form_with_incomplete_data=True, # since some may not have decimals input correctly ) bpt_event = BalancerBPTEvent( tx_hash=tx_hash, log_index=log_index, address=user_address, event_type=event_type, pool_address_token=pool_address_token, amount=amount, ) return bpt_event
def deserialize_pool_share( userdb: 'DBHandler', raw_pool_share: Dict[str, Any], ) -> Tuple[ChecksumEthAddress, BalancerPoolBalance]: """May raise DeserializationError""" try: raw_user_address = raw_pool_share['userAddress']['id'] user_amount = deserialize_asset_amount(raw_pool_share['balance']) raw_pool = raw_pool_share['poolId'] total_amount = deserialize_asset_amount(raw_pool['totalShares']) raw_address = raw_pool['id'] raw_tokens = raw_pool['tokens'] total_weight = deserialize_asset_amount(raw_pool['totalWeight']) except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e if total_weight == ZERO: raise DeserializationError('Pool weight is zero.') user_address = deserialize_ethereum_address(raw_user_address) pool_address = deserialize_ethereum_address(raw_address) pool_tokens = [] pool_token_balances = [] for raw_token in raw_tokens: try: raw_token_address = raw_token['address'] token_symbol = raw_token['symbol'] token_name = raw_token['name'] token_decimals = raw_token['decimals'] token_total_amount = deserialize_asset_amount(raw_token['balance']) token_weight = deserialize_asset_amount(raw_token['denormWeight']) except KeyError as e: raise DeserializationError(f'Missing key: {str(e)}.') from e token_address = deserialize_ethereum_address(raw_token_address) token = get_or_create_ethereum_token( userdb=userdb, symbol=token_symbol, ethereum_address=token_address, name=token_name, decimals=token_decimals, ) if token_total_amount == ZERO: raise DeserializationError(f'Token {token.identifier} balance is zero.') weight = token_weight * 100 / total_weight token_user_amount = user_amount / total_amount * token_total_amount pool_token_balance = BalancerPoolTokenBalance( token=token, total_amount=token_total_amount, user_balance=Balance(amount=token_user_amount), weight=weight, ) pool_token_balances.append(pool_token_balance) pool_token = UnderlyingToken(address=token.ethereum_address, weight=weight / 100) pool_tokens.append(pool_token) pool_tokens.sort(key=lambda x: x.address) pool_token_balances.sort(key=lambda x: x.token.ethereum_address) balancer_pool_token = get_or_create_ethereum_token( userdb=userdb, symbol='BPT', ethereum_address=pool_address, protocol='balancer', decimals=18, # All BPT tokens have 18 decimals underlying_tokens=pool_tokens, form_with_incomplete_data=True, # since some may not have had decimals input correctly ) pool = BalancerPoolBalance( pool_token=balancer_pool_token, underlying_tokens_balance=pool_token_balances, total_amount=total_amount, user_balance=Balance(amount=user_amount), ) return user_address, pool