Exemple #1
0
def _handle_pooltogether(normalized_balance: FVal,
                         token_name: str) -> Optional[DefiBalance]:
    """Special handling for pooltogether

    https://github.com/rotki/rotki/issues/1429
    """
    if 'DAI' in token_name:
        dai_price = Inquirer.find_usd_price(A_DAI)
        return DefiBalance(
            token_address=string_to_ethereum_address(
                '0x49d716DFe60b37379010A75329ae09428f17118d'),
            token_name='Pool Together DAI token',
            token_symbol='plDAI',
            balance=Balance(
                amount=normalized_balance,
                usd_value=normalized_balance * dai_price,
            ),
        )
    if 'USDC' in token_name:
        usdc_price = Inquirer.find_usd_price(A_USDC)
        return DefiBalance(
            token_address=string_to_ethereum_address(
                '0xBD87447F48ad729C5c4b8bcb503e1395F62e8B98'),
            token_name='Pool Together USDC token',
            token_symbol='plUSDC',
            balance=Balance(
                amount=normalized_balance,
                usd_value=normalized_balance * usdc_price,
            ),
        )
    # else
    return None
Exemple #2
0
    def _process_vault_events(self, events: List[YearnVaultEvent]) -> Balance:
        """Process the events for a single vault and returns total profit/loss after all events"""
        total = Balance()
        profit_so_far = Balance()

        if len(events) < 2:
            return total

        for event in events:
            if event.event_type == 'deposit':
                total -= event.from_value
            else:  # withdraws
                profit_amount = total.amount + event.to_value.amount - profit_so_far.amount
                profit: Optional[Balance]
                if profit_amount >= 0:
                    usd_price = get_usd_price_zero_if_error(
                        asset=event.to_asset,
                        time=event.timestamp,
                        location=f'yearn vault event {event.tx_hash} processing',
                        msg_aggregator=self.msg_aggregator,
                    )
                    profit = Balance(profit_amount, profit_amount * usd_price)
                    profit_so_far += profit
                else:
                    profit = None

                event.realized_pnl = profit
                total += event.to_value

        return total
Exemple #3
0
def test_query_balances_sandbox(sandbox_kuckoin, inquirer):  # pylint: disable=unused-argument
    assets_balance, msg = sandbox_kuckoin.query_balances()
    assert assets_balance == {
        A_BTC:
        Balance(
            amount=FVal('2.61018067'),
            usd_value=FVal('3.915271005'),
        ),
        A_ETH:
        Balance(
            amount=FVal('47.43934995'),
            usd_value=FVal('71.159024925'),
        ),
        A_KCS:
        Balance(
            amount=FVal('0.2'),
            usd_value=FVal('0.30'),
        ),
        A_USDT:
        Balance(
            amount=FVal('45097.26244755'),
            usd_value=FVal('67645.893671325'),
        ),
    }
    assert msg == ''
Exemple #4
0
def test_get_vault_balance(
    inquirer,  # pylint: disable=unused-argument
    mocked_current_prices,
):
    debt_value = FVal('2000')
    owner = make_ethereum_address()
    vault = MakerdaoVault(
        identifier=1,
        collateral_type='ETH-A',
        collateral_asset=A_ETH,
        owner=owner,
        collateral=Balance(FVal('100'), FVal('20000')),
        debt=Balance(debt_value, debt_value * mocked_current_prices['DAI']),
        collateralization_ratio='990%',
        liquidation_ratio=FVal(1.5),
        liquidation_price=FVal('50'),  # not calculated to be correct
        urn=make_ethereum_address(),
        stability_fee=ZERO,
    )
    expected_result = BalanceSheet(
        assets=defaultdict(Balance,
                           {A_ETH: Balance(FVal('100'), FVal('20000'))}),
        liabilities=defaultdict(Balance,
                                {A_DAI: Balance(FVal('2000'), FVal('2020'))}),
    )
    assert vault.get_balance() == expected_result
Exemple #5
0
def test_balance_sheet_to_dict():
    a = BalanceSheet(
        assets={
            A_USD: Balance(amount=FVal('2'), usd_value=FVal('2')),
            A_ETH: Balance(amount=FVal('3'), usd_value=FVal('900')),
        },
        liabilities={
            A_DAI: Balance(amount=FVal('5'), usd_value=FVal('5.1')),
            A_ETH: Balance(amount=FVal('0.5'), usd_value=FVal('150')),
        },
    )
    assert a.to_dict() == {
        'assets': {
            'USD': {
                'amount': FVal('2'),
                'usd_value': FVal('2')
            },
            'ETH': {
                'amount': FVal('3'),
                'usd_value': FVal('900')
            },
        },
        'liabilities': {
            ethaddress_to_identifier('0x6B175474E89094C44Da98b954EedeAC495271d0F'):
            {
                'amount': FVal('5'),
                'usd_value': FVal('5.1')
            },  # noqa: E501
            'ETH': {
                'amount': FVal('0.5'),
                'usd_value': FVal('150')
            },
        },
    }
