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',
    }
Beispiel #2
0
def test_add_and_get_aave_events(data_dir, username):
    """Test that get aave events works fine and returns only events for what we need"""
    msg_aggregator = MessagesAggregator()
    data = DataHandler(data_dir, msg_aggregator)
    data.unlock(username, '123', create_new=True)

    addr1 = make_ethereum_address()
    addr1_events = [
        AaveDepositWithdrawalEvent(
            event_type='deposit',
            asset=A_DAI,
            atoken=A_ADAI_V1,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=1,
            timestamp=Timestamp(1),
            tx_hash=
            '0x01653e88600a6492ad6e9ae2af415c990e623479057e4e93b163e65cfb2d4436',
            log_index=1,
        ),
        AaveDepositWithdrawalEvent(
            event_type='withdrawal',
            asset=A_DAI,
            atoken=A_ADAI_V1,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=2,
            timestamp=Timestamp(2),
            tx_hash=
            '0x4147da3e5d3c0565a99192ce0b32182ab30b8e1067921d9b2a8ef3bd60b7e2ce',
            log_index=2,
        )
    ]
    data.db.add_aave_events(address=addr1, events=addr1_events)

    addr2 = make_ethereum_address()
    addr2_events = [
        AaveDepositWithdrawalEvent(
            event_type='deposit',
            asset=A_DAI,
            atoken=A_ADAI_V1,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=1,
            timestamp=Timestamp(1),
            tx_hash=
            '0x8c094d58f33e8dedcd348cb33b58f3bd447602f1fecb99e51b1c2868029eab55',
            log_index=1,
        ),
        AaveDepositWithdrawalEvent(
            event_type='withdrawal',
            asset=A_DAI,
            atoken=A_ADAI_V1,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=2,
            timestamp=Timestamp(2),
            tx_hash=
            '0x58c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de',
            log_index=2,
        )
    ]
    data.db.add_aave_events(address=addr2, events=addr2_events)

    # addr3 has all types of aave events so we test serialization/deserialization
    addr3 = make_ethereum_address()
    addr3_events = [
        AaveDepositWithdrawalEvent(
            event_type='deposit',
            asset=A_DAI,
            atoken=A_ADAI_V1,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=1,
            timestamp=Timestamp(1),
            tx_hash=
            '0x9e394d58f33e8dedcd348cb33b58f3bd447602f1fecb99e51b1c2868029eab55',
            log_index=1,
        ),
        AaveDepositWithdrawalEvent(
            event_type='withdrawal',
            asset=A_DAI,
            atoken=A_ADAI_V1,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=2,
            timestamp=Timestamp(2),
            tx_hash=
            '0x4c167445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de',
            log_index=2,
        ),
        AaveInterestEvent(
            event_type='interest',
            asset=A_WBTC,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=4,
            timestamp=Timestamp(4),
            tx_hash=
            '0x49c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de',
            log_index=4,
        ),
        AaveBorrowEvent(
            event_type='borrow',
            asset=A_ETH,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=5,
            timestamp=Timestamp(5),
            tx_hash=
            '0x19c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de',
            log_index=5,
            borrow_rate_mode='stable',
            borrow_rate=FVal('0.05233232323423432'),
            accrued_borrow_interest=FVal('5.112234'),
        ),
        AaveRepayEvent(
            event_type='repay',
            asset=A_MANA,
            value=Balance(amount=ONE, usd_value=ONE),
            block_number=6,
            timestamp=Timestamp(6),
            tx_hash=
            '0x29c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de',
            log_index=6,
            fee=Balance(amount=FVal('0.1'), usd_value=FVal('0.1')),
        ),
        AaveLiquidationEvent(
            event_type='liquidation',
            collateral_asset=A_ETH,
            collateral_balance=Balance(amount=ONE, usd_value=ONE),
            principal_asset=A_ETH,
            principal_balance=Balance(amount=ONE, usd_value=ONE),
            block_number=7,
            log_index=7,
            timestamp=Timestamp(7),
            tx_hash=
            '0x39c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de',
        )
    ]
    data.db.add_aave_events(address=addr3, events=addr3_events)

    events = data.db.get_aave_events(address=addr1, atoken=A_ADAI_V1)
    assert events == addr1_events
    events = data.db.get_aave_events(address=addr2, atoken=A_ADAI_V1)
    assert events == addr2_events
    events = data.db.get_aave_events(address=addr3)
    assert events == addr3_events

    # check that all aave events are properly hashable (aka can go in a set)
    test_set = set()
    for event in addr3_events:
        test_set.add(event)
    assert len(test_set) == len(addr3_events)
