Esempio n. 1
0
def _decode_result(
        data: Tuple,
        known_assets: Set[EthereumToken],
        unknown_assets: Set[UnknownEthereumToken],
) -> LiquidityPool:
    pool_token = _decode_token(data[0])
    token0 = _decode_token(data[1][0])
    token1 = _decode_token(data[1][1])

    assets = []
    for token in (token0, token1):
        asset = get_ethereum_token(
            symbol=token.symbol,
            ethereum_address=token.address,
            name=token.name,
            decimals=token.decimals,
        )
        # Classify the asset either as known or unknown
        if isinstance(asset, EthereumToken):
            known_assets.add(asset)
        elif isinstance(asset, UnknownEthereumToken):
            unknown_assets.add(asset)
        assets.append(LiquidityPoolAsset(
            asset=asset,
            total_amount=None,
            user_balance=Balance(amount=token.amount),
        ))

    pool = LiquidityPool(
        address=pool_token.address,
        assets=assets,
        total_supply=None,
        user_balance=Balance(amount=pool_token.amount),
    )
    return pool
Esempio n. 2
0
def deserialize_bpt_event(
    raw_event: Dict[str, Any],
    event_type: Literal[BalancerBPTEventType.MINT, BalancerBPTEventType.BURN],
) -> BalancerBPTEvent:
    """May raise DeserializationError"""
    try:
        tx_hash, log_index = deserialize_transaction_id(raw_event['id'])
        raw_user_address = raw_event['user']['id']
        amount = deserialize_asset_amount(raw_event['amount'])
        raw_pool = raw_event['pool']
        raw_pool_address = raw_pool['id']
        raw_pool_tokens = raw_pool['tokens']
        total_weight = deserialize_asset_amount(raw_pool['totalWeight'])
    except KeyError as e:
        raise DeserializationError(f'Missing key: {str(e)}.') from e

    if total_weight == ZERO:
        raise DeserializationError('Pool weight is zero.')

    user_address = deserialize_ethereum_address(raw_user_address)
    pool_address = deserialize_ethereum_address(raw_pool_address)

    pool_tokens = []
    for raw_token in raw_pool_tokens:
        try:
            raw_token_address = raw_token['address']
            token_symbol = raw_token['symbol']
            token_name = raw_token['name']
            token_decimals = raw_token['decimals']
            token_weight = deserialize_asset_amount(raw_token['denormWeight'])
        except KeyError as e:
            raise DeserializationError(f'Missing key: {str(e)}.') from e

        token_address = deserialize_ethereum_address(raw_token_address)

        token = get_ethereum_token(
            symbol=token_symbol,
            ethereum_address=token_address,
            name=token_name,
            decimals=token_decimals,
        )
        pool_token = BalancerBPTEventPoolToken(
            token=token,
            weight=token_weight * 100 / total_weight,
        )
        pool_tokens.append(pool_token)

    pool_tokens.sort(key=lambda pool_token: pool_token.token.ethereum_address)
    bpt_event = BalancerBPTEvent(
        tx_hash=tx_hash,
        log_index=log_index,
        address=user_address,
        event_type=event_type,
        pool_address=pool_address,
        pool_tokens=pool_tokens,
        amount=amount,
    )
    return bpt_event
Esempio n. 3
0
def test_get_ethereum_token():
    assert A_DAI == get_ethereum_token(
        symbol='DAI',
        ethereum_address='0x6B175474E89094C44Da98b954EedeAC495271d0F',
    )
    unknown_token = UnknownEthereumToken(
        symbol='DAI',
        ethereum_address='0xA379B8204A49A72FF9703e18eE61402FAfCCdD60',
        decimals=18,
    )
    assert unknown_token == get_ethereum_token(
        symbol='DAI',
        ethereum_address='0xA379B8204A49A72FF9703e18eE61402FAfCCdD60',
    ), 'correct symbol but wrong address should result in unknown token'
    unknown_token = UnknownEthereumToken(
        symbol='DOT',
        ethereum_address='0xA379B8204A49A72FF9703e18eE61402FAfCCdD60',
        decimals=18,
    )
    assert unknown_token == get_ethereum_token(
        symbol='DOT',
        ethereum_address='0xA379B8204A49A72FF9703e18eE61402FAfCCdD60',
    ), 'symbol of normal chain (polkadot here) should result in unknown token'