Exemple #6
0
    def _get_staking_history(
            staking_balances: Dict[ChecksumAddress, List[Dict[str, Any]]],
            staking_events: ADXStakingEvents,
    ) -> Dict[ChecksumAddress, ADXStakingHistory]:
        """Given the following params:
          - staking_balances: the balances of the addresses per pool.
          - staking_events: all the events of the addresses mixed but grouped by
          type.

        Return a map between an address and its <ADXStakingDetail>, which contains
        all the events that belong to the address, and the performance details
        per staking pool.
        """
        staking_history = {}
        address_staking_events = defaultdict(list)
        all_events = staking_events.get_all()
        # Map addresses with their events
        for event in all_events:
            address_staking_events[event.address].append(event)

        # Sort staking events per address by timestamp (older first) and event type
        for address in address_staking_events.keys():
            address_staking_events[address].sort(
                key=lambda event: (event.timestamp, EVENT_TYPE_ORDER_IN_ASC[type(event)]),
            )

        for address, adx_staking_balances in staking_balances.items():
            adx_staking_details = []
            for adx_staking_balance in adx_staking_balances:
                adx_total_profit = Balance()
                adx_balance = Balance(
                    amount=adx_staking_balance['adx_balance']['amount'],
                    usd_value=adx_staking_balance['adx_balance']['usd_value'],
                )
                # Add claimed amounts and their historical usd value
                for event in address_staking_events[address]:
                    if isinstance(event, ChannelWithdraw):
                        if event.token == A_ADX:
                            adx_total_profit += event.value

                pool_staking_detail = ADXStakingDetail(
                    contract_address=adx_staking_balance['contract_address'],
                    pool_id=adx_staking_balance['pool_id'],
                    pool_name=adx_staking_balance['pool_name'],
                    total_staked_amount=ZERO,  # unable to calculate for now
                    apr=ZERO,  # unable to calculate for now
                    adx_balance=adx_balance,
                    adx_unclaimed_balance=Balance(),  # unable to calculate for now
                    dai_unclaimed_balance=Balance(),  # unable to calculate for now
                    adx_profit_loss=adx_total_profit,
                    dai_profit_loss=Balance(),  # unable to calculate for now
                )
                adx_staking_details.append(pool_staking_detail)

            staking_history[address] = ADXStakingHistory(
                events=address_staking_events[address],
                staking_details=adx_staking_details,
            )
        return staking_history
Exemple #7
0
def test_compound_ether_withdraw(database, ethereum_manager,
                                 function_scope_messages_aggregator):
    """Data taken from:
    https://etherscan.io/tx/0x024bd402420c3ba2f95b875f55ce2a762338d2a14dac4887b78174254c9ab807
    """
    # TODO: For faster tests hard-code the transaction and the logs here so no remote query needed
    tx_hash = deserialize_evm_tx_hash(
        '0x024bd402420c3ba2f95b875f55ce2a762338d2a14dac4887b78174254c9ab807'
    )  # noqa: E501
    events = get_decoded_events_of_transaction(
        ethereum_manager=ethereum_manager,
        database=database,
        msg_aggregator=function_scope_messages_aggregator,
        tx_hash=tx_hash,
    )
    expected_events = [
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=0,
            timestamp=1598813490000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.02858544'), usd_value=ZERO),
            location_label=ADDY,
            notes=f'Burned 0.02858544 ETH in gas from {ADDY}',
            counterparty=CPT_GAS,
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=1,
            timestamp=1598813490000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.RETURN_WRAPPED,
            asset=A_CETH,
            balance=Balance(amount=FVal('24.97649991'), usd_value=ZERO),
            location_label=ADDY,
            notes='Return 24.97649991 cETH to compound',
            counterparty=CPT_COMPOUND,
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=2,
            timestamp=1598813490000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.WITHDRAWAL,
            event_subtype=HistoryEventSubType.REMOVE_ASSET,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.500003923413507454'),
                            usd_value=ZERO),
            location_label=ADDY,
            notes='Withdraw 0.500003923413507454 ETH from compound',
            counterparty=CPT_COMPOUND,
        )
    ]
    assert events == expected_events
Exemple #8
0
    def _query_vault_data(
        self,
        identifier: int,
        owner: ChecksumEthAddress,
        urn: ChecksumEthAddress,
        ilk: bytes,
    ) -> Optional[MakerdaoVault]:
        collateral_type = ilk.split(b'\0', 1)[0].decode()
        asset = COLLATERAL_TYPE_MAPPING.get(collateral_type, None)
        if asset is None:
            self.msg_aggregator.add_warning(
                f'Detected vault with collateral_type {collateral_type}. That '
                f'is not yet supported by rotki. Skipping...', )
            return None

        result = MAKERDAO_VAT.call(self.ethereum, 'urns', arguments=[ilk, urn])
        # also known as ink in their contract
        collateral_amount = FVal(result[0] / WAD)
        normalized_debt = result[1]  # known as art in their contract
        result = MAKERDAO_VAT.call(self.ethereum, 'ilks', arguments=[ilk])
        rate = result[1]  # Accumulated Rates
        spot = FVal(result[2])  # Price with Safety Margin
        # How many DAI owner needs to pay back to the vault
        debt_value = FVal(((normalized_debt / WAD) * rate) / RAY)
        result = MAKERDAO_SPOT.call(self.ethereum, 'ilks', arguments=[ilk])
        mat = result[1]
        liquidation_ratio = FVal(mat / RAY)
        price = FVal((spot / RAY) * liquidation_ratio)
        self.usd_price[asset.identifier] = price
        collateral_value = FVal(price * collateral_amount)
        if debt_value == 0:
            collateralization_ratio = None
        else:
            collateralization_ratio = FVal(collateral_value /
                                           debt_value).to_percentage(2)

        collateral_usd_value = price * collateral_amount
        if collateral_amount == 0:
            liquidation_price = None
        else:
            liquidation_price = (debt_value *
                                 liquidation_ratio) / collateral_amount

        dai_usd_price = Inquirer().find_usd_price(A_DAI)
        return MakerdaoVault(
            identifier=identifier,
            owner=owner,
            collateral_type=collateral_type,
            collateral_asset=asset,
            collateral=Balance(collateral_amount, collateral_usd_value),
            debt=Balance(debt_value, dai_usd_price * debt_value),
            liquidation_ratio=liquidation_ratio,
            collateralization_ratio=collateralization_ratio,
            liquidation_price=liquidation_price,
            urn=urn,
            stability_fee=self.get_stability_fee(ilk),
        )
