From 65c2d925978093325db68ded9979789632072347 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Wed, 12 Feb 2025 11:59:27 -0800 Subject: [PATCH] feat: add max latency field (#59) --- pythclient/pythaccounts.py | 21 ++- setup.py | 2 +- tests/test_price_account.py | 327 +++++++++++++++++++++++++++--------- tests/test_pyth_client.py | 6 +- 4 files changed, 269 insertions(+), 87 deletions(-) diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index 0577f7e..6013b25 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -17,9 +17,9 @@ _VERSION_1 = 1 _VERSION_2 = 2 _SUPPORTED_VERSIONS = set((_VERSION_1, _VERSION_2)) -_ACCOUNT_HEADER_BYTES = 16 # magic + version + type + size, u32 * 4 +ACCOUNT_HEADER_BYTES = 16 # magic + version + type + size, u32 * 4 _NULL_KEY_BYTES = b'\x00' * SolanaPublicKey.LENGTH -MAX_SLOT_DIFFERENCE = 25 +DEFAULT_MAX_LATENCY = 25 class PythAccountType(Enum): @@ -81,7 +81,7 @@ def _read_attribute_string(buffer: bytes, offset: int) -> Tuple[Optional[str], i def _parse_header(buffer: bytes, offset: int = 0, *, key: SolanaPublicKeyOrStr) -> Tuple[PythAccountType, int, int]: - if len(buffer) - offset < _ACCOUNT_HEADER_BYTES: + if len(buffer) - offset < ACCOUNT_HEADER_BYTES: raise ValueError("Pyth account data too short") # Pyth magic (u32) == MAGIC @@ -141,7 +141,7 @@ def update_with_rpc_response(self, slot: int, value: Dict[str, Any]) -> None: f"wrong Pyth account type {type_} for {type(self)}") try: - self.update_from(data[:size], version=version, offset=_ACCOUNT_HEADER_BYTES) + self.update_from(data[:size], version=version, offset=ACCOUNT_HEADER_BYTES) except Exception as e: logger.exception("error while parsing account", exception=e) @@ -482,6 +482,7 @@ class PythPriceAccount(PythAccount): aggregate price is composed of slot (int): the slot time when this account was last fetched product (Optional[PythProductAccount]): the product this price is for, if loaded + max_latency (int): the maximum allowed slot difference for this feed """ def __init__(self, key: SolanaPublicKey, solana: SolanaClient, *, product: Optional[PythProductAccount] = None) -> None: @@ -503,6 +504,7 @@ def __init__(self, key: SolanaPublicKey, solana: SolanaClient, *, product: Optio self.prev_price: float = field(init=False) self.prev_conf: float = field(init=False) self.prev_timestamp: int = 0 # unix timestamp in seconds + self.max_latency: int = 0 # maximum allowed slot difference for this feed @property def aggregate_price(self) -> Optional[float]: @@ -537,7 +539,7 @@ def get_aggregate_price_status_with_slot(self, slot: int) -> Optional[PythPriceS You might consider using this function with the latest solana slot to make sure the price has not gone stale. """ if self.aggregate_price_info.price_status == PythPriceStatus.TRADING and \ - slot - self.aggregate_price_info.pub_slot > MAX_SLOT_DIFFERENCE: + slot - self.aggregate_price_info.pub_slot > self.max_latency: return PythPriceStatus.UNKNOWN return self.aggregate_price_info.price_status @@ -571,9 +573,12 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None: derivations = list(struct.unpack_from("<6q", buffer, offset)) self.derivations = dict((type_, derivations[type_.value - 1]) for type_ in [EmaType.EMA_CONFIDENCE_VALUE, EmaType.EMA_PRICE_VALUE]) offset += 48 # struct.calcsize("6q") - # drv[2-4]_ fields are currently unused timestamp, min_publishers = struct.unpack_from(" None: self.prev_price = prev_price self.prev_conf = prev_conf self.prev_timestamp = prev_timestamp + # a max latency of 0 is the default max latency + self.max_latency = max_latency if max_latency != 0 else DEFAULT_MAX_LATENCY def __str__(self) -> str: if self.product: diff --git a/setup.py b/setup.py index 51f0adf..339a2f0 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='pythclient', - version='0.2.0', + version='0.2.1', packages=['pythclient'], author='Pyth Developers', author_email='contact@pyth.network', diff --git a/tests/test_price_account.py b/tests/test_price_account.py index 2ac3254..62e7c02 100644 --- a/tests/test_price_account.py +++ b/tests/test_price_account.py @@ -3,7 +3,8 @@ from dataclasses import asdict from pythclient.pythaccounts import ( - MAX_SLOT_DIFFERENCE, + ACCOUNT_HEADER_BYTES, + DEFAULT_MAX_LATENCY, PythPriceAccount, PythPriceType, PythPriceStatus, @@ -12,49 +13,222 @@ from pythclient.solana import SolanaPublicKey, SolanaClient -# Yes, this sucks, but it is actually a monster datastructure (2K) +# Yes, this sucks, but it is actually a monster datastructure +# Equity.US.AAPL/USD symbol @pytest.fixture def price_account_bytes(): return base64.b64decode(( - b'AQAAAPj///8TAAAAEAAAANupUgYAAAAA2qlSBgAAAAB4XGx3EAAAAJ86jskAAAAA3CH+HAEA' - b'AAD6ORUDAAAAABzzZ5MAAAAA3CH+HAEAAAABAAAAAAAAAAAAAAAAAAAASNYDPXM+J5UMLgNR' - b'4lBUkc2RVIJPcW2VE1FMdLn5j1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANmp' - b'UgYAAAAAIB/LdhAAAADEKi0CAAAAAAAAAAAAAAAAIB/LdhAAAADk7y4CAAAAAAEAAAAAAAAA' - b'26lSBgAAAAD3Zn27jXzTFyGJQUojwmtSHYwZxkUEVVXj4o6CVo5leGBYnXYQAAAAYBBIAQAA' - b'AAABAAAAAAAAANipUgYAAAAAYFiddhAAAABgEEgBAAAAAAEAAAAAAAAA2alSBgAAAAAWD7rB' - b'Ovfd2AXTFwo94Ma9lxJqHgLA0lnQqG74IdblxyAfy3YQAAAACyXuAAAAAAABAAAAAAAAANip' - b'UgYAAAAAIB/LdhAAAAALJe4AAAAAAAEAAAAAAAAA2alSBgAAAAAF0gZPMxz/3cq+lvo2VSTd' - b'ZPSzhuiFo00VLL6uBCzq9cgcPHEQAAAAVAF9BAAAAAABAAAAAAAAANmpUgYAAAAAyBw8cRAA' - b'AABUAX0EAAAAAAEAAAAAAAAA2qlSBgAAAADiuY8mkITUiAURyBFdzvBPU8fiB5kuA//RJt+U' - b'TeTbBEB4h9UNAAAAQFSJAAAAAAABAAAAAAAAANfOOgYAAAAAQHiH1Q0AAABAVIkAAAAAAAEA' - b'AAAAAAAA1846BgAAAAAa5QKj6UK4sRzDdElrTZxcOfgMXawfRZ81og7BuHMIndC7Q3UQAAAA' - b'cNddAAAAAAABAAAAAAAAANipUgYAAAAA0LtDdRAAAABw110AAAAAAAEAAAAAAAAA2alSBgAA' - b'AAANw7zqkVVpdgiwXwSnCtCaQVFyqpu190CHOsB4KysaRYA0/X4QAAAA6PWmKgAAAAABAAAA' - b'AAAAANipUgYAAAAAgDT9fhAAAADo9aYqAAAAAAEAAAAAAAAA2alSBgAAAAAH8ss5/bAp3FF4' - b'TSjvF5Edl8GmnIVyOhtiVbNCU0OtdaDilHgQAAAAQHh9AQAAAAABAAAAAAAAANmpUgYAAAAA' - b'oOKUeBAAAABAeH0BAAAAAAEAAAAAAAAA2qlSBgAAAACfPqV71Am6AMQNkq5XE0HCfjwvft+s' - b'4cJKUbGhXDGytwB+w3YQAAAAAJDQAwAAAAABAAAAAAAAANapUgYAAAAAAH7DdhAAAAAAkNAD' - b'AAAAAAEAAAAAAAAA1qlSBgAAAABDgo+jYZ2mvK7WiRfeHXzOkhfexyuuEjBj/3vn3S+WPp+1' - b'Y3cQAAAAIND8AwAAAAABAAAAAAAAANipUgYAAAAAn7VjdxAAAADQDPwDAAAAAAEAAAAAAAAA' - b'2alSBgAAAAAYg7EkbdpdBxc9vTjVZwAHFYQsH9DolucLCm3S5RpPl5CJPXoQAAAAoH4mAQAA' - b'AAABAAAAAAAAANSpUgYAAAAAkIk9ehAAAACgfiYBAAAAAAEAAAAAAAAA1KlSBgAAAABDt3hL' - b'b4VmyzKDZfvOC0BGFSO67OeFF7MVXDHaozgpj6CarHYQAAAA4MrUAgAAAAABAAAAAAAAANip' - b'UgYAAAAAoJqsdhAAAADgytQCAAAAAAEAAAAAAAAA2alSBgAAAAD1nd3vzBZrLYmko8zz/sS7' - b'S5ihUbTAN/9hXrt4QuM9dQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACp6tPj00vMhTS7LGUOsnqMjD8aItaIKEMoU4xC' - b'qOgjQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' - b'AAAAAAAAAAAAAAAAAADQyjMc9dnucWvIxpCjAKuoQDs3FBy2OwJlwJjAxY5jrEDJD3cQAAAA' - b'iO90AgAAAAABAAAAAAAAANipUgYAAAAAQMkPdxAAAACI73QCAAAAAAEAAAAAAAAA2alSBgAA' - b'AABfyWT+IQLDTV2m/OVBHX+euZaDX9doeSPt8Afh6snTmyAfy3YQAAAA8MjSAAAAAAABAAAA' - b'AAAAANipUgYAAAAAIB/LdhAAAADwyNIAAAAAAAEAAAAAAAAA2alSBgAAAADshtw0V/2qFXMo' - b'0kCdNAHTz61GgIqwRBk8Hn7J+tXPYra163kQAAAAmhIJAwAAAAABAAAAAAAAANipUgYAAAAA' - b'trXreRAAAACaEgkDAAAAAAEAAAAAAAAA2alSBgAAAADYb4QN6+LtpmaFm/jCx0LD5ke+Thdt' - b'/FIl2ATx1J/iLU9xK3YQAAAAUQDeAQAAAAABAAAAAAAAANipUgYAAAAAT3ErdhAAAABRAN4B' - b'AAAAAAEAAAAAAAAA2alSBgAAAAD3oTB6i0MnB/D217PntQNBRQJinx7o+cT2tZFViRokLtlj' - b'UXkQAAAAhB8gAgAAAAABAAAAAAAAANipUgYAAAAA2WNReRAAAACEHyACAAAAAAEAAAAAAAAA' - b'2alSBgAAAAAQObH1+gS8Ag0HeG1UdQRs2fQLBW50YN8kJo4QAHwOnZ+DNHgQAAAAYe1IAgAA' - b'AAABAAAAAAAAANipUgYAAAAAn4M0eBAAAABh7UgCAAAAAAEAAAAAAAAA2alSBgAAAAA=' - )) + b'1MOyoQIAAAADAAAAIDEAAAEAAAD7////HQAAABsAAAD/rccLAAAAAP6txwsAAAAATKVnAQAAAACfFQcN' + b'AQAAAP0gJHIAAAAAczcAAAAAAAA8DgiiAAAAAP0gJHIAAAAA9fasZwAAAAADADIDPQEAACkunmg3xiSw' + b'fCBPOBN1xaL8HmQRPUjcgostWu2uVecsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+rccL' + b'AAAAAEp3aAEAAAAA+TcAAAAAAAD09qxnAAAAAM12aAEAAAAAdjgAAAAAAAABAAAAAAAAAP+txwsAAAAA' + b'BHkihWa8qHaHujLYgFXDIwjMb1piz6Z/GIGZQeOFsrJwcWgBAAAAALvEAAAAAAAAAQAAAAAAAAD8rccL' + b'AAAAAHBxaAEAAAAAu8QAAAAAAAABAAAAAAAAAPytxwsAAAAABw99DZApUxxfQ451HfqUNEecEJ+K3q4L' + b'ImQn81mfXBnOemgBAAAAANkUAQAAAAAAAQAAAAAAAADzrccLAAAAAM56aAEAAAAA2RQBAAAAAAABAAAA' + b'AAAAAPOtxwsAAAAAB/LLOf2wKdxReE0o7xeRHZfBppyFcjobYlWzQlNDrXVMd2gBAAAAAEhcAAAAAAAA' + b'AQAAAAAAAAD3rccLAAAAAEx3aAEAAAAASFwAAAAAAAABAAAAAAAAAPetxwsAAAAAC7W169huq2IOUmHg' + b'hY4UR1FAoCOpXo1cicOJgwqilmeucmgBAAAAAPQBAAAAAAAAAQAAAAAAAAD9rccLAAAAAK5yaAEAAAAA' + b'9AEAAAAAAAABAAAAAAAAAP2txwsAAAAAFzpASQCO7GVI83hRl/cs7iBjSV0Av1Bj68V8d837GUHzPVwB' + b'AAAAANBpwAAAAAAABAAAAAAAAAAd35ILAAAAAPM9XAEAAAAA0GnAAAAAAAAEAAAAAAAAAB3fkgsAAAAA' + b'GIOxJG3aXQcXPb041WcABxWELB/Q6JbnCwpt0uUaT5cJbWgBAAAAAFgHAwAAAAAAAQAAAAAAAAD8rccL' + b'AAAAAAltaAEAAAAAWAcDAAAAAAABAAAAAAAAAPytxwsAAAAAJh54j4GAISD3TwZWpS7jDYp6d0mcRf2n' + b'xlxmID4iZ25tgWgBAAAAAOWaAwAAAAAAAQAAAAAAAAD0rccLAAAAAG2BaAEAAAAA5ZoDAAAAAAABAAAA' + b'AAAAAPStxwsAAAAANIa+/riGb203XbXQ8h0HwnTKrhg+e3cLJXNgHPRZHSRRd2gBAAAAABwwAAAAAAAA' + b'AQAAAAAAAAD6rccLAAAAAFF3aAEAAAAAHDAAAAAAAAABAAAAAAAAAPqtxwsAAAAAQ4KPo2Gdpryu1okX' + b'3h18zpIX3scrrhIwY/97590vlj5nf2gBAAAAABh+AAAAAAAAAQAAAAAAAAD8rccLAAAAAGd/aAEAAAAA' + b'GH4AAAAAAAABAAAAAAAAAPytxwsAAAAATXYO0eWeK9NQsMMZj+HvA16XRS7UvMYr42xvExZSkdFcdWgB' + b'AAAAABmLAAAAAAAAAQAAAAAAAAD8rccLAAAAAFx1aAEAAAAAGYsAAAAAAAABAAAAAAAAAPytxwsAAAAA' + b'Tjqyi56CYuBQyurc9ATAapzuKuOgdEwh/hm0Mt5mkOtkdWgBAAAAANsQAQAAAAAAAQAAAAAAAAD+rccL' + b'AAAAAGR1aAEAAAAA2xABAAAAAAABAAAAAAAAAP6txwsAAAAATrAvjfOs/kT57qji7Ps3wu5XqD3//AFC' + b'0CdHbBz0M3QEnGgBAAAAAIpIAgAAAAAAAQAAAAAAAAD7rccLAAAAAAScaAEAAAAAikgCAAAAAAABAAAA' + b'AAAAAPutxwsAAAAAVBkdg3Zb8Ej6G4LYAW466xu/DHb3ezUTWu9Vo3T3/ms8e2gBAAAAAAc0AAAAAAAA' + b'AQAAAAAAAADyrccLAAAAADx7aAEAAAAABzQAAAAAAAABAAAAAAAAAPKtxwsAAAAAaj2lMUYld1Wxfrwl' + b'0Lo22hdeJPxpkprmafPfHmPVnUBafWgBAAAAAElcAAAAAAAAAQAAAAAAAADxrccLAAAAAFp9aAEAAAAA' + b'SVwAAAAAAAABAAAAAAAAAPGtxwsAAAAAfEFChNuJaWdU8R/x7GUP3o44600xL/0IC/SH/5J1561Nd2gB' + b'AAAAAEdcAAAAAAAAAQAAAAAAAAD4rccLAAAAAE13aAEAAAAAR1wAAAAAAAABAAAAAAAAAPitxwsAAAAA' + b'fcK1rXWbYoQKtCq2nzJiCmvpYCTjfvXYuWgji0GQsGpwcWgBAAAAAOAuAAAAAAAAAQAAAAAAAAD5rccL' + b'AAAAAHBxaAEAAAAA4C4AAAAAAAABAAAAAAAAAPmtxwsAAAAAh2GV5NQWzsgLKj06RBPx0QCB97kCA1OV' + b'UrDxEcZNvhhKd2gBAAAAANCPAAAAAAAAAQAAAAAAAAD4rccLAAAAAEp3aAEAAAAA0I8AAAAAAAABAAAA' + b'AAAAAPitxwsAAAAAibazYiCMITlc2drXqvTlt3fSCnk7W1heG3EouJogjZd4eGgBAAAAAGfNAQAAAAAA' + b'AQAAAAAAAAD8rccLAAAAAHh4aAEAAAAAZ80BAAAAAAABAAAAAAAAAPytxwsAAAAAi0AFlC/4wcwisiCx' + b'v13ss5/vcrirPwLzrSXGpy8fewyuZ2gBAAAAAN+IAAAAAAAAAQAAAAAAAAD/rccLAAAAAK5naAEAAAAA' + b'34gAAAAAAAABAAAAAAAAAP+txwsAAAAArU0itxPC4r5fWaGWOzot71pBjR2EcS+WEjK4Bzkzs3Crc2gB' + b'AAAAAPdaAAAAAAAAAQAAAAAAAAD6rccLAAAAAKtzaAEAAAAA91oAAAAAAAABAAAAAAAAAPqtxwsAAAAA' + b'vFRslRVZlbwHP1fHn9TC4H0gHT4cvadEJLsMYazqQb5kpmgBAAAAAG/6AAAAAAAAAQAAAAAAAADxrccL' + b'AAAAAGSmaAEAAAAAb/oAAAAAAAABAAAAAAAAAPGtxwsAAAAAxeks08X3OzuidkIc+gZFbXnnuyIHgNNb' + b'7PpPEpd/qijgKWMBAAAAAHBvAgAAAAAAAQAAAAAAAACdb60LAAAAAOApYwEAAAAAcG8CAAAAAAABAAAA' + b'AAAAAJ1vrQsAAAAA0HoGOdUHEoMy5c1/vlS8fo3SBHH6TZX9zKxofXfx7YOddWgBAAAAAKS2AwAAAAAA' + b'AQAAAAAAAAD6rccLAAAAAJ11aAEAAAAApLYDAAAAAAABAAAAAAAAAPqtxwsAAAAA0sj8lXSClC3CIjOA' + b'kkwaV8JH5xFY0ct7hVWuwlD+R7jNdmgBAAAAABdxAQAAAAAAAQAAAAAAAAD7rccLAAAAAM12aAEAAAAA' + b'F3EBAAAAAAABAAAAAAAAAPutxwsAAAAA1S855pC2mSbP8jFQCvvqX3MpTOXs5/BYHTl/r3O5RalFe2gB' + b'AAAAABBLAAAAAAAAAQAAAAAAAAD+rccLAAAAAEV7aAEAAAAAEEsAAAAAAAABAAAAAAAAAP6txwsAAAAA' + b'4nYQ5DOiRvjmu8YoeyW1DLXF7pdywpOP4PGqQglGiGhkc2gBAAAAAPUBAAAAAAAAAQAAAAAAAAD7rccL' + b'AAAAAGRzaAEAAAAA9QEAAAAAAAABAAAAAAAAAPutxwsAAAAA4tX2SZD3l3FAyKYJNSbTLHNOH4n15gMg' + b'uh53FIMjwtkzjGgBAAAAAHwvAAAAAAAAAQAAAAAAAAD1rccLAAAAADOMaAEAAAAAfC8AAAAAAAABAAAA' + b'AAAAAPWtxwsAAAAA6RNLhwN/jdZo6gCE/jH7lRcwks1xI3vR8WRwtPd0ihQbcGgBAAAAAOwZAAAAAAAA' + b'AQAAAAAAAAD6rccLAAAAABtwaAEAAAAA7BkAAAAAAAABAAAAAAAAAPqtxwsAAAAA75mJdHHcLE7j7fIP' + b'srWt70W9Qm7X3gxVvFw7bbzVMJJrfWgBAAAAAOUrAAAAAAAAAQAAAAAAAAD6rccLAAAAAGt9aAEAAAAA' + b'5SsAAAAAAAABAAAAAAAAAPqtxwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7xHUr72YBwAAAAAA' + b'AAAAAJNnayVdBgAAAAAAAAAAAACBeRAFAAAAAAAAAAAAAAAA' +)) @pytest.fixture def price_account(solana_client: SolanaClient) -> PythPriceAccount: @@ -64,53 +238,54 @@ def price_account(solana_client: SolanaClient) -> PythPriceAccount: ) def test_price_account_update_from(price_account_bytes: bytes, price_account: PythPriceAccount): - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) assert price_account.price_type == PythPriceType.PRICE - assert price_account.exponent == -8 - assert price_account.num_components == 19 + assert price_account.exponent == -5 + assert price_account.num_components == 29 assert len(price_account.price_components) == price_account.num_components - assert price_account.last_slot == 106080731 - assert price_account.valid_slot == 106080730 + assert price_account.last_slot == 197635583 + assert price_account.valid_slot == 197635582 assert price_account.product_account_key == SolanaPublicKey( - "5uKdRzB3FzdmwyCHrqSGq4u2URja617jqtKkM71BVrkw" + "3mkwqdkawySvAm1VjD4f2THN5mmXzb76fvft2hWpAANo" ) + assert price_account.max_latency == 50 assert price_account.next_price_account_key is None assert asdict(price_account.aggregate_price_info) == { - "raw_price": 70712500000, - "raw_confidence_interval": 36630500, + "raw_price": 23623373, + "raw_confidence_interval": 14454, "price_status": PythPriceStatus.TRADING, - "pub_slot": 106080731, - "exponent": -8, - "price": 707.125, - "confidence_interval": 0.366305, + "pub_slot": 197635583, + "exponent": -5, + "price": 236.23373, + "confidence_interval": 0.14454, } # Only assert the first element of the 19 price components assert asdict(price_account.price_components[0]) == { "publisher_key": SolanaPublicKey( - "HekM1hBawXQu6wK6Ah1yw1YXXeMUDD2bfCHEzo25vnEB" + "JTmFx5zX9mM94itfk2nQcJnQQDPjcv4UPD7SYj6xDCV" ), "last_aggregate_price_info": { - "raw_price": 70709500000, - "raw_confidence_interval": 21500000, + "raw_price": 23622000, + "raw_confidence_interval": 50363, "price_status": PythPriceStatus.TRADING, - "pub_slot": 106080728, - "exponent": -8, - "price": 707.095, - "confidence_interval": 0.215, + "pub_slot": 197635580, + "exponent": -5, + "price": 236.22000000000003, + "confidence_interval": 0.50363, }, "latest_price_info": { - "raw_price": 70709500000, - "raw_confidence_interval": 21500000, + "raw_price": 23622000, + "raw_confidence_interval": 50363, "price_status": PythPriceStatus.TRADING, - "pub_slot": 106080729, - "exponent": -8, - "price": 707.095, - "confidence_interval": 0.215, + "pub_slot": 197635580, + "exponent": -5, + "price": 236.22000000000003, + "confidence_interval": 0.50363, }, - "exponent": -8, + "exponent": -5, } - assert price_account.min_publishers == 0 + assert price_account.min_publishers == 3 def test_price_account_str( @@ -120,7 +295,7 @@ def test_price_account_str( assert str(price_account) == expected_empty expected = "PythPriceAccount PythPriceType.PRICE (5ALDzwcRJfSyGdGyhP3kP628aqBNHZzLuVww7o9kdspe)" - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) assert str(price_account) == expected price_account.product = PythProductAccount( @@ -139,7 +314,7 @@ def test_price_account_str( def test_price_account_agregate_conf_interval( price_account_bytes: bytes, price_account: PythPriceAccount, ): - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) price_account.slot = price_account.aggregate_price_info.pub_slot assert price_account.aggregate_price_confidence_interval == 0.366305 @@ -147,14 +322,14 @@ def test_price_account_agregate_conf_interval( def test_price_account_agregate_price( price_account_bytes: bytes, price_account: PythPriceAccount, ): - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) price_account.slot = price_account.aggregate_price_info.pub_slot assert price_account.aggregate_price == 707.125 def test_price_account_unknown_status( price_account_bytes: bytes, price_account: PythPriceAccount, ): - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) price_account.slot = price_account.aggregate_price_info.pub_slot price_account.aggregate_price_info.price_status = PythPriceStatus.UNKNOWN @@ -164,8 +339,8 @@ def test_price_account_unknown_status( def test_price_account_get_aggregate_price_status_still_trading( price_account_bytes: bytes, price_account: PythPriceAccount ): - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) - price_account.slot = price_account.aggregate_price_info.pub_slot + MAX_SLOT_DIFFERENCE + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) + price_account.slot = price_account.aggregate_price_info.pub_slot + DEFAULT_MAX_LATENCY price_status = price_account.aggregate_price_status assert price_status == PythPriceStatus.TRADING @@ -173,8 +348,8 @@ def test_price_account_get_aggregate_price_status_still_trading( def test_price_account_get_aggregate_price_status_got_stale( price_account_bytes: bytes, price_account: PythPriceAccount ): - price_account.update_from(buffer=price_account_bytes, version=2, offset=0) - price_account.slot = price_account.aggregate_price_info.pub_slot + MAX_SLOT_DIFFERENCE + 1 + price_account.update_from(buffer=price_account_bytes, version=2, offset=ACCOUNT_HEADER_BYTES) + price_account.slot = price_account.aggregate_price_info.pub_slot + DEFAULT_MAX_LATENCY + 1 price_status = price_account.aggregate_price_status assert price_status == PythPriceStatus.UNKNOWN diff --git a/tests/test_pyth_client.py b/tests/test_pyth_client.py index 7c467e3..4bc8ad9 100644 --- a/tests/test_pyth_client.py +++ b/tests/test_pyth_client.py @@ -3,7 +3,7 @@ import base64 from pythclient.exceptions import NotLoadedException from pythclient.pythaccounts import ( - _ACCOUNT_HEADER_BYTES, _VERSION_2, PythMappingAccount, PythPriceType, PythProductAccount, PythPriceAccount + ACCOUNT_HEADER_BYTES, _VERSION_2, PythMappingAccount, PythPriceType, PythProductAccount, PythPriceAccount ) from pythclient.pythclient import PythClient, WatchSession @@ -234,7 +234,7 @@ def product_account(solana_client: SolanaClient) -> PythProductAccount: @ pytest.fixture def product_account_bytes() -> bytes: - return base64.b64decode(PRODUCT_ACCOUNT_B64_DATA)[_ACCOUNT_HEADER_BYTES:] + return base64.b64decode(PRODUCT_ACCOUNT_B64_DATA)[ACCOUNT_HEADER_BYTES:] @ pytest.fixture @@ -247,7 +247,7 @@ def price_account(solana_client: SolanaClient) -> PythPriceAccount: @ pytest.fixture def price_account_bytes() -> bytes: - return base64.b64decode(PRICE_ACCOUNT_B64_DATA)[_ACCOUNT_HEADER_BYTES:] + return base64.b64decode(PRICE_ACCOUNT_B64_DATA)[ACCOUNT_HEADER_BYTES:] @pytest.fixture()