def test_query_transactions_removed_address( rotkehlchen_api_server, ethereum_accounts, ): """Make sure that if an address is removed so are the transactions from the DB""" start_ts = 0 end_ts = 1598453214 rotki = rotkehlchen_api_server.rest_api.rotkehlchen db = rotki.data.db transactions = [EthereumTransaction( tx_hash=b'1', timestamp=0, block_number=0, from_address=ethereum_accounts[0], to_address=make_ethereum_address(), value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=0, ), EthereumTransaction( tx_hash=b'2', timestamp=0, block_number=0, from_address=ethereum_accounts[0], to_address=make_ethereum_address(), value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=1, ), EthereumTransaction( # should remain after deletining account[0] tx_hash=b'3', timestamp=0, block_number=0, from_address=make_ethereum_address(), to_address=ethereum_accounts[1], value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=55, ), EthereumTransaction( # should remain after deletining account[0] tx_hash=b'4', timestamp=0, block_number=0, from_address=ethereum_accounts[1], to_address=ethereum_accounts[0], value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=0, ), EthereumTransaction( # should remain after deletining account[0] tx_hash=b'5', timestamp=0, block_number=0, from_address=ethereum_accounts[0], to_address=ethereum_accounts[1], value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=0, )] db.add_ethereum_transactions(transactions, from_etherscan=True) # Also make sure to update query ranges so as not to query etherscan at all for address in ethereum_accounts: DBQueryRanges(db).update_used_query_range( location_string=f'ethtxs_{address}', start_ts=start_ts, end_ts=end_ts, ranges_to_query=[], ) # Now remove the first account (do the mocking to not query etherscan for balances) setup = setup_balances( rotki, ethereum_accounts=ethereum_accounts, btc_accounts=[], eth_balances=['10000', '10000'], ) with ExitStack() as stack: setup.enter_ethereum_patches(stack) response = requests.delete(api_url_for( rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH', ), json={'accounts': [ethereum_accounts[0]]}) assert_proper_response_with_result(response) # Check that only the 3 remanining transactions from the other account is returned response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumtransactionsresource', ), ) result = assert_proper_response_with_result(response) assert len(result['entries']) == 3 assert result['entries_found'] == 3
def test_query_transactions_over_limit( rotkehlchen_api_server, ethereum_accounts, start_with_valid_premium, ): start_ts = 0 end_ts = 1598453214 rotki = rotkehlchen_api_server.rest_api.rotkehlchen db = rotki.data.db all_transactions_num = FREE_ETH_TX_LIMIT + 50 transactions = [EthereumTransaction( tx_hash=x.to_bytes(2, byteorder='little'), timestamp=x, block_number=x, from_address=ethereum_accounts[0], to_address=make_ethereum_address(), value=x, gas=x, gas_price=x, gas_used=x, input_data=x.to_bytes(2, byteorder='little'), nonce=x, ) for x in range(FREE_ETH_TX_LIMIT - 10)] transactions.extend([EthereumTransaction( tx_hash=(x + 500).to_bytes(2, byteorder='little'), timestamp=x, block_number=x, from_address=ethereum_accounts[1], to_address=make_ethereum_address(), value=x, gas=x, gas_price=x, gas_used=x, input_data=x.to_bytes(2, byteorder='little'), nonce=x, ) for x in range(60)]) db.add_ethereum_transactions(transactions, from_etherscan=True) # Also make sure to update query ranges so as not to query etherscan at all for address in ethereum_accounts: DBQueryRanges(db).update_used_query_range( location_string=f'ethtxs_{address}', start_ts=start_ts, end_ts=end_ts, ranges_to_query=[], ) free_expected_entries = [FREE_ETH_TX_LIMIT - 10, 10] premium_expected_entries = [FREE_ETH_TX_LIMIT - 10, 60] # Check that we get all transactions correctly even if we query two times for _ in range(2): for idx, address in enumerate(ethereum_accounts): response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumtransactionsresource', ), json={'from_timestamp': start_ts, 'to_timestamp': end_ts, 'address': address}, ) result = assert_proper_response_with_result(response) if start_with_valid_premium: assert len(result['entries']) == premium_expected_entries[idx] assert result['entries_found'] == all_transactions_num assert result['entries_limit'] == -1 else: assert len(result['entries']) == free_expected_entries[idx] assert result['entries_found'] == all_transactions_num assert result['entries_limit'] == FREE_ETH_TX_LIMIT
def test_query_transactions_from_to_address( rotkehlchen_api_server, ethereum_accounts, ): """Make sure that if a transaction is just being sent to an address it's also returned.""" start_ts = 0 end_ts = 1598453214 rotki = rotkehlchen_api_server.rest_api.rotkehlchen db = rotki.data.db transactions = [EthereumTransaction( tx_hash=b'1', timestamp=0, block_number=0, from_address=ethereum_accounts[0], to_address=make_ethereum_address(), value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=0, ), EthereumTransaction( tx_hash=b'2', timestamp=0, block_number=0, from_address=ethereum_accounts[0], to_address=ethereum_accounts[1], value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=1, ), EthereumTransaction( tx_hash=b'3', timestamp=0, block_number=0, from_address=make_ethereum_address(), to_address=ethereum_accounts[0], value=1, gas=1, gas_price=1, gas_used=1, input_data=b'', nonce=55, )] db.add_ethereum_transactions(transactions, from_etherscan=True) # Also make sure to update query ranges so as not to query etherscan at all for address in ethereum_accounts: DBQueryRanges(db).update_used_query_range( location_string=f'ethtxs_{address}', start_ts=start_ts, end_ts=end_ts, ranges_to_query=[], ) expected_entries = {ethereum_accounts[0]: 3, ethereum_accounts[1]: 1} # Check that we get all transactions correctly even if we query two times for _ in range(2): for address in ethereum_accounts: response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumtransactionsresource', ), json={'from_timestamp': start_ts, 'to_timestamp': end_ts, 'address': address}, ) result = assert_proper_response_with_result(response) assert len(result['entries']) == expected_entries[address] assert result['entries_found'] == 3
from rotkehlchen.assets.asset import EthereumToken, UnderlyingToken from rotkehlchen.fval import FVal from rotkehlchen.tests.utils.constants import A_MKR from rotkehlchen.tests.utils.factories import make_ethereum_address from rotkehlchen.typing import Timestamp 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')), ], ),
def test_queried_addresses_per_protocol(rotkehlchen_api_server): # First add some queried addresses per protocol address1 = make_ethereum_address() data = {'module': 'aave', 'address': address1} response = requests.put( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) result = assert_proper_response_with_result(response) assert result == {'aave': [address1]} address2 = make_ethereum_address() data = {'module': 'makerdao_vaults', 'address': address2} response = requests.put( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) result = assert_proper_response_with_result(response) assert_queried_addresses_match(result, { 'aave': [address1], 'makerdao_vaults': [address2], }) # add same address to another module/protocol data = {'module': 'aave', 'address': address2} response = requests.put( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) result = assert_proper_response_with_result(response) assert_queried_addresses_match(result, { 'aave': [address1, address2], 'makerdao_vaults': [address2], }) # try to add an address that already exists for a module/protocol and assert we get an error data = {'module': 'aave', 'address': address1} response = requests.put( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) assert_error_response( response=response, contained_in_msg= f'{address1} is already in the queried addresses for aave', status_code=HTTPStatus.CONFLICT, ) # add an address and then remove it address3 = make_ethereum_address() data = {'module': 'makerdao_dsr', 'address': address3} response = requests.put( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) result = assert_proper_response_with_result(response) assert_queried_addresses_match( result, { 'aave': [address1, address2], 'makerdao_vaults': [address2], 'makerdao_dsr': [address3], }) response = requests.delete( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) result = assert_proper_response_with_result(response) assert_queried_addresses_match(result, { 'aave': [address1, address2], 'makerdao_vaults': [address2], }) # try to remove a non-existing address and module combination and assert we get an error data = {'module': 'makerdao_vaults', 'address': address1} response = requests.delete( api_url_for(rotkehlchen_api_server, "queriedaddressesresource"), json=data, ) assert_error_response( response=response, contained_in_msg= f'{address1} is not in the queried addresses for makerdao_vaults', status_code=HTTPStatus.CONFLICT, ) # test that getting the queried addresses per module works response = requests.get( api_url_for(rotkehlchen_api_server, "queriedaddressesresource")) result = assert_proper_response_with_result(response) assert_queried_addresses_match(result, { 'aave': [address1, address2], 'makerdao_vaults': [address2], })
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_blockchain_accounts_endpoint_errors(rotkehlchen_api_server, api_port, method): """ Test /api/(version)/blockchains/(name) for edge cases and errors. Test for errors when both adding and removing a blockhain account. Both put/delete """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen rotki.blockchain.cache_ttl_secs = 0 # Provide unsupported blockchain name account = '0x00d74c25bbf93df8b2a41d82b0076843b4db0349' checksummed_account = to_checksum_address(account) data = {'accounts': [account]} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='DDASDAS'), json=data, ) assert_error_response( response=response, contained_in_msg='Unrecognized value DDASDAS given for blockchain name', ) # Provide no blockchain name response = getattr(requests, method)( f'http://localhost:{api_port}/api/1/blockchains', json=data, ) assert_error_response( response=response, status_code=HTTPStatus.NOT_FOUND, ) # Do not provide accounts data = {'dsadsad': 'foo'} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', ) # Provide wrong type of account data = {'accounts': 'foo'} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) assert_error_response( response=response, contained_in_msg='is not a valid ETH address', ) assert 'foo' not in rotki.blockchain.accounts.eth # Provide empty list data = {'accounts': []} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) assert_error_response( response=response, contained_in_msg='Empty list of blockchain accounts to add was given', ) # Provide invalid ETH account (more byes) data = {'accounts': ['0x554FFc77f4251a9fB3c0E3590a6a205f8d4e067d01']} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) msg = 'string 0x554FFc77f4251a9fB3c0E3590a6a205f8d4e067d01 is not a valid ETH address' assert_error_response( response=response, contained_in_msg=msg, ) # Provide invalid BTC account data = {'accounts': ['18ddjB7HWTaxzvTbLp1nWvaixU3U2oTZ1']} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='BTC'), json=data, ) # Since validation depends on the querying of the balance for BTC the error only # is seen at addition and at removal another error is seen. But that's okay if method == 'put': msg = 'string 18ddjB7HWTaxzvTbLp1nWvaixU3U2oTZ1 is not a valid BTC address' else: msg = 'Tried to remove a non existing BTC account' assert_error_response( response=response, contained_in_msg=msg, ) assert_msg = 'Invalid BTC account should not have been added' assert '18ddjB7HWTaxzvTbLp1nWvaixU3U2oTZ1' not in rotki.blockchain.accounts.btc, assert_msg # Provide not existing but valid ETH account for removal data = {'accounts': [make_ethereum_address()]} response = requests.delete( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) assert_error_response( response=response, contained_in_msg='Tried to remove a non existing ETH account', ) # Provide not existing but valid BTC account for removal data = {'accounts': ['18ddjB7HWTVxzvTbLp1nWvaBxU3U2oTZF2']} response = requests.delete( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='BTC'), json=data, ) assert_error_response( response=response, contained_in_msg='Tried to remove a non existing BTC account', ) # Provide list with one valid and one invalid account and make sure that the # valid one is added/removed but we get an error for the invalid one if method == 'delete': # Account should be an existing account account = rotki.blockchain.accounts.eth[0] # else keep the new account to add data = {'accounts': ['142', account]} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) assert_proper_response(response=response) assert '142 is not a valid ETH address' in response.json()['message'] if method == 'put': assert checksummed_account in rotki.blockchain.accounts.eth else: assert checksummed_account not in rotki.blockchain.accounts.eth # Provide invalid type for accounts data = {'accounts': [15]} response = getattr(requests, method)( api_url_for(rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH'), json=data, ) assert_error_response( response=response, contained_in_msg='Not a valid string', )
for key in keys: msg = f'Unexpected data for event with idx "{idx}" and key "{key}" of {name} vault' assert s[key] == result['events'][idx][key], msg assert FVal(s['from_value']['amount']) == FVal( result['events'][idx]['from_value']['amount']) # noqa: E501 assert FVal(s['to_value']['amount']) == FVal( result['events'][idx]['to_value']['amount']) # noqa: E501 if s['realized_pnl']: assert FVal(s['realized_pnl']['amount']) == FVal( result['events'][idx]['realized_pnl']['amount']) # noqa: E501 # Try with 2 addresses to make sure that if an address does not have yearn vault history # nothing breaks @pytest.mark.parametrize('ethereum_accounts', [[TEST_ACC1, make_ethereum_address()]]) @pytest.mark.parametrize('ethereum_modules', [['yearn_vaults']]) @pytest.mark.parametrize('should_mock_current_price_queries', [True]) @pytest.mark.parametrize('should_mock_price_queries', [True]) @pytest.mark.parametrize('default_mock_price_value', [FVal(1)]) @pytest.mark.parametrize('start_with_valid_premium', [True]) @pytest.mark.parametrize( # Force infura to make sure one of our history tests work with web3 'ethrpc_endpoint,ethereum_manager_connect_at_start', [( INFURA_TEST, (NodeName.OWN, ), )], ) def test_query_yearn_vault_history(rotkehlchen_api_server, ethereum_accounts): """Check querying the yearn vaults history endpoint works. Uses real data.""" async_query = random.choice([True, False])
def test_deleting_custom_tokens(rotkehlchen_api_server): """Test that the endpoint for deleting a custom ethereum token works""" token0_id = ETHEREUM_DIRECTIVE + INITIAL_TOKENS[0].address token1_id = ETHEREUM_DIRECTIVE + INITIAL_TOKENS[1].address underlying1_id = ETHEREUM_DIRECTIVE + underlying_address1 underlying2_id = ETHEREUM_DIRECTIVE + underlying_address2 underlying3_id = ETHEREUM_DIRECTIVE + underlying_address3 cursor = GlobalDBHandler()._conn.cursor() # Make sure the equivalent assets we will delete exist in the DB result = cursor.execute( 'SELECT COUNT(*) from assets WHERE identifier IN (?, ?, ?, ?, ?)', (token0_id, token1_id, underlying1_id, underlying2_id, underlying3_id), ).fetchone()[0] assert result == 5 response = requests.delete( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': INITIAL_TOKENS[1].address}, ) result = assert_proper_response_with_result(response) assert result == {'identifier': token1_id} response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), ) result = assert_proper_response_with_result(response) expected_tokens = INITIAL_EXPECTED_TOKENS[:-1] expected_result = [x.serialize() for x in expected_tokens] assert_token_entry_exists_in_result(result, expected_result) # also check the mapping for the underlying still tokens exists result = cursor.execute( 'SELECT COUNT(*) from underlying_tokens_list').fetchone()[0] assert result == 3 # test that deleting a non existing address is handled properly non_existing_address = make_ethereum_address() response = requests.delete( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': non_existing_address}, ) expected_msg = ( f'Tried to delete ethereum token with address {non_existing_address} ' f'but it was not found in the DB', ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) # test that trying to delete an underlying token that exists in a mapping # of another token is handled correctly non_existing_address = make_ethereum_address() response = requests.delete( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': underlying_address1}, ) expected_msg = ( f'Tried to delete ethereum token with address {underlying_address1} ' f'but its deletion would violate a constraint so deletion failed') assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) # Check that the initial token of the test has MKR as swapped for token # this is just a sanity check as the fixture initialization should take care of it response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': INITIAL_TOKENS[0].address}, ) result = assert_proper_response_with_result(response) assert result['swapped_for'] == 'MKR' # test that trying to delete a token (MKR) that is used as swapped_for # of another token is handled correctly non_existing_address = make_ethereum_address() response = requests.delete( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': A_MKR.ethereum_address}, ) expected_msg = ( f'Tried to delete ethereum token with address {A_MKR.ethereum_address} ' f'but its deletion would violate a constraint so deletion failed') assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) # now test that deleting the token with underlying tokens works response = requests.delete( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': INITIAL_TOKENS[0].address}, ) result = assert_proper_response_with_result(response) assert result == {'identifier': token0_id} response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), ) result = assert_proper_response_with_result(response) expected_tokens = INITIAL_EXPECTED_TOKENS[1:-1] expected_result = [x.serialize() for x in expected_tokens] assert_token_entry_exists_in_result(result, expected_result) # and removes the mapping of all underlying tokens result = cursor.execute( 'SELECT COUNT(*) from underlying_tokens_list').fetchone()[0] assert result == 0 # and that the equivalent asset entries were also deleted result = cursor.execute( 'SELECT COUNT(*) from assets WHERE identifier IN (?, ?)', (token0_id, token1_id), ).fetchone()[0] assert result == 0
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)
def test_editing_custom_tokens(rotkehlchen_api_server): """Test that the endpoint for editing a custom ethereum token works""" new_token1 = INITIAL_TOKENS[0].serialize_all_info() del new_token1['identifier'] new_name = 'Edited token' new_symbol = 'ESMBL' new_protocol = 'curve' new_swapped_for = A_BAT.identifier new_token1['name'] = new_name new_token1['symbol'] = new_symbol new_token1['swapped_for'] = new_swapped_for new_token1['protocol'] = new_protocol response = requests.patch( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': new_token1}, ) result = assert_proper_response_with_result(response) token0_id = ETHEREUM_DIRECTIVE + INITIAL_TOKENS[0].ethereum_address assert result == {'identifier': token0_id} response = requests.get( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), ) result = assert_proper_response_with_result(response) expected_tokens = deepcopy(INITIAL_EXPECTED_TOKENS) object.__setattr__(expected_tokens[0], 'name', new_name) object.__setattr__(expected_tokens[0], 'symbol', new_symbol) object.__setattr__(expected_tokens[0], 'protocol', new_protocol) object.__setattr__(expected_tokens[0], 'swapped_for', A_BAT) expected_result = [x.serialize_all_info() for x in expected_tokens] assert_token_entry_exists_in_result(result, expected_result) # test that editing an non existing address is handled properly non_existing_token = INITIAL_TOKENS[0].serialize_all_info() del non_existing_token['identifier'] non_existing_address = make_ethereum_address() non_existing_token['address'] = non_existing_address response = requests.patch( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': non_existing_token}, ) expected_msg = ( f'Tried to edit non existing ethereum token with ' f'address {non_existing_address}', ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) # test that editing with an invalid coingecko identifier is handled bad_token = new_token1.copy() bad_identifier = 'INVALIDID' bad_token['coingecko'] = bad_identifier response = requests.patch( 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 editing with an invalid cryptocompare identifier is handled bad_token = new_token1.copy() bad_identifier = 'INVALIDID' bad_token['cryptocompare'] = bad_identifier response = requests.patch( 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 fixture_ethereum_accounts(number_of_eth_accounts) -> List[ChecksumEthAddress]: return [make_ethereum_address() for x in range(number_of_eth_accounts)]
def test_add_and_get_yearn_vault_events(data_dir, username): """Test that get yearn vault events works fine and returns only events for what we need""" msg_aggregator = MessagesAggregator() data = DataHandler(data_dir, msg_aggregator) data.unlock(username, '123', create_new=True) addr1 = make_ethereum_address() addr1_events = [YearnVaultEvent( event_type='deposit', from_asset=A_DAI, from_value=Balance(amount=FVal(1), usd_value=FVal(1)), to_asset=Asset('yDAI'), to_value=Balance(amount=FVal(1), usd_value=FVal(1)), realized_pnl=None, block_number=1, timestamp=Timestamp(1), tx_hash='0x01653e88600a6492ad6e9ae2af415c990e623479057e4e93b163e65cfb2d4436', log_index=1, ), YearnVaultEvent( event_type='withdraw', from_asset=Asset('yDAI'), from_value=Balance(amount=FVal(1), usd_value=FVal(1)), to_asset=A_DAI, to_value=Balance(amount=FVal(1), usd_value=FVal(1)), realized_pnl=Balance(amount=FVal('0.01'), usd_value=FVal('0.01')), block_number=2, timestamp=Timestamp(2), tx_hash='0x4147da3e5d3c0565a99192ce0b32182ab30b8e1067921d9b2a8ef3bd60b7e2ce', log_index=2, )] data.db.add_yearn_vaults_events(address=addr1, events=addr1_events) addr2 = make_ethereum_address() addr2_events = [YearnVaultEvent( event_type='deposit', from_asset=A_DAI, from_value=Balance(amount=FVal(1), usd_value=FVal(1)), to_asset=Asset('yDAI'), to_value=Balance(amount=FVal(1), usd_value=FVal(1)), realized_pnl=None, block_number=1, timestamp=Timestamp(1), tx_hash='0x8c094d58f33e8dedcd348cb33b58f3bd447602f1fecb99e51b1c2868029eab55', log_index=1, ), YearnVaultEvent( event_type='withdraw', from_asset=Asset('yDAI'), from_value=Balance(amount=FVal(1), usd_value=FVal(1)), to_asset=A_DAI, to_value=Balance(amount=FVal(1), usd_value=FVal(1)), realized_pnl=Balance(amount=FVal('0.01'), usd_value=FVal('0.01')), block_number=2, timestamp=Timestamp(2), tx_hash='0x58c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=2, )] data.db.add_yearn_vaults_events(address=addr2, events=addr2_events) events = data.db.get_yearn_vaults_events(address=addr1, vault=YEARN_VAULTS['yDAI']) assert events == addr1_events events = data.db.get_yearn_vaults_events(address=addr2, vault=YEARN_VAULTS['yDAI']) assert events == addr2_events
def test_add_and_get_aave_events(data_dir, username): """Test that get aave events works fine and returns only events for what we need""" msg_aggregator = MessagesAggregator() data = DataHandler(data_dir, msg_aggregator) data.unlock(username, '123', create_new=True) addr1 = make_ethereum_address() addr1_events = [AaveDepositWithdrawalEvent( event_type='deposit', asset=A_DAI, atoken=A_ADAI_V1, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=1, timestamp=Timestamp(1), tx_hash='0x01653e88600a6492ad6e9ae2af415c990e623479057e4e93b163e65cfb2d4436', log_index=1, ), AaveDepositWithdrawalEvent( event_type='withdrawal', asset=A_DAI, atoken=A_ADAI_V1, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=2, timestamp=Timestamp(2), tx_hash='0x4147da3e5d3c0565a99192ce0b32182ab30b8e1067921d9b2a8ef3bd60b7e2ce', log_index=2, )] data.db.add_aave_events(address=addr1, events=addr1_events) addr2 = make_ethereum_address() addr2_events = [AaveDepositWithdrawalEvent( event_type='deposit', asset=A_DAI, atoken=A_ADAI_V1, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=1, timestamp=Timestamp(1), tx_hash='0x8c094d58f33e8dedcd348cb33b58f3bd447602f1fecb99e51b1c2868029eab55', log_index=1, ), AaveDepositWithdrawalEvent( event_type='withdrawal', asset=A_DAI, atoken=A_ADAI_V1, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=2, timestamp=Timestamp(2), tx_hash='0x58c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=2, )] data.db.add_aave_events(address=addr2, events=addr2_events) # addr3 has all types of aave events so we test serialization/deserialization addr3 = make_ethereum_address() addr3_events = [AaveDepositWithdrawalEvent( event_type='deposit', asset=A_DAI, atoken=A_ADAI_V1, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=1, timestamp=Timestamp(1), tx_hash='0x9e394d58f33e8dedcd348cb33b58f3bd447602f1fecb99e51b1c2868029eab55', log_index=1, ), AaveDepositWithdrawalEvent( event_type='withdrawal', asset=A_DAI, atoken=A_ADAI_V1, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=2, timestamp=Timestamp(2), tx_hash='0x4c167445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=2, ), AaveInterestEvent( event_type='interest', asset=A_WBTC, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=4, timestamp=Timestamp(4), tx_hash='0x49c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=4, ), AaveBorrowEvent( event_type='borrow', asset=A_ETH, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=5, timestamp=Timestamp(5), tx_hash='0x19c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=5, borrow_rate_mode='stable', borrow_rate=FVal('0.05233232323423432'), accrued_borrow_interest=FVal('5.112234'), ), AaveRepayEvent( event_type='repay', asset=A_MANA, value=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=6, timestamp=Timestamp(6), tx_hash='0x29c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=6, fee=Balance(amount=FVal('0.1'), usd_value=FVal('0.1')), ), AaveLiquidationEvent( event_type='liquidation', collateral_asset=A_ETH, collateral_balance=Balance(amount=FVal(1), usd_value=FVal(1)), principal_asset=A_ETH, principal_balance=Balance(amount=FVal(1), usd_value=FVal(1)), block_number=7, log_index=7, timestamp=Timestamp(7), tx_hash='0x39c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', )] data.db.add_aave_events(address=addr3, events=addr3_events) events = data.db.get_aave_events(address=addr1, atoken=A_ADAI_V1) assert events == addr1_events events = data.db.get_aave_events(address=addr2, atoken=A_ADAI_V1) assert events == addr2_events events = data.db.get_aave_events(address=addr3) assert events == addr3_events # check that all aave events are properly hashable (aka can go in a set) test_set = set() for event in addr3_events: test_set.add(event) assert len(test_set) == len(addr3_events)
def mock_etherscan_for_dsr( etherscan: Etherscan, account1: ChecksumEthAddress, account2: ChecksumEthAddress, original_requests_get, params: DSRMockParameters, ) -> _patch: proxy1 = make_ethereum_address() proxy2 = make_ethereum_address() account1_join1_event = f"""{{"address": "{MAKERDAO_POT_ADDRESS}", "topics": ["0x049878f300000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(params.account1_join1_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0xwedontcare", "blockNumber": "{hex(params.account1_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join1_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontacre", "transactionIndex": "0x79"}}""" # noqa: E501 account1_join1_deposit = params.account1_join1_normalized_balance * params.account1_join1_chi account1_join1_move_event = f"""{{"address": "{MAKERDAO_VAT_ADDRESS}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{MAKERDAO_POT_ADDRESS}", "{int_to_32byteshexstr(account1_join1_deposit)}"], "data": "0xwedontcare", "blockNumber": "{hex(params.account1_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join1_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontcare", "transactionIndex": "0x79"}}""" # noqa: E501 account1_join2_event = f"""{{"address": "{MAKERDAO_POT_ADDRESS}", "topics": ["0x049878f300000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(params.account1_join2_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0xwedontcare", "blockNumber": "{hex(params.account1_join2_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join2_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontacre", "transactionIndex": "0x79"}}""" # noqa: E501 account1_join2_deposit = params.account1_join2_normalized_balance * params.account1_join2_chi account1_join2_move_event = f"""{{"address": "{MAKERDAO_VAT_ADDRESS}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{MAKERDAO_POT_ADDRESS}", "{int_to_32byteshexstr(account1_join2_deposit)}"], "data": "0xwedontcare", "blockNumber": "{hex(params.account1_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join2_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontcare", "transactionIndex": "0x79"}}""" # noqa: E501 account1_exit1_event = f"""{{"address": "{MAKERDAO_POT_ADDRESS}", "topics": ["0x7f8661a100000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(params.account1_exit1_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0xwedontcare", "blockNumber": "{hex(params.account1_exit1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_exit1_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontacre", "transactionIndex": "0x79"}}""" # noqa: E501 account1_exit1_withdrawal = (params.account1_exit1_normalized_balance * params.account1_exit1_chi) account1_exit1_move_event = f"""{{"address": "{MAKERDAO_VAT_ADDRESS}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{MAKERDAO_POT_ADDRESS}", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(account1_exit1_withdrawal)}"], "data": "0xwedontcare", "blockNumber": "{hex(params.account1_exit1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_exit1_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontcare", "transactionIndex": "0x79"}}""" # noqa: E501 account2_join1_event = f"""{{"address": "{MAKERDAO_POT_ADDRESS}", "topics": ["0x049878f300000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy2)}", "{int_to_32byteshexstr(params.account2_join1_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0xwedontcare", "blockNumber": "{hex(params.account2_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account2_join1_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontacre", "transactionIndex": "0x79"}}""" # noqa: E501 account2_join1_deposit = params.account2_join1_normalized_balance * params.account2_join1_chi account2_join1_move_event = f"""{{"address": "{MAKERDAO_VAT_ADDRESS}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy2)}", "{MAKERDAO_POT_ADDRESS}", "{int_to_32byteshexstr(account2_join1_deposit)}"], "data": "0xwedontcare", "blockNumber": "{hex(params.account2_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account2_join1_blocknumber))}", "gasPrice": "dontcare", "gasUsed": "dontcare", "logIndex": "0x6c", "transactionHash": "dontcare", "transactionIndex": "0x79"}}""" # noqa: E501 def mock_requests_get(url, *args, **kwargs): if 'etherscan.io/api?module=proxy&action=eth_blockNumber' in url: response = f'{{"status":"1","message":"OK","result":"{TEST_LATEST_BLOCKNUMBER_HEX}"}}' elif 'etherscan.io/api?module=proxy&action=eth_call' in url: to_address = url.split( 'https://api.etherscan.io/api?module=proxy&action=eth_call&to=', )[1][:42] input_data = url.split('data=')[1].split('&apikey')[0] if to_address == MAKERDAO_PROXY_REGISTRY_ADDRESS: if not input_data.startswith('0xc4552791'): raise AssertionError( 'Call to unexpected method of DSR ProxyRegistry during tests', ) # It's a call to proxy registry. Return the mapping if account1[2:].lower() in input_data: proxy_account = address_to_32byteshexstr(proxy1) elif account2[2:].lower() in input_data: proxy_account = address_to_32byteshexstr(proxy2) else: proxy_account = '0x' + '0' * 64 response = f'{{"status":"1","message":"OK","result":"{proxy_account}"}}' elif to_address == MAKERDAO_POT_ADDRESS: if input_data.startswith('0x0bebac86'): # pie if proxy1[2:].lower() in input_data: result = int_to_32byteshexstr( params.account1_current_normalized_balance) elif proxy2[2:].lower() in input_data: result = int_to_32byteshexstr( params.account2_current_normalized_balance) else: # result = int_to_32byteshexstr(0) raise AssertionError( 'Pie call for unexpected account during tests') elif input_data.startswith('0xc92aecc4'): # chi result = int_to_32byteshexstr(params.current_chi) elif input_data.startswith('0x487bf082'): # dsr result = int_to_32byteshexstr(params.current_dsr) else: raise AssertionError( 'Call to unexpected method of MakerDAO pot during tests', ) response = f'{{"status":"1","message":"OK","result":"{result}"}}' else: raise AssertionError( f'Etherscan call to unknown contract {to_address} during tests', ) elif 'etherscan.io/api?module=logs&action=getLogs' in url: contract_address = url.split('&address=')[1].split('&topic0')[0] topic0 = url.split('&topic0=')[1].split('&topic0_1')[0] topic1 = url.split('&topic1=')[1].split('&topic1_2')[0] topic2 = None if '&topic2=' in url: topic2 = url.split('&topic2=')[1].split('&')[0] from_block = int(url.split('&fromBlock=')[1].split('&')[0]) to_block = int(url.split('&toBlock=')[1].split('&')[0]) if contract_address == MAKERDAO_POT_ADDRESS: if topic0.startswith('0x049878f3'): # join events = [] if proxy1[2:].lower() in topic1: if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_event) elif proxy2[2:].lower() in topic1: if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_event) else: raise AssertionError( f'Etherscan log query to makerdao POT contract for ' f'join for unknown account {topic1}', ) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' elif topic0.startswith('0x7f8661a1'): # exit events = [] if proxy1[2:].lower() in topic1: if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_event) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( 'Etherscan unknown log query to makerdao POT contract') elif contract_address == MAKERDAO_VAT_ADDRESS: if topic0.startswith('0xbb35783b'): # move events = [] if proxy1[2:].lower() in topic1: # deposit from acc1 if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_move_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_move_event) elif proxy2[2:].lower() in topic1: # deposit from acc2 if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_move_event) elif proxy1[2:].lower() in topic2: # withdrawal from acc1 if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_move_event) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( 'Etherscan unknown log query to makerdao VAT contract') else: raise AssertionError( f'Etherscan getLogs call to unknown contract {contract_address} during tests', ) else: return original_requests_get(url, *args, **kwargs) return MockResponse(200, response) return patch.object(etherscan.session, 'get', wraps=mock_requests_get)
def test_adding_custom_tokens(rotkehlchen_api_server): """Test that the endpoint for adding a custom ethereum token works""" response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': CUSTOM_TOKEN3.serialize()}, ) result = assert_proper_response_with_result(response) assert result == {'identifier': ETHEREUM_DIRECTIVE + CUSTOM_TOKEN3.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, CustomEthereumToken(address=underlying_address4), ] expected_result = [x.serialize() for x in expected_tokens] assert_token_entry_exists_in_result(result, expected_result) # test that adding an already existing address is handled properly response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': INITIAL_TOKENS[1].serialize()}, ) expected_msg = ( f'Ethereum token with address {INITIAL_TOKENS[1].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 = CustomEthereumToken( 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')), ], ) response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': bad_token.serialize()}, ) expected_msg = ( f'The sum of underlying token weights for {bad_token.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 = CustomEthereumToken( 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')), ], ) response = requests.put( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'token': bad_token.serialize()}, ) expected_msg = ( f'The sum of underlying token weights for {bad_token.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 = CustomEthereumToken( address=make_ethereum_address(), decimals=18, name='foo', symbol='BBB', underlying_tokens=[], ) serialized_bad_token = bad_token.serialize() 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.address}') assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.BAD_REQUEST, )
def test_eth2_deposits_serialization(): addr1 = make_ethereum_address() addr2 = make_ethereum_address() deposits = [ Eth2Deposit( from_address=addr1, pubkey= '0xb016e31f633a21fbe42a015152399361184f1e2c0803d89823c224994af74a561c4ad8cfc94b18781d589d03e952cd5b', # noqa: E501 withdrawal_credentials= '0x004c7691c2085648f394ffaef851f3b1d51b95f7263114bc923fc5338f5fc499', # noqa: E501 value=Balance(FVal(32), FVal(64)), tx_hash=deserialize_evm_tx_hash( '0xd9eca1c2a0c5ff2f25071713432b21cc4d0ff2e8963edc63a48478e395e08db1' ), # noqa: E501 tx_index=22, timestamp=Timestamp(int(1604506685)), ), Eth2Deposit( from_address=addr2, pubkey= '0xa8ff5fc88412d080a297683c25a791ef77eb52d75b265fabab1f2c2591bb927c35818ac6289bc6680ab252787d0ebab3', # noqa: E501 withdrawal_credentials= '0x00cfe1c10347d642a8b8daf86d23bcb368076972691445de2cf517ff43765817', # noqa: E501 value=Balance(FVal(32), FVal(64)), tx_hash=deserialize_evm_tx_hash( '0x6905f4d1843fb8c003c1fbbc2c8e6c5f9792f4f44ddb1122553412ee0b128da7' ), # noqa: E501 tx_index=221, timestamp=Timestamp(int(1605043544)), ), ] serialized = process_result_list(deposits) assert serialized == [ { 'from_address': addr1, 'pubkey': '0xb016e31f633a21fbe42a015152399361184f1e2c0803d89823c224994af74a561c4ad8cfc94b18781d589d03e952cd5b', # noqa: E501 'withdrawal_credentials': '0x004c7691c2085648f394ffaef851f3b1d51b95f7263114bc923fc5338f5fc499', # noqa: E501 'value': { 'amount': '32', 'usd_value': '64' }, 'tx_hash': '0xd9eca1c2a0c5ff2f25071713432b21cc4d0ff2e8963edc63a48478e395e08db1', 'tx_index': 22, 'timestamp': 1604506685, }, { 'from_address': addr2, 'pubkey': '0xa8ff5fc88412d080a297683c25a791ef77eb52d75b265fabab1f2c2591bb927c35818ac6289bc6680ab252787d0ebab3', # noqa: E501 'withdrawal_credentials': '0x00cfe1c10347d642a8b8daf86d23bcb368076972691445de2cf517ff43765817', # noqa: E501 'value': { 'amount': '32', 'usd_value': '64' }, 'tx_hash': '0x6905f4d1843fb8c003c1fbbc2c8e6c5f9792f4f44ddb1122553412ee0b128da7', 'tx_index': 221, 'timestamp': 1605043544, }, ]
def mock_etherscan_for_dsr( etherscan: Etherscan, account1: ChecksumEthAddress, account2: ChecksumEthAddress, account3: ChecksumEthAddress, original_requests_get, params: DSRMockParameters, ) -> _patch: proxy1 = make_ethereum_address() proxy2 = make_ethereum_address() targets = [ address_to_32byteshexstr(proxy1), address_to_32byteshexstr(proxy2), '0x0000000000000000000000000000000000000000000000000000000000000000' ] # noqa: E501 sorted_accounts = sorted(zip([account1, account2, account3], targets), key=lambda x: x[0]) proxies = [y[1] for y in sorted_accounts] account1_join1_event = f"""{{"address": "{MAKERDAO_POT.address}", "topics": ["0x049878f300000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(params.account1_join1_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0x1", "blockNumber": "{hex(params.account1_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join1_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account1_join1_deposit = params.account1_join1_normalized_balance * params.account1_join1_chi account1_join1_move_event = f"""{{"address": "{MAKERDAO_VAT.address}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{MAKERDAO_POT.address}", "{int_to_32byteshexstr(account1_join1_deposit // 10 ** 27)}"], "data": "0x1", "blockNumber": "{hex(params.account1_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join1_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account1_join2_event = f"""{{"address": "{MAKERDAO_POT.address}", "topics": ["0x049878f300000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(params.account1_join2_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0x1", "blockNumber": "{hex(params.account1_join2_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join2_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account1_join2_deposit = params.account1_join2_normalized_balance * params.account1_join2_chi account1_join2_move_event = f"""{{"address": "{MAKERDAO_VAT.address}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{MAKERDAO_POT.address}", "{int_to_32byteshexstr(account1_join2_deposit // 10 ** 27)}"], "data": "0x1", "blockNumber": "{hex(params.account1_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_join2_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account1_exit1_event = f"""{{"address": "{MAKERDAO_POT.address}", "topics": ["0x7f8661a100000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(params.account1_exit1_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0x1", "blockNumber": "{hex(params.account1_exit1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_exit1_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account1_exit1_withdrawal = (params.account1_exit1_normalized_balance * params.account1_exit1_chi) account1_exit1_move_event = f"""{{"address": "{MAKERDAO_VAT.address}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{MAKERDAO_POT.address}", "{address_to_32byteshexstr(proxy1)}", "{int_to_32byteshexstr(account1_exit1_withdrawal // 10 ** 27)}"], "data": "0x1", "blockNumber": "{hex(params.account1_exit1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account1_exit1_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account2_join1_event = f"""{{"address": "{MAKERDAO_POT.address}", "topics": ["0x049878f300000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy2)}", "{int_to_32byteshexstr(params.account2_join1_normalized_balance)}", "0x0000000000000000000000000000000000000000000000000000000000000000"], "data": "0x1", "blockNumber": "{hex(params.account2_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account2_join1_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0xf00", "transactionIndex": "0x79"}}""" # noqa: E501 account2_join1_deposit = params.account2_join1_normalized_balance * params.account2_join1_chi account2_join1_move_event = f"""{{"address": "{MAKERDAO_VAT.address}", "topics": ["0xbb35783b00000000000000000000000000000000000000000000000000000000", "{address_to_32byteshexstr(proxy2)}", "{MAKERDAO_POT.address}", "{int_to_32byteshexstr(account2_join1_deposit // 10 ** 27)}"], "data": "0x1", "blockNumber": "{hex(params.account2_join1_blocknumber)}", "timeStamp": "{hex(blocknumber_to_timestamp(params.account2_join1_blocknumber))}", "gasPrice": "0x1", "gasUsed": "0x1", "logIndex": "0x6c", "transactionHash": "0fx00", "transactionIndex": "0x79"}}""" # noqa: E501 # Not sure how to convince pylint that this ChecksumEthAddress IS a subscriptable object proxy1_contents = proxy1[2:].lower() # pylint: disable=unsubscriptable-object proxy2_contents = proxy2[2:].lower() # pylint: disable=unsubscriptable-object def mock_requests_get(url, *args, **kwargs): if 'etherscan.io/api?module=proxy&action=eth_blockNumber' in url: response = f'{{"status":"1","message":"OK","result":"{TEST_LATEST_BLOCKNUMBER_HEX}"}}' elif 'etherscan.io/api?module=proxy&action=eth_call' in url: to_address = url.split( 'https://api.etherscan.io/api?module=proxy&action=eth_call&to=', )[1][:42] input_data = url.split('data=')[1].split('&apikey')[0] if to_address == DS_PROXY_REGISTRY.address: if not input_data.startswith('0xc4552791'): raise AssertionError( 'Call to unexpected method of DSR ProxyRegistry during tests', ) # It's a call to proxy registry. Return the mapping if account1[2:].lower() in input_data: proxy_account = address_to_32byteshexstr(proxy1) elif account2[2:].lower() in input_data: proxy_account = address_to_32byteshexstr(proxy2) else: proxy_account = '0x' + '0' * 64 response = f'{{"status":"1","message":"OK","result":"{proxy_account}"}}' elif to_address == ETH_MULTICALL.address: web3 = Web3() contract = web3.eth.contract(address=ETH_MULTICALL.address, abi=ETH_MULTICALL.abi) data = url.split('data=')[1] if '&apikey' in data: data = data.split('&apikey')[0] fn_abi = contract.functions.abi[1] output_types = get_abi_output_types(fn_abi) args = [1, proxies] result = '0x' + web3.codec.encode_abi(output_types, args).hex() response = f'{{"status":"1","message":"OK","result":"{result}"}}' elif to_address == MAKERDAO_POT.address: if input_data.startswith('0x0bebac86'): # pie if proxy1_contents in input_data: result = int_to_32byteshexstr( params.account1_current_normalized_balance) elif proxy2_contents in input_data: result = int_to_32byteshexstr( params.account2_current_normalized_balance) else: # result = int_to_32byteshexstr(0) raise AssertionError( 'Pie call for unexpected account during tests') elif input_data.startswith('0xc92aecc4'): # chi result = int_to_32byteshexstr(params.current_chi) elif input_data.startswith('0x487bf082'): # dsr result = int_to_32byteshexstr(params.current_dsr) else: raise AssertionError( 'Call to unexpected method of MakerDao pot during tests', ) response = f'{{"status":"1","message":"OK","result":"{result}"}}' else: raise AssertionError( f'Etherscan call to unknown contract {to_address} during tests', ) elif 'etherscan.io/api?module=logs&action=getLogs' in url: contract_address = url.split('&address=')[1].split('&topic0')[0] topic0 = url.split('&topic0=')[1].split('&topic0_1')[0] topic1 = url.split('&topic1=')[1].split('&topic1_2')[0] topic2 = None if '&topic2=' in url: topic2 = url.split('&topic2=')[1].split('&')[0] from_block = int(url.split('&fromBlock=')[1].split('&')[0]) to_block = int(url.split('&toBlock=')[1].split('&')[0]) if contract_address == MAKERDAO_POT.address: if topic0.startswith('0x049878f3'): # join events = [] if proxy1_contents in topic1: if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_event) elif proxy2_contents in topic1: if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_event) else: raise AssertionError( f'Etherscan log query to makerdao POT contract for ' f'join for unknown account {topic1}', ) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' elif topic0.startswith('0x7f8661a1'): # exit events = [] if proxy1_contents in topic1: if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_event) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( 'Etherscan unknown log query to makerdao POT contract') elif contract_address == MAKERDAO_VAT.address: if topic0.startswith('0xbb35783b'): # move events = [] if proxy1_contents in topic1: # deposit from acc1 if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_move_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_move_event) elif proxy2_contents in topic1: # deposit from acc2 if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_move_event) elif proxy1_contents in topic2: # withdrawal from acc1 if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_move_event) response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( 'Etherscan unknown log query to makerdao VAT contract') elif contract_address == MAKERDAO_DAI_JOIN.address: events = [] if topic0.startswith('0x3b4da69f'): # join if proxy1_contents in topic1: # deposit from acc1 if from_block <= params.account1_join1_blocknumber <= to_block: events.append(account1_join1_move_event) if from_block <= params.account1_join2_blocknumber <= to_block: events.append(account1_join2_move_event) elif proxy2_contents in topic1: # deposit from acc2 if from_block <= params.account2_join1_blocknumber <= to_block: events.append(account2_join1_move_event) elif topic0.startswith('0xef693bed'): # exit if from_block <= params.account1_exit1_blocknumber <= to_block: events.append(account1_exit1_move_event) else: raise AssertionError( 'Etherscan unknown call to makerdao DAIJOIN contract') response = f'{{"status":"1","message":"OK","result":[{",".join(events)}]}}' else: raise AssertionError( f'Etherscan getLogs call to unknown contract {contract_address} during tests', ) else: return original_requests_get(url, *args, **kwargs) return MockResponse(200, response) return patch.object(etherscan.session, 'get', wraps=mock_requests_get)
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_add_blockchain_accounts_async( rotkehlchen_api_server, ethereum_accounts, btc_accounts, number_of_eth_accounts, ): """A simpler version of the above test for adding blockchain accounts for async The main purpose of this test is to see that querying the endpoint asynchronously also works""" # Disable caching of query results rotki = rotkehlchen_api_server.rest_api.rotkehlchen rotki.blockchain.cache_ttl_secs = 0 # Test by having balances queried before adding an account eth_balances = ['1000000', '2000000'] token_balances = {'RDN': ['0', '4000000']} setup = setup_balances( rotki, ethereum_accounts=ethereum_accounts, btc_accounts=btc_accounts, eth_balances=eth_balances, token_balances=token_balances, ) new_eth_accounts = [make_ethereum_address(), make_ethereum_address()] all_eth_accounts = ethereum_accounts + new_eth_accounts eth_balances = ['1000000', '2000000', '3000000', '4000000'] token_balances = {'RDN': ['0', '4000000', '0', '250000000']} setup = setup_balances( rotki, ethereum_accounts=all_eth_accounts, btc_accounts=btc_accounts, eth_balances=eth_balances, token_balances=token_balances, ) # The application has started only with 2 ethereum accounts. Let's add two more with setup.etherscan_patch: response = requests.put(api_url_for( rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='ETH', ), json={ 'accounts': new_eth_accounts, 'async_query': True }) task_id = assert_ok_async_response(response) outcome = wait_for_async_task(rotkehlchen_api_server, task_id) assert_eth_balances_result( rotki=rotki, json_data=outcome, eth_accounts=all_eth_accounts, eth_balances=setup.eth_balances, token_balances=setup.token_balances, also_btc=False, # All blockchain assets have not been queried yet ) # Also make sure they are added in the DB accounts = rotki.data.db.get_blockchain_accounts() assert len(accounts.eth) == 4 assert all(acc in accounts.eth for acc in all_eth_accounts) assert len(accounts.btc) == 2 assert all(acc in accounts.btc for acc in btc_accounts) # Now try to query all balances to make sure the result is the stored with setup.etherscan_patch, setup.bitcoin_patch: response = requests.get( api_url_for( rotkehlchen_api_server, "blockchainbalancesresource", )) assert_proper_response(response) json_data = response.json() assert json_data['message'] == '' assert_eth_balances_result( rotki=rotki, json_data=json_data, eth_accounts=all_eth_accounts, eth_balances=setup.eth_balances, token_balances=setup.token_balances, also_btc=True, ) # Now we will try to add a new BTC account. Setup the mocking infrastructure again all_btc_accounts = btc_accounts + [UNIT_BTC_ADDRESS3] setup = setup_balances( rotki, ethereum_accounts=all_eth_accounts, btc_accounts=all_btc_accounts, eth_balances=eth_balances, token_balances=token_balances, btc_balances=['3000000', '5000000', '600000000'], ) # add the new BTC account with setup.bitcoin_patch: response = requests.put(api_url_for( rotkehlchen_api_server, "blockchainsaccountsresource", blockchain='BTC', ), json={ 'accounts': [UNIT_BTC_ADDRESS3], 'async_query': True }) task_id = assert_ok_async_response(response) outcome = wait_for_async_task(rotkehlchen_api_server, task_id) assert_btc_balances_result( json_data=outcome, btc_accounts=all_btc_accounts, btc_balances=setup.btc_balances, also_eth=True, ) # Also make sure it's added in the DB accounts = rotki.data.db.get_blockchain_accounts() assert len(accounts.eth) == 4 assert all(acc in accounts.eth for acc in all_eth_accounts) assert len(accounts.btc) == 3 assert all(acc in accounts.btc for acc in all_btc_accounts) # Now try to query all balances to make sure the result is also stored with setup.etherscan_patch, setup.bitcoin_patch: response = requests.get( api_url_for( rotkehlchen_api_server, "blockchainbalancesresource", )) assert_proper_response(response) json_data = response.json() assert json_data['message'] == '' assert_btc_balances_result( json_data=json_data, btc_accounts=all_btc_accounts, btc_balances=setup.btc_balances, also_eth=True, )