Exemple #9
0
def test_compound_ether_deposit(database, ethereum_manager,
                                function_scope_messages_aggregator):
    """Data taken from:
    https://etherscan.io/tx/0x06a8b9f758b0471886186c2a48dea189b3044916c7f94ee7f559026fefd91c39
    """
    # TODO: For faster tests hard-code the transaction and the logs here so no remote query needed
    tx_hash = deserialize_evm_tx_hash(
        '0x06a8b9f758b0471886186c2a48dea189b3044916c7f94ee7f559026fefd91c39'
    )  # noqa: E501
    events = get_decoded_events_of_transaction(
        ethereum_manager=ethereum_manager,
        database=database,
        msg_aggregator=function_scope_messages_aggregator,
        tx_hash=tx_hash,
    )
    expected_events = [
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=0,
            timestamp=1598639099000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.014122318'), usd_value=ZERO),
            location_label=ADDY,
            notes=f'Burned 0.014122318 ETH in gas from {ADDY}',
            counterparty=CPT_GAS,
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=1,
            timestamp=1598639099000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.DEPOSIT,
            event_subtype=HistoryEventSubType.DEPOSIT_ASSET,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.5'), usd_value=ZERO),
            location_label=ADDY,
            notes='Deposit 0.5 ETH to compound',
            counterparty=CPT_COMPOUND,
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=33,
            timestamp=1598639099000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.RECEIVE,
            event_subtype=HistoryEventSubType.RECEIVE_WRAPPED,
            asset=A_CETH,
            balance=Balance(amount=FVal('24.97649991'), usd_value=ZERO),
            location_label=ADDY,
            notes='Receive 24.97649991 cETH from compound',
            counterparty=CPT_COMPOUND,
        )
    ]
    assert events == expected_events
def test_tx_decode(evm_transaction_decoder, database):
    dbethtx = DBEthTx(database)
    addr1 = '0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12'
    approve_tx_hash = deserialize_evm_tx_hash('0x5cc0e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde7a')  # noqa: E501
    transactions = dbethtx.get_ethereum_transactions(
        filter_=ETHTransactionsFilterQuery.make(
            addresses=[addr1],
            tx_hash=approve_tx_hash,
        ),
        has_premium=True,
    )
    decoder = evm_transaction_decoder
    with patch.object(decoder, 'decode_transaction', wraps=decoder.decode_transaction) as decode_mock:  # noqa: E501
        for tx in transactions:
            receipt = dbethtx.get_receipt(tx.tx_hash)
            assert receipt is not None, 'all receipts should be queried in the test DB'
            events = decoder.get_or_decode_transaction_events(tx, receipt, ignore_cache=False)
            if tx.tx_hash == approve_tx_hash:
                assert len(events) == 2
                assert_events_equal(events[0], HistoryBaseEntry(
                    # The no-member is due to https://github.com/PyCQA/pylint/issues/3162
                    event_identifier=approve_tx_hash.hex(),  # pylint: disable=no-member
                    sequence_index=0,
                    timestamp=1569924574000,
                    location=Location.BLOCKCHAIN,
                    location_label=addr1,
                    asset=A_ETH,
                    balance=Balance(amount=FVal('0.000030921')),
                    # The no-member is due to https://github.com/PyCQA/pylint/issues/3162
                    notes=f'Burned 0.000030921 ETH in gas from {addr1}',
                    event_type=HistoryEventType.SPEND,
                    event_subtype=HistoryEventSubType.FEE,
                    counterparty=CPT_GAS,
                ))
                assert_events_equal(events[1], HistoryBaseEntry(
                    # The no-member is due to https://github.com/PyCQA/pylint/issues/3162
                    event_identifier=approve_tx_hash.hex(),  # pylint: disable=no-member
                    sequence_index=163,
                    timestamp=1569924574000,
                    location=Location.BLOCKCHAIN,
                    location_label=addr1,
                    asset=A_SAI,
                    balance=Balance(amount=1),
                    notes=f'Approve 1 SAI of {addr1} for spending by 0xdf869FAD6dB91f437B59F1EdEFab319493D4C4cE',  # noqa: E501
                    event_type=HistoryEventType.INFORMATIONAL,
                    event_subtype=HistoryEventSubType.APPROVE,
                    counterparty='0xdf869FAD6dB91f437B59F1EdEFab319493D4C4cE',
                ))

        assert decode_mock.call_count == len(transactions)
        # now go again, and see that no more decoding happens as it's all pulled from the DB
        for tx in transactions:
            receipt = dbethtx.get_receipt(tx.tx_hash)
            assert receipt is not None, 'all receipts should be queried in the test DB'
            events = decoder.get_or_decode_transaction_events(tx, receipt, ignore_cache=False)
        assert decode_mock.call_count == len(transactions)
