def _decode_order_placement( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument 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]]: """Some docs: https://docs.gnosis.io/protocol/docs/tutorial-limit-orders/""" topic_data, log_data = self.contract.decode_event( tx_log=tx_log, event_name='OrderPlacement', argument_names=('owner', 'index', 'buyToken', 'sellToken', 'validFrom', 'validUntil', 'priceNumerator', 'priceDenominator'), # noqa: E501 ) owner = topic_data[0] if not self.base.is_tracked(owner): return None, None result = multicall_specific( ethereum=self.ethereum, contract=self.contract, method_name='tokenIdToAddressMap', arguments=[[topic_data[1]], [topic_data[2]]], ) # The resulting addresses are non checksumed but they can be found in the DB buy_token = ethaddress_to_asset(result[0][0]) if buy_token is None: return None, None sell_token = ethaddress_to_asset(result[1][0]) if sell_token is None: return None, None buy_amount = asset_normalized_value(amount=log_data[3], asset=buy_token) sell_amount = asset_normalized_value(amount=log_data[4], asset=sell_token) 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, # Asset means nothing here since the event is informational. TODO: Improve? asset=sell_token, balance=Balance(amount=sell_amount), notes= f'Place an order in DXDao Mesa to sell {sell_amount} {sell_token.symbol} for {buy_amount} {buy_token.symbol}', # noqa: E501 event_type=HistoryEventType.INFORMATIONAL, event_subtype=HistoryEventSubType.PLACE_ORDER, counterparty=CPT_DXDAO_MESA, ) return event, None
def _decode_swapped( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument decoded_events: List[HistoryBaseEntry], # pylint: disable=unused-argument all_logs: List[EthereumTxReceiptLog], # pylint: disable=unused-argument ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]: sender = hex_or_bytes_to_address(tx_log.topics[1]) source_token_address = hex_or_bytes_to_address(tx_log.topics[2]) destination_token_address = hex_or_bytes_to_address(tx_log.topics[3]) source_token = ethaddress_to_asset(source_token_address) if source_token is None: return None, None destination_token = ethaddress_to_asset(destination_token_address) if destination_token is None: return None, None receiver = hex_or_bytes_to_address(tx_log.data[0:32]) spent_amount_raw = hex_or_bytes_to_int(tx_log.data[64:96]) return_amount_raw = hex_or_bytes_to_int(tx_log.data[96:128]) spent_amount = asset_normalized_value(amount=spent_amount_raw, asset=source_token) return_amount = asset_normalized_value(amount=return_amount_raw, asset=destination_token) out_event = in_event = None for event in decoded_events: # Now find the sending and receiving events if event.event_type == HistoryEventType.SPEND and event.location_label == sender and spent_amount == event.balance.amount and source_token == event.asset: # noqa: E501 event.event_type = HistoryEventType.TRADE event.event_subtype = HistoryEventSubType.SPEND event.counterparty = CPT_ONEINCH_V2 event.notes = f'Swap {spent_amount} {source_token.symbol} in {CPT_ONEINCH_V2}' # noqa: E501 out_event = event elif event.event_type == HistoryEventType.RECEIVE and event.location_label == sender and receiver == event.location_label and return_amount == event.balance.amount and destination_token == event.asset: # noqa: E501 event.event_type = HistoryEventType.TRADE event.event_subtype = HistoryEventSubType.RECEIVE event.counterparty = CPT_ONEINCH_V2 event.notes = f'Receive {return_amount} {destination_token.symbol} from {CPT_ONEINCH_V2} swap' # noqa: E501 # use this index as the event may be an ETH transfer and appear at the start event.sequence_index = tx_log.log_index in_event = event maybe_reshuffle_events(out_event=out_event, in_event=in_event, events_list=decoded_events) return None, None
def _legacy_contracts_basic_info(tx_log: EthereumTxReceiptLog) -> Tuple[ChecksumEthAddress, Optional[Asset], Optional[Asset]]: # noqa: E501 """ Returns: - address of the sender - source token (can be none) - destination token (can be none) May raise: - DeserializationError when using hex_or_bytes_to_address """ sender = hex_or_bytes_to_address(tx_log.topics[1]) source_token_address = hex_or_bytes_to_address(tx_log.data[:32]) destination_token_address = hex_or_bytes_to_address(tx_log.data[32:64]) source_token = ethaddress_to_asset(source_token_address) destination_token = ethaddress_to_asset(destination_token_address) return sender, source_token, destination_token
def _decode_claim( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument decoded_events: List[HistoryBaseEntry], all_logs: List[EthereumTxReceiptLog], # pylint: disable=unused-argument action_items: Optional[List[ActionItem]], # pylint: disable=unused-argument ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]: if tx_log.topics[0] != VOTIUM_CLAIM: return None, None claimed_token_address = hex_or_bytes_to_address(tx_log.topics[1]) claimed_token = ethaddress_to_asset(claimed_token_address) if claimed_token is None: return None, None receiver = hex_or_bytes_to_address(tx_log.topics[2]) claimed_amount_raw = hex_or_bytes_to_int(tx_log.data[32:64]) amount = asset_normalized_value(amount=claimed_amount_raw, asset=claimed_token) for event in decoded_events: if event.event_type == HistoryEventType.RECEIVE and event.location_label == receiver and event.balance.amount == amount and claimed_token == event.asset: # noqa: E501 event.event_subtype = HistoryEventSubType.REWARD event.counterparty = CPT_VOTIUM event.notes = f'Receive {event.balance.amount} {event.asset.symbol} from votium bribe' # noqa: E501 return None, None
def _decode_deposit( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument 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]]: topic_data, log_data = self.contract.decode_event( tx_log=tx_log, event_name='Deposit', argument_names=('user', 'token', 'amount', 'batchId'), ) deposited_asset = ethaddress_to_asset(topic_data[1]) if deposited_asset is None: return None, None amount = asset_normalized_value(amount=log_data[0], asset=deposited_asset) for event in decoded_events: # Find the transfer event which should come before the deposit if event.event_type == HistoryEventType.SPEND and event.asset == deposited_asset and event.balance.amount == amount and event.counterparty == self.contract.address: # noqa: E501 event.event_type = HistoryEventType.DEPOSIT event.event_subtype = HistoryEventSubType.DEPOSIT_ASSET event.counterparty = CPT_DXDAO_MESA event.notes = f'Deposit {amount} {deposited_asset.symbol} to DXDao mesa exchange' # noqa: E501 break return None, None
def _decode_withdraw( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument 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]]: topic_data, log_data = self.contract.decode_event( tx_log=tx_log, event_name='Withdraw', argument_names=('user', 'token', 'amount'), ) withdraw_asset = ethaddress_to_asset(topic_data[1]) if withdraw_asset is None: return None, None amount = asset_normalized_value(amount=log_data[0], asset=withdraw_asset) for event in decoded_events: # Find the transfer event which should come before the withdraw if event.event_type == HistoryEventType.RECEIVE and event.asset == withdraw_asset and event.balance.amount == amount and event.counterparty == self.contract.address: # noqa: E501 event.event_type = HistoryEventType.WITHDRAWAL event.event_subtype = HistoryEventSubType.REMOVE_ASSET event.counterparty = CPT_DXDAO_MESA event.notes = f'Withdraw {amount} {withdraw_asset.symbol} from DXDao mesa exchange' # noqa: E501 break return None, None
def _decode_history( self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument decoded_events: List[HistoryBaseEntry], # pylint: disable=unused-argument all_logs: List[EthereumTxReceiptLog], # pylint: disable=unused-argument ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]: sender = hex_or_bytes_to_address(tx_log.topics[1]) if not self.base.is_tracked(sender): return None, None from_token_address = hex_or_bytes_to_address(tx_log.data[0:32]) to_token_address = hex_or_bytes_to_address(tx_log.data[32:64]) from_asset = ethaddress_to_asset(from_token_address) to_asset = ethaddress_to_asset(to_token_address) if None in (from_asset, to_asset): return None, None from_raw = hex_or_bytes_to_int(tx_log.data[64:96]) from_amount = asset_normalized_value(from_raw, from_asset) # type: ignore to_raw = hex_or_bytes_to_int(tx_log.data[96:128]) to_amount = asset_normalized_value(to_raw, to_asset) # type: ignore out_event = in_event = None for event in decoded_events: if event.event_type == HistoryEventType.SPEND and event.location_label == sender and from_amount == event.balance.amount and from_asset == event.asset: # noqa: E501 # find the send event event.event_type = HistoryEventType.TRADE event.event_subtype = HistoryEventSubType.SPEND event.counterparty = CPT_ONEINCH_V1 event.notes = f'Swap {from_amount} {from_asset.symbol} in {CPT_ONEINCH_V1} from {event.location_label}' # noqa: E501 out_event = event elif event.event_type == HistoryEventType.RECEIVE and event.location_label == sender and to_amount == event.balance.amount and to_asset == event.asset: # noqa: E501 # find the receive event event.event_type = HistoryEventType.TRADE event.event_subtype = HistoryEventSubType.RECEIVE event.counterparty = CPT_ONEINCH_V1 event.notes = f'Receive {to_amount} {to_asset.symbol} from {CPT_ONEINCH_V1} swap in {event.location_label}' # noqa: E501 # use this index as the event may be an ETH transfer and appear at the start event.sequence_index = tx_log.log_index in_event = event maybe_reshuffle_events(out_event=out_event, in_event=in_event, events_list=decoded_events) return None, None
def _parse_atoken_balance_history( history: List[Dict[str, Any]], from_ts: Timestamp, to_ts: Timestamp, ) -> List[ATokenBalanceHistory]: result = [] for entry in history: timestamp = entry['timestamp'] if timestamp < from_ts or timestamp > to_ts: continue entry_id = entry['id'] pairs = entry_id.split('0x') if len(pairs) not in (4, 5): log.error( f'Expected to find 3-4 hashes in graph\'s aTokenBalanceHistory ' f'id but the encountered id does not match: {entry_id}. Skipping entry...', ) continue try: address_s = '0x' + pairs[2] reserve_address = deserialize_ethereum_address(address_s) except DeserializationError: log.error(f'Error deserializing reserve address {address_s}', ) continue version = _get_version_from_reserveid(pairs, 3) tx_hash = '0x' + pairs[4] asset = ethaddress_to_asset(reserve_address) if asset is None: log.error( f'Unknown aave reserve address returned by atoken balance history ' f' graph query: {reserve_address}. Skipping entry ...', ) continue _, decimals = _get_reserve_address_decimals(asset) if 'currentATokenBalance' in entry: balance = token_normalized_value_decimals( int(entry['currentATokenBalance']), token_decimals=decimals, ) else: balance = token_normalized_value_decimals( int(entry['balance']), token_decimals=decimals, ) result.append( ATokenBalanceHistory( reserve_address=reserve_address, balance=balance, tx_hash=tx_hash, timestamp=timestamp, version=version, )) return result
def test_aave_reserve_mapping(): atokensv1 = GlobalDBHandler().get_ethereum_tokens(protocol='aave') for token in atokensv1: underlying_asset = ATOKENV1_TO_ASSET[token] if underlying_asset == A_ETH: assert asset_to_aave_reserve_address(underlying_asset) == ETH_SPECIAL_ADDRESS continue assert ethaddress_to_asset(underlying_asset.ethereum_address) == underlying_asset assert asset_to_aave_reserve_address(underlying_asset) == underlying_asset.ethereum_address
def _decode_swapped( # pylint: disable=no-self-use 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]]: """We use the Swapped event to get the fee kept by 1inch""" to_token_address = hex_or_bytes_to_address(tx_log.topics[2]) to_asset = ethaddress_to_asset(to_token_address) if to_asset is None: return None, None to_raw = hex_or_bytes_to_int(tx_log.data[32:64]) fee_raw = hex_or_bytes_to_int(tx_log.data[96:128]) if fee_raw == 0: return None, None # no need to do anything for zero fee taken full_amount = asset_normalized_value(to_raw + fee_raw, to_asset) sender_address = None for event in decoded_events: # Edit the full amount in the swap's receive event if event.event_type == HistoryEventType.TRADE and event.event_subtype == HistoryEventSubType.RECEIVE and event.counterparty == CPT_ONEINCH_V1: # noqa: E501 event.balance.amount = full_amount event.notes = f'Receive {full_amount} {event.asset.symbol} from {CPT_ONEINCH_V1} swap in {event.location_label}' # noqa: E501 sender_address = event.location_label break if sender_address is None: return None, None # And now create a new event for the fee fee_amount = asset_normalized_value(fee_raw, to_asset) fee_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=sender_address, asset=to_asset, balance=Balance(amount=fee_amount), notes= f'Deduct {fee_amount} {to_asset.symbol} from {sender_address} as {CPT_ONEINCH_V1} fees', # noqa: E501 event_type=HistoryEventType.SPEND, event_subtype=HistoryEventSubType.FEE, counterparty=CPT_ONEINCH_V1, ) return fee_event, None
def _decode_redeem_underlying_event( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument 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]]: reserve_address = hex_or_bytes_to_address(tx_log.topics[1]) reserve_asset = ethaddress_to_asset(reserve_address) if reserve_asset is None: return None, None user_address = hex_or_bytes_to_address(tx_log.topics[2]) raw_amount = hex_or_bytes_to_int(tx_log.data[0:32]) amount = asset_normalized_value(raw_amount, reserve_asset) atoken = asset_to_atoken(asset=reserve_asset, version=1) if atoken is None: return None, None receive_event = return_event = None for event in decoded_events: if event.event_type == HistoryEventType.RECEIVE and event.location_label == user_address and amount == event.balance.amount and reserve_asset == event.asset: # noqa: E501 event.event_type = HistoryEventType.WITHDRAWAL event.event_subtype = HistoryEventSubType.REMOVE_ASSET event.counterparty = CPT_AAVE_V1 event.notes = f'Withdraw {amount} {reserve_asset.symbol} from aave-v1' receive_event = event elif event.event_type == HistoryEventType.SPEND and event.location_label == user_address and amount == event.balance.amount and atoken == event.asset: # noqa: E501 # find the redeem aToken transfer event.event_type = HistoryEventType.SPEND event.event_subtype = HistoryEventSubType.RETURN_WRAPPED event.counterparty = CPT_AAVE_V1 event.notes = f'Return {amount} {atoken.symbol} to aave-v1' return_event = event elif event.event_type == HistoryEventType.RECEIVE and event.location_label == user_address and event.counterparty == ZERO_ADDRESS and event.asset == atoken: # noqa: E501 event.event_subtype = HistoryEventSubType.REWARD event.counterparty = CPT_AAVE_V1 event.notes = f'Gain {event.balance.amount} {atoken.symbol} from aave-v1 as interest' # noqa: E501 maybe_reshuffle_events(out_event=return_event, in_event=receive_event, events_list=decoded_events) # noqa: E501 return None, None
def _decode_withdraw_request( # pylint: disable=no-self-use self, tx_log: EthereumTxReceiptLog, transaction: EthereumTransaction, # pylint: disable=unused-argument 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]]: topic_data, log_data = self.contract.decode_event( tx_log=tx_log, event_name='WithdrawRequest', argument_names=('user', 'token', 'amount', 'batchId'), ) user = topic_data[0] if not self.base.is_tracked(user): return None, None token = ethaddress_to_asset(topic_data[1]) if token is None: return None, None amount = asset_normalized_value(amount=log_data[0], asset=token) 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=user, # Asset means nothing here since the event is informational. TODO: Improve? asset=token, balance=Balance(amount=amount), notes= f'Request a withdrawal of {amount} {token.symbol} from DXDao Mesa', event_type=HistoryEventType.INFORMATIONAL, event_subtype=HistoryEventSubType.REMOVE_ASSET, counterparty=CPT_DXDAO_MESA, ) return event, None
def _get_reserve_asset_and_decimals( entry: Dict[str, Any], reserve_key: str, ) -> Optional[Tuple[Asset, int]]: try: # The ID of reserve is the address of the asset and the address of the market's LendingPoolAddressProvider, in lower case # noqa: E501 reserve_address = deserialize_ethereum_address( entry[reserve_key]['id'][:42]) except DeserializationError: log.error( f'Failed to Deserialize reserve address {entry[reserve_key]["id"]}' ) return None asset = ethaddress_to_asset(reserve_address) if asset is None: log.error( f'Unknown aave reserve address returned by graph query: ' f'{reserve_address}. Skipping entry ...', ) return None _, decimals = _get_reserve_address_decimals(asset) return asset, decimals