예제 #1
0
    def _decode_curve_events(  # pylint: disable=no-self-use
        self,
        tx_log: EthereumTxReceiptLog,
        transaction: EthereumTransaction,
        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] in (
            REMOVE_LIQUIDITY,
            REMOVE_ONE,
            REMOVE_LIQUIDITY_IMBALANCE,
            REMOVE_LIQUIDITY_3_ASSETS,
            REMOVE_LIQUIDITY_4_ASSETS,
        ):
            user_address = hex_or_bytes_to_address(tx_log.topics[1])
            self._decode_curve_remove_events(
                tx_log=tx_log,
                transaction=transaction,
                decoded_events=decoded_events,
                user_address=user_address,
            )
        elif tx_log.topics[0] in (
            ADD_LIQUIDITY,
            ADD_LIQUIDITY_2_ASSETS,
            ADD_LIQUIDITY_4_ASSETS,
        ):
            user_address = hex_or_bytes_to_address(tx_log.topics[1])
            self._decode_curve_deposit_events(
                tx_log=tx_log,
                decoded_events=decoded_events,
                user_address=user_address,
            )

        return None, None
예제 #2
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
예제 #3
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,
        )
예제 #4
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
예제 #5
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
예제 #6
0
    def _decode_elfi_claim(  # 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]]:
        """Example:
        https://etherscan.io/tx/0x1e58aed1baf70b57e6e3e880e1890e7fe607fddc94d62986c38fe70e483e594b
        """
        if tx_log.topics[0] != ELFI_VOTE_CHANGE:
            return None, None

        user_address = hex_or_bytes_to_address(tx_log.topics[1])
        delegate_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(amount=raw_amount, asset=A_ELFI)

        # now we need to find the transfer, but can't use decoded events
        # since the transfer is from one of at least 2 airdrop contracts to
        # vote/locking contract. Since neither the from, nor the to is a
        # tracked address there won't be a decoded transfer. So we search for
        # the raw log
        for other_log in all_logs:
            if other_log.topics[0] != ERC20_OR_ERC721_TRANSFER:
                continue

            transfer_raw = hex_or_bytes_to_int(other_log.data[0:32])
            if other_log.address == A_ELFI.ethereum_address and transfer_raw == raw_amount:
                delegate_str = 'self-delegate' if user_address == delegate_address else f'delegate it to {delegate_address}'  # noqa: E501
                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_address,
                    asset=A_ELFI,
                    balance=Balance(amount=amount),
                    notes=
                    f'Claim {amount} ELFI from element-finance airdrop and {delegate_str}',
                    event_type=HistoryEventType.RECEIVE,
                    event_subtype=HistoryEventSubType.AIRDROP,
                    counterparty=CPT_ELEMENT_FINANCE,
                )
                return event, None

        return None, None