Esempio n. 4
0
    def _get_trades_graph_for_address(
            self,
            address: ChecksumEthAddress,
            start_ts: Timestamp,
            end_ts: Timestamp,
    ) -> List[AMMTrade]:
        """Get the address' trades data querying the Uniswap subgraph

        Each trade (swap) instantiates an <AMMTrade>.

        The trade pair (i.e. BASE_QUOTE) is determined by `reserve0_reserve1`.
        Translated to Uniswap lingo:

        Trade type BUY:
        - `asset1In` (QUOTE, reserve1) is gt 0.
        - `asset0Out` (BASE, reserve0) is gt 0.

        Trade type SELL:
        - `asset0In` (BASE, reserve0) is gt 0.
        - `asset1Out` (QUOTE, reserve1) is gt 0.
        """
        trades: List[AMMTrade] = []
        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$address': 'Bytes!',
            '$start_ts': 'BigInt!',
            '$end_ts': 'BigInt!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'address': address.lower(),
            'start_ts': str(start_ts),
            'end_ts': str(end_ts),
        }
        querystr = format_query_indentation(SWAPS_QUERY.format())

        while True:
            result = self.graph.query(  # type: ignore # caller already checks
                querystr=querystr,
                param_types=param_types,
                param_values=param_values,
            )
            result_data = result['swaps']
            for entry in result_data:
                swaps = []
                for swap in entry['transaction']['swaps']:
                    timestamp = swap['timestamp']
                    swap_token0 = swap['pair']['token0']
                    swap_token1 = swap['pair']['token1']
                    token0 = get_ethereum_token(
                        symbol=swap_token0['symbol'],
                        ethereum_address=to_checksum_address(swap_token0['id']),
                        name=swap_token0['name'],
                        decimals=swap_token0['decimals'],
                    )
                    token1 = get_ethereum_token(
                        symbol=swap_token1['symbol'],
                        ethereum_address=to_checksum_address(swap_token1['id']),
                        name=swap_token1['name'],
                        decimals=int(swap_token1['decimals']),
                    )
                    amount0_in = FVal(swap['amount0In'])
                    amount1_in = FVal(swap['amount1In'])
                    amount0_out = FVal(swap['amount0Out'])
                    amount1_out = FVal(swap['amount1Out'])
                    swaps.append(AMMSwap(
                        tx_hash=swap['id'].split('-')[0],
                        log_index=int(swap['logIndex']),
                        address=address,
                        from_address=to_checksum_address(swap['sender']),
                        to_address=to_checksum_address(swap['to']),
                        timestamp=Timestamp(int(timestamp)),
                        location=Location.UNISWAP,
                        token0=token0,
                        token1=token1,
                        amount0_in=AssetAmount(amount0_in),
                        amount1_in=AssetAmount(amount1_in),
                        amount0_out=AssetAmount(amount0_out),
                        amount1_out=AssetAmount(amount1_out),
                    ))

                # Now that we got all swaps for a transaction, create the trade object
                trades.extend(self._tx_swaps_to_trades(swaps))

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            param_values = {
                **param_values,
                'offset': param_values['offset'] + GRAPH_QUERY_LIMIT,  # type: ignore
            }
        return trades
