Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
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
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
    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
Beispiel #11
0
    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
Beispiel #12
0
    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
Beispiel #13
0
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