def test_defi_event_zero_amount(accountant): """Test that if a Defi Event with a zero amount obtained comes in we don't raise an error Regression test for a division by zero error a user reported """ defi_events = [ DefiEvent( timestamp=1467279735, wrapped_event=AaveInterestEvent( event_type='interest', asset=A_WBTC, value=Balance(amount=FVal(0), usd_value=FVal(0)), block_number=4, timestamp=Timestamp(1467279735), tx_hash= '0x49c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', log_index=4, ), event_type=DefiEventType.AAVE_EVENT, got_asset=A_WBTC, got_balance=Balance(amount=FVal(0), usd_value=FVal(0)), spent_asset=A_WBTC, spent_balance=Balance(amount=FVal(0), usd_value=FVal(0)), pnl=[ AssetBalance(asset=A_WBTC, balance=Balance(amount=FVal(0), usd_value=FVal(0))) ], count_spent_got_cost_basis=True, tx_hash= '0x49c67445d26679623f9b7d56a8be260a275cb6744a1c1ae5a8d6883a5a5c03de', ) ] result = accounting_history_process( accountant=accountant, start_ts=1466979735, end_ts=1519693374, history_list=[], defi_events_list=defi_events, ) assert result['all_events'][0] == { 'cost_basis': None, 'is_virtual': False, 'location': 'blockchain', 'net_profit_or_loss': ZERO, 'paid_asset': 'WBTC(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599)', 'paid_in_asset': ZERO, 'paid_in_profit_currency': ZERO, 'received_asset': '', 'received_in_asset': ZERO, 'taxable_amount': ZERO, 'taxable_bought_cost_in_profit_currency': ZERO, 'taxable_received_in_profit_currency': ZERO, 'time': 1467279735, 'type': 'defi_event', }
def 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)
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
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',
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