Esempio n. 5
0
    def _get_events_graph(
            self,
            address: ChecksumEthAddress,
            start_ts: Timestamp,
            end_ts: Timestamp,
            event_type: EventType,
    ) -> List[LiquidityPoolEvent]:
        """Get the address' events (mints & burns) querying the Uniswap subgraph
        Each event data is stored in a <LiquidityPoolEvent>.
        """
        address_events: List[LiquidityPoolEvent] = []
        if event_type == EventType.MINT:
            query = MINTS_QUERY
            query_schema = 'mints'
        elif event_type == EventType.BURN:
            query = BURNS_QUERY
            query_schema = 'burns'
        else:
            log.error(f'Unexpected event_type: {event_type}. Skipping events query.')
            return address_events

        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$address': 'Bytes!',
            '$start_ts': 'BigInt!',
            '$end_ts': 'BigInt!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'address': address.lower(),
            'start_ts': str(start_ts),
            'end_ts': str(end_ts),
        }
        querystr = format_query_indentation(query.format())

        while True:
            result = self.graph.query(  # type: ignore # caller already checks
                querystr=querystr,
                param_types=param_types,
                param_values=param_values,
            )
            result_data = result[query_schema]

            for event in result_data:
                token0_ = event['pair']['token0']
                token1_ = event['pair']['token1']
                token0 = get_ethereum_token(
                    symbol=token0_['symbol'],
                    ethereum_address=to_checksum_address(token0_['id']),
                    name=token0_['name'],
                    decimals=token0_['decimals'],
                )
                token1 = get_ethereum_token(
                    symbol=token1_['symbol'],
                    ethereum_address=to_checksum_address(token1_['id']),
                    name=token1_['name'],
                    decimals=int(token1_['decimals']),
                )
                lp_event = LiquidityPoolEvent(
                    tx_hash=event['transaction']['id'],
                    log_index=int(event['logIndex']),
                    address=address,
                    timestamp=Timestamp(int(event['timestamp'])),
                    event_type=event_type,
                    pool_address=to_checksum_address(event['pair']['id']),
                    token0=token0,
                    token1=token1,
                    amount0=AssetAmount(FVal(event['amount0'])),
                    amount1=AssetAmount(FVal(event['amount1'])),
                    usd_price=Price(FVal(event['amountUSD'])),
                    lp_amount=AssetAmount(FVal(event['liquidity'])),
                )
                address_events.append(lp_event)

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            param_values = {
                **param_values,
                'offset': param_values['offset'] + GRAPH_QUERY_LIMIT,  # type: ignore
            }

        return address_events
Esempio n. 6
0
    def _get_balances_graph(
            self,
            addresses: List[ChecksumEthAddress],
    ) -> ProtocolBalance:
        """Get the addresses' pools data querying the Uniswap subgraph

        Each liquidity position is converted into a <LiquidityPool>.
        """
        address_balances: DDAddressBalances = defaultdict(list)
        known_assets: Set[EthereumToken] = set()
        unknown_assets: Set[UnknownEthereumToken] = set()

        addresses_lower = [address.lower() for address in addresses]
        querystr = format_query_indentation(LIQUIDITY_POSITIONS_QUERY.format())
        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$addresses': '[String!]',
            '$balance': 'BigDecimal!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'addresses': addresses_lower,
            'balance': '0',
        }
        while True:
            result = self.graph.query(  # type: ignore # caller already checks
                querystr=querystr,
                param_types=param_types,
                param_values=param_values,
            )
            result_data = result['liquidityPositions']

            for lp in result_data:
                user_address = to_checksum_address(lp['user']['id'])
                user_lp_balance = FVal(lp['liquidityTokenBalance'])
                lp_pair = lp['pair']
                lp_address = to_checksum_address(lp_pair['id'])
                lp_total_supply = FVal(lp_pair['totalSupply'])

                # Insert LP tokens reserves within tokens dicts
                token0 = lp_pair['token0']
                token0['total_amount'] = lp_pair['reserve0']
                token1 = lp_pair['token1']
                token1['total_amount'] = lp_pair['reserve1']

                liquidity_pool_assets = []

                for token in token0, token1:
                    # Get the token <EthereumToken> or <UnknownEthereumToken>
                    asset = get_ethereum_token(
                        symbol=token['symbol'],
                        ethereum_address=to_checksum_address(token['id']),
                        name=token['name'],
                        decimals=int(token['decimals']),
                    )

                    # Classify the asset either as known or unknown
                    if isinstance(asset, EthereumToken):
                        known_assets.add(asset)
                    elif isinstance(asset, UnknownEthereumToken):
                        unknown_assets.add(asset)

                    # Estimate the underlying asset total_amount
                    asset_total_amount = FVal(token['total_amount'])
                    user_asset_balance = (
                        user_lp_balance / lp_total_supply * asset_total_amount
                    )

                    liquidity_pool_asset = LiquidityPoolAsset(
                        asset=asset,
                        total_amount=asset_total_amount,
                        user_balance=Balance(amount=user_asset_balance),
                    )
                    liquidity_pool_assets.append(liquidity_pool_asset)

                liquidity_pool = LiquidityPool(
                    address=lp_address,
                    assets=liquidity_pool_assets,
                    total_supply=lp_total_supply,
                    user_balance=Balance(amount=user_lp_balance),
                )
                address_balances[user_address].append(liquidity_pool)

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            param_values = {
                **param_values,
                'offset': param_values['offset'] + GRAPH_QUERY_LIMIT,  # type: ignore
            }

        protocol_balance = ProtocolBalance(
            address_balances=dict(address_balances),
            known_assets=known_assets,
            unknown_assets=unknown_assets,
        )
        return protocol_balance
