Exemplo n.º 1
0
 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
Exemplo n.º 2
0
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',
    }
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
Arquivo: eth2.py Projeto: step21/rotki
    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)]
Exemplo n.º 6
0
 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
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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
Exemplo n.º 15
0
    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
Exemplo n.º 16
0
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