Exemple #11
0
def test_balance_raddition():
    a = Balance(amount=FVal('1.5'), usd_value=FVal('1.6'))
    b = Balance(amount=FVal('2.5'), usd_value=FVal('2.7'))
    c = Balance(amount=FVal('3'), usd_value=FVal('3.21'))

    result = sum([a, b, c])

    assert isinstance(result, Balance)
    assert result.amount == FVal('7')
    assert result.usd_value == FVal('7.51')
Exemple #12
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
Exemple #13
0
def test_kraken_staking_events(accountant, google_service):
    """
    Test that staking events from kraken are correctly processed
    """
    history = [
        HistoryBaseEntry(
            event_identifier='XXX',
            sequence_index=0,
            timestamp=1640493374000,
            location=Location.KRAKEN,
            location_label='Kraken 1',
            asset=A_ETH2,
            balance=Balance(
                amount=FVal(0.0000541090),
                usd_value=FVal(0.212353475950),
            ),
            notes=None,
            event_type=HistoryEventType.STAKING,
            event_subtype=HistoryEventSubType.REWARD,
        ),
        HistoryBaseEntry(
            event_identifier='YYY',
            sequence_index=0,
            timestamp=1636638550000,
            location=Location.KRAKEN,
            location_label='Kraken 1',
            asset=A_ETH2,
            balance=Balance(
                amount=FVal(0.0000541090),
                usd_value=FVal(0.212353475950),
            ),
            notes=None,
            event_type=HistoryEventType.STAKING,
            event_subtype=HistoryEventSubType.REWARD,
        )
    ]
    _, events = accounting_history_process(
        accountant,
        start_ts=1636638549,
        end_ts=1640493376,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.STAKING:
        PNL(taxable=FVal('0.471505826'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
    assert len(events) == 2
    expected_pnls = [FVal('0.25114638241'), FVal('0.22035944359')]
    for idx, event in enumerate(events):
        assert event.pnl.taxable == expected_pnls[idx]
        assert event.type == AccountingEventType.STAKING
Exemple #14
0
    def _parse_repays(
        self,
        repays: List[Dict[str, Any]],
        from_ts: Timestamp,
        to_ts: Timestamp,
    ) -> List[AaveRepayEvent]:
        events = []
        for entry in repays:
            common = _parse_common_event_data(entry, from_ts, to_ts)
            if common is None:
                continue  # either timestamp out of range or error (logged in the function above)
            timestamp, tx_hash, index = common
            result = _get_reserve_asset_and_decimals(entry,
                                                     reserve_key='reserve')
            if result is None:
                continue  # problem parsing, error already logged
            asset, decimals = result
            if 'amountAfterFee' in entry:
                amount_after_fee = token_normalized_value_decimals(
                    int(entry['amountAfterFee']),
                    token_decimals=decimals,
                )
                fee = token_normalized_value_decimals(int(entry['fee']),
                                                      token_decimals=decimals)
            else:
                # In the V2 subgraph the amountAfterFee and Fee keys are replaced by amount
                amount_after_fee = token_normalized_value_decimals(
                    int(entry['amount']),
                    token_decimals=decimals,
                )
                fee = ZERO
            usd_price = query_usd_price_zero_if_error(
                asset=asset,
                time=timestamp,
                location=f'aave repay event {tx_hash} from graph query',
                msg_aggregator=self.msg_aggregator,
            )
            events.append(
                AaveRepayEvent(
                    event_type='repay',
                    asset=asset,
                    value=Balance(amount=amount_after_fee,
                                  usd_value=amount_after_fee * usd_price),
                    fee=Balance(amount=fee, usd_value=fee * usd_price),
                    block_number=0,  # can't get from graph query
                    timestamp=timestamp,
                    tx_hash=tx_hash,
                    log_index=
                    index,  # not really the log index, but should also be unique
                ))

        return events
def test_gitcoin_old_donation(database, ethereum_manager, function_scope_messages_aggregator):
    """Data taken from
    https://etherscan.io/tx/0x811ba23a10c76111289133ec6f90d3c33a604baa50053739210e870687a456d9
    """
    # TODO: For faster tests hard-code the transaction and the logs here so no remote query needed
    tx_hash = deserialize_evm_tx_hash('0x811ba23a10c76111289133ec6f90d3c33a604baa50053739210e870687a456d9')  # noqa: E501
    events = get_decoded_events_of_transaction(
        ethereum_manager=ethereum_manager,
        database=database,
        msg_aggregator=function_scope_messages_aggregator,
        tx_hash=tx_hash,
    )
    expected_events = [
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=0,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.000055118'), usd_value=ZERO),
            location_label=ADDY,
            notes=f'Burned 0.000055118 ETH in gas from {ADDY}',
            counterparty=CPT_GAS,
        ), HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=164,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.DONATE,
            asset=A_SAI,
            balance=Balance(amount=FVal('0.95'), usd_value=ZERO),
            location_label=ADDY,
            notes='Donate 0.95 SAI to 0xEbDb626C95a25f4e304336b1adcAd0521a1Bdca1 via gitcoin',  # noqa: E501
            counterparty=CPT_GITCOIN,
        ), HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=165,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.DONATE,
            asset=A_SAI,
            balance=Balance(amount=FVal('0.05'), usd_value=ZERO),
            location_label=ADDY,
            notes='Donate 0.05 SAI to 0x00De4B13153673BCAE2616b67bf822500d325Fc3 via gitcoin',  # noqa: E501
            counterparty=CPT_GITCOIN,
        ),
    ]
    assert events == expected_events