Esempio n. 7
0
def deserialize_swap(raw_swap: Dict[str, Any]) -> AMMSwap:
    """May raise DeserializationError"""
    try:
        tx_hash, log_index = deserialize_transaction_id(raw_swap['id'])
        timestamp = deserialize_timestamp(raw_swap['timestamp'])
        raw_tokens = raw_swap['poolAddress']['tokens']
        token0_symbol = raw_swap['tokenInSym']
        token1_symbol = raw_swap['tokenOutSym']
        amount0_in = deserialize_asset_amount(raw_swap['tokenAmountIn'])
        amount1_out = deserialize_asset_amount(raw_swap['tokenAmountOut'])
        raw_user_address = raw_swap['userAddress']['id']  # address
        raw_caller_address = raw_swap['caller']  # from_address
        raw_pool_address = raw_swap['poolAddress']['id']  # to_address
        raw_token_in_address = raw_swap['tokenIn']  # token0_address
        raw_token_out_address = raw_swap['tokenOut']  # token1_address
    except KeyError as e:
        raise DeserializationError(f'Missing key: {str(e)}.') from e

    if amount0_in == ZERO:
        # Prevent a division by zero error when creating the trade
        raise DeserializationError('TokenAmountIn balance is zero.')

    # Checksum addresses
    try:
        user_address = to_checksum_address(raw_user_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_user_address} in userAddress.id.',
        ) from e

    try:
        caller_address = to_checksum_address(raw_caller_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_caller_address} in caller.',
        ) from e

    try:
        pool_address = to_checksum_address(raw_pool_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_pool_address} in poolAddress.id.',
        ) from e

    try:
        token_in_address = to_checksum_address(raw_token_in_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_token_in_address} in tokenIn.',
        ) from e

    try:
        token_out_address = to_checksum_address(raw_token_out_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_token_out_address} in tokenOut.',
        ) from e

    # Get token0 and token1
    # When the controller removes all the tokens from a pool, `raw_tokens` will
    # be an empty list. Therefore it won't be possible to get their names and
    # decimals. In case of having to instantiate an UnknownEthereumToken both
    # params will be None.
    if len(raw_tokens) != 0:
        try:
            raw_address_tokens = {
                raw_token['address']: raw_token
                for raw_token in raw_tokens
            }
            raw_token0 = raw_address_tokens[raw_token_in_address]
            raw_token1 = raw_address_tokens[raw_token_out_address]
            token0_name = raw_token0['name']
            token0_decimals = raw_token0['decimals']
            token1_name = raw_token1['name']
            token1_decimals = raw_token1['decimals']
        except KeyError as e:
            raise DeserializationError(f'Missing key: {str(e)}.') from e
    else:
        token0_name = None
        token0_decimals = None
        token1_name = None
        token1_decimals = None

    token0 = get_ethereum_token(
        symbol=token0_symbol,
        ethereum_address=token_in_address,
        name=token0_name,
        decimals=token0_decimals,
    )
    token1 = get_ethereum_token(
        symbol=token1_symbol,
        ethereum_address=token_out_address,
        name=token1_name,
        decimals=token1_decimals,
    )
    amm_swap = AMMSwap(
        tx_hash=tx_hash,
        log_index=log_index,
        address=user_address,
        from_address=caller_address,
        to_address=pool_address,
        timestamp=timestamp,
        location=Location.BALANCER,
        token0=token0,
        token1=token1,
        amount0_in=amount0_in,
        amount1_in=AssetAmount(ZERO),
        amount0_out=AssetAmount(ZERO),
        amount1_out=amount1_out,
    )
    return amm_swap