Beispiel #3
0
    def _calculate_interest_and_profit(
        self,
        user_address: ChecksumEthAddress,
        user_result: Dict[str, Any],
        actions: List[AaveDepositWithdrawalEvent],
        balances: AaveBalances,
        db_interest_events: Set[AaveInterestEvent],
        from_ts: Timestamp,
        to_ts: Timestamp,
    ) -> Tuple[List[AaveInterestEvent], Dict[Asset, Balance]]:
        reserve_history = {}
        for reserve in user_result['reserves']:
            pairs = reserve['id'].split('0x')
            if len(pairs) != 4:
                log.error(
                    f'Expected to find 3 addresses in graph\'s reserve history id '
                    f'but the encountered id does not match: {reserve["id"]}. Skipping entry...',
                )
                continue

            try:
                address_s = '0x' + pairs[2]
                reserve_address = deserialize_ethereum_address(address_s)
            except DeserializationError:
                log.error(
                    f'Failed to deserialize reserve address {address_s} '
                    f'Skipping reserve address {address_s} for user address {user_address}',
                )
                continue

            atoken_history = _parse_atoken_balance_history(
                history=reserve['aTokenBalanceHistory'],
                from_ts=from_ts,
                to_ts=to_ts,
            )
            reserve_history[reserve_address] = atoken_history

        interest_events: List[AaveInterestEvent] = []
        atoken_balances: Dict[Asset, FVal] = defaultdict(FVal)
        used_history_indices = set()
        total_earned: Dict[Asset, Balance] = defaultdict(Balance)

        # Go through the existing db interest events and add total earned
        for interest_event in db_interest_events:
            total_earned[interest_event.asset] += interest_event.value

        # Create all new interest events in the query
        actions.sort(key=lambda event: event.timestamp)
        for action in actions:
            if action.event_type == 'deposit':
                atoken_balances[action.asset] += action.value.amount
            else:  # withdrawal
                atoken_balances[action.asset] -= action.value.amount

            action_reserve_address = asset_to_aave_reserve(action.asset)
            if action_reserve_address is None:
                log.error(
                    f'Could not find aave reserve address for asset'
                    f'{action.asset} in an aave graph response.'
                    f' Skipping entry...', )
                continue
            history = reserve_history.get(action_reserve_address, None)
            if history is None:
                log.error(
                    f'Could not find aTokenBalanceHistory for reserve '
                    f'{action_reserve_address} in an aave graph response.'
                    f' Skipping entry...', )
                continue
            history.sort(key=lambda event: event.timestamp)

            for idx, entry in enumerate(history):
                if idx in used_history_indices:
                    continue
                used_history_indices.add(idx)

                if entry.tx_hash == action.tx_hash:
                    diff = entry.balance - atoken_balances[action.asset]
                    if diff != ZERO:
                        atoken_balances[action.asset] = entry.balance
                        asset = ASSET_TO_ATOKENV1.get(action.asset, None)
                        if asset is None:
                            log.error(
                                f'Could not find corresponding aToken to '
                                f'{action.asset.identifier} during an aave graph query'
                                f' Skipping entry...', )
                            continue
                        timestamp = entry.timestamp
                        usd_price = query_usd_price_zero_if_error(
                            asset=asset,
                            time=timestamp,
                            location='aave interest event from graph query',
                            msg_aggregator=self.msg_aggregator,
                        )
                        earned_balance = Balance(amount=diff,
                                                 usd_value=diff * usd_price)
                        interest_event = AaveInterestEvent(
                            event_type='interest',
                            asset=asset,
                            value=earned_balance,
                            block_number=0,  # can't get from graph query
                            timestamp=timestamp,
                            tx_hash=entry.tx_hash,
                            # not really the log index, but should also be unique
                            log_index=action.log_index + 1,
                        )
                        if interest_event in db_interest_events:
                            # This should not really happen since we already query
                            # historical atoken balance history in the new range
                            log.warning(
                                f'During aave subgraph query interest and profit calculation '
                                f'tried to generate interest event {interest_event} that '
                                f'already existed in the DB ', )
                            continue

                        interest_events.append(interest_event)
                        total_earned[asset] += earned_balance

                    # and once done break off the loop
                    break

                # else this atoken history is not due to an action, so skip it.
                # It's probably due to a simple transfer
                atoken_balances[action.asset] = entry.balance
                if action.event_type == 'deposit':
                    atoken_balances[action.asset] += action.value.amount
                else:  # withdrawal
                    atoken_balances[action.asset] -= action.value.amount

        # Take aave unpaid interest into account
        for balance_asset, lending_balance in balances.lending.items():
            atoken = ASSET_TO_ATOKENV1.get(balance_asset, None)
            if atoken is None:
                log.error(
                    f'Could not find corresponding aToken to '
                    f'{balance_asset.identifier} during an aave graph unpair interest '
                    f'query. Skipping entry...', )
                continue
            principal_balance = self.ethereum.call_contract(
                contract_address=atoken.ethereum_address,
                abi=ATOKEN_ABI,
                method_name='principalBalanceOf',
                arguments=[user_address],
            )
            unpaid_interest = lending_balance.balance.amount - (
                principal_balance /
                (FVal(10)**FVal(atoken.decimals)))  # noqa: E501
            usd_price = Inquirer().find_usd_price(atoken)
            total_earned[atoken] += Balance(
                amount=unpaid_interest,
                usd_value=unpaid_interest * usd_price,
            )

        return interest_events, total_earned