def test_query_some_balances(
        function_scope_independentreserve,
        inquirer,  # pylint: disable=unused-argument
):
    """Just like test_query_balances but make sure 0 balances are skipped"""
    exchange = function_scope_independentreserve

    def mock_api_return(method, url, **kwargs):  # pylint: disable=unused-argument
        assert method == 'post'
        response = """[{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 1.2, "CurrencyCode": "Aud", "TotalBalance": 2.5},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Usd", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Nzd", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Sgd", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Xbt", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Eth", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Xrp", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Ada", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Dot", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Uni", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Link", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Usdt", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Usdc", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Bch", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Ltc", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Mkr", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Dai", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Comp", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Snx", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Grt", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Eos", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Xlm", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Etc", "TotalBalance": 100.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Bat", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Pmgt", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Yfi", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Aave", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Zrx", "TotalBalance": 0.0},
{"AccountGuid": "foo", "AccountStatus": "Active", "AvailableBalance": 0.0, "CurrencyCode": "Omg", "TotalBalance": 0.0}]"""  # noqa: E501
        return MockResponse(200, response)

    with patch.object(exchange.session, 'request',
                      side_effect=mock_api_return):
        balances, msg = exchange.query_balances()

    assert msg == ''
    assert balances == {
        A_AUD: Balance(amount=FVal(2.5), usd_value=FVal(3.75)),
        A_ETC: Balance(amount=FVal(100), usd_value=FVal(150)),
    }
Exemple #17
0
    def _update_events_value(
            self,
            staking_events: ADXStakingEvents,
    ) -> None:
        # Update amounts for unbonds and unbond requests
        bond_id_bond_map: Dict[str, Optional[Bond]] = {
            bond.bond_id: bond for bond in staking_events.bonds
        }
        for event in (
            staking_events.unbonds +
            staking_events.unbond_requests  # type: ignore # mypy bug concatenating lists
        ):
            has_bond = True
            bond = bond_id_bond_map.get(event.bond_id, None)
            if bond:
                event.value = Balance(amount=bond.value.amount)
                event.pool_id = bond.pool_id
            elif event.bond_id not in bond_id_bond_map:
                bond_id_bond_map[event.bond_id] = None
                db_bonds = cast(List[Bond], self.database.get_adex_events(
                    bond_id=event.bond_id,
                    event_type=AdexEventType.BOND,
                ))
                if db_bonds:
                    db_bond = db_bonds[0]
                    bond_id_bond_map[event.bond_id] = db_bond
                    event.value = Balance(amount=db_bond.value.amount)
                    event.pool_id = db_bond.pool_id
                else:
                    has_bond = False
            else:
                has_bond = False

            if has_bond is False:
                log.warning(
                    'Failed to update an AdEx event data. Unable to find its related bond event',
                    event=event,
                )

        # Update usd_value for all events
        for event in staking_events.get_all():  # type: ignore # event can have all types
            token = event.token if isinstance(event, ChannelWithdraw) else A_ADX
            usd_price = PriceHistorian().query_historical_price(
                from_asset=token,
                to_asset=A_USD,
                timestamp=event.timestamp,
            )
            event.value.usd_value = event.value.amount * usd_price
