def liquity_staking_balances( self, addresses: List[ChecksumEthAddress], ) -> Dict[ChecksumEthAddress, StakePosition]: staked = self._get_raw_history(addresses, 'stake') lqty_price = Inquirer().find_usd_price(A_LQTY) data = {} for stake in staked['lqtyStakes']: try: owner = to_checksum_address(stake['id']) amount = deserialize_optional_to_fval( value=stake['amount'], name='amount', location='liquity', ) position = AssetBalance( asset=A_LQTY, balance=Balance( amount=amount, usd_value=lqty_price * amount, ), ) data[owner] = StakePosition(position) except (DeserializationError, KeyError) as e: msg = str(e) if isinstance(e, KeyError): msg = f'Missing key entry for {msg}.' self.msg_aggregator.add_warning( f'Ignoring Liquity staking information. ' f'Failed to decode remote response. {msg}.', ) continue return data
def test_defi_event_zero_amount(accountant): """Test that if a Defi Event with a zero amount obtained comes in we don't raise an error Regression test for a division by zero error a user reported """ defi_events = [ DefiEvent( timestamp=1467279735, wrapped_event=AaveInterestEvent( event_type='interest', asset=A_WBTC, value=Balance(amount=FVal(0), usd_value=FVal(0)), block_number=4, timestamp=Timestamp(1467279735), tx_hash= '0x49c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=4, ), event_type=DefiEventType.AAVE_EVENT, got_asset=A_WBTC, got_balance=Balance(amount=FVal(0), usd_value=FVal(0)), spent_asset=A_WBTC, spent_balance=Balance(amount=FVal(0), usd_value=FVal(0)), pnl=[ AssetBalance(asset=A_WBTC, balance=Balance(amount=FVal(0), usd_value=FVal(0))) ], count_spent_got_cost_basis=True, tx_hash= '0x49c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', ) ] result = accounting_history_process( accountant=accountant, start_ts=1466979735, end_ts=1519693374, history_list=[], defi_events_list=defi_events, ) assert result['all_events'][0] == { 'cost_basis': None, 'is_virtual': False, 'location': 'blockchain', 'net_profit_or_loss': ZERO, 'paid_asset': 'WBTC(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599)', 'paid_in_asset': ZERO, 'paid_in_profit_currency': ZERO, 'received_asset': '', 'received_in_asset': ZERO, 'taxable_amount': ZERO, 'taxable_bought_cost_in_profit_currency': ZERO, 'taxable_received_in_profit_currency': ZERO, 'time': 1467279735, 'type': 'defi_event', }
def get_history_events( self, from_timestamp: Timestamp, to_timestamp: Timestamp, ) -> List[DefiEvent]: """Gets the history events from DSR for accounting This is a premium only call. Check happens only in the API level. """ history = self.get_historical_dsr() events = [] for _, report in history.items(): total_balance = Balance() counted_profit = Balance() for movement in report.movements: if movement.timestamp < from_timestamp: continue if movement.timestamp > to_timestamp: break pnl = got_asset = got_balance = spent_asset = spent_balance = None # noqa: E501 balance = Balance( amount=_dsrdai_to_dai(movement.amount), usd_value=movement.amount_usd_value, ) if movement.movement_type == 'deposit': spent_asset = A_DAI spent_balance = balance total_balance -= balance else: got_asset = A_DAI got_balance = balance total_balance += balance if total_balance.amount - counted_profit.amount > ZERO: pnl_balance = total_balance - counted_profit counted_profit += pnl_balance pnl = [AssetBalance(asset=A_DAI, balance=pnl_balance)] events.append( DefiEvent( timestamp=movement.timestamp, wrapped_event=movement, event_type=DefiEventType.DSR_EVENT, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, # Depositing and withdrawing from DSR is not counted in # cost basis. DAI were always yours, you did not rebuy them count_spent_got_cost_basis=False, tx_hash=movement.tx_hash, )) return events
def get_history_events( self, from_timestamp: Timestamp, to_timestamp: Timestamp, addresses: List[ChecksumEthAddress], ) -> List[DefiEvent]: if len(addresses) == 0: return [] mapping = self.get_history( addresses=addresses, reset_db_data=False, from_timestamp=from_timestamp, to_timestamp=to_timestamp, ) events = [] for _, history in mapping.items(): for event in history.events: pnl = got_asset = got_balance = spent_asset = spent_balance = None # noqa: E501 if isinstance(event, Bond): spent_asset = A_ADX spent_balance = event.value elif isinstance(event, Unbond): got_asset = A_ADX got_balance = event.value elif isinstance(event, UnbondRequest): continue # just ignore those for accounting purposes elif isinstance(event, ChannelWithdraw): got_asset = event.token got_balance = event.value pnl = [AssetBalance(asset=got_asset, balance=got_balance)] else: raise AssertionError( f'Unexpected adex event type {type(event)}') events.append( DefiEvent( timestamp=event.timestamp, wrapped_event=event, event_type=DefiEventType.ADEX_EVENT, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, # Do not count staking deposit/withdrawals as cost basis events # the ADX was always ours. PnL will ofc still be counted. count_spent_got_cost_basis=False, tx_hash=event.tx_hash, )) return events
def on_account_addition( self, address: ChecksumEthAddress) -> Optional[List['AssetBalance']]: try: result = self.get_balances([address]) except RemoteError as e: self.msg_aggregator.add_error( f'Did not manage to query beaconcha.in api for address {address} due to {str(e)}.' f' If you have Eth2 staked balances the final balance results may not be accurate', ) return None balance = result.get(address) if balance is None: return None return [AssetBalance(asset=A_ETH2, balance=balance)]
def on_account_addition( self, address: ChecksumEthAddress) -> Optional[List[AssetBalance]]: """When an account is added for adex check its balances""" balance = self.staking_pool.call(self.ethereum, 'balanceOf', arguments=[address]) if balance == 0: return None # else the address has staked adex usd_price = Inquirer().find_usd_price(A_ADX) share_price = self.staking_pool.call(self.ethereum, 'shareValue') amount = token_normalized_value_decimals( token_amount=balance * share_price / (FVal(10)**18), token_decimals=18, ) return [ AssetBalance(asset=A_ADX, balance=Balance(amount=amount, usd_value=amount * usd_price)) ] # noqa: E501
def get_history_events( self, from_timestamp: Timestamp, to_timestamp: Timestamp, ) -> List[DefiEvent]: """Gets the history events from maker vaults for accounting This is a premium only call. Check happens only in the API level. """ vault_details = self.get_vault_details() events = [] for detail in vault_details: total_vault_dai_balance = Balance() realized_vault_dai_loss = Balance() for event in detail.events: timestamp = event.timestamp if timestamp < from_timestamp: continue if timestamp > to_timestamp: break got_asset: Optional[Asset] spent_asset: Optional[Asset] pnl = got_asset = got_balance = spent_asset = spent_balance = None # noqa: E501 count_spent_got_cost_basis = False if event.event_type == VaultEventType.GENERATE_DEBT: count_spent_got_cost_basis = True got_asset = A_DAI got_balance = event.value total_vault_dai_balance += event.value elif event.event_type == VaultEventType.PAYBACK_DEBT: count_spent_got_cost_basis = True spent_asset = A_DAI spent_balance = event.value total_vault_dai_balance -= event.value if total_vault_dai_balance.amount + realized_vault_dai_loss.amount < ZERO: pnl_balance = total_vault_dai_balance + realized_vault_dai_loss realized_vault_dai_loss += -pnl_balance pnl = [AssetBalance(asset=A_DAI, balance=pnl_balance)] elif event.event_type == VaultEventType.DEPOSIT_COLLATERAL: spent_asset = detail.collateral_asset spent_balance = event.value elif event.event_type == VaultEventType.WITHDRAW_COLLATERAL: got_asset = detail.collateral_asset got_balance = event.value elif event.event_type == VaultEventType.LIQUIDATION: count_spent_got_cost_basis = True # TODO: Don't you also get the dai here -- but how to calculate it? spent_asset = detail.collateral_asset spent_balance = event.value pnl = [ AssetBalance(asset=detail.collateral_asset, balance=-spent_balance) ] else: raise AssertionError( f'Invalid Makerdao vault event type {event.event_type}' ) events.append( DefiEvent( timestamp=timestamp, wrapped_event=event, event_type=DefiEventType.MAKERDAO_VAULT_EVENT, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, # Depositing and withdrawing from a vault is not counted in # cost basis. Assets were always yours, you did not rebuy them. # Other actions are counted though to track debt and liquidations count_spent_got_cost_basis=count_spent_got_cost_basis, tx_hash=event.tx_hash, )) return events
def get_history_events( self, from_timestamp: Timestamp, to_timestamp: Timestamp, addresses: List[ChecksumEthAddress], ) -> List[DefiEvent]: history = self.get_history( given_defi_balances={}, addresses=addresses, reset_db_data=False, from_timestamp=from_timestamp, to_timestamp=to_timestamp, ) events = [] for event in history['events']: pnl = got_asset = got_balance = spent_asset = spent_balance = None # noqa: E501 if event.event_type == 'mint': spent_asset = event.asset spent_balance = event.value got_asset = event.to_asset got_balance = event.to_value elif event.event_type == 'redeem': spent_asset = event.asset spent_balance = event.value got_asset = event.to_asset got_balance = event.to_value if event.realized_pnl is not None: pnl = [ AssetBalance(asset=got_asset, balance=event.realized_pnl) ] elif event.event_type == 'borrow': got_asset = event.asset got_balance = event.value elif event.event_type == 'repay': spent_asset = event.asset spent_balance = event.value if event.realized_pnl is not None: pnl = [ AssetBalance(asset=spent_asset, balance=-event.realized_pnl) ] elif event.event_type == 'liquidation': spent_asset = event.to_asset spent_balance = event.to_value got_asset = event.asset got_balance = event.value pnl = [ # collateral lost AssetBalance(asset=spent_asset, balance=-spent_balance), # borrowed asset gained since you can keep it AssetBalance(asset=got_asset, balance=got_balance), ] elif event.event_type == 'comp': got_asset = event.asset got_balance = event.value if event.realized_pnl is not None: pnl = [ AssetBalance(asset=got_asset, balance=event.realized_pnl) ] else: raise AssertionError( f'Unexpected compound event {event.event_type}') events.append( DefiEvent( timestamp=event.timestamp, wrapped_event=event, event_type=DefiEventType.COMPOUND_EVENT, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, # Count all compound events in cost basis since there is a swap # from normal to cToken and back involved. Also to track debt. count_spent_got_cost_basis=True, tx_hash=event.tx_hash, )) return events
def get_history_events( self, from_timestamp: Timestamp, to_timestamp: Timestamp, addresses: List[ChecksumEthAddress], ) -> List[DefiEvent]: if len(addresses) == 0: return [] mapping = self.get_history( addresses=addresses, reset_db_data=False, from_timestamp=from_timestamp, to_timestamp=to_timestamp, given_defi_balances={}, ) events = [] for _, history in mapping.items(): total_borrow: Dict[Asset, Balance] = defaultdict(Balance) realized_borrow_loss: Dict[Asset, Balance] = defaultdict(Balance) for event in history.events: got_asset: Optional[Asset] spent_asset: Optional[Asset] pnl = got_asset = got_balance = spent_asset = spent_balance = None # noqa: E501 if event.event_type == 'deposit': event = cast(AaveDepositWithdrawalEvent, event) spent_asset = event.asset spent_balance = event.value # this will need editing for v2 got_asset = event.atoken got_balance = event.value elif event.event_type == 'withdrawal': event = cast(AaveDepositWithdrawalEvent, event) got_asset = event.asset got_balance = event.value # this will need editing for v2 spent_asset = event.atoken spent_balance = got_balance elif event.event_type == 'interest': event = cast(AaveInterestEvent, event) pnl = [ AssetBalance(asset=event.asset, balance=event.value) ] elif event.event_type == 'borrow': event = cast(AaveBorrowEvent, event) got_asset = event.asset got_balance = event.value total_borrow[got_asset] += got_balance elif event.event_type == 'repay': event = cast(AaveRepayEvent, event) spent_asset = event.asset spent_balance = event.value if total_borrow[spent_asset].amount + realized_borrow_loss[ spent_asset].amount < ZERO: # noqa: E501 pnl_balance = total_borrow[ spent_asset] + realized_borrow_loss[spent_asset] realized_borrow_loss[spent_asset] += -pnl_balance pnl = [ AssetBalance(asset=spent_asset, balance=pnl_balance) ] elif event.event_type == 'liquidation': event = cast(AaveLiquidationEvent, event) got_asset = event.principal_asset got_balance = event.principal_balance spent_asset = event.collateral_asset spent_balance = event.collateral_balance pnl = [ AssetBalance(asset=spent_asset, balance=-spent_balance), AssetBalance(asset=got_asset, balance=got_balance), ] # The principal needs to also be removed from the total_borrow total_borrow[got_asset] -= got_balance else: raise AssertionError( f'Unexpected aave event {event.event_type}') events.append( DefiEvent( timestamp=event.timestamp, wrapped_event=event, event_type=DefiEventType.AAVE_EVENT, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, # Count all aave events in cost basis since there is a swap # involved from normal to aTokens and then back again. Also # borrowing/repaying for debt tracking. count_spent_got_cost_basis=True, tx_hash=event.tx_hash, )) return events
def get_staking_history( self, addresses: List[ChecksumEthAddress], from_timestamp: Timestamp, to_timestamp: Timestamp, ) -> Dict[ChecksumEthAddress, List[LiquityEvent]]: try: staked = self._get_raw_history(addresses, 'stake') except RemoteError as e: log.error( f'Failed to query stake graph events for liquity. {str(e)}') staked = {} result: Dict[ChecksumEthAddress, List[LiquityEvent]] = defaultdict(list) for stake in staked.get('lqtyStakes', []): owner = to_checksum_address(stake['id']) for change in stake['changes']: try: timestamp = change['transaction']['timestamp'] if timestamp < from_timestamp: continue if timestamp > to_timestamp: break operation_stake = LiquityStakeEventType.deserialize( change['stakeOperation']) lqty_price = PriceHistorian().query_historical_price( from_asset=A_LQTY, to_asset=A_USD, timestamp=timestamp, ) lusd_price = PriceHistorian().query_historical_price( from_asset=A_LUSD, to_asset=A_USD, timestamp=timestamp, ) stake_after = deserialize_optional_to_fval( value=change['stakedAmountAfter'], name='stakedAmountAfter', location='liquity', ) stake_change = deserialize_optional_to_fval( value=change['stakedAmountChange'], name='stakedAmountChange', location='liquity', ) issuance_gain = deserialize_optional_to_fval( value=change['issuanceGain'], name='issuanceGain', location='liquity', ) redemption_gain = deserialize_optional_to_fval( value=change['redemptionGain'], name='redemptionGain', location='liquity', ) stake_event = LiquityStakeEvent( kind='stake', tx=change['transaction']['id'], address=owner, timestamp=timestamp, stake_after=AssetBalance( asset=A_LQTY, balance=Balance( amount=stake_after, usd_value=lqty_price * stake_after, ), ), stake_change=AssetBalance( asset=A_LQTY, balance=Balance( amount=stake_change, usd_value=lqty_price * stake_change, ), ), issuance_gain=AssetBalance( asset=A_LUSD, balance=Balance( amount=issuance_gain, usd_value=lusd_price * issuance_gain, ), ), redemption_gain=AssetBalance( asset=A_LUSD, balance=Balance( amount=redemption_gain, usd_value=lusd_price * redemption_gain, ), ), stake_operation=operation_stake, sequence_number=str( change['transaction']['sequenceNumber']), ) result[owner].append(stake_event) except (DeserializationError, KeyError) as e: msg = str(e) log.debug(f'Failed to deserialize Liquity entry: {change}') if isinstance(e, KeyError): msg = f'Missing key entry for {msg}.' self.msg_aggregator.add_warning( f'Ignoring Liquity Stake event in Liquity. ' f'Failed to decode remote information. {msg}.', ) continue return result
def get_trove_history( self, addresses: List[ChecksumEthAddress], from_timestamp: Timestamp, to_timestamp: Timestamp, ) -> Dict[ChecksumEthAddress, List[LiquityEvent]]: addresses_to_query = list(addresses) proxied_addresses = self._get_accounts_having_proxy() proxies_to_address = {v: k for k, v in proxied_addresses.items()} addresses_to_query += proxied_addresses.values() try: query = self._get_raw_history(addresses_to_query, 'trove') except RemoteError as e: log.error( f'Failed to query trove graph events for liquity. {str(e)}') query = {} result: Dict[ChecksumEthAddress, List[LiquityEvent]] = defaultdict(list) for trove in query.get('troves', []): owner = to_checksum_address(trove['owner']['id']) if owner in proxies_to_address: owner = proxies_to_address[owner] for change in trove['changes']: try: timestamp = change['transaction']['timestamp'] if timestamp < from_timestamp: continue if timestamp > to_timestamp: break operation = TroveOperation.deserialize( change['troveOperation']) collateral_change = deserialize_optional_to_fval( value=change['collateralChange'], name='collateralChange', location='liquity', ) debt_change = deserialize_optional_to_fval( value=change['debtChange'], name='debtChange', location='liquity', ) lusd_price = PriceHistorian().query_historical_price( from_asset=A_LUSD, to_asset=A_USD, timestamp=timestamp, ) eth_price = PriceHistorian().query_historical_price( from_asset=A_ETH, to_asset=A_USD, timestamp=timestamp, ) debt_after_amount = deserialize_optional_to_fval( value=change['debtAfter'], name='debtAfter', location='liquity', ) collateral_after_amount = deserialize_optional_to_fval( value=change['collateralAfter'], name='collateralAfter', location='liquity', ) event = LiquityTroveEvent( kind='trove', tx=change['transaction']['id'], address=owner, timestamp=timestamp, debt_after=AssetBalance( asset=A_LUSD, balance=Balance( amount=debt_after_amount, usd_value=lusd_price * debt_after_amount, ), ), collateral_after=AssetBalance( asset=A_ETH, balance=Balance( amount=collateral_after_amount, usd_value=eth_price * collateral_after_amount, ), ), debt_delta=AssetBalance( asset=A_LUSD, balance=Balance( amount=debt_change, usd_value=lusd_price * debt_change, ), ), collateral_delta=AssetBalance( asset=A_ETH, balance=Balance( amount=collateral_change, usd_value=eth_price * collateral_change, ), ), trove_operation=operation, sequence_number=str(change['sequenceNumber']), ) result[owner].append(event) except (DeserializationError, KeyError) as e: log.debug( f'Failed to deserialize Liquity trove event: {change}') msg = str(e) if isinstance(e, KeyError): msg = f'Missing key entry for {msg}.' self.msg_aggregator.add_warning( f'Ignoring Liquity Trove event in Liquity. ' f'Failed to decode remote information. {msg}.', ) continue return result
def _process_trove_events( self, changes: List[Dict[str, Any]], from_timestamp: Timestamp, to_timestamp: Timestamp, ) -> List[DefiEvent]: events = [] total_lusd_trove_balance = Balance() realized_trove_lusd_loss = Balance() for change in changes: try: operation = TroveOperation.deserialize( change['troveOperation']) collateral_change = deserialize_asset_amount( change['collateralChange']) debt_change = deserialize_asset_amount(change['debtChange']) timestamp = change['transaction']['timestamp'] if timestamp < from_timestamp: continue if timestamp > to_timestamp: break got_asset: Optional[Asset] spent_asset: Optional[Asset] pnl = got_asset = got_balance = spent_asset = spent_balance = None count_spent_got_cost_basis = False # In one transaction it is possible to generate debt and change the collateral if debt_change != AssetAmount(ZERO): if debt_change > ZERO: # Generate debt count_spent_got_cost_basis = True got_asset = A_LUSD got_balance = Balance( amount=debt_change, usd_value=query_usd_price_or_use_default( asset=A_LUSD, time=timestamp, default_value=ZERO, location='Liquity', ), ) total_lusd_trove_balance += got_balance else: # payback debt count_spent_got_cost_basis = True spent_asset = A_LUSD spent_balance = Balance( amount=abs(debt_change), usd_value=query_usd_price_or_use_default( asset=A_LUSD, time=timestamp, default_value=ZERO, location='Liquity', ), ) total_lusd_trove_balance -= spent_balance balance = total_lusd_trove_balance.amount + realized_trove_lusd_loss.amount if balance < ZERO: pnl_balance = total_lusd_trove_balance + realized_trove_lusd_loss realized_trove_lusd_loss += -pnl_balance pnl = [ AssetBalance(asset=A_LUSD, balance=pnl_balance) ] if collateral_change != AssetAmount(ZERO): if collateral_change < ZERO: # Withdraw collateral got_asset = A_ETH got_balance = Balance( amount=abs(collateral_change), usd_value=query_usd_price_or_use_default( asset=A_ETH, time=timestamp, default_value=ZERO, location='Liquity', ), ) else: # Deposit collateral spent_asset = A_ETH spent_balance = Balance( amount=collateral_change, usd_value=query_usd_price_or_use_default( asset=A_ETH, time=timestamp, default_value=ZERO, location='Liquity', ), ) if operation in ( TroveOperation.LIQUIDATEINNORMALMODE, TroveOperation.LIQUIDATEINRECOVERYMODE, ): count_spent_got_cost_basis = True spent_asset = A_ETH spent_balance = Balance( amount=abs(collateral_change), usd_value=query_usd_price_or_use_default( asset=A_ETH, time=timestamp, default_value=ZERO, location='Liquity', ), ) pnl = [AssetBalance(asset=A_ETH, balance=-spent_balance)] event = DefiEvent( timestamp=Timestamp(change['transaction']['timestamp']), wrapped_event=change, event_type=DefiEventType.LIQUITY, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, count_spent_got_cost_basis=count_spent_got_cost_basis, tx_hash=change['transaction']['id'], ) events.append(event) except (DeserializationError, KeyError) as e: msg = str(e) if isinstance(e, KeyError): msg = f'Missing key entry for {msg}.' log.debug( f'Failed to extract defievent in Liquity from {change}') self.msg_aggregator.add_warning( f'Ignoring Liquity Trove event in Liquity. ' f'Failed to decode remote information. {msg}.', ) continue return events
def get_positions( self, addresses_list: List[ChecksumEthAddress], ) -> Dict[ChecksumEthAddress, Trove]: contract = EthereumContract( address=LIQUITY_TROVE_MANAGER.address, abi=LIQUITY_TROVE_MANAGER.abi, deployed_block=LIQUITY_TROVE_MANAGER.deployed_block, ) # make a copy of the list to avoid modifications in the list that is passed as argument addresses = list(addresses_list) proxied_addresses = self._get_accounts_having_proxy() proxies_to_address = {v: k for k, v in proxied_addresses.items()} addresses += proxied_addresses.values() calls = [(LIQUITY_TROVE_MANAGER.address, contract.encode(method_name='Troves', arguments=[x])) for x in addresses] outputs = multicall_2( ethereum=self.ethereum, require_success=False, calls=calls, ) data: Dict[ChecksumEthAddress, Trove] = {} eth_price = Inquirer().find_usd_price(A_ETH) lusd_price = Inquirer().find_usd_price(A_LUSD) for idx, output in enumerate(outputs): status, result = output if status is True: try: trove_info = contract.decode(result, 'Troves', arguments=[addresses[idx]]) trove_is_active = bool(trove_info[3]) # pylint: disable=unsubscriptable-object if not trove_is_active: continue collateral = deserialize_asset_amount( token_normalized_value_decimals(trove_info[1], 18), # noqa: E501 pylint: disable=unsubscriptable-object ) debt = deserialize_asset_amount( token_normalized_value_decimals(trove_info[0], 18), # noqa: E501 pylint: disable=unsubscriptable-object ) collateral_balance = AssetBalance( asset=A_ETH, balance=Balance( amount=collateral, usd_value=eth_price * collateral, ), ) debt_balance = AssetBalance( asset=A_LUSD, balance=Balance( amount=debt, usd_value=lusd_price * debt, ), ) # Avoid division errors collateralization_ratio: Optional[FVal] liquidation_price: Optional[FVal] if debt > 0: collateralization_ratio = eth_price * collateral / debt * 100 else: collateralization_ratio = None if collateral > 0: liquidation_price = debt * lusd_price * FVal( MIN_COLL_RATE) / collateral else: liquidation_price = None account_address = addresses[idx] if account_address in proxies_to_address: account_address = proxies_to_address[account_address] data[account_address] = Trove( collateral=collateral_balance, debt=debt_balance, collateralization_ratio=collateralization_ratio, liquidation_price=liquidation_price, active=trove_is_active, trove_id=trove_info[4], # pylint: disable=unsubscriptable-object ) except DeserializationError as e: self.msg_aggregator.add_warning( f'Ignoring Liquity trove information. ' f'Failed to decode contract information. {str(e)}.', ) return data
def get_dill_balances( self, addresses: List[ChecksumEthAddress], ) -> Dict[ChecksumEthAddress, DillBalance]: """ Query information for amount locked, pending rewards and time until unlock for Pickle's dill. """ api_output = {} rewards_calls = [( PICKLE_DILL_REWARDS.address, self.rewards_contract.encode(method_name='claim', arguments=[x]), ) for x in addresses] balance_calls = [(PICKLE_DILL.address, self.dill_contract.encode(method_name='locked', arguments=[x])) for x in addresses] outputs = multicall_2( ethereum=self.ethereum, require_success=False, calls=rewards_calls + balance_calls, ) reward_outputs, dill_outputs = outputs[:len(addresses)], outputs[ len(addresses):] pickle_price = Inquirer().find_usd_price(A_PICKLE) for idx, output in enumerate(reward_outputs): status_rewards, result = output status_dill, result_dill = dill_outputs[idx] address = addresses[idx] if all((status_rewards, status_dill)): try: rewards = self.rewards_contract.decode(result, 'claim', arguments=[address]) dill_amounts = self.dill_contract.decode( result_dill, 'locked', arguments=[address], ) dill_rewards = token_normalized_value_decimals( token_amount=rewards[0], # pylint: disable=unsubscriptable-object token_decimals=A_PICKLE.decimals, ) dill_locked = token_normalized_value_decimals( token_amount=dill_amounts[0], # pylint: disable=unsubscriptable-object token_decimals=A_PICKLE.decimals, ) balance = DillBalance( dill_amount=AssetBalance( asset=A_PICKLE, balance=Balance( amount=dill_locked, usd_value=pickle_price * dill_locked, ), ), pending_rewards=AssetBalance( asset=A_PICKLE, balance=Balance( amount=dill_rewards, usd_value=pickle_price * dill_rewards, ), ), lock_time=deserialize_timestamp(dill_amounts[1]), # noqa: E501 pylint: disable=unsubscriptable-object ) api_output[address] = balance except (DeserializationError, IndexError) as e: self.msg_aggregator.add_error( f'Failed to query dill information for address {address}. {str(e)}', ) return api_output
def get_history_events( self, from_timestamp: Timestamp, to_timestamp: Timestamp, addresses: List[ChecksumEthAddress], ) -> List[DefiEvent]: """Gets the history events from maker vaults for accounting This is a premium only call. Check happens only in the API level. """ if len(addresses) == 0: return [] from_block = self.ethereum.get_blocknumber_by_time(from_timestamp) to_block = self.ethereum.get_blocknumber_by_time(to_timestamp) events = [] for address in addresses: for _, vault in YEARN_VAULTS.items(): vault_history = self.get_vault_history( defi_balances=[], vault=vault, address=address, from_block=from_block, to_block=to_block, ) if vault_history is None: continue if len(vault_history.events) != 0: # process the vault's events to populate realized_pnl self._process_vault_events(vault_history.events) for event in vault_history.events: pnl = got_asset = got_balance = spent_asset = spent_balance = None # noqa: E501 if event.event_type == 'deposit': spent_asset = event.from_asset spent_balance = event.from_value got_asset = event.to_asset got_balance = event.to_value else: # withdraw spent_asset = event.from_asset spent_balance = event.from_value got_asset = event.to_asset got_balance = event.to_value if event.realized_pnl is not None: pnl = [ AssetBalance(asset=got_asset, balance=event.realized_pnl) ] events.append( DefiEvent( timestamp=event.timestamp, wrapped_event=event, event_type=DefiEventType.YEARN_VAULTS_EVENT, got_asset=got_asset, got_balance=got_balance, spent_asset=spent_asset, spent_balance=spent_balance, pnl=pnl, # Depositing and withdrawing from a vault is not counted in # cost basis. Assets were always yours, you did not rebuy them count_spent_got_cost_basis=False, tx_hash=event.tx_hash, )) return events
def history_event_from_kraken( events: List[Dict[str, Any]], name: str, msg_aggregator: MessagesAggregator, ) -> Tuple[List[HistoryBaseEntry], bool]: """ This function gets raw data from kraken and creates a list of related history events to be used in the app. It returns a list of events and a boolean in the case that an unknown type is found. """ group_events = [] found_unknown_event = False for idx, raw_event in enumerate(events): try: timestamp = Timestamp((deserialize_fval( value=raw_event['time'], name='time', location='kraken ledger processing', ) * KRAKEN_TS_MULTIPLIER).to_int(exact=False)) identifier = raw_event['refid'] event_type = HistoryEventType.from_string(raw_event['type']) asset = asset_from_kraken(raw_event['asset']) event_subtype = None notes = None raw_amount = deserialize_asset_amount(raw_event['amount']) # If we don't know how to handle an event atm or we find an unsupported # event type the logic will be to store it as unknown and if in the future # we need some information from it we can take actions to process them if event_type == HistoryEventType.TRANSFER: if raw_event['subtype'] == 'spottostaking': event_type = HistoryEventType.STAKING event_subtype = HistoryEventSubType.STAKING_DEPOSIT_ASSET elif raw_event['subtype'] == 'stakingfromspot': event_type = HistoryEventType.STAKING event_subtype = HistoryEventSubType.STAKING_RECEIVE_ASSET elif raw_event['subtype'] == 'stakingtospot': event_type = HistoryEventType.UNSTAKING event_subtype = HistoryEventSubType.STAKING_REMOVE_ASSET elif raw_event['subtype'] == 'spotfromstaking': event_type = HistoryEventType.UNSTAKING event_subtype = HistoryEventSubType.STAKING_RECEIVE_ASSET elif event_type == HistoryEventType.ADJUSTMENT: if raw_amount < ZERO: event_subtype = HistoryEventSubType.SPEND else: event_subtype = HistoryEventSubType.RECEIVE elif event_type == HistoryEventType.UNKNOWN: found_unknown_event = True notes = raw_event['type'] log.warning( f'Encountered kraken historic event type we do not process. {raw_event}', ) fee_amount = deserialize_asset_amount(raw_event['fee']) group_events.append( HistoryBaseEntry( event_identifier=identifier, sequence_index=idx, timestamp=timestamp, location=Location.KRAKEN, location_label=name, asset_balance=AssetBalance( asset=asset, balance=Balance( amount=raw_amount, usd_value=ZERO, ), ), notes=notes, event_type=event_type, event_subtype=event_subtype, )) if fee_amount != ZERO: group_events.append( HistoryBaseEntry( event_identifier=identifier, sequence_index=len(events), timestamp=timestamp, location=Location.KRAKEN, location_label=name, asset_balance=AssetBalance( asset=asset, balance=Balance( amount=fee_amount, usd_value=ZERO, ), ), notes=notes, event_type=event_type, event_subtype=HistoryEventSubType.FEE, )) except (DeserializationError, KeyError, UnknownAsset) as e: msg = str(e) if isinstance(e, KeyError): msg = f'Keyrror {msg}' msg_aggregator.add_error( f'Failed to read ledger event from kraken {raw_event} due to {msg}', ) return [], False return group_events, found_unknown_event