Esempio n. 8
0
def deserialize_pool_share(
    raw_pool_share: Dict[str, Any],
) -> Tuple[ChecksumEthAddress, BalancerPoolBalance]:
    """May raise DeserializationError"""
    try:
        raw_user_address = raw_pool_share['userAddress']['id']
        user_amount = deserialize_asset_amount(raw_pool_share['balance'])
        raw_pool = raw_pool_share['poolId']
        total_amount = deserialize_asset_amount(raw_pool['totalShares'])
        raw_address = raw_pool['id']
        raw_tokens = raw_pool['tokens']
        total_weight = deserialize_asset_amount(raw_pool['totalWeight'])
    except KeyError as e:
        raise DeserializationError(f'Missing key: {str(e)}.') from e

    if total_weight == ZERO:
        raise DeserializationError('Pool weight is zero.')

    try:
        user_address = to_checksum_address(raw_user_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_user_address} in userAddress.id.',
        ) from e

    try:
        address = to_checksum_address(raw_address)
    except ValueError as e:
        raise DeserializationError(
            f'Invalid ethereum address: {raw_address} in poolId.id.', ) from e

    pool_tokens = []
    for raw_token in raw_tokens:
        try:
            raw_token_address = raw_token['address']
            token_symbol = raw_token['symbol']
            token_name = raw_token['name']
            token_decimals = raw_token['decimals']
            token_total_amount = deserialize_asset_amount(raw_token['balance'])
            token_weight = deserialize_asset_amount(raw_token['denormWeight'])
        except KeyError as e:
            raise DeserializationError(f'Missing key: {str(e)}.') from e

        try:
            token_address = to_checksum_address(raw_token_address)
        except ValueError as e:
            raise DeserializationError(
                f'Invalid ethereum address: {raw_token_address} in {token_symbol} token.address.',  # noqa: E501
            ) from e

        token = get_ethereum_token(
            symbol=token_symbol,
            ethereum_address=token_address,
            name=token_name,
            decimals=token_decimals,
        )
        if token_total_amount == ZERO:
            raise DeserializationError(
                f'Token {token.identifier} balance is zero.')

        token_user_amount = user_amount / total_amount * token_total_amount
        weight = token_weight * 100 / total_weight
        pool_token = BalancerPoolTokenBalance(
            token=token,
            total_amount=token_total_amount,
            user_balance=Balance(amount=token_user_amount),
            weight=weight,
        )
        pool_tokens.append(pool_token)

    pool_tokens.sort(key=lambda pool_token: pool_token.token.ethereum_address)
    pool = BalancerPoolBalance(
        address=address,
        tokens=pool_tokens,
        total_amount=total_amount,
        user_balance=Balance(amount=user_amount),
    )
    return user_address, pool
Esempio n. 9
0
    def _get_trades_graph_for_address(
        self,
        address: ChecksumEthAddress,
        start_ts: Timestamp,
        end_ts: Timestamp,
    ) -> List[AMMTrade]:
        """Get the address' trades data querying the Uniswap subgraph

        Each trade (swap) instantiates an <AMMTrade>.

        The trade pair (i.e. BASE_QUOTE) is determined by `reserve0_reserve1`.
        Translated to Uniswap lingo:

        Trade type BUY:
        - `asset1In` (QUOTE, reserve1) is gt 0.
        - `asset0Out` (BASE, reserve0) is gt 0.

        Trade type SELL:
        - `asset0In` (BASE, reserve0) is gt 0.
        - `asset1Out` (QUOTE, reserve1) is gt 0.
        """
        trades: List[AMMTrade] = []
        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$address': 'Bytes!',
            '$start_ts': 'BigInt!',
            '$end_ts': 'BigInt!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'address': address.lower(),
            'start_ts': str(start_ts),
            'end_ts': str(end_ts),
        }
        querystr = format_query_indentation(SWAPS_QUERY.format())

        while True:
            try:
                result = self.graph.query(
                    querystr=querystr,
                    param_types=param_types,
                    param_values=param_values,
                )
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e)))
                raise

            # Combine the two list of the query
            result_data = result['swaps_from']
            result_data.extend(result['swaps_to'])

            # Store ids of swaps to avoid possible duplicates
            fetched_ids = set()

            for entry in result_data:
                swaps = []
                for swap in entry['transaction']['swaps']:
                    if swap['id'] in fetched_ids:
                        continue
                    fetched_ids.add(swap['id'])

                    timestamp = swap['timestamp']
                    swap_token0 = swap['pair']['token0']
                    swap_token1 = swap['pair']['token1']

                    try:
                        token0_deserialized = deserialize_ethereum_address(
                            swap_token0['id'])
                        token1_deserialized = deserialize_ethereum_address(
                            swap_token1['id'])
                        from_address_deserialized = deserialize_ethereum_address(
                            swap['sender'])
                        to_address_deserialized = deserialize_ethereum_address(
                            swap['to'])
                    except DeserializationError as e:
                        msg = (
                            f'Failed to deserialize addresses in trade from uniswap graph with '
                            f'token 0: {swap_token0["id"]}, token 1: {swap_token1["id"]}, '
                            f'swap sender: {swap["sender"]}, swap receiver {swap["to"]}'
                        )
                        log.error(msg)
                        raise RemoteError(msg) from e

                    token0 = get_ethereum_token(
                        symbol=swap_token0['symbol'],
                        ethereum_address=token0_deserialized,
                        name=swap_token0['name'],
                        decimals=swap_token0['decimals'],
                    )
                    token1 = get_ethereum_token(
                        symbol=swap_token1['symbol'],
                        ethereum_address=token1_deserialized,
                        name=swap_token1['name'],
                        decimals=int(swap_token1['decimals']),
                    )
                    amount0_in = FVal(swap['amount0In'])
                    amount1_in = FVal(swap['amount1In'])
                    amount0_out = FVal(swap['amount0Out'])
                    amount1_out = FVal(swap['amount1Out'])
                    swaps.append(
                        AMMSwap(
                            tx_hash=swap['id'].split('-')[0],
                            log_index=int(swap['logIndex']),
                            address=address,
                            from_address=from_address_deserialized,
                            to_address=to_address_deserialized,
                            timestamp=Timestamp(int(timestamp)),
                            location=Location.UNISWAP,
                            token0=token0,
                            token1=token1,
                            amount0_in=AssetAmount(amount0_in),
                            amount1_in=AssetAmount(amount1_in),
                            amount0_out=AssetAmount(amount0_out),
                            amount1_out=AssetAmount(amount1_out),
                        ))

                # with the new logic the list of swaps can be empty, in that case don't try
                # to make trades from the swaps
                if len(swaps) == 0:
                    continue

                # Now that we got all swaps for a transaction, create the trade object
                trades.extend(self._tx_swaps_to_trades(swaps))

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            param_values = {
                **param_values,
                'offset':
                param_values['offset'] + GRAPH_QUERY_LIMIT,  # type: ignore
            }
        return trades