Exemple #18
0
    def decode_proxy_creation(
        self,
        tx_log: EthereumTxReceiptLog,
        transaction: EthereumTransaction,
        decoded_events: List[HistoryBaseEntry],  # pylint: disable=unused-argument
        all_logs: List[EthereumTxReceiptLog],  # pylint: disable=unused-argument
        action_items: List[ActionItem],  # pylint: disable=unused-argument
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        if tx_log.topics[
                0] == b'%\x9b0\xca9\x88\\m\x80\x1a\x0b]\xbc\x98\x86@\xf3\xc2^/7S\x1f\xe18\xc5\xc5\xaf\x89U\xd4\x1b':  # noqa: E501
            owner_address = hex_or_bytes_to_address(tx_log.topics[2])
            if not self.base.is_tracked(owner_address):
                return None, None

            proxy_address = hex_or_bytes_to_address(tx_log.data[0:32])
            notes = f'Create DSR proxy {proxy_address} with owner {owner_address}'
            event = HistoryBaseEntry(
                event_identifier=transaction.tx_hash.hex(),
                sequence_index=self.base.get_sequence_index(tx_log),
                timestamp=ts_sec_to_ms(transaction.timestamp),
                location=Location.BLOCKCHAIN,
                location_label=owner_address,
                # TODO: This should be null for proposals and other informational events
                asset=A_ETH,
                balance=Balance(),
                notes=notes,
                event_type=HistoryEventType.INFORMATIONAL,
                event_subtype=HistoryEventSubType.DEPLOY,
                counterparty=proxy_address,
            )
            return event, None

        return None, None
Exemple #19
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
Exemple #20
0
    def _maybe_decode_erc20_approve(
            self,
            token: Optional[EthereumToken],
            tx_log: EthereumTxReceiptLog,
            transaction: EthereumTransaction,
            decoded_events: List[HistoryBaseEntry],  # pylint: disable=unused-argument
            action_items: List[ActionItem],  # pylint: disable=unused-argument
    ) -> Optional[HistoryBaseEntry]:
        if tx_log.topics[0] != ERC20_APPROVE or token is None:
            return None

        owner_address = hex_or_bytes_to_address(tx_log.topics[1])
        spender_address = hex_or_bytes_to_address(tx_log.topics[2])

        if not any(
                self.base.is_tracked(x)
                for x in (owner_address, spender_address)):
            return None

        amount_raw = hex_or_bytes_to_int(tx_log.data)
        amount = token_normalized_value(token_amount=amount_raw, token=token)
        notes = f'Approve {amount} {token.symbol} of {owner_address} for spending by {spender_address}'  # noqa: E501
        return HistoryBaseEntry(
            event_identifier=transaction.tx_hash.hex(),
            sequence_index=self.base.get_sequence_index(tx_log),
            timestamp=ts_sec_to_ms(transaction.timestamp),
            location=Location.BLOCKCHAIN,
            location_label=owner_address,
            asset=token,
            balance=Balance(amount=amount),
            notes=notes,
            event_type=HistoryEventType.INFORMATIONAL,
            event_subtype=HistoryEventSubType.APPROVE,
            counterparty=spender_address,
        )
def test_receiving_value_from_tx(accountant, google_service):
    """
    Test that receiving a transaction that provides value works fine
    """
    addr2 = make_ethereum_address()
    tx_hash = '0x5cc0e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde7a'
    history = [
        HistoryBaseEntry(
            event_identifier=tx_hash,
            sequence_index=0,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            location_label=make_ethereum_address(),
            asset=A_ETH,
            balance=Balance(amount=FVal('1.5')),
            notes=f'Received 1.5 ETH from {addr2}',
            event_type=HistoryEventType.RECEIVE,
            event_subtype=HistoryEventSubType.NONE,
            counterparty=addr2,
        )
    ]
    accounting_history_process(
        accountant,
        start_ts=0,
        end_ts=1640493376,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRANSACTION_EVENT:
        PNL(taxable=FVal('242.385'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Exemple #22
0
    def _decode_vault_creation(
        self,
        tx_log: EthereumTxReceiptLog,
        transaction: EthereumTransaction,
        decoded_events: List[HistoryBaseEntry],  # pylint: disable=unused-argument
        all_logs: List[EthereumTxReceiptLog],  # pylint: disable=unused-argument
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        owner_address = self._get_address_or_proxy(
            hex_or_bytes_to_address(tx_log.topics[2]))
        if owner_address is None:
            return None, None

        if not self.base.is_tracked(owner_address):
            return None, None

        cdp_id = hex_or_bytes_to_int(tx_log.topics[3])
        notes = f'Create MakerDAO vault with id {cdp_id} and owner {owner_address}'
        event = HistoryBaseEntry(
            event_identifier=transaction.tx_hash.hex(),
            sequence_index=self.base.get_sequence_index(tx_log),
            timestamp=ts_sec_to_ms(transaction.timestamp),
            location=Location.BLOCKCHAIN,
            location_label=owner_address,
            # TODO: This should be null for proposals and other informational events
            asset=A_ETH,
            balance=Balance(),
            notes=notes,
            event_type=HistoryEventType.INFORMATIONAL,
            event_subtype=HistoryEventSubType.DEPLOY,
            counterparty='makerdao vault',
        )
        return event, None
Exemple #23
0
def get_manually_tracked_balances(
    db: 'DBHandler',
    balance_type: Optional[BalanceType] = None,
) -> List[ManuallyTrackedBalanceWithValue]:
    """Gets the manually tracked balances"""
    balances = db.get_manually_tracked_balances(balance_type=balance_type)
    balances_with_value = []
    for entry in balances:
        try:
            price = Inquirer().find_usd_price(entry.asset)
        except RemoteError as e:
            db.msg_aggregator.add_warning(
                f'Could not find price for {entry.asset.identifier} during '
                f'manually tracked balance querying due to {str(e)}', )
            price = Price(ZERO)

        value = Balance(amount=entry.amount, usd_value=price * entry.amount)
        balances_with_value.append(
            ManuallyTrackedBalanceWithValue(
                id=entry.id,
                asset=entry.asset,
                label=entry.label,
                value=value,
                location=entry.location,
                tags=entry.tags,
                balance_type=entry.balance_type,
            ))

    return balances_with_value
Exemple #24
0
    def deserialize_from_db(
            cls,
            deposit_tuple: Eth2DepositDBTuple,
    ) -> 'Eth2Deposit':
        """Turns a tuple read from DB into an appropriate LiquidityPoolEvent.

        Deposit_tuple index - Schema columns
        ------------------------------------
        0 - tx_hash
        1 - tx_index
        2 - from_address
        3 - timestamp
        4 - pubkey
        5 - withdrawal_credentials
        6 - amount
        7 - usd_value
        """
        return cls(
            tx_hash=make_evm_tx_hash(deposit_tuple[0]),
            tx_index=int(deposit_tuple[1]),
            from_address=string_to_ethereum_address(deposit_tuple[2]),
            timestamp=Timestamp(int(deposit_tuple[3])),
            pubkey=deposit_tuple[4],
            withdrawal_credentials=deposit_tuple[5],
            value=Balance(amount=FVal(deposit_tuple[6]), usd_value=FVal(deposit_tuple[7])),
        )
Exemple #25
0
    def query_balances(self) -> ExchangeQueryBalances:
        returned_balances: Dict[Asset, Balance] = {}
        try:
            resp = self._api_query_dict('get', 'user/wallet',
                                        {'currency': 'XBt'})
            # Bitmex shows only BTC balance
            usd_price = Inquirer().find_usd_price(A_BTC)
        except RemoteError as e:
            msg = f'Bitmex API request failed due to: {str(e)}'
            log.error(msg)
            return None, msg

        # result is in satoshis
        try:
            amount = satoshis_to_btc(deserialize_asset_amount(resp['amount']))
        except DeserializationError as e:
            msg = f'Bitmex API request failed. Failed to deserialized amount due to {str(e)}'
            log.error(msg)
            return None, msg

        usd_value = amount * usd_price
        returned_balances[A_BTC] = Balance(
            amount=amount,
            usd_value=usd_value,
        )
        log.debug(
            'Bitmex balance query result',
            currency='BTC',
            amount=amount,
            usd_value=usd_value,
        )

        return returned_balances, ''
Exemple #26
0
    def query_balances(self) -> ExchangeQueryBalances:
        try:
            balances = self._private_api_query('balances')
            balances.extend(self._private_api_query('balances/earn'))
        except (GeminiPermissionError, RemoteError) as e:
            msg = f'Gemini API request failed. {str(e)}'
            log.error(msg)
            return None, msg

        returned_balances: DefaultDict[Asset, Balance] = defaultdict(Balance)
        for entry in balances:
            try:
                balance_type = entry['type']
                if balance_type == 'exchange':
                    amount = deserialize_asset_amount(entry['amount'])
                else:  # should be 'Earn'
                    amount = deserialize_asset_amount(entry['balance'])
                # ignore empty balances
                if amount == ZERO:
                    continue

                asset = asset_from_gemini(entry['currency'])
                try:
                    usd_price = Inquirer().find_usd_price(asset=asset)
                except RemoteError as e:
                    self.msg_aggregator.add_error(
                        f'Error processing gemini {balance_type} balance result due to '
                        f'inability to query USD price: {str(e)}. Skipping balance entry',
                    )
                    continue

                returned_balances[asset] += Balance(
                    amount=amount,
                    usd_value=amount * usd_price,
                )
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found gemini balance result with unknown asset '
                    f'{e.asset_name}. Ignoring it.', )
                continue
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found gemini {balance_type} balance result with unsupported '
                    f'asset {e.asset_name}. Ignoring it.', )
                continue
            except (DeserializationError, KeyError) as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'
                self.msg_aggregator.add_error(
                    f'Error processing a gemini {balance_type} balance. Check logs '
                    f'for details. Ignoring it.', )
                log.error(
                    f'Error processing a gemini {balance_type} balance',
                    error=msg,
                )
                continue

        return returned_balances, ''
Exemple #27
0
    def deserialize_from_db(
            cls,
            event_tuple: BalancerEventDBTuple,
    ) -> 'BalancerEvent':
        """May raise DeserializationError

        Event_tuple index - Schema columns
        ----------------------------------
        0 - tx_hash
        1 - log_index
        2 - address
        3 - timestamp
        4 - type
        5 - pool_address_token
        6 - lp_amount
        7 - usd_value
        8 - amount0
        9 - amount1
        10 - amount2
        11 - amount3
        12 - amount4
        13 - amount5
        14 - amount6
        15 - amount7
        """
        event_tuple_type = event_tuple[4]
        try:
            event_type = getattr(BalancerBPTEventType, event_tuple_type.upper())
        except AttributeError as e:
            raise DeserializationError(f'Unexpected event type: {event_tuple_type}.') from e

        pool_address_token = EthereumToken.from_identifier(
            event_tuple[5],
            form_with_incomplete_data=True,  # since some may not have decimals input correctly
        )
        if pool_address_token is None:
            raise DeserializationError(
                f'Balancer event pool token: {event_tuple[5]} not found in the DB.',
            )

        amounts: List[AssetAmount] = [
            deserialize_asset_amount(item)
            for item in event_tuple[8:16]
            if item is not None
        ]
        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_token=pool_address_token,
            lp_balance=Balance(
                amount=deserialize_asset_amount(event_tuple[6]),
                usd_value=deserialize_price(event_tuple[7]),
            ),
            amounts=amounts,
        )
Exemple #28
0
def test_hop_l2_deposit(database, ethereum_manager,
                        function_scope_messages_aggregator):
    """Data taken from
    https://etherscan.io/tx/0xd46640417a686b399b2f2a920b0c58a35095759365cbe7b795bddec34b8c5eee
    """
    # TODO: For faster tests hard-code the transaction and the logs here so no remote query needed
    tx_hash = deserialize_evm_tx_hash(
        '0xd46640417a686b399b2f2a920b0c58a35095759365cbe7b795bddec34b8c5eee'
    )  # noqa: E501
    events = get_decoded_events_of_transaction(
        ethereum_manager=ethereum_manager,
        database=database,
        msg_aggregator=function_scope_messages_aggregator,
        tx_hash=tx_hash,
    )
    expected_events = [
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=0,
            timestamp=1653219722000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.001964214783875487')),
            location_label=ADDY,
            notes=f'Burned 0.001964214783875487 ETH in gas from {ADDY}',
            counterparty=CPT_GAS,
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash.hex(),  # pylint: disable=no-member
            sequence_index=1,
            timestamp=1653219722000,
            location=Location.BLOCKCHAIN,
            event_type=HistoryEventType.TRANSFER,
            event_subtype=HistoryEventSubType.BRIDGE,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.2')),
            location_label=ADDY,
            notes=
            'Bridge 0.2 ETH to Optimism at the same address via Hop protocol',
            counterparty=CPT_HOP,
        )
    ]
    assert expected_events == events
Exemple #29
0
    def query_balances(self) -> ExchangeQueryBalances:
        try:
            wallets, _, _ = self._api_query('wallets')
            # asset_wallets = self._api_query('asset-wallets')
            fiat_wallets, _, _ = self._api_query('fiatwallets')
        except RemoteError as e:
            msg = f'Failed to query Bitpanda balances. {str(e)}'
            return None, msg

        assets_balance: DefaultDict[Asset, Balance] = defaultdict(Balance)
        wallets_len = len(wallets)
        for idx, entry in enumerate(wallets + fiat_wallets):

            if idx < wallets_len:
                symbol_key = 'cryptocoin_symbol'
            else:
                symbol_key = 'fiat_symbol'

            try:
                amount = deserialize_asset_amount(
                    entry['attributes']['balance'])
                asset = asset_from_bitpanda(entry['attributes'][symbol_key])
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found unsupported/unknown Bitpanda asset {e.asset_name}. '
                    f' Ignoring its balance query.', )
                continue
            except (DeserializationError, KeyError) as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'
                self.msg_aggregator.add_error(
                    'Error processing Bitpanda balance. Check logs '
                    'for details. Ignoring it.', )
                log.error(
                    'Error processing bitpanda balance',
                    entry=entry,
                    error=msg,
                )
                continue

            if amount == ZERO:
                continue

            try:
                usd_price = Inquirer().find_usd_price(asset=asset)
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    f'Error processing Bitpanda balance entry due to inability to '
                    f'query USD price: {str(e)}. Skipping balance entry', )
                continue
            assets_balance[asset] += Balance(
                amount=amount,
                usd_value=amount * usd_price,
            )

        return dict(assets_balance), ''