Beispiel #4
0
     asset=A_DAI,
     atoken=A_ADAI_V1,
     value=Balance(
         amount=FVal('160'),
         usd_value=FVal('161.440'),
     ),
     block_number=9987395,
     timestamp=Timestamp(1588430911),
     tx_hash='0x78ae48d93e0284d1f9a5e1cd4a7e5f2e3daf65ab5dafb0c4bd626aa90e783d60',
     log_index=146,
 ), AaveInterestEvent(
     event_type='interest',
     asset=A_ADAI,
     value=Balance(
         amount=FVal('0.037901731034995483'),
         usd_value=FVal('0.038242846614310442347'),
     ),
     block_number=9987395,
     timestamp=Timestamp(1588430911),
     tx_hash='0x78ae48d93e0284d1f9a5e1cd4a7e5f2e3daf65ab5dafb0c4bd626aa90e783d60',
     log_index=142,
 ), AaveDepositWithdrawalEvent(
     event_type='deposit',
     asset=A_DAI,
     atoken=A_ADAI_V1,
     value=Balance(
         amount=FVal('390'),
         usd_value=FVal('393.510'),
     ),
     block_number=9989872,
     timestamp=Timestamp(1588463542),
     tx_hash='0xb9999b06b706dcc973bcf381d69f12620f1bef887082bce9679cf256f7e8023c',
Beispiel #5
0
    def get_events_for_atoken_and_address(
        self,
        user_address: ChecksumEthAddress,
        atoken: EthereumToken,
        deposit_events: List[Dict[str, Any]],
        withdraw_events: List[Dict[str, Any]],
        from_block: int,
        to_block: int,
    ) -> List[AaveEvent]:
        """This function should be entered while holding the history_lock
        semaphore"""
        argument_filters = {
            'from': ZERO_ADDRESS,
            'to': user_address,
        }
        mint_events = self.ethereum.get_logs(
            contract_address=atoken.ethereum_address,
            abi=ATOKEN_ABI,
            event_name='Transfer',
            argument_filters=argument_filters,
            from_block=from_block,
            to_block=to_block,
        )
        mint_data = set()
        mint_data_to_log_index = {}
        for event in mint_events:
            amount = hexstr_to_int(event['data'])
            if amount == 0:
                continue  # first mint can be for 0. Ignore
            entry = (
                event['blockNumber'],
                amount,
                self.ethereum.get_event_timestamp(event),
                event['transactionHash'],
            )
            mint_data.add(entry)
            mint_data_to_log_index[entry] = event['logIndex']

        reserve_asset = ATOKENV1_TO_ASSET[
            atoken]  # should never raise KeyError
        reserve_address, decimals = _get_reserve_address_decimals(
            reserve_asset)
        aave_events: List[AaveEvent] = []
        for event in deposit_events:
            if hex_or_bytes_to_address(event['topics'][1]) == reserve_address:
                # first 32 bytes of the data are the amount
                deposit = hexstr_to_int(event['data'][:66])
                block_number = event['blockNumber']
                timestamp = self.ethereum.get_event_timestamp(event)
                tx_hash = event['transactionHash']
                log_index = event['logIndex']
                # If there is a corresponding deposit event remove the minting event data
                entry = (block_number, deposit, timestamp, tx_hash)
                if entry in mint_data:
                    mint_data.remove(entry)
                    del mint_data_to_log_index[entry]

                usd_price = query_usd_price_zero_if_error(
                    asset=reserve_asset,
                    time=timestamp,
                    location='aave deposit',
                    msg_aggregator=self.msg_aggregator,
                )
                deposit_amount = deposit / (FVal(10)**FVal(decimals))
                aave_events.append(
                    AaveDepositWithdrawalEvent(
                        event_type='deposit',
                        asset=reserve_asset,
                        atoken=atoken,
                        value=Balance(
                            amount=deposit_amount,
                            usd_value=deposit_amount * usd_price,
                        ),
                        block_number=block_number,
                        timestamp=timestamp,
                        tx_hash=tx_hash,
                        log_index=log_index,
                    ))

        for data in mint_data:
            usd_price = query_usd_price_zero_if_error(
                asset=atoken,
                time=data[2],
                location='aave interest profit',
                msg_aggregator=self.msg_aggregator,
            )
            interest_amount = data[1] / (FVal(10)**FVal(decimals))
            aave_events.append(
                AaveInterestEvent(
                    event_type='interest',
                    asset=atoken,
                    value=Balance(
                        amount=interest_amount,
                        usd_value=interest_amount * usd_price,
                    ),
                    block_number=data[0],
                    timestamp=data[2],
                    tx_hash=data[3],
                    log_index=mint_data_to_log_index[data],
                ))

        for event in withdraw_events:
            if hex_or_bytes_to_address(event['topics'][1]) == reserve_address:
                # first 32 bytes of the data are the amount
                withdrawal = hexstr_to_int(event['data'][:66])
                block_number = event['blockNumber']
                timestamp = self.ethereum.get_event_timestamp(event)
                tx_hash = event['transactionHash']
                usd_price = query_usd_price_zero_if_error(
                    asset=reserve_asset,
                    time=timestamp,
                    location='aave withdrawal',
                    msg_aggregator=self.msg_aggregator,
                )
                withdrawal_amount = withdrawal / (FVal(10)**FVal(decimals))
                aave_events.append(
                    AaveDepositWithdrawalEvent(
                        event_type='withdrawal',
                        asset=reserve_asset,
                        atoken=atoken,
                        value=Balance(
                            amount=withdrawal_amount,
                            usd_value=withdrawal_amount * usd_price,
                        ),
                        block_number=block_number,
                        timestamp=timestamp,
                        tx_hash=tx_hash,
                        log_index=event['logIndex'],
                    ))

        return aave_events