Esempio n. 10
0
    def _get_events_graph(
        self,
        address: ChecksumEthAddress,
        start_ts: Timestamp,
        end_ts: Timestamp,
        event_type: EventType,
    ) -> List[LiquidityPoolEvent]:
        """Get the address' events (mints & burns) querying the Uniswap subgraph
        Each event data is stored in a <LiquidityPoolEvent>.
        """
        address_events: List[LiquidityPoolEvent] = []
        if event_type == EventType.MINT:
            query = MINTS_QUERY
            query_schema = 'mints'
        elif event_type == EventType.BURN:
            query = BURNS_QUERY
            query_schema = 'burns'
        else:
            log.error(
                f'Unexpected event_type: {event_type}. Skipping events query.')
            return address_events

        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$address': 'Bytes!',
            '$start_ts': 'BigInt!',
            '$end_ts': 'BigInt!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'address': address.lower(),
            'start_ts': str(start_ts),
            'end_ts': str(end_ts),
        }
        querystr = format_query_indentation(query.format())

        while True:
            try:
                result = self.graph.query(
                    querystr=querystr,
                    param_types=param_types,
                    param_values=param_values,
                )
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e)))
                raise

            result_data = result[query_schema]

            for event in result_data:
                token0_ = event['pair']['token0']
                token1_ = event['pair']['token1']

                try:
                    token0_deserialized = deserialize_ethereum_address(
                        token0_['id'])
                    token1_deserialized = deserialize_ethereum_address(
                        token1_['id'])
                    pool_deserialized = deserialize_ethereum_address(
                        event['pair']['id'])
                except DeserializationError as e:
                    msg = (
                        f'Failed to deserialize address involved in liquidity pool event. '
                        f'Token 0: {token0_["id"]}, token 1: {token0_["id"]},'
                        f' pair: {event["pair"]["id"]}.')
                    log.error(msg)
                    raise RemoteError(msg) from e

                token0 = get_ethereum_token(
                    symbol=token0_['symbol'],
                    ethereum_address=token0_deserialized,
                    name=token0_['name'],
                    decimals=token0_['decimals'],
                )
                token1 = get_ethereum_token(
                    symbol=token1_['symbol'],
                    ethereum_address=token1_deserialized,
                    name=token1_['name'],
                    decimals=int(token1_['decimals']),
                )
                lp_event = LiquidityPoolEvent(
                    tx_hash=event['transaction']['id'],
                    log_index=int(event['logIndex']),
                    address=address,
                    timestamp=Timestamp(int(event['timestamp'])),
                    event_type=event_type,
                    pool_address=pool_deserialized,
                    token0=token0,
                    token1=token1,
                    amount0=AssetAmount(FVal(event['amount0'])),
                    amount1=AssetAmount(FVal(event['amount1'])),
                    usd_price=Price(FVal(event['amountUSD'])),
                    lp_amount=AssetAmount(FVal(event['liquidity'])),
                )
                address_events.append(lp_event)

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            param_values = {
                **param_values,
                'offset':
                param_values['offset'] + GRAPH_QUERY_LIMIT,  # type: ignore
            }

        return address_events
Esempio n. 11
0
    def _get_balances_graph(
        self,
        addresses: List[ChecksumEthAddress],
    ) -> ProtocolBalance:
        """Get the addresses' pools data querying the Uniswap subgraph

        Each liquidity position is converted into a <LiquidityPool>.
        """
        address_balances: DDAddressBalances = defaultdict(list)
        known_assets: Set[EthereumToken] = set()
        unknown_assets: Set[UnknownEthereumToken] = set()

        addresses_lower = [address.lower() for address in addresses]
        querystr = format_query_indentation(LIQUIDITY_POSITIONS_QUERY.format())
        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$addresses': '[String!]',
            '$balance': 'BigDecimal!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'addresses': addresses_lower,
            'balance': '0',
        }
        while True:
            try:
                result = self.graph.query(
                    querystr=querystr,
                    param_types=param_types,
                    param_values=param_values,
                )
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e)))
                raise

            result_data = result['liquidityPositions']

            for lp in result_data:
                lp_pair = lp['pair']
                lp_total_supply = FVal(lp_pair['totalSupply'])
                user_lp_balance = FVal(lp['liquidityTokenBalance'])
                try:
                    user_address = deserialize_ethereum_address(
                        lp['user']['id'])
                    lp_address = deserialize_ethereum_address(lp_pair['id'])
                except DeserializationError as e:
                    msg = (
                        f'Failed to Deserialize address. Skipping pool {lp_pair}'
                        f'with user address {lp["user"]["id"]}')
                    log.error(msg)
                    raise RemoteError(msg) from e

                # Insert LP tokens reserves within tokens dicts
                token0 = lp_pair['token0']
                token0['total_amount'] = lp_pair['reserve0']
                token1 = lp_pair['token1']
                token1['total_amount'] = lp_pair['reserve1']

                liquidity_pool_assets = []

                for token in token0, token1:
                    # Get the token <EthereumToken> or <UnknownEthereumToken>
                    try:
                        deserialized_eth_address = deserialize_ethereum_address(
                            token['id'])
                    except DeserializationError as e:
                        msg = (
                            f'Failed to deserialize token address {token["id"]}'
                            f'Bad token address in lp pair came from the graph.'
                        )
                        log.error(msg)
                        raise RemoteError(msg) from e

                    asset = get_ethereum_token(
                        symbol=token['symbol'],
                        ethereum_address=deserialized_eth_address,
                        name=token['name'],
                        decimals=int(token['decimals']),
                    )

                    # Classify the asset either as known or unknown
                    if isinstance(asset, EthereumToken):
                        known_assets.add(asset)
                    elif isinstance(asset, UnknownEthereumToken):
                        unknown_assets.add(asset)

                    # Estimate the underlying asset total_amount
                    asset_total_amount = FVal(token['total_amount'])
                    user_asset_balance = (user_lp_balance / lp_total_supply *
                                          asset_total_amount)

                    liquidity_pool_asset = LiquidityPoolAsset(
                        asset=asset,
                        total_amount=asset_total_amount,
                        user_balance=Balance(amount=user_asset_balance),
                    )
                    liquidity_pool_assets.append(liquidity_pool_asset)

                liquidity_pool = LiquidityPool(
                    address=lp_address,
                    assets=liquidity_pool_assets,
                    total_supply=lp_total_supply,
                    user_balance=Balance(amount=user_lp_balance),
                )
                address_balances[user_address].append(liquidity_pool)

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            param_values = {
                **param_values,
                'offset':
                param_values['offset'] + GRAPH_QUERY_LIMIT,  # type: ignore
            }

        protocol_balance = ProtocolBalance(
            address_balances=dict(address_balances),
            known_assets=known_assets,
            unknown_assets=unknown_assets,
        )
        return protocol_balance