예제 #7
0
    def decode_comp_claim(
            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: Optional[List[ActionItem]],  # pylint: disable=unused-argument
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        """Example tx:
        https://etherscan.io/tx/0x024bd402420c3ba2f95b875f55ce2a762338d2a14dac4887b78174254c9ab807
        """
        if tx_log.topics[0] != DISTRIBUTED_SUPPLIER_COMP:
            return None, None

        supplier_address = hex_or_bytes_to_address(tx_log.topics[2])
        if not self.base.is_tracked(supplier_address):
            return None, None

        comp_raw_amount = hex_or_bytes_to_int(tx_log.data[0:32])
        if comp_raw_amount == 0:
            return None, None  # do not count zero comp collection

        # A DistributedSupplierComp event can happen without a transfer. Just accrues
        # comp in the Comptroller until enough for a transfer is there. We should only
        # count a payout if the transfer occurs
        for event in decoded_events:
            if event.event_type == HistoryEventType.RECEIVE and event.location_label == supplier_address and event.asset == A_COMP and event.counterparty == COMPTROLLER_PROXY.address:  # noqa: E501
                event.event_subtype = HistoryEventSubType.REWARD
                event.counterparty = CPT_COMPOUND
                event.notes = f'Collect {event.balance.amount} COMP from compound'
                break

        return None, None
예제 #8
0
    def _decode_fpis_claim(  # 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
        airdrop: Literal['convex', 'fpis'],
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        if tx_log.topics[0] != FPIS_CONVEX_CLAIM:
            return None, None

        user_address = hex_or_bytes_to_address(tx_log.data[0:32])
        raw_amount = hex_or_bytes_to_int(tx_log.data[32:64])
        if airdrop == CPT_CONVEX:
            amount = asset_normalized_value(amount=raw_amount, asset=A_CVX)
            note_location = 'from convex airdrop'
            counterparty = CPT_CONVEX
        else:
            amount = asset_normalized_value(amount=raw_amount, asset=A_FPIS)
            note_location = 'from FPIS airdrop'
            counterparty = CPT_FRAX

        for event in decoded_events:
            if event.event_type == HistoryEventType.RECEIVE and event.location_label == user_address and amount == event.balance.amount and A_FPIS == event.asset:  # noqa: E501
                event.event_type = HistoryEventType.RECEIVE
                event.event_subtype = HistoryEventSubType.AIRDROP
                event.counterparty = counterparty
                event.notes = f'Claim {amount} {event.asset.symbol} {note_location}'  # noqa: E501
                break

        return None, None
예제 #9
0
    def _decode_redeem(
            self,
            tx_log: EthereumTxReceiptLog,
            decoded_events: List[HistoryBaseEntry],
            compound_token: EthereumToken,
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        redeemer = hex_or_bytes_to_address(tx_log.data[0:32])
        if not self.base.is_tracked(redeemer):
            return None, None

        redeem_amount_raw = hex_or_bytes_to_int(tx_log.data[32:64])
        redeem_tokens_raw = hex_or_bytes_to_int(tx_log.data[64:96])
        underlying_token = symbol_to_asset_or_token(compound_token.symbol[1:])
        redeem_amount = asset_normalized_value(redeem_amount_raw, underlying_token)
        redeem_tokens = token_normalized_value(redeem_tokens_raw, compound_token)
        out_event = in_event = None
        for event in decoded_events:
            # Find the transfer event which should have come before the redeeming
            if event.event_type == HistoryEventType.RECEIVE and event.asset == underlying_token and event.balance.amount == redeem_amount:  # noqa: E501
                event.event_type = HistoryEventType.WITHDRAWAL
                event.event_subtype = HistoryEventSubType.REMOVE_ASSET
                event.counterparty = CPT_COMPOUND
                event.notes = f'Withdraw {redeem_amount} {underlying_token.symbol} from compound'
                in_event = event
            if event.event_type == HistoryEventType.SPEND and event.asset == compound_token and event.balance.amount == redeem_tokens:  # noqa: E501
                event.event_type = HistoryEventType.SPEND
                event.event_subtype = HistoryEventSubType.RETURN_WRAPPED
                event.counterparty = CPT_COMPOUND
                event.notes = f'Return {redeem_tokens} {compound_token.symbol} to compound'
                out_event = event

        maybe_reshuffle_events(out_event=out_event, in_event=in_event, events_list=decoded_events)
        return None, None
예제 #10
0
    def _decode_send_eth(  # 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]]:
        if tx_log.topics[0] != TRANSFER_TO_L2:
            return None, None

        chain_id = hex_or_bytes_to_int(tx_log.topics[1])
        recipient = hex_or_bytes_to_address(tx_log.topics[2])
        amount_raw = hex_or_bytes_to_int(tx_log.data[:32])

        name = chainid_to_name.get(chain_id,
                                   f'Unknown Chain with id {chain_id}')
        amount = from_wei(FVal(amount_raw))

        for event in decoded_events:
            if event.event_type == HistoryEventType.SPEND and event.counterparty == ETH_BRIDGE and event.asset == A_ETH and event.balance.amount == amount:  # noqa: E501
                if recipient == event.location_label:
                    target_str = 'at the same address'
                else:
                    target_str = f'at address {recipient}'
                event.event_type = HistoryEventType.TRANSFER
                event.event_subtype = HistoryEventSubType.BRIDGE
                event.counterparty = CPT_HOP
                event.notes = f'Bridge {amount} ETH to {name} {target_str} via Hop protocol'
                break

        return None, None
예제 #11
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]]:
        """Match a zksync deposit with the transfer to decode it

        TODO: This is now quite bad. We don't use the token id of zksync as we should.
        Example: https://etherscan.io/tx/0xdd6d1f92980faf622c09acd84dbff4fe0bd7ae466a23c2479df709f8996d250e#eventlog
        We should include the zksync api querying module which is in this PR:
        https://github.com/rotki/rotki/pull/3985/files
        to get the ids of tokens and then match them to what is deposited.
        """  # noqa: E501
        user_address = hex_or_bytes_to_address(tx_log.topics[1])
        amount_raw = hex_or_bytes_to_int(tx_log.data)

        for event in decoded_events:
            if event.event_type == HistoryEventType.SPEND and event.location_label == user_address:
                event_raw_amount = asset_raw_value(amount=event.balance.amount, asset=event.asset)
                if event_raw_amount != amount_raw:
                    continue

                # found the deposit transfer
                event.event_type = HistoryEventType.DEPOSIT
                event.event_subtype = HistoryEventSubType.BRIDGE
                event.counterparty = CPT_ZKSYNC
                event.notes = f'Deposit {event.balance.amount} {event.asset.symbol} to zksync'  # noqa: E501
                break

        return None, None
예제 #12
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
예제 #13
0
    def decode_makerdao_debt_payback(  # 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]]:
        if tx_log.topics[0] == GENERIC_JOIN:
            join_user_address = hex_or_bytes_to_address(tx_log.topics[2])
            raw_amount = hex_or_bytes_to_int(tx_log.topics[3])
            amount = token_normalized_value(
                token_amount=raw_amount,
                token=A_DAI,
            )
            # The transfer comes right before, but we don't have enough information
            # yet to make sure that this transfer is indeed a vault payback debt. We
            # need to get a cdp frob event and compare vault id to address matches
            action_item = ActionItem(
                action='transform',
                sequence_index=tx_log.log_index,
                from_event_type=HistoryEventType.SPEND,
                from_event_subtype=HistoryEventSubType.NONE,
                asset=A_DAI,
                amount=amount,
                to_event_subtype=HistoryEventSubType.PAYBACK_DEBT,
                to_counterparty=CPT_VAULT,
                extra_data={'vault_address': join_user_address},
            )
            return None, action_item

        return None, None
예제 #14
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
예제 #15
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
예제 #16
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
예제 #17
0
 def _maybe_enrich_curve_transfers(  # pylint: disable=no-self-use
         self,
         token: EthereumToken,  # pylint: disable=unused-argument
         tx_log: EthereumTxReceiptLog,
         transaction: EthereumTransaction,
         event: HistoryBaseEntry,
         action_items: List[ActionItem],  # pylint: disable=unused-argument
 ) -> bool:
     source_address = hex_or_bytes_to_address(tx_log.topics[1])
     to_address = hex_or_bytes_to_address(tx_log.topics[2])
     if (  # deposit give asset
         event.event_type == HistoryEventType.RECEIVE and
         event.event_subtype == HistoryEventSubType.NONE and
         source_address == CURVE_Y_DEPOSIT and
         transaction.from_address == to_address
     ):
         event.event_type = HistoryEventType.WITHDRAWAL
         event.event_subtype = HistoryEventSubType.REMOVE_ASSET
         event.counterparty = CPT_CURVE
         event.notes = f'Receive {event.balance.amount} {event.asset.symbol} from the curve pool {CURVE_Y_DEPOSIT}'  # noqa: E501
         return True
     return False
예제 #18
0
    def _maybe_decode_swap(  # pylint: disable=no-self-use
            self,
            token: Optional[EthereumToken],  # pylint: disable=unused-argument
            tx_log: EthereumTxReceiptLog,
            transaction: EthereumTransaction,  # pylint: disable=unused-argument
            decoded_events: List[HistoryBaseEntry],
            action_items: List[ActionItem],  # pylint: disable=unused-argument
    ) -> None:
        """Search for both events. Since the order is not guaranteed try reshuffle in both cases"""
        out_event = in_event = None
        if tx_log.topics[0] == TOKEN_PURCHASE:
            buyer = hex_or_bytes_to_address(tx_log.topics[1])
            # search for a send to buyer from a tracked address
            for event in decoded_events:
                if event.event_type == HistoryEventType.SPEND and event.counterparty == buyer:
                    event.event_type = HistoryEventType.TRADE
                    event.event_subtype = HistoryEventSubType.SPEND
                    event.counterparty = CPT_UNISWAP_V1
                    event.notes = f'Swap {event.balance.amount} {event.asset.symbol} in uniswap-v1 from {event.location_label}'  # noqa: E501
                    out_event = event
                elif event.event_type == HistoryEventType.TRADE and event.event_subtype == HistoryEventSubType.RECEIVE and event.counterparty == CPT_UNISWAP_V1:  # noqa: :E501
                    in_event = event

        elif tx_log.topics[0] == ETH_PURCHASE:
            buyer = hex_or_bytes_to_address(tx_log.topics[1])
            # search for a receive to buyer
            for event in decoded_events:
                if event.event_type == HistoryEventType.RECEIVE and event.location_label == buyer:
                    event.event_type = HistoryEventType.TRADE
                    event.event_subtype = HistoryEventSubType.RECEIVE
                    event.counterparty = CPT_UNISWAP_V1
                    event.notes = f'Receive {event.balance.amount} {event.asset.symbol} from uniswap-v1 swap in {event.location_label}'  # noqa: E501
                    in_event = event
                elif event.event_type == HistoryEventType.TRADE and event.event_subtype == HistoryEventSubType.SPEND and event.counterparty == CPT_UNISWAP_V1:  # noqa: :E501
                    out_event = event

        maybe_reshuffle_events(out_event=out_event, in_event=in_event)
예제 #19
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
예제 #20
0
    def _decode_mint(
            self,
            transaction: EthereumTransaction,
            tx_log: EthereumTxReceiptLog,
            decoded_events: List[HistoryBaseEntry],
            compound_token: EthereumToken,
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        minter = hex_or_bytes_to_address(tx_log.data[0:32])
        if not self.base.is_tracked(minter):
            return None, None

        mint_amount_raw = hex_or_bytes_to_int(tx_log.data[32:64])
        minted_amount_raw = hex_or_bytes_to_int(tx_log.data[64:96])
        underlying_asset = symbol_to_asset_or_token(compound_token.symbol[1:])
        mint_amount = asset_normalized_value(mint_amount_raw, underlying_asset)
        minted_amount = token_normalized_value(minted_amount_raw, compound_token)
        out_event = None
        for event in decoded_events:
            # Find the transfer event which should have come before the minting
            if event.event_type == HistoryEventType.SPEND and event.asset == underlying_asset and event.balance.amount == mint_amount:  # noqa: E501
                event.event_type = HistoryEventType.DEPOSIT
                event.event_subtype = HistoryEventSubType.DEPOSIT_ASSET
                event.counterparty = CPT_COMPOUND
                event.notes = f'Deposit {mint_amount} {underlying_asset.symbol} to compound'
                out_event = event
                break

        if out_event is None:
            log.debug(f'At compound mint decoding of tx {transaction.tx_hash.hex()} the out event was not found')  # noqa: E501
            return None, None

        # also create an action item for the receive of the cTokens
        action_item = ActionItem(
            action='transform',
            sequence_index=tx_log.log_index,
            from_event_type=HistoryEventType.RECEIVE,
            from_event_subtype=HistoryEventSubType.NONE,
            asset=compound_token,
            amount=minted_amount,
            to_event_subtype=HistoryEventSubType.RECEIVE_WRAPPED,
            to_notes=f'Receive {minted_amount} {compound_token.symbol} from compound',
            to_counterparty=CPT_COMPOUND,
            paired_event_data=(out_event, True),
        )
        return None, action_item
예제 #21
0
    def decode_saidai_migration(
        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] == ERC20_OR_ERC721_TRANSFER:
            to_address = hex_or_bytes_to_address(tx_log.topics[2])
            if to_address != '0xc73e0383F3Aff3215E6f04B0331D58CeCf0Ab849':
                return None, None

            # sending SAI to migration contract
            transfer = self.base.decode_erc20_721_transfer(
                token=A_SAI, tx_log=tx_log,
                transaction=transaction)  # noqa: E501
            if transfer is None:
                return None, None

            transfer.event_type = HistoryEventType.MIGRATE
            transfer.event_subtype = HistoryEventSubType.SPEND
            transfer.notes = f'Migrate {transfer.balance.amount} SAI to DAI'
            transfer.counterparty = CPT_MIGRATION

            # also create action item for the receive transfer
            action_item = ActionItem(
                action='transform',
                sequence_index=tx_log.log_index,
                from_event_type=HistoryEventType.RECEIVE,
                from_event_subtype=HistoryEventSubType.NONE,
                asset=A_DAI,
                amount=transfer.balance.amount,
                to_event_type=HistoryEventType.MIGRATE,
                to_event_subtype=HistoryEventSubType.RECEIVE,
                to_notes=
                f'Receive {transfer.balance.amount} DAI from SAI->DAI migration',
                to_counterparty='makerdao migration',
            )
            return transfer, action_item

        return None, None
예제 #22
0
    def _decode_vault_debt_generation(
        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]]:
        """Decode vault debt generation by parsing a lognote for cdpmanager move"""
        cdp_id = hex_or_bytes_to_int(tx_log.topics[2])
        destination = hex_or_bytes_to_address(tx_log.topics[3])

        owner = self._get_address_or_proxy(destination)
        if owner is None:
            return None, None

        # now we need to get the rad and since it's the 3rd argument its not in the indexed topics
        # but it's part of the data location after the first 132 bytes.
        # also need to shift by ray since it's in rad
        raw_amount = shift_num_right_by(
            hex_or_bytes_to_int(tx_log.data[132:164]), RAY_DIGITS)
        amount = token_normalized_value(
            token_amount=raw_amount,
            token=A_DAI,
        )

        # The transfer event appears after the debt generation event, so we need to transform it
        action_item = ActionItem(
            action='transform',
            sequence_index=tx_log.log_index,
            from_event_type=HistoryEventType.RECEIVE,
            from_event_subtype=HistoryEventSubType.NONE,
            asset=A_DAI,
            amount=amount,
            to_event_type=HistoryEventType.WITHDRAWAL,
            to_event_subtype=HistoryEventSubType.GENERATE_DEBT,
            to_counterparty=CPT_VAULT,
            to_notes=f'Generate {amount} DAI from MakerDAO vault {cdp_id}',
            extra_data={'cdp_id': cdp_id},
        )
        return None, action_item
예제 #23
0
    def _decode_fox_claim(  # 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]]:
        if tx_log.topics[0] != FOX_CLAIMED:
            return None, None

        user_address = hex_or_bytes_to_address(tx_log.topics[1])
        raw_amount = hex_or_bytes_to_int(tx_log.data[64:96])
        amount = asset_normalized_value(amount=raw_amount, asset=A_FOX)

        for event in decoded_events:
            if event.event_type == HistoryEventType.RECEIVE and event.location_label == user_address and amount == event.balance.amount and A_FOX == event.asset:  # noqa: E501
                event.event_type = HistoryEventType.RECEIVE
                event.event_subtype = HistoryEventSubType.AIRDROP
                event.counterparty = CPT_SHAPESHIFT
                event.notes = f'Claim {amount} FOX from shapeshift airdrop'  # noqa: E501
                break

        return None, None
예제 #24
0
    def decode_pot_for_dsr(
        self,
        tx_log: EthereumTxReceiptLog,
        transaction: EthereumTransaction,  # pylint: disable=unused-argument
        decoded_events: List[HistoryBaseEntry],
        all_logs: List[EthereumTxReceiptLog],
        action_items: List[ActionItem],  # pylint: disable=unused-argument
    ) -> Tuple[Optional[HistoryBaseEntry], Optional[ActionItem]]:
        if tx_log.topics[0] == POT_JOIN:
            potjoin_user_address = hex_or_bytes_to_address(tx_log.topics[1])
            user = self._get_address_or_proxy(potjoin_user_address)
            if user is None:
                return None, None

            # Now gotta find the DAI join event to get actual DAI value
            daijoin_log = None
            for event_log in all_logs:
                if event_log.address == MAKERDAO_DAI_JOIN.address and event_log.topics[
                        0] == GENERIC_JOIN:  # noqa: E501
                    daijoin_user_address = hex_or_bytes_to_address(
                        event_log.topics[1])
                    if daijoin_user_address != potjoin_user_address:
                        continue  # not a match

                    daijoin_log = event_log
                    break

            if daijoin_log is None:
                return None, None  # no matching daijoin for potjoin

            raw_amount = hex_or_bytes_to_int(daijoin_log.topics[3])
            amount = token_normalized_value(
                token_amount=raw_amount,
                token=A_DAI,
            )

            # The transfer event should be right before
            for event in decoded_events:
                if event.asset == A_DAI and event.event_type == HistoryEventType.SPEND and event.balance.amount == amount:  # noqa: E501
                    # found the event
                    event.location_label = user
                    event.counterparty = CPT_DSR
                    event.event_type = HistoryEventType.DEPOSIT
                    event.event_subtype = HistoryEventSubType.DEPOSIT_ASSET
                    event.notes = f'Deposit {amount} DAI in the DSR'
                    return None, None

        elif tx_log.topics[0] == POT_EXIT:
            pot_exit_address = hex_or_bytes_to_address(tx_log.topics[1])
            user = self._get_address_or_proxy(pot_exit_address)
            if user is None:
                return None, None

            # Now gotta find the DAI exit event to get actual DAI value
            daiexit_log = None
            for event_log in all_logs:
                if event_log.address == MAKERDAO_DAI_JOIN.address and event_log.topics[
                        0] == GENERIC_EXIT:  # noqa: E501
                    daiexit_user_address = hex_or_bytes_to_address(
                        event_log.topics[2])
                    if daiexit_user_address != user:
                        continue  # not a match

                    daiexit_log = event_log
                    break

            if daiexit_log is None:
                return None, None  # no matching daiexit for potexit

            raw_amount = hex_or_bytes_to_int(daiexit_log.topics[3])
            amount = token_normalized_value(
                token_amount=raw_amount,
                token=A_DAI,
            )
            # The transfer event will be in a subsequent logs
            action_item = ActionItem(
                action='transform',
                sequence_index=tx_log.log_index,
                from_event_type=HistoryEventType.RECEIVE,
                from_event_subtype=HistoryEventSubType.NONE,
                asset=A_DAI,
                amount=amount,
                to_event_type=HistoryEventType.WITHDRAWAL,
                to_event_subtype=HistoryEventSubType.REMOVE_ASSET,
                to_notes=f'Withdraw {amount} DAI from the DSR',
                to_counterparty=CPT_DSR,
            )
            return None, action_item

        return None, None
예제 #25
0
파일: aave.py 프로젝트: tiemonl/rotki
    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 = hex_or_bytes_to_int(event['data'])
            if amount == 0:
                continue  # first mint can be for 0. Ignore
            entry = (
                deserialize_blocknumber(event['blockNumber']),
                amount,
                self.ethereum.get_event_timestamp(event),
                event['transactionHash'],
            )
            mint_data.add(entry)
            mint_data_to_log_index[entry] = deserialize_int_from_hex_or_int(
                event['logIndex'],
                'aave log index',
            )

        reserve_asset = _atoken_to_reserve_asset(atoken)
        reserve_address, decimals = _get_reserve_address_decimals(
            reserve_asset.identifier)
        aave_events = []
        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 = hex_or_bytes_to_int(event['data'][:66])
                block_number = deserialize_blocknumber(event['blockNumber'])
                timestamp = self.ethereum.get_event_timestamp(event)
                tx_hash = event['transactionHash']
                log_index = deserialize_int_from_hex_or_int(
                    event['logIndex'], 'aave log index')
                # 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(
                    AaveEvent(
                        event_type='deposit',
                        asset=reserve_asset,
                        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(
                AaveEvent(
                    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 = hex_or_bytes_to_int(event['data'][:66])
                block_number = deserialize_blocknumber(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(
                    AaveEvent(
                        event_type='withdrawal',
                        asset=reserve_asset,
                        value=Balance(
                            amount=withdrawal_amount,
                            usd_value=withdrawal_amount * usd_price,
                        ),
                        block_number=block_number,
                        timestamp=timestamp,
                        tx_hash=tx_hash,
                        log_index=deserialize_int_from_hex_or_int(
                            event['logIndex'], 'aave log index'),
                    ))

        return aave_events
예제 #26
0
    def _maybe_enrich_pickle_transfers(  # pylint: disable=no-self-use
            self,
            token: EthereumToken,  # pylint: disable=unused-argument
            tx_log: EthereumTxReceiptLog,
            transaction: EthereumTransaction,
            event: HistoryBaseEntry,
            action_items: List[ActionItem],  # pylint: disable=unused-argument
    ) -> bool:
        """Enrich tranfer transactions to address for jar deposits and withdrawals"""
        if not (
            hex_or_bytes_to_address(tx_log.topics[2]) in self.pickle_contracts or
            hex_or_bytes_to_address(tx_log.topics[1]) in self.pickle_contracts or
            tx_log.address in self.pickle_contracts
        ):
            return False

        if (  # Deposit give asset
            event.event_type == HistoryEventType.SPEND and
            event.event_subtype == HistoryEventSubType.NONE and
            event.location_label == transaction.from_address and
            hex_or_bytes_to_address(tx_log.topics[2]) in self.pickle_contracts
        ):
            if EthereumToken(tx_log.address) != event.asset:
                return True
            amount_raw = hex_or_bytes_to_int(tx_log.data)
            amount = asset_normalized_value(amount=amount_raw, asset=event.asset)
            if event.balance.amount == amount:
                event.event_type = HistoryEventType.DEPOSIT
                event.event_subtype = HistoryEventSubType.DEPOSIT_ASSET
                event.counterparty = CPT_PICKLE
                event.notes = f'Deposit {event.balance.amount} {event.asset.symbol} in pickle contract'  # noqa: E501
        elif (  # Deposit receive wrapped
            event.event_type == HistoryEventType.RECEIVE and
            event.event_subtype == HistoryEventSubType.NONE and
            tx_log.address in self.pickle_contracts
        ):
            amount_raw = hex_or_bytes_to_int(tx_log.data)
            amount = asset_normalized_value(amount=amount_raw, asset=event.asset)
            if event.balance.amount == amount:  # noqa: E501
                event.event_type = HistoryEventType.RECEIVE
                event.event_subtype = HistoryEventSubType.RECEIVE_WRAPPED
                event.counterparty = CPT_PICKLE
                event.notes = f'Receive {event.balance.amount} {event.asset.symbol} after depositing in pickle contract'  # noqa: E501
        elif (  # Withdraw send wrapped
            event.event_type == HistoryEventType.SPEND and
            event.event_subtype == HistoryEventSubType.NONE and
            event.location_label == transaction.from_address and
            hex_or_bytes_to_address(tx_log.topics[2]) == ZERO_ADDRESS and
            hex_or_bytes_to_address(tx_log.topics[1]) in transaction.from_address
        ):
            if event.asset != EthereumToken(tx_log.address):
                return True
            amount_raw = hex_or_bytes_to_int(tx_log.data)
            amount = asset_normalized_value(amount=amount_raw, asset=event.asset)
            if event.balance.amount == amount:  # noqa: E501
                event.event_type = HistoryEventType.SPEND
                event.event_subtype = HistoryEventSubType.RETURN_WRAPPED
                event.counterparty = CPT_PICKLE
                event.notes = f'Return {event.balance.amount} {event.asset.symbol} to the pickle contract'  # noqa: E501
        elif (  # Withdraw receive asset
            event.event_type == HistoryEventType.RECEIVE and
            event.event_subtype == HistoryEventSubType.NONE and
            event.location_label == transaction.from_address and
            hex_or_bytes_to_address(tx_log.topics[2]) == transaction.from_address and
            hex_or_bytes_to_address(tx_log.topics[1]) in self.pickle_contracts
        ):
            if event.asset != EthereumToken(tx_log.address):
                return True
            amount_raw = hex_or_bytes_to_int(tx_log.data)
            amount = asset_normalized_value(amount=amount_raw, asset=event.asset)
            if event.balance.amount == amount:  # noqa: E501
                event.event_type = HistoryEventType.WITHDRAWAL
                event.event_subtype = HistoryEventSubType.REMOVE_ASSET
                event.counterparty = CPT_PICKLE
                event.notes = f'Unstake {event.balance.amount} {event.asset.symbol} from the pickle contract'  # noqa: E501

        return True
예제 #27
0
    def _try_get_chi_close_to(self, time: Timestamp) -> FVal:
        """Best effort attempt to get a chi value close to the given timestamp

        It can't be 100% accurate since we use the logs of join() or exit()
        in order to find the closest time chi was changed. It also may not work
        if for some reason there is no logs in the block range we are looking for.

        Better solution would have been an archive node's query.

        May raise:
        - RemoteError if there are problems with querying etherscan
        - ChiRetrievalError if we are unable to query chi at the given timestamp
        - BlockchainQueryError if an ethereum node is used and the contract call
        queries fail for some reason
        """

        if time > 1584386100:
            # If the time is after 16/03/2020 19:15 GMT we know that
            # makerdao DSR was set to 0% we know chi has not changed
            # https://twitter.com/MakerDAO/status/1239270910810411008
            return FVal('1018008449363110619399951035')

        block_number = self.ethereum.etherscan.get_blocknumber_by_time(time)
        latest_block = self.ethereum.get_latest_block_number()
        blocks_queried = 0
        counter = 1
        # Keep trying to find events that could reveal the chi to us. Go back
        # as far as MAX_BLOCKS_TO_QUERY and only then give up
        while blocks_queried < MAX_BLOCKS_TO_QUERY:
            back_from_block = max(
                MAKERDAO_POT.deployed_block,
                block_number - counter * CHI_BLOCKS_SEARCH_DISTANCE,
            )
            back_to_block = block_number - (counter -
                                            1) * CHI_BLOCKS_SEARCH_DISTANCE
            forward_from_block = min(
                latest_block,
                block_number + (counter - 1) * CHI_BLOCKS_SEARCH_DISTANCE,
            )
            forward_to_block = min(
                latest_block,
                block_number + CHI_BLOCKS_SEARCH_DISTANCE,
            )
            back_joins, back_exits = self._get_join_exit_events(
                back_from_block, back_to_block)
            forward_joins, forward_exits = self._get_join_exit_events(
                from_block=forward_from_block,
                to_block=forward_to_block,
            )

            no_results = all(
                len(x) == 0 for x in (back_joins, back_exits, forward_joins,
                                      forward_exits))
            if latest_block == forward_to_block and no_results:
                # if our forward querying got us to the latest block and there is
                # still no other results, then take current chi
                return self.ethereum.call_contract(
                    contract_address=MAKERDAO_POT.address,
                    abi=MAKERDAO_POT.abi,
                    method_name='chi',
                )

            if not no_results:
                # got results!
                break

            blocks_queried += 2 * CHI_BLOCKS_SEARCH_DISTANCE
            counter += 1

        if no_results:
            raise ChiRetrievalError(
                f'Found no DSR events around timestamp {time}. Cant query chi.',
            )

        # Find the closest event to the to_block number, looking both at events
        # in the blocks before and in the blocks after block_number
        found_event = None
        back_event = _find_closest_event(back_joins, back_exits, -1,
                                         operator.gt)
        forward_event = _find_closest_event(forward_joins, forward_exits, 0,
                                            operator.lt)

        if back_event and not forward_event:
            found_event = back_event
        elif forward_event and not back_event:
            found_event = forward_event
        else:
            # We have both backward and forward events, get the one closer to block number
            back_block_number = back_event['blockNumber']  # type: ignore
            forward_block_number = forward_event['blockNumber']  # type: ignore

            if block_number - back_block_number <= forward_block_number - block_number:
                found_event = back_event
            else:
                found_event = forward_event

        assert found_event, 'at this point found_event should be populated'  # helps mypy
        event_block_number = found_event['blockNumber']
        first_topic = found_event['topics'][0]

        amount = self._get_vat_join_exit_at_transaction(
            movement_type='join'
            if first_topic.startswith('0x049878f3') else 'exit',
            proxy_address=hex_or_bytes_to_address(found_event['topics'][1]),
            block_number=event_block_number,
            transaction_index=found_event['transactionIndex'],
        )
        if amount is None:
            raise ChiRetrievalError(
                f'Found no VAT.move events around timestamp {time}. Cant query chi.',
            )

        wad_val = hexstr_to_int(found_event['topics'][2])
        chi = FVal(amount) / FVal(wad_val)
        return chi