def asset_from_uphold(symbol: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(symbol, str): raise DeserializationError( f'Got non-string type {type(symbol)} for uphold asset') name = UPHOLD_TO_WORLD.get(symbol, symbol) return symbol_to_asset_or_token(name)
def deserialize_timestamp_from_date(date: str, formatstr: str, location: str) -> Timestamp: """Deserializes a timestamp from a date entry depending on the format str Can throw DeserializationError if the data is not as expected """ if not date: raise DeserializationError( f'Failed to deserialize a timestamp from a null entry in {location}', ) if not isinstance(date, str): raise DeserializationError( f'Failed to deserialize a timestamp from a {type(date)} entry in {location}', ) try: return Timestamp(create_timestamp(datestr=date, formatstr=formatstr)) except ValueError: raise DeserializationError( f'Failed to deserialize {date} {location} timestamp entry')
def deserialize_timestamp_from_date( date: Optional[str], formatstr: str, location: str, skip_milliseconds: bool = False, ) -> Timestamp: """Deserializes a timestamp from a date entry depending on the format str formatstr can also have a special value of 'iso8601' in which case the iso8601 function will be used. Can throw DeserializationError if the data is not as expected """ if not date: raise DeserializationError( f'Failed to deserialize a timestamp from a null entry in {location}', ) if not isinstance(date, str): raise DeserializationError( f'Failed to deserialize a timestamp from a {type(date)} entry in {location}', ) if skip_milliseconds: # Seems that poloniex added milliseconds in their timestamps. # https://github.com/rotki/rotki/issues/1631 # We don't deal with milliseconds in Rotki times so we can safely remove it splits = date.split('.', 1) if len(splits) == 2: date = splits[0] if formatstr == 'iso8601': return iso8601ts_to_timestamp(date) try: return Timestamp(create_timestamp(datestr=date, formatstr=formatstr)) except ValueError as e: raise DeserializationError( f'Failed to deserialize {date} {location} timestamp entry', ) from e
def _deserialize_bond( self, raw_event: Dict[str, Any], identity_address_map: Dict[ChecksumAddress, ChecksumAddress], ) -> Bond: """Deserialize a bond event. May raise DeserializationError. """ try: adex_event = self._deserialize_adex_staking_event( raw_event=raw_event, identity_address_map=identity_address_map, case='bond', ) amount_int = int(raw_event['amount']) amount = FVal(raw_event['amount']) / ADX_AMOUNT_MANTISSA pool_id = raw_event['poolId'] nonce = int(raw_event['nonce']) bond_id = self._get_bond_id( identity_address=adex_event.identity_address, amount=amount_int, pool_id=pool_id, nonce=nonce, ) slashed_at = deserialize_timestamp(raw_event['slashedAtStart']) except (DeserializationError, KeyError, ValueError) as e: msg = str(e) if isinstance(e, KeyError): msg = f'Missing key in event: {msg}.' log.error( 'Failed to deserialize an AdEx bond event', error=msg, raw_event=raw_event, identity_address_map=identity_address_map, ) raise DeserializationError( 'Failed to deserialize an AdEx bond event. Check logs for more details', ) from e return Bond( tx_hash=adex_event.tx_hash, address=adex_event.address, identity_address=adex_event.identity_address, timestamp=adex_event.timestamp, bond_id=bond_id, value=Balance(amount=amount), pool_id=pool_id, nonce=nonce, slashed_at=slashed_at, )
def _deserialize_asset_movement( raw_movement: Dict[str, Any], ) -> AssetMovement: """Process a deposit/withdrawal user transaction from Bitstamp and deserialize it. Can raise DeserializationError. From Bitstamp documentation, deposits/withdrawals can have a fee (the amount is expected to be in the currency involved) https://www.bitstamp.net/fee-schedule/ Endpoint docs: https://www.bitstamp.net/api/#user-transactions """ type_ = deserialize_int_from_str(raw_movement['type'], 'bitstamp asset movement') category: AssetMovementCategory if type_ == 0: category = AssetMovementCategory.DEPOSIT elif type_ == 1: category = AssetMovementCategory.WITHDRAWAL else: raise AssertionError(f'Unexpected Bitstamp asset movement case: {type_}.') timestamp = deserialize_timestamp_from_bitstamp_date(raw_movement['datetime']) amount: FVal fee_asset: Asset for symbol in BITSTAMP_ASSET_MOVEMENT_SYMBOLS: amount = deserialize_asset_amount(raw_movement.get(symbol, '0')) if amount != ZERO: fee_asset = Asset(symbol) break if amount == ZERO: raise DeserializationError( 'Could not deserialize Bitstamp asset movement from user transaction. ' f'Unexpected asset amount combination found in: {raw_movement}.', ) asset_movement = AssetMovement( timestamp=timestamp, location=Location.BITSTAMP, category=category, address=None, # requires query "crypto_transactions" endpoint transaction_id=None, # requires query "crypto_transactions" endpoint asset=fee_asset, amount=abs(amount), fee_asset=fee_asset, fee=deserialize_fee(raw_movement['fee']), link=str(raw_movement['id']), ) return asset_movement
def deserialize_int_from_hex(symbol: str, location: str) -> int: """Takes a hex string and turns it into an integer. Some apis returns 0x as a hex int and this may be an error. So we handle this as return 0 here. May Raise: - DeserializationError if the given data are in an unexpected format. """ if not isinstance(symbol, str): raise DeserializationError( 'Expected hex string but got {type(symbol)} at {location}') if symbol == '0x': return 0 try: result = int(symbol, 16) except ValueError: raise DeserializationError( f'Could not turn string "{symbol}" into an integer at {location}', ) return result
def deserialize(cls: Type['TradeType'], symbol: str) -> 'TradeType': """Overriding deserialize here since it can have different wordings for the same type so the automatic deserialization does not work """ if not isinstance(symbol, str): raise DeserializationError( f'Failed to deserialize trade type symbol from {type(symbol)} entry', ) if symbol in ('buy', 'LIMIT_BUY', 'BUY', 'Buy'): return TradeType.BUY if symbol in ('sell', 'LIMIT_SELL', 'SELL', 'Sell'): return TradeType.SELL if symbol in ('settlement_buy', 'settlement buy'): return TradeType.SETTLEMENT_BUY if symbol in ('settlement_sell', 'settlement sell'): return TradeType.SETTLEMENT_SELL # else raise DeserializationError( f'Failed to deserialize trade type symbol. Unknown symbol {symbol} for trade type', )
def asset_from_binance(binance_name: str) -> Asset: if not isinstance(binance_name, str): raise DeserializationError( f'Got non-string type {type(binance_name)} for binance asset') if binance_name in UNSUPPORTED_BINANCE_ASSETS: raise UnsupportedAsset(binance_name) if binance_name in RENAMED_BINANCE_ASSETS: return Asset(RENAMED_BINANCE_ASSETS[binance_name]) name = BINANCE_TO_WORLD.get(binance_name, binance_name) return Asset(name)
def deserialize_blocknumber(symbol: Union[str, int]) -> int: """Takes a block number value which can either be an int or a hex string and turns it into an integer block number value May Raise: - DeserializationError if the given data are in an unexpected format. """ if isinstance(symbol, int): block_number = symbol elif isinstance(symbol, str): try: block_number = int(symbol, 16) except ValueError: raise DeserializationError( f'Could not turn string "{symbol}" into an integer block number', ) else: raise DeserializationError( f'Unexpected type {type(symbol)} given to deserialize_blocknumber()', ) return block_number
def deserialize_trade_type_from_db(symbol: str) -> TradeType: """Takes a string from the DB and attempts to turn it into a TradeType Can throw DeserializationError if the symbol is not as expected """ if not isinstance(symbol, str): raise DeserializationError( f'Failed to deserialize trade type symbol from {type(symbol)} entry', ) if symbol == 'A': return TradeType.BUY elif symbol == 'B': return TradeType.SELL elif symbol == 'C': return TradeType.SETTLEMENT_BUY elif symbol == 'D': return TradeType.SETTLEMENT_SELL else: raise DeserializationError( f'Failed to deserialize trade type symbol. Unknown DB symbol {symbol} for trade type', )
def deserialize_timestamp(timestamp: Union[int, str, FVal]) -> Timestamp: """Deserializes a timestamp from a json entry. Given entry can either be a string or an int. Can throw DeserializationError if the data is not as expected """ if timestamp is None: raise DeserializationError( 'Failed to deserialize a timestamp entry from a null entry') if isinstance(timestamp, int): processed_timestamp = Timestamp(timestamp) elif isinstance(timestamp, FVal): try: processed_timestamp = Timestamp(timestamp.to_int(exact=True)) except ConversionError: # An fval was not representing an exact int raise DeserializationError( 'Tried to deserialize a timestamp fron a non-exact int FVal entry', ) elif isinstance(timestamp, str): try: processed_timestamp = Timestamp(int(timestamp)) except ValueError: # String could not be turned to an int raise DeserializationError( f'Failed to deserialize a timestamp entry from string {timestamp}', ) else: raise DeserializationError( f'Failed to deserialize a timestamp entry. Unexpected type {type(timestamp)} given', ) if processed_timestamp < 0: raise DeserializationError( f'Failed to deserialize a timestamp entry. Timestamps can not have' f' negative values. Given value was {processed_timestamp}', ) return processed_timestamp
def deserialize_timestamp_from_binance(time: int) -> Timestamp: """Deserializes a timestamp from a binance api query result entry Kraken has timestamps in integer but also including milliseconds Can throw DeserializationError if the data is not as expected """ if not isinstance(time, int): raise DeserializationError( f'Failed to deserialize a timestamp entry from a {type(time)} entry in binance', ) return Timestamp(int(time / 1000))
def deserialize_trade_type(symbol: str) -> TradeType: """Takes a string and attempts to turn it into a TradeType Can throw DeserializationError if the symbol is not as expected """ if not isinstance(symbol, str): raise DeserializationError( f'Failed to deserialize trade type symbol from {type(symbol)} entry', ) if symbol in ('buy', 'LIMIT_BUY', 'BUY', 'Buy'): return TradeType.BUY elif symbol in ('sell', 'LIMIT_SELL', 'SELL', 'Sell'): return TradeType.SELL elif symbol == 'settlement_buy': return TradeType.SETTLEMENT_BUY elif symbol == 'settlement_sell': return TradeType.SETTLEMENT_SELL else: raise DeserializationError( f'Failed to deserialize trade type symbol. Unknown symbol {symbol} for trade type', )
def asset_from_iconomi(symbol: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(symbol, str): raise DeserializationError(f'Got non-string type {type(symbol)} for iconomi asset') symbol = symbol.upper() if symbol in UNSUPPORTED_ICONOMI_ASSETS: raise UnsupportedAsset(symbol) name = ICONOMI_TO_WORLD.get(symbol, symbol) return symbol_to_asset_or_token(name)
def asset_from_cryptocom(cryptocom_name: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(cryptocom_name, str): raise DeserializationError( f'Got non-string type {type(cryptocom_name)} for cryptocom asset', ) symbol = CRYPTOCOM_TO_WORLD.get(cryptocom_name, cryptocom_name) return symbol_to_asset_or_token(symbol)
def hex_or_bytes_to_address(value: Union[bytes, str]) -> ChecksumEthAddress: """Turns a 32bit bytes/HexBytes or a hexstring into an address May raise: - DeserializationError if it can't convert a value to an int or if an unexpected type is given. """ try: hexstr = hex_or_bytes_to_str(value) except ConversionError as e: raise DeserializationError( f'Could not turn {value!r} to an ethereum address') from e return ChecksumEthAddress(to_checksum_address('0x' + hexstr[26:]))
def deserialize_fval( value: AcceptableFValInitInput, name: str, location: str, ) -> FVal: try: result = FVal(value) except ValueError as e: raise DeserializationError( f'Failed to deserialize value entry: {str(e)} for {name} during {location}' ) from e # noqa: E501 return result
def deserialize_ethereum_address(symbol: str) -> ChecksumEthAddress: """Deserialize a symbol, check that it's a valid ethereum address and return it checksummed. This function can raise DeserializationError if the address is not valid """ try: return to_checksum_address(symbol) except ValueError as e: raise DeserializationError( f'Invalid ethereum address: {symbol}', ) from e
def _get_trade_pair_data_from_transaction(raw_result: Dict[str, Any]) -> TradePairData: """Given a user transaction that contains the base and quote assets' symbol as keys, return the Bitstamp trade pair data (raw pair str, base/quote assets raw symbols, and TradePair). NB: any custom pair conversion (e.g. from Bitstamp asset symbol to world) should happen here. Can raise DeserializationError. """ try: pair = [key for key in raw_result.keys() if '_' in key and key != 'order_id'][0] except IndexError as e: raise DeserializationError( 'Could not deserialize Bitstamp trade pair from user transaction. ' f'Trade pair not found in: {raw_result}.', ) from e # NB: `pair_get_assets()` is not used for simplifying the calls and # storing the raw pair strings. base_asset_symbol, quote_asset_symbol = pair.split('_') try: base_asset = asset_from_bitstamp(base_asset_symbol) quote_asset = asset_from_bitstamp(quote_asset_symbol) except (UnknownAsset, UnsupportedAsset) as e: log.error(str(e)) asset_tag = 'Unknown' if isinstance(e, UnknownAsset) else 'Unsupported' raise DeserializationError( f'{asset_tag} {e.asset_name} found while processing trade pair.', ) from e return TradePairData( pair=pair, base_asset_symbol=base_asset_symbol, quote_asset_symbol=quote_asset_symbol, base_asset=base_asset, quote_asset=quote_asset, trade_pair=TradePair(f'{base_asset.identifier}_{quote_asset.identifier}'), )
def deserialize_from_db( cls, event_tuple: LiquidityPoolEventDBTuple, ) -> 'LiquidityPoolEvent': """Turns a tuple read from DB into an appropriate LiquidityPoolEvent. May raise a DeserializationError if something is wrong with the DB data Event_tuple index - Schema columns ---------------------------------- 0 - tx_hash 1 - log_index 2 - address 3 - timestamp 4 - type 5 - pool_address 6 - token0_identifier 7 - token1_identifier 8 - amount0 9 - amount1 10 - usd_price 11 - lp_amount """ db_event_type = event_tuple[4] if db_event_type not in {str(event_type) for event_type in EventType}: raise DeserializationError( f'Failed to deserialize event type. Unknown event: {db_event_type}.', ) if db_event_type == str(EventType.MINT): event_type = EventType.MINT elif db_event_type == str(EventType.BURN): event_type = EventType.BURN else: raise ValueError(f'Unexpected event type case: {db_event_type}.') token0 = deserialize_ethereum_token_from_db(identifier=event_tuple[6]) token1 = deserialize_ethereum_token_from_db(identifier=event_tuple[7]) return cls( tx_hash=event_tuple[0], log_index=event_tuple[1], address=string_to_ethereum_address(event_tuple[2]), timestamp=deserialize_timestamp(event_tuple[3]), event_type=event_type, pool_address=string_to_ethereum_address(event_tuple[5]), token0=token0, token1=token1, amount0=deserialize_asset_amount(event_tuple[8]), amount1=deserialize_asset_amount(event_tuple[9]), usd_price=deserialize_price(event_tuple[10]), lp_amount=deserialize_asset_amount(event_tuple[11]), )
def deserialize_action_type_from_db(symbol: str) -> ActionType: """Takes a string from the DB and attempts to turn it into an ActionType Can throw DeserializationError if the symbol is not as expected """ if not isinstance(symbol, str): raise DeserializationError( f'Failed to deserialize action type symbol from {type(symbol)} entry', ) if symbol == 'A': return ActionType.TRADE if symbol == 'B': return ActionType.ASSET_MOVEMENT if symbol == 'C': return ActionType.ETHEREUM_TX if symbol == 'D': return ActionType.LEDGER_ACTION # else raise DeserializationError( f'Failed to deserialize action type symbol. Unknown DB ' f'symbol {symbol} for trade type', )
def deserialize_optional_to_fval( value: Optional[AcceptableFValInitInput], name: str, location: str, ) -> FVal: """ Deserializes an FVal from a field that was optional and if None raises DeserializationError """ if value is None: raise DeserializationError( f'Failed to deserialize value entry for {name} during {location} since null was given', ) return deserialize_fval(value=value, name=name, location=location)
def __post_init__(self, form_with_incomplete_data: bool = False) -> None: super().__post_init__() # TODO: figure out a way to move this out. Moved in here due to cyclic imports from rotkehlchen.assets.resolver import AssetResolver # isort:skip # noqa: E501 # pylint: disable=import-outside-toplevel data = AssetResolver().get_asset_data(self.identifier) # pylint: disable=no-member if not data.ethereum_address: raise DeserializationError( 'Tried to initialize a non Ethereum asset as Ethereum Token', ) object.__setattr__(self, 'ethereum_address', data.ethereum_address) object.__setattr__(self, 'decimals', data.decimals)
def asset_from_bittrex(bittrex_name: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(bittrex_name, str): raise DeserializationError(f'Got non-string type {type(bittrex_name)} for bittrex asset') if bittrex_name in UNSUPPORTED_BITTREX_ASSETS: raise UnsupportedAsset(bittrex_name) name = BITTREX_TO_WORLD.get(bittrex_name, bittrex_name) return Asset(name)
def hexstr_to_int(value: str) -> int: """Turns a hexstring into an int May raise: - DeserializationError if it can't convert a value to an int or if an unexpected type is given. """ try: int_value = int(value, 16) except ValueError as e: raise DeserializationError( f'Could not convert string "{value}" to an int') from e return int_value
def asset_from_ftx(ftx_name: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(ftx_name, str): raise DeserializationError(f'Got non-string type {type(ftx_name)} for ftx asset') if ftx_name in UNSUPPORTED_FTX_ASSETS: raise UnsupportedAsset(ftx_name) name = FTX_TO_WORLD.get(ftx_name, ftx_name) return symbol_to_asset_or_token(name)
def asset_from_poloniex(poloniex_name: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(poloniex_name, str): raise DeserializationError(f'Got non-string type {type(poloniex_name)} for poloniex asset') if poloniex_name in UNSUPPORTED_POLONIEX_ASSETS: raise UnsupportedAsset(poloniex_name) our_name = POLONIEX_TO_WORLD.get(poloniex_name, poloniex_name) return Asset(our_name)
def deserialize(value: str) -> 'LiquityStakeEventType': if value == 'stakeCreated': return LiquityStakeEventType.STAKE_CREATED if value == 'stakeIncreased': return LiquityStakeEventType.STAKE_INCREASED if value == 'stakeDecreased': return LiquityStakeEventType.STAKE_DECREASED if value == 'stakeRemoved': return LiquityStakeEventType.STAKE_REMOVED if value == 'gainsWithdrawn': return LiquityStakeEventType.STAKE_WITHDRAWN # else raise DeserializationError( f'Encountered unknown LiquityStakeEventType value {value}')
def asset_from_kucoin(kucoin_name: str) -> Asset: """May raise: - DeserializationError - UnsupportedAsset - UnknownAsset """ if not isinstance(kucoin_name, str): raise DeserializationError(f'Got non-string type {type(kucoin_name)} for kucoin asset') if kucoin_name in UNSUPPORTED_KUCOIN_ASSETS: raise UnsupportedAsset(kucoin_name) name = KUCOIN_TO_WORLD.get(kucoin_name, kucoin_name) return Asset(name)
def deserialize_int_from_hex_or_int(symbol: Union[str, int], location: str) -> int: """Takes a symbol which can either be an int or a hex string and turns it into an integer May Raise: - DeserializationError if the given data are in an unexpected format. """ if isinstance(symbol, int): result = symbol elif isinstance(symbol, str): try: result = int(symbol, 16) except ValueError: raise DeserializationError( f'Could not turn string "{symbol}" into an integer {location}', ) else: raise DeserializationError( f'Unexpected type {type(symbol)} given to ' f'deserialize_int_from_hex_or_int() for {location}', ) return result