From 24287d768be48ff1256a9f508379a376454dce9b Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Fri, 18 Aug 2023 12:16:23 +0800 Subject: [PATCH 1/4] support accumulator update data --- pythclient/price_feeds.py | 134 +++++++++++++++++++++++++++++++++++--- tests/test_price_feeds.py | 21 ++++++ 2 files changed, 147 insertions(+), 8 deletions(-) diff --git a/pythclient/price_feeds.py b/pythclient/price_feeds.py index 17d6570..f6ba12e 100644 --- a/pythclient/price_feeds.py +++ b/pythclient/price_feeds.py @@ -1,8 +1,7 @@ import base64 import binascii -import logging from struct import unpack -from typing import List +from typing import Any, Dict, List, Optional from Crypto.Hash import keccak @@ -13,6 +12,8 @@ DEFAULT_VAA_ENCODING = "hex" +ACCUMULATOR_MAGIC = "504e4155" + class Price: def __init__(self, conf, expo, price, publish_time): @@ -110,7 +111,7 @@ def to_dict(self, verbose=False, vaa_format=DEFAULT_VAA_ENCODING): return result -# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main/price_service/server/src/encoding.ts#L24 +# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/110caed6be3be7885773d2f6070b143cc13fb0ee/price_service/server/src/encoding.ts#L24 def encode_vaa_for_chain(vaa, vaa_format, buffer=False): # check if vaa is already in vaa_format if isinstance(vaa, str): @@ -337,12 +338,9 @@ def parse_price_attestation(bytes_): } -# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main/price_service/server/src/rest.ts#L139 +# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/110caed6be3be7885773d2f6070b143cc13fb0ee/price_service/server/src/rest.ts#L139 def vaa_to_price_infos(vaa, encoding=DEFAULT_VAA_ENCODING) -> List[PriceInfo]: parsed_vaa = parse_vaa(vaa, encoding) - - # TODO: support accumulators - batch_attestation = parse_batch_price_attestation(parsed_vaa["payload"]) price_infos = [] for price_attestation in batch_attestation["price_attestations"]: @@ -359,6 +357,11 @@ def vaa_to_price_infos(vaa, encoding=DEFAULT_VAA_ENCODING) -> List[PriceInfo]: def vaa_to_price_info(price_feed_id, vaa, encoding=DEFAULT_VAA_ENCODING) -> PriceInfo: + encoded_vaa = encode_vaa_for_chain(vaa, encoding, buffer=True) + if encoded_vaa[:4].hex() == ACCUMULATOR_MAGIC: + return extract_price_info_from_accumulator_update( + price_feed_id, encoded_vaa, encoding + ) price_infos = vaa_to_price_infos(vaa, encoding) for price_info in price_infos: if price_info.price_feed.id == price_feed_id: @@ -367,7 +370,7 @@ def vaa_to_price_info(price_feed_id, vaa, encoding=DEFAULT_VAA_ENCODING) -> Pric return None -# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main/price_service/server/src/listen.ts#L37 +# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/110caed6be3be7885773d2f6070b143cc13fb0ee/price_service/server/src/listen.ts#L37 def create_price_info(price_attestation, vaa, sequence, emitter_chain): price_feed = price_attestation_to_price_feed(price_attestation) return PriceInfo( @@ -407,3 +410,118 @@ def price_attestation_to_price_feed(price_attestation): ema_price.publish_time = price_attestation["prev_publish_time"] return PriceUpdate(ema_price, price_attestation["price_id"], price) + + +# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main/price_service/server/src/rest.ts#L137 +def extract_price_info_from_accumulator_update( + price_feed_id, update_data, encoding +) -> Optional[Dict[str, Any]]: + offset = 0 + offset += 4 # magic + offset += 1 # major version + offset += 1 # minor version + + trailing_header_size = update_data[offset] + offset += 1 + trailing_header_size + + update_type = update_data[offset] + offset += 1 + + if update_type != 0: + print(f"Invalid accumulator update type: {update_type}") + return None + + vaa_length = int.from_bytes(update_data[offset : offset + 2], byteorder="big") + offset += 2 + + vaa_buffer = update_data[offset : offset + vaa_length] + # convert vaa_buffer to string based on encoding + if encoding == "hex": + vaa_str = vaa_buffer.hex() + elif encoding == "base64": + vaa_str = base64.b64encode(vaa_buffer).decode("ascii") + parsed_vaa = parse_vaa(vaa_str, encoding) + offset += vaa_length + + num_updates = update_data[offset] + offset += 1 + + for _ in range(num_updates): + message_length = int.from_bytes( + update_data[offset : offset + 2], byteorder="big" + ) + offset += 2 + + message = update_data[offset : offset + message_length] + offset += message_length + + proof_length = update_data[offset] + offset += 1 + offset += proof_length # ignore proofs + + message_offset = 0 + message_type = message[message_offset] + message_offset += 1 + + if message_type != 0: + continue + + price_id = message[message_offset : message_offset + 32].hex() + message_offset += 32 + + if price_id != price_feed_id: + continue + + price = int.from_bytes( + message[message_offset : message_offset + 8], byteorder="big", signed=True + ) + message_offset += 8 + conf = int.from_bytes( + message[message_offset : message_offset + 8], byteorder="big", signed=False + ) + message_offset += 8 + expo = int.from_bytes( + message[message_offset : message_offset + 4], byteorder="big", signed=True + ) + message_offset += 4 + publish_time = int.from_bytes( + message[message_offset : message_offset + 8], byteorder="big", signed=True + ) + message_offset += 8 + prev_publish_time = int.from_bytes( + message[message_offset : message_offset + 8], byteorder="big", signed=True + ) + message_offset += 8 + ema_price = int.from_bytes( + message[message_offset : message_offset + 8], byteorder="big", signed=True + ) + message_offset += 8 + ema_conf = int.from_bytes( + message[message_offset : message_offset + 8], byteorder="big", signed=False + ) + + return PriceInfo( + seq_num=parsed_vaa["sequence"], + vaa=vaa_str, + publish_time=publish_time, + attestation_time=publish_time, + last_attested_publish_time=prev_publish_time, + price_feed=PriceUpdate( + ema_price=Price( + price=str(ema_price), + conf=str(ema_conf), + expo=expo, + publish_time=publish_time, + ), + price_id=price_id, + price=Price( + price=str(price), + conf=str(conf), + expo=expo, + publish_time=publish_time, + ), + ), + emitter_chain_id=parsed_vaa["emitter_chain"], + ) + + return None diff --git a/tests/test_price_feeds.py b/tests/test_price_feeds.py index 3cfa5f6..2f61aac 100644 --- a/tests/test_price_feeds.py +++ b/tests/test_price_feeds.py @@ -3,6 +3,8 @@ ID = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" # BTC/USD HEX_VAA = "01000000030d00ca037dde1419ea373de7474bff80268e41f55e84dd8e4fa5e5167808111e63e4064efcfd93075d9d162090bc478a017603ef56c4d26e0c46e0af6128c08984470002e9abd7c4e62bbc6b453c5ca014b7313bbadb17d6443190d6679b7c0ccb03bae619d21dcb5d6d5a484f9e4fd5586dca7c20403dc80d61dbe682a436b4d15d10bc0003a5e748513b75e34d9db61c6fd0fa55533e8424d88fbe6cda8e7cf61d50cb9467192a5cbd47b14b205b38123ad4b7c39d6ce7d6bf02c4a1452d135dc29fc83ef600049e4cd56e3c2e0cfc4cc831bc4ec54410590c15b4b20b63cc91be3436a4d09c0c22be4ab96962ad6e94d83e14c770e122aebae6fdbdea97f98cec7da5d30ed2a40106a7565c6387a700e02ee09652b40bdeef044441ca1e3b3c9376d4a129d22ca861501c3f5c0e8469c9a0e5d1b09d9f84c6517c0a2b400c0b47552006fff1dad3a5000a4db87004c483f899b5fd766c14334dfb5ca2fa5698964cf9644669b325bd3485207cbc4180a360023d1412da68bb11a0a82fee70a6bf03dda30b7aae53e0e465010ba3a6e45c9d8ef1d1041fdc7a926a9f41075531d45824144bbc720d111ee7270a77dd6dd65558b30d0f03692e075bd7d96cdfb24f5a68fecc22e441ded230c9cc010c09380e394e2b30fd036f13152b115dab7a206270d52255dfbbf0505c67bf510e70d0a6075f9bae19235eaf8a0893a4af9ed0df1b8cd67e1fe7b2ec61178d3ca4010dc491600d07d10a6468fb5955d94bc114efab46104e2ae530931231fea52cf7e32964a1c8bfe0ee38aaa8abfe8edcb7c079b6dd97b2c317c9d71cb5973bb53c72010f787e3c59ac484fdca7d5e41b29cebee08cb1789d61a0f29ccd0353118fd667ab1473a626eb6c237cff70ffb1eb2a556862197b08f183d5852168f5ce0f92632b0110f7ee4abdedc936ebebe86b3493292a9fa6625ab910b4a1340b46478a819508d1261f3d559d5cc95dead635c215b80b1cb2df348639d1ca572d3d14f07dc38908011103e3cdc9936ffbb7c0af5d77a4c092c5c42de161c9254919d19af718defd71a757fcbb1e3772e72c3a8c8291ab36f628a060030abf8ffb43923bb1a05cf9605d0112ddea2ce8ec77b9e222db5f1a95861c3da2ed3f54f7e937008bcc14b2458b98990eeb5910c7e9b2a27ff47a9568d0a3fedc12f357323905cbc8a1be6acbc5986b0064c37bca00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000002144b1420150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000059cc51c400000000000e4e1bffffffff8000000059b3f3c700000000000eae895010000001a0000001e0000000064c37bca0000000064c37bca0000000064c37bc9000000059cc51c400000000000e4e1bf0000000064c37bc948d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a300000005a7462c060000000000d206a2fffffff800000005a5c499380000000000f44b7d010000001c000000200000000064c37bca0000000064c37bca0000000064c37bc900000005a74653cc0000000000d1dedc0000000064c37bc83515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000002a724c9d1000000000032396fc5fffffff8000002a6e3e0fec0000000002ee4815c010000001b000000200000000064c37bca0000000064c37bca0000000064c37bc9000002a724c9d1000000000032396fc50000000064c37bc99b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae800000000045152eb000000000001bb63fffffff800000000044de0b500000000000185df0100000015000000160000000064c37bca0000000064c37bca0000000064c37bc900000000045152eb000000000001bb630000000064c37bc8e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c000000000074990500000000000011d9fffffff80000000000748c2f000000000000116f010000001b000000200000000064c37bca0000000064c37bca0000000064c37bc900000000007498d400000000000011a80000000064c37bc9" BASE64_VAA = "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" +ACCUMULATOR_UPDATE_DATA = "UE5BVQEAAAADuAEAAAADDQAsKPsmb7Vz7io3taJQKgoi1m/z0kqKgtpmlkv+ZuunX2Iegsf+8fuUtpHPLKgCWPU8PN2x9NyAZz5BY9M3SWwJAALYlM0U7f2GFWfEjKwSJlHZ5sf+n6KXCocVC66ImS2o0TD0SBhTWcp0KdcuzR1rY1jfIHaFpVneroRLbTjNrk/WAAMuAYxPVPf1DR30wYQo12Dbf+in3akTjhKERNQ+nPwRjxAyIQD+52LU3Rh2VL7nOIStMNTiBMaiWHywaPoXowWAAQbillhhX4MR+7h81PfxHIbiXBmER4c5M7spilWKkROb+VXhrqnVJL162t9TdhYk56PDIhvXO1Tm/ldjVJw130y0AAk6qpccfsxDZEmVN8LI4z8739Ni/kb+CB3yW2l2dWhKTjBeNanhK6TCCoNH/jRzWfrjrEk5zjNrUr82JwL4fR1OAQrYZescxbH26m8QHiH+RHzwlXpUKJgbHD5NnWtB7oFb9AFM15jbjd4yIEBEtAlXPE0Q4j+X+DLnCtZbLSQiYNh5AQvz70LTbYry1lEExuUcO+IRJiysw5AFyqZ9Y1E//WKIqgEysfcnHwoOxtDtAc5Z9sTUEYfPqQ1d27k3Yk0X7dvCAQ10cdG0qYHb+bQrYRIKKnb0aeCjkCs0HZQY2fXYmimyfTNfECclmPW9k+CfOvW0JKuFxC1l11zJ3zjsgN/peA8BAQ5oIFQGjq9qmf5gegE1DjuzXsGksKao6nsjTXYIspCczCe2h5KNQ9l5hws11hauUKS20JoOYjHwxPD2x0adJKvkAQ+4UjVcZgVEQP8y3caqUDH81Ikcadz2bESpYg93dpnzZTH6A7Ue+RL34PTNx6cCRzukwQuhiStuyL1WYEIrLI4nABAjGv3EBXjWaPLUj59OzVnGkzxkr6C4KDjMmpsYNzx7I2lp2iQV46TM78El8i9h7twiEDUOSdC5CmfQjRpkP72yABGVAQELUm2/SjkpF0O+/rVDgA/Y2/wMacD1ZDahdyvSNSFThn5NyRYA1JXGgIDxoYeAZgkr1gL1cjCLWiO+Bs9QARIiCvHfIkn2aYhYHQq/u6cHB/2DxE3OgbCZyTv8OVO55hQDkJ1gDwAec+IJ4M5Od4OxWEu+OywhJT7zUmwZko9MAGTeJ+kAAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAWllxAUFVV1YAAAAAAAVZ/XAAACcQ8Xfx5wQ+nj1rn6IeTUAy+VER1nUBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKUUTJXzwAAAADDrAZ1////+AAAAABk3ifoAAAAAGTeJ+cAAAKWepR2oAAAAAC/b8SsCasjFzENKvXWwOycuzCVaDWfm0IuuuesmamDKl2lNXss15orlNN+xHVNEEIIq7Xg8GRZGVLt43fkg7xli6EPQ/Nyxl6SixiYteNt1uTTh4M1lQTUjPxKnkE5JEea4RnhOWgmSAWMf8ft4KgE7hvRifV1JP0rOsNgsOYFRbs6iDKW1qLpxgZLMAiOclwS3Tjw2hj8sPfq1NHeVttsBEK5SIM14GjAuD/p2V0+NqHqMHxU/kfftg==" +ACCUMULATOR_VAA = "AQAAAAMNACwo+yZvtXPuKje1olAqCiLWb/PSSoqC2maWS/5m66dfYh6Cx/7x+5S2kc8sqAJY9Tw83bH03IBnPkFj0zdJbAkAAtiUzRTt/YYVZ8SMrBImUdnmx/6fopcKhxULroiZLajRMPRIGFNZynQp1y7NHWtjWN8gdoWlWd6uhEttOM2uT9YAAy4BjE9U9/UNHfTBhCjXYNt/6KfdqROOEoRE1D6c/BGPEDIhAP7nYtTdGHZUvuc4hK0w1OIExqJYfLBo+hejBYABBuKWWGFfgxH7uHzU9/EchuJcGYRHhzkzuymKVYqRE5v5VeGuqdUkvXra31N2FiTno8MiG9c7VOb+V2NUnDXfTLQACTqqlxx+zENkSZU3wsjjPzvf02L+Rv4IHfJbaXZ1aEpOMF41qeErpMIKg0f+NHNZ+uOsSTnOM2tSvzYnAvh9HU4BCthl6xzFsfbqbxAeIf5EfPCVelQomBscPk2da0HugVv0AUzXmNuN3jIgQES0CVc8TRDiP5f4MucK1lstJCJg2HkBC/PvQtNtivLWUQTG5Rw74hEmLKzDkAXKpn1jUT/9YoiqATKx9ycfCg7G0O0Bzln2xNQRh8+pDV3buTdiTRft28IBDXRx0bSpgdv5tCthEgoqdvRp4KOQKzQdlBjZ9diaKbJ9M18QJyWY9b2T4J869bQkq4XELWXXXMnfOOyA3+l4DwEBDmggVAaOr2qZ/mB6ATUOO7NewaSwpqjqeyNNdgiykJzMJ7aHko1D2XmHCzXWFq5QpLbQmg5iMfDE8PbHRp0kq+QBD7hSNVxmBURA/zLdxqpQMfzUiRxp3PZsRKliD3d2mfNlMfoDtR75Evfg9M3HpwJHO6TBC6GJK27IvVZgQissjicAECMa/cQFeNZo8tSPn07NWcaTPGSvoLgoOMyamxg3PHsjaWnaJBXjpMzvwSXyL2Hu3CIQNQ5J0LkKZ9CNGmQ/vbIAEZUBAQtSbb9KOSkXQ77+tUOAD9jb/AxpwPVkNqF3K9I1IVOGfk3JFgDUlcaAgPGhh4BmCSvWAvVyMItaI74Gz1ABEiIK8d8iSfZpiFgdCr+7pwcH/YPETc6BsJnJO/w5U7nmFAOQnWAPAB5z4gngzk53g7FYS747LCElPvNSbBmSj0wAZN4n6QAAAAAAGuEB+u2sWFHjK5sjtflBGowrrEquPtTde4Ed0acupKpxAAAAAABaWXEBQVVXVgAAAAAABVn9cAAAJxDxd/HnBD6ePWufoh5NQDL5URHWdQ==" def test_valid_hex_vaa_to_price_info(): @@ -66,3 +68,22 @@ def test_encode_vaa_for_chain(): # Test that encoding a base64 VAA as hex returns hex VAA encoded_vaa = encode_vaa_for_chain(BASE64_VAA, "hex") assert encoded_vaa == HEX_VAA + + +def test_valid_accumulator_vaa_to_price_info(): + price_info = vaa_to_price_info(ID, ACCUMULATOR_UPDATE_DATA, "base64") + assert price_info.seq_num == 5921137 + assert price_info.vaa == ACCUMULATOR_VAA + assert price_info.publish_time == 1692280808 + assert price_info.attestation_time == 1692280808 + assert price_info.last_attested_publish_time == 1692280807 + assert price_info.price_feed.ema_price.price == "2845324900000" + assert price_info.price_feed.ema_price.conf == "3211773100" + assert price_info.price_feed.ema_price.expo == -8 + assert price_info.price_feed.ema_price.publish_time == 1692280808 + assert price_info.price_feed.id == ID + assert price_info.price_feed.price.price == "2836040669135" + assert price_info.price_feed.price.conf == "3282830965" + assert price_info.price_feed.price.expo == -8 + assert price_info.price_feed.price.publish_time == 1692280808 + assert price_info.emitter_chain_id == 26 From c60e1d1370fc766960d0436400aa235a9731c678 Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Fri, 18 Aug 2023 12:16:42 +0800 Subject: [PATCH 2/4] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 03d5070..d757f92 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='pythclient', - version='0.1.10', + version='0.1.11', packages=['pythclient'], author='Pyth Developers', author_email='contact@pyth.network', From 504e71050c28688b17f643503fecfb0b80223b1f Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Fri, 18 Aug 2023 16:36:44 +0800 Subject: [PATCH 3/4] address comments --- pythclient/price_feeds.py | 32 +++++++++++++++++--------------- tests/test_price_feeds.py | 3 +-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/pythclient/price_feeds.py b/pythclient/price_feeds.py index f6ba12e..e6ed09a 100644 --- a/pythclient/price_feeds.py +++ b/pythclient/price_feeds.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional from Crypto.Hash import keccak +from loguru import logger P2W_FORMAT_MAGIC = "P2WH" P2W_FORMAT_VER_MAJOR = 3 @@ -357,11 +358,8 @@ def vaa_to_price_infos(vaa, encoding=DEFAULT_VAA_ENCODING) -> List[PriceInfo]: def vaa_to_price_info(price_feed_id, vaa, encoding=DEFAULT_VAA_ENCODING) -> PriceInfo: - encoded_vaa = encode_vaa_for_chain(vaa, encoding, buffer=True) - if encoded_vaa[:4].hex() == ACCUMULATOR_MAGIC: - return extract_price_info_from_accumulator_update( - price_feed_id, encoded_vaa, encoding - ) + if encode_vaa_for_chain(vaa, encoding, buffer=True)[:4].hex() == ACCUMULATOR_MAGIC: + return extract_price_info_from_accumulator_update(price_feed_id, vaa, encoding) price_infos = vaa_to_price_infos(vaa, encoding) for price_info in price_infos: if price_info.price_feed.id == price_feed_id: @@ -416,25 +414,28 @@ def price_attestation_to_price_feed(price_attestation): def extract_price_info_from_accumulator_update( price_feed_id, update_data, encoding ) -> Optional[Dict[str, Any]]: + encoded_update_data = encode_vaa_for_chain(update_data, encoding, buffer=True) offset = 0 offset += 4 # magic offset += 1 # major version offset += 1 # minor version - trailing_header_size = update_data[offset] + trailing_header_size = encoded_update_data[offset] offset += 1 + trailing_header_size - update_type = update_data[offset] + update_type = encoded_update_data[offset] offset += 1 if update_type != 0: - print(f"Invalid accumulator update type: {update_type}") + logger.info(f"Invalid accumulator update type: {update_type}") return None - vaa_length = int.from_bytes(update_data[offset : offset + 2], byteorder="big") + vaa_length = int.from_bytes( + encoded_update_data[offset : offset + 2], byteorder="big" + ) offset += 2 - vaa_buffer = update_data[offset : offset + vaa_length] + vaa_buffer = encoded_update_data[offset : offset + vaa_length] # convert vaa_buffer to string based on encoding if encoding == "hex": vaa_str = vaa_buffer.hex() @@ -443,19 +444,19 @@ def extract_price_info_from_accumulator_update( parsed_vaa = parse_vaa(vaa_str, encoding) offset += vaa_length - num_updates = update_data[offset] + num_updates = encoded_update_data[offset] offset += 1 for _ in range(num_updates): message_length = int.from_bytes( - update_data[offset : offset + 2], byteorder="big" + encoded_update_data[offset : offset + 2], byteorder="big" ) offset += 2 - message = update_data[offset : offset + message_length] + message = encoded_update_data[offset : offset + message_length] offset += message_length - proof_length = update_data[offset] + proof_length = encoded_update_data[offset] offset += 1 offset += proof_length # ignore proofs @@ -463,6 +464,7 @@ def extract_price_info_from_accumulator_update( message_type = message[message_offset] message_offset += 1 + # Message Type 0 is a price update and we ignore the rest if message_type != 0: continue @@ -502,7 +504,7 @@ def extract_price_info_from_accumulator_update( return PriceInfo( seq_num=parsed_vaa["sequence"], - vaa=vaa_str, + vaa=update_data, publish_time=publish_time, attestation_time=publish_time, last_attested_publish_time=prev_publish_time, diff --git a/tests/test_price_feeds.py b/tests/test_price_feeds.py index 2f61aac..4e7c627 100644 --- a/tests/test_price_feeds.py +++ b/tests/test_price_feeds.py @@ -4,7 +4,6 @@ HEX_VAA = "01000000030d00ca037dde1419ea373de7474bff80268e41f55e84dd8e4fa5e5167808111e63e4064efcfd93075d9d162090bc478a017603ef56c4d26e0c46e0af6128c08984470002e9abd7c4e62bbc6b453c5ca014b7313bbadb17d6443190d6679b7c0ccb03bae619d21dcb5d6d5a484f9e4fd5586dca7c20403dc80d61dbe682a436b4d15d10bc0003a5e748513b75e34d9db61c6fd0fa55533e8424d88fbe6cda8e7cf61d50cb9467192a5cbd47b14b205b38123ad4b7c39d6ce7d6bf02c4a1452d135dc29fc83ef600049e4cd56e3c2e0cfc4cc831bc4ec54410590c15b4b20b63cc91be3436a4d09c0c22be4ab96962ad6e94d83e14c770e122aebae6fdbdea97f98cec7da5d30ed2a40106a7565c6387a700e02ee09652b40bdeef044441ca1e3b3c9376d4a129d22ca861501c3f5c0e8469c9a0e5d1b09d9f84c6517c0a2b400c0b47552006fff1dad3a5000a4db87004c483f899b5fd766c14334dfb5ca2fa5698964cf9644669b325bd3485207cbc4180a360023d1412da68bb11a0a82fee70a6bf03dda30b7aae53e0e465010ba3a6e45c9d8ef1d1041fdc7a926a9f41075531d45824144bbc720d111ee7270a77dd6dd65558b30d0f03692e075bd7d96cdfb24f5a68fecc22e441ded230c9cc010c09380e394e2b30fd036f13152b115dab7a206270d52255dfbbf0505c67bf510e70d0a6075f9bae19235eaf8a0893a4af9ed0df1b8cd67e1fe7b2ec61178d3ca4010dc491600d07d10a6468fb5955d94bc114efab46104e2ae530931231fea52cf7e32964a1c8bfe0ee38aaa8abfe8edcb7c079b6dd97b2c317c9d71cb5973bb53c72010f787e3c59ac484fdca7d5e41b29cebee08cb1789d61a0f29ccd0353118fd667ab1473a626eb6c237cff70ffb1eb2a556862197b08f183d5852168f5ce0f92632b0110f7ee4abdedc936ebebe86b3493292a9fa6625ab910b4a1340b46478a819508d1261f3d559d5cc95dead635c215b80b1cb2df348639d1ca572d3d14f07dc38908011103e3cdc9936ffbb7c0af5d77a4c092c5c42de161c9254919d19af718defd71a757fcbb1e3772e72c3a8c8291ab36f628a060030abf8ffb43923bb1a05cf9605d0112ddea2ce8ec77b9e222db5f1a95861c3da2ed3f54f7e937008bcc14b2458b98990eeb5910c7e9b2a27ff47a9568d0a3fedc12f357323905cbc8a1be6acbc5986b0064c37bca00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000002144b1420150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000059cc51c400000000000e4e1bffffffff8000000059b3f3c700000000000eae895010000001a0000001e0000000064c37bca0000000064c37bca0000000064c37bc9000000059cc51c400000000000e4e1bf0000000064c37bc948d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a300000005a7462c060000000000d206a2fffffff800000005a5c499380000000000f44b7d010000001c000000200000000064c37bca0000000064c37bca0000000064c37bc900000005a74653cc0000000000d1dedc0000000064c37bc83515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000002a724c9d1000000000032396fc5fffffff8000002a6e3e0fec0000000002ee4815c010000001b000000200000000064c37bca0000000064c37bca0000000064c37bc9000002a724c9d1000000000032396fc50000000064c37bc99b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae800000000045152eb000000000001bb63fffffff800000000044de0b500000000000185df0100000015000000160000000064c37bca0000000064c37bca0000000064c37bc900000000045152eb000000000001bb630000000064c37bc8e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c000000000074990500000000000011d9fffffff80000000000748c2f000000000000116f010000001b000000200000000064c37bca0000000064c37bca0000000064c37bc900000000007498d400000000000011a80000000064c37bc9" BASE64_VAA = "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" ACCUMULATOR_UPDATE_DATA = "UE5BVQEAAAADuAEAAAADDQAsKPsmb7Vz7io3taJQKgoi1m/z0kqKgtpmlkv+ZuunX2Iegsf+8fuUtpHPLKgCWPU8PN2x9NyAZz5BY9M3SWwJAALYlM0U7f2GFWfEjKwSJlHZ5sf+n6KXCocVC66ImS2o0TD0SBhTWcp0KdcuzR1rY1jfIHaFpVneroRLbTjNrk/WAAMuAYxPVPf1DR30wYQo12Dbf+in3akTjhKERNQ+nPwRjxAyIQD+52LU3Rh2VL7nOIStMNTiBMaiWHywaPoXowWAAQbillhhX4MR+7h81PfxHIbiXBmER4c5M7spilWKkROb+VXhrqnVJL162t9TdhYk56PDIhvXO1Tm/ldjVJw130y0AAk6qpccfsxDZEmVN8LI4z8739Ni/kb+CB3yW2l2dWhKTjBeNanhK6TCCoNH/jRzWfrjrEk5zjNrUr82JwL4fR1OAQrYZescxbH26m8QHiH+RHzwlXpUKJgbHD5NnWtB7oFb9AFM15jbjd4yIEBEtAlXPE0Q4j+X+DLnCtZbLSQiYNh5AQvz70LTbYry1lEExuUcO+IRJiysw5AFyqZ9Y1E//WKIqgEysfcnHwoOxtDtAc5Z9sTUEYfPqQ1d27k3Yk0X7dvCAQ10cdG0qYHb+bQrYRIKKnb0aeCjkCs0HZQY2fXYmimyfTNfECclmPW9k+CfOvW0JKuFxC1l11zJ3zjsgN/peA8BAQ5oIFQGjq9qmf5gegE1DjuzXsGksKao6nsjTXYIspCczCe2h5KNQ9l5hws11hauUKS20JoOYjHwxPD2x0adJKvkAQ+4UjVcZgVEQP8y3caqUDH81Ikcadz2bESpYg93dpnzZTH6A7Ue+RL34PTNx6cCRzukwQuhiStuyL1WYEIrLI4nABAjGv3EBXjWaPLUj59OzVnGkzxkr6C4KDjMmpsYNzx7I2lp2iQV46TM78El8i9h7twiEDUOSdC5CmfQjRpkP72yABGVAQELUm2/SjkpF0O+/rVDgA/Y2/wMacD1ZDahdyvSNSFThn5NyRYA1JXGgIDxoYeAZgkr1gL1cjCLWiO+Bs9QARIiCvHfIkn2aYhYHQq/u6cHB/2DxE3OgbCZyTv8OVO55hQDkJ1gDwAec+IJ4M5Od4OxWEu+OywhJT7zUmwZko9MAGTeJ+kAAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAWllxAUFVV1YAAAAAAAVZ/XAAACcQ8Xfx5wQ+nj1rn6IeTUAy+VER1nUBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKUUTJXzwAAAADDrAZ1////+AAAAABk3ifoAAAAAGTeJ+cAAAKWepR2oAAAAAC/b8SsCasjFzENKvXWwOycuzCVaDWfm0IuuuesmamDKl2lNXss15orlNN+xHVNEEIIq7Xg8GRZGVLt43fkg7xli6EPQ/Nyxl6SixiYteNt1uTTh4M1lQTUjPxKnkE5JEea4RnhOWgmSAWMf8ft4KgE7hvRifV1JP0rOsNgsOYFRbs6iDKW1qLpxgZLMAiOclwS3Tjw2hj8sPfq1NHeVttsBEK5SIM14GjAuD/p2V0+NqHqMHxU/kfftg==" -ACCUMULATOR_VAA = "AQAAAAMNACwo+yZvtXPuKje1olAqCiLWb/PSSoqC2maWS/5m66dfYh6Cx/7x+5S2kc8sqAJY9Tw83bH03IBnPkFj0zdJbAkAAtiUzRTt/YYVZ8SMrBImUdnmx/6fopcKhxULroiZLajRMPRIGFNZynQp1y7NHWtjWN8gdoWlWd6uhEttOM2uT9YAAy4BjE9U9/UNHfTBhCjXYNt/6KfdqROOEoRE1D6c/BGPEDIhAP7nYtTdGHZUvuc4hK0w1OIExqJYfLBo+hejBYABBuKWWGFfgxH7uHzU9/EchuJcGYRHhzkzuymKVYqRE5v5VeGuqdUkvXra31N2FiTno8MiG9c7VOb+V2NUnDXfTLQACTqqlxx+zENkSZU3wsjjPzvf02L+Rv4IHfJbaXZ1aEpOMF41qeErpMIKg0f+NHNZ+uOsSTnOM2tSvzYnAvh9HU4BCthl6xzFsfbqbxAeIf5EfPCVelQomBscPk2da0HugVv0AUzXmNuN3jIgQES0CVc8TRDiP5f4MucK1lstJCJg2HkBC/PvQtNtivLWUQTG5Rw74hEmLKzDkAXKpn1jUT/9YoiqATKx9ycfCg7G0O0Bzln2xNQRh8+pDV3buTdiTRft28IBDXRx0bSpgdv5tCthEgoqdvRp4KOQKzQdlBjZ9diaKbJ9M18QJyWY9b2T4J869bQkq4XELWXXXMnfOOyA3+l4DwEBDmggVAaOr2qZ/mB6ATUOO7NewaSwpqjqeyNNdgiykJzMJ7aHko1D2XmHCzXWFq5QpLbQmg5iMfDE8PbHRp0kq+QBD7hSNVxmBURA/zLdxqpQMfzUiRxp3PZsRKliD3d2mfNlMfoDtR75Evfg9M3HpwJHO6TBC6GJK27IvVZgQissjicAECMa/cQFeNZo8tSPn07NWcaTPGSvoLgoOMyamxg3PHsjaWnaJBXjpMzvwSXyL2Hu3CIQNQ5J0LkKZ9CNGmQ/vbIAEZUBAQtSbb9KOSkXQ77+tUOAD9jb/AxpwPVkNqF3K9I1IVOGfk3JFgDUlcaAgPGhh4BmCSvWAvVyMItaI74Gz1ABEiIK8d8iSfZpiFgdCr+7pwcH/YPETc6BsJnJO/w5U7nmFAOQnWAPAB5z4gngzk53g7FYS747LCElPvNSbBmSj0wAZN4n6QAAAAAAGuEB+u2sWFHjK5sjtflBGowrrEquPtTde4Ed0acupKpxAAAAAABaWXEBQVVXVgAAAAAABVn9cAAAJxDxd/HnBD6ePWufoh5NQDL5URHWdQ==" def test_valid_hex_vaa_to_price_info(): @@ -73,7 +72,7 @@ def test_encode_vaa_for_chain(): def test_valid_accumulator_vaa_to_price_info(): price_info = vaa_to_price_info(ID, ACCUMULATOR_UPDATE_DATA, "base64") assert price_info.seq_num == 5921137 - assert price_info.vaa == ACCUMULATOR_VAA + assert price_info.vaa == ACCUMULATOR_UPDATE_DATA assert price_info.publish_time == 1692280808 assert price_info.attestation_time == 1692280808 assert price_info.last_attested_publish_time == 1692280807 From 76962c6ea2f6cf2d2bff6a0283496a584feb8b2a Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Fri, 18 Aug 2023 16:39:48 +0800 Subject: [PATCH 4/4] update comment --- pythclient/price_feeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythclient/price_feeds.py b/pythclient/price_feeds.py index e6ed09a..10c4d3c 100644 --- a/pythclient/price_feeds.py +++ b/pythclient/price_feeds.py @@ -410,7 +410,7 @@ def price_attestation_to_price_feed(price_attestation): return PriceUpdate(ema_price, price_attestation["price_id"], price) -# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main/price_service/server/src/rest.ts#L137 +# Referenced from https://github.com/pyth-network/pyth-crosschain/blob/1a00598334e52fc5faf967eb1170d7fc23ad828b/price_service/server/src/rest.ts#L137 def extract_price_info_from_accumulator_update( price_feed_id, update_data, encoding ) -> Optional[Dict[str, Any]]: