示例#1
0
def get_token_info_from_blockchain_task(token_address: ChecksumAddress) -> bool:
    """
    Retrieve token information from blockchain

    :param token_address:
    :return: `True` if found, `False` otherwise
    """
    redis = get_redis()
    key = f"token-task:{token_address}"
    if result := redis.get(key):
        return bool(int(result))
示例#2
0
def calculate_token_eth_price_task(
    token_address: ChecksumAddress, redis_key: str, force_recalculation: bool = False
) -> Optional[EthValueWithTimestamp]:
    """
    Do price calculation for token in an async way and store it with its timestamp on redis

    :param token_address: Token address
    :param redis_key: Redis key for token price
    :param force_recalculation: Force a new calculation even if an old one is on cache
    :return: token price (in ether) when calculated
    """
    from .services.price_service import PriceServiceProvider

    redis_expiration_time = 60 * 30  # Expire in 30 minutes
    redis = get_redis()
    now = timezone.now()
    current_timestamp = int(now.timestamp())
    key_was_set = redis.set(
        redis_key, f"0:{current_timestamp}", ex=60 * 15, nx=True
    )  # Expire in 15 minutes
    if key_was_set or force_recalculation:
        price_service = PriceServiceProvider()
        eth_price = (
            price_service.get_token_eth_value(token_address)
            or price_service.get_token_usd_price(token_address)
            / price_service.get_native_coin_usd_price()
        )
        if not eth_price:  # Try composed oracles
            if underlying_tokens := price_service.get_underlying_tokens(token_address):
                eth_price = 0
                for underlying_token in underlying_tokens:
                    # Find underlying token price and multiply by quantity
                    address = underlying_token.address
                    eth_price += (
                        calculate_token_eth_price_task(
                            address,
                            f"price-service:{address}:eth-price",  # TODO Refactor all the calculation logic
                        ).eth_value
                        * underlying_token.quantity
                    )
        if eth_price:
            eth_value_with_timestamp = EthValueWithTimestamp(eth_price, now)
            redis.setex(redis_key, redis_expiration_time, str(eth_value_with_timestamp))
            if not getattr(settings, "CELERY_ALWAYS_EAGER", False):
                # Recalculate price before cache expires and prevents recursion checking Celery Eager property
                calculate_token_eth_price_task.apply_async(
                    (token_address, redis_key),
                    {"force_recalculation": True},
                    countdown=redis_expiration_time - 300,
                )
        else:
            logger.warning("Cannot calculate eth price for token=%s", token_address)
        return EthValueWithTimestamp(eth_price, now)
    def test_get_token_uris(self, get_token_uris_mock: MagicMock):
        redis = get_redis()
        redis.flushall()
        token_uris = [
            "http://testing.com/12",
            None,
            "",
        ]  # '' will be parsed as None by the service
        expected_token_uris = ["http://testing.com/12", None, None]
        get_token_uris_mock.return_value = token_uris
        addresses_with_token_ids = [(Account.create().address, i) for i in range(3)]
        collectibles_service = CollectiblesServiceProvider()
        self.assertFalse(collectibles_service.cache_token_uri)
        self.assertEqual(
            collectibles_service.get_token_uris(addresses_with_token_ids),
            expected_token_uris,
        )

        # Test redis cache
        redis_keys = redis.keys("token-uri:*")
        self.assertEqual(len(redis_keys), 3)

        # Test cache
        self.assertEqual(len(collectibles_service.cache_token_uri), 3)
        get_token_uris_mock.return_value = []
        for address_with_token_id, token_uri in zip(
            addresses_with_token_ids, expected_token_uris
        ):
            self.assertEqual(
                collectibles_service.cache_token_uri[address_with_token_id], token_uri
            )

        # Test redis cache working
        collectibles_service.cache_token_uri = {}
        self.assertEqual(
            collectibles_service.get_token_uris(addresses_with_token_ids),
            expected_token_uris,
        )
示例#4
0
 def setUpClass(cls) -> None:
     cls.price_service = PriceServiceProvider()
     cls.redis = get_redis()
示例#5
0
 def __new__(cls):
     if not hasattr(cls, "instance"):
         cls.instance = TransactionService(EthereumClientProvider(),
                                           get_redis())
     return cls.instance
示例#6
0
    def __new__(cls):
        if not hasattr(cls, "instance"):
            cls.instance = CollectiblesService(EthereumClientProvider(),
                                               get_redis())

        return cls.instance
示例#7
0
 def __init__(self, address: Optional[str], payload: Dict[str, Any]):
     self.redis = get_redis()
     self.address = address
     self.payload = payload
     self.redis_payload = self._get_redis_payload(address, payload)
 def __new__(cls):
     if not hasattr(cls, "instance"):
         cls.instance = BalanceService(
             EthereumClientProvider(), PriceServiceProvider(), get_redis()
         )
     return cls.instance
    def test_get_collectibles(self):
        mainnet_node = just_test_if_mainnet_node()
        try:
            ethereum_client = EthereumClient(mainnet_node)
            EthereumClientProvider.instance = ethereum_client
            collectibles_service = CollectiblesService(ethereum_client, get_redis())

            # Caches empty
            self.assertFalse(collectibles_service.cache_token_info)
            self.assertFalse(collectibles_service.cache_uri_metadata)

            safe_address = "0xfF501B324DC6d78dC9F983f140B9211c3EdB4dc7"
            ens_address = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"
            ens_logo_uri = "/media/tokens/logos/ENS.png"
            ens_token_id = 93288724337340885726942883352789513739931149355867373088241393067029827792979
            dappcon_2020_address = "0x202d2f33449Bf46d6d32Ae7644aDA130876461a4"
            dappcon_token_id = 13
            dappcon_logo_uri = Token(
                address=dappcon_2020_address, name="", symbol=""
            ).get_full_logo_uri()
            self.assertEqual(collectibles_service.get_collectibles(safe_address), [])

            erc721_addresses = [
                (dappcon_2020_address, dappcon_token_id),
                (ens_address, ens_token_id),  # ENS
            ]

            for erc721_address, token_id in erc721_addresses:
                ERC721TransferFactory(
                    to=safe_address, address=erc721_address, token_id=token_id
                )

            expected = [
                Collectible(
                    token_name="Ethereum Name Service",
                    token_symbol="ENS",
                    logo_uri=ens_logo_uri,
                    address=ens_address,
                    id=ens_token_id,
                    uri=None,
                ),
                Collectible(
                    token_name="DappCon2020",
                    token_symbol="D20",
                    logo_uri=dappcon_logo_uri,
                    address=dappcon_2020_address,
                    id=dappcon_token_id,
                    uri="https://us-central1-thing-1d2be.cloudfunctions.net/getThing?thingId=Q1c8y3PwYomxjW25sW3l",
                ),
            ]
            collectibles = collectibles_service.get_collectibles(safe_address)
            self.assertEqual(len(collectibles), len(expected))
            self.assertCountEqual(collectibles, expected)

            expected = [
                CollectibleWithMetadata(
                    token_name="Ethereum Name Service",
                    token_symbol="ENS",
                    logo_uri=ens_logo_uri,
                    address=ens_address,
                    id=93288724337340885726942883352789513739931149355867373088241393067029827792979,
                    uri=None,
                    metadata={
                        "name": "safe-multisig.eth",
                        "description": ".eth ENS Domain",
                        "image": collectibles_service.ENS_IMAGE_URL,
                    },
                ),
                CollectibleWithMetadata(
                    token_name="DappCon2020",
                    token_symbol="D20",
                    logo_uri=dappcon_logo_uri,
                    address=dappcon_2020_address,
                    id=13,
                    uri="https://us-central1-thing-1d2be.cloudfunctions.net/getThing?thingId=Q1c8y3PwYomxjW25sW3l",
                    metadata={
                        "minted": "Minted on Mintbase.io",
                        "image": "https://firebasestorage.googleapis.com/v0/b/thing-1d2be.appspot.com/o/token%2Fasset-1581932081565?alt=media&token=57b47904-1782-40e0-ab6d-4f8ca82e6884",
                        "name": "Earlybird Ticket",
                        "forSale": False,
                        "minter": "",
                        "external_url": "https://mintbase.io/my-market/0x202d2f33449bf46d6d32ae7644ada130876461a4",
                        "fiatPrice": "$278.66",
                        "tags": [],
                        "mintedOn": {"_seconds": 1581932237, "_nanoseconds": 580000000},
                        "amountToMint": 10,
                        "contractAddress": "0x202d2f33449bf46d6d32ae7644ada130876461a4",
                        "type": "ERC721",
                        "attributes": [
                            {
                                "display_type": "date",
                                "value": 1599516000,
                                "trait_type": "Start Date",
                            },
                            {
                                "display_type": "date",
                                "value": 1599688800,
                                "trait_type": "End Date",
                            },
                            {
                                "value": "Holzmarktstraße 33, 10243 Berlin, Germany",
                                "trait_type": "location",
                            },
                            {
                                "value": "ChIJhz8mADlOqEcR2lw7-iNCoDM",
                                "trait_type": "place_id",
                            },
                            {"value": "https://dappcon.io/", "trait_type": "website"},
                        ],
                        "price": "1.1",
                        "description": "This NFT ticket gives you full access to the 3-day conference. \nDate: 8 - 10 September *** Location: Holzmarktstraße 33 I 10243 Berlin",
                        "numAvailable": 0,
                    },
                ),
            ]
            collectibles_with_metadata = (
                collectibles_service.get_collectibles_with_metadata(safe_address)
            )
            self.assertCountEqual(collectibles_with_metadata, expected)

            # Set ens trusted
            Token.objects.filter(address=ens_address).update(trusted=True)
            collectibles_with_metadata = (
                collectibles_service.get_collectibles_with_metadata(
                    safe_address, only_trusted=True
                )
            )
            self.assertCountEqual(collectibles_with_metadata, expected[:1])

            # Set ens spam
            Token.objects.filter(address=ens_address).update(trusted=False, spam=True)
            collectibles_with_metadata = (
                collectibles_service.get_collectibles_with_metadata(
                    safe_address, exclude_spam=True
                )
            )
            self.assertCountEqual(collectibles_with_metadata, expected[1:])

            # Caches not empty
            self.assertTrue(collectibles_service.cache_token_info)
            self.assertTrue(collectibles_service.cache_uri_metadata)
        finally:
            del EthereumClientProvider.instance