Exemple #30
0
    def get_validator_deposits(
            self,
            indices_or_pubkeys: Union[List[int], List[Eth2PubKey]],
    ) -> List[Eth2Deposit]:
        """Get the deposits of all the validators given from the list of indices or pubkeys

        Queries in chunks of 100 due to api limitations

        May raise:
        - RemoteError due to problems querying beaconcha.in API
        """
        chunks = _calculate_query_chunks(indices_or_pubkeys)
        data = []
        for chunk in chunks:
            result = self._query(
                module='validator',
                endpoint='deposits',
                encoded_args=','.join(str(x) for x in chunk),
            )
            if isinstance(result, list):
                data.extend(result)
            else:
                data.append(result)

        deposits = []
        for entry in data:
            try:
                amount = from_gwei(FVal(entry['amount']))
                timestamp = entry['block_ts']
                usd_price = query_usd_price_zero_if_error(
                    asset=A_ETH,
                    time=timestamp,
                    location=f'Eth2 staking deposit at time {timestamp}',
                    msg_aggregator=self.msg_aggregator,
                )
                deposits.append(Eth2Deposit(
                    from_address=deserialize_ethereum_address(entry['from_address']),
                    pubkey=entry['publickey'],
                    withdrawal_credentials=entry['withdrawal_credentials'],
                    value=Balance(
                        amount=amount,
                        usd_value=amount * usd_price,
                    ),
                    tx_hash=deserialize_evm_tx_hash(entry['tx_hash']),
                    tx_index=entry['tx_index'],
                    timestamp=entry['block_ts'],
                ))
            except (DeserializationError, KeyError) as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'
                raise RemoteError(
                    f'Beaconchai.in deposits response processing error. {msg}',
                ) from e

        return deposits