From 1c3bc7ba0f4010e3cb98ed0600d935f6b1161c89 Mon Sep 17 00:00:00 2001 From: Ming Xu Date: Wed, 5 Mar 2025 20:42:56 +0000 Subject: [PATCH] update mapping account size, add support for update price --- program_admin/__init__.py | 28 +++++++++++++++-- program_admin/cli.py | 40 ++++++++++++++++++++++-- program_admin/instructions.py | 57 ++++++++++++++++++++++++++++++++++- program_admin/keys.py | 1 - program_admin/util.py | 2 +- 5 files changed, 120 insertions(+), 8 deletions(-) diff --git a/program_admin/__init__.py b/program_admin/__init__.py index 1fa8dc7..393ed94 100644 --- a/program_admin/__init__.py +++ b/program_admin/__init__.py @@ -114,7 +114,7 @@ async def fetch_minimum_balance(self, size: int) -> int: async def refresh_program_accounts(self): async with AsyncClient(self.rpc_endpoint) as client: - logger.info("Refreshing program accounts") + logger.info(f"Refreshing program accounts for {self.program_key}") result = ( await client.get_program_accounts( pubkey=self.program_key, @@ -395,7 +395,7 @@ async def sync_mapping_instructions( ) ) - logger.debug("Building pyth_program.init_mapping instruction") + logger.debug(f"Building pyth_program.init_mapping instruction: {funding_keypair.public_key}, {mapping_0_keypair.public_key}") instructions.append( pyth_program.init_mapping( self.program_key, @@ -603,6 +603,30 @@ async def sync_price_instructions( return (instructions, [funding_keypair, price_keypair]) + + def update_price_instructions( + self, + publisher_keypair: Keypair, + price_pubkey: PublicKey, + price: int, + confidence: int, + price_slot: int + ) -> Tuple[List[TransactionInstruction], List[Keypair]]: + instructions = [] + instructions.append( + pyth_program.upd_price( + self.program_key, + publisher_keypair.public_key, + price_pubkey, + 1, + price, + confidence, + price_slot + ) + ) + return (instructions, [publisher_keypair]) + + async def sync_authority_permissions_instructions( self, reference_authority_permissions: ReferenceAuthorityPermissions, diff --git a/program_admin/cli.py b/program_admin/cli.py index 0431de0..8c36f72 100644 --- a/program_admin/cli.py +++ b/program_admin/cli.py @@ -297,15 +297,21 @@ def list_accounts(network, rpc_endpoint, program_key, keys, publishers, commitme for product_key in mapping_account.data.product_account_keys: product_account = program_admin.get_product_account(product_key) + print(f" Product Public Key: {product_account.public_key}") print(f" Product: {product_account.data.metadata['symbol']}") if product_account.data.first_price_account_key != PublicKey(0): price_account = program_admin.get_price_account( product_account.data.first_price_account_key ) + print(f" Price Account: {price_account.public_key}") print( f" Price: {price_account.data.exponent} exponent ({price_account.data.components_count} components)" ) + print(f" numPublishers: {price_account.data.components_count}") + print(f" numPrices: {price_account.data.quoters_count}") + print(f" numComponents: {len(price_account.data.price_components)}") + print(f" Aggregate: {price_account.data.aggregate}") for component in price_account.data.price_components: try: @@ -313,7 +319,7 @@ def list_accounts(network, rpc_endpoint, program_key, keys, publishers, commitme except KeyError: name = f"??? ({component.publisher_key})" - print(f" Publisher: {name}") + print(f" Publisher: {name}: {component.latest_price} {component.aggregate_price}") mapping_key = mapping_account.data.next_mapping_account_key @@ -462,11 +468,9 @@ def sync( ref_permissions = parse_permissions_with_overrides( Path(permissions), Path(overrides), network ) - ref_authority_permissions = parse_authority_permissions_json( Path(authority_permissions) ) - asyncio.run( program_admin.sync( ref_products=ref_products, @@ -479,6 +483,35 @@ def sync( ) ) +@click.command() +@click.option("--network", help="Solana network", envvar="NETWORK") +@click.option("--rpc-endpoint", help="Solana RPC endpoint", envvar="RPC_ENDPOINT") +@click.option("--program-key", help="Pyth program key", envvar="PROGRAM_KEY") +@click.option("--keys", help="Path to keys directory", envvar="KEYS") +@click.option("--publisher", help="key file of the publisher") +@click.option("--price-account", help="Public key of the price account") +@click.option("--price", help="Price to set") +@click.option("--price-slot", help="Price slot to set") +def update_price(network, rpc_endpoint, program_key, keys, publisher, price_account, price, price_slot): + program_admin = ProgramAdmin( + network=network, + rpc_endpoint=rpc_endpoint, + key_dir=keys, + program_key=program_key, + price_store_key=None, + commitment="confirmed", + ) + publisher_keypair = load_keypair(publisher, key_dir=keys) + price_account = PublicKey(price_account) + (instructions, signers, ) = program_admin.update_price_instructions( + publisher_keypair, + price_account, + int(price), + 100, + int(price_slot) + ) + asyncio.run(program_admin.send_transaction(instructions, signers)) + @click.command() @click.option("--network", help="Solana network", envvar="NETWORK") @@ -578,5 +611,6 @@ def resize_price_accounts_v2( cli.add_command(update_product_metadata) cli.add_command(migrate_upgrade_authority) cli.add_command(resize_price_accounts_v2) +cli.add_command(update_price) logger.remove() logger.add(sys.stdout, serialize=(not os.environ.get("DEV_MODE"))) diff --git a/program_admin/instructions.py b/program_admin/instructions.py index 0f1d59c..b860f4f 100644 --- a/program_admin/instructions.py +++ b/program_admin/instructions.py @@ -1,8 +1,9 @@ from typing import Dict -from construct import Bytes, Int32sl, Int32ul, Struct +from construct import Bytes, Int32sl, Int32ul, Struct, Int64sl from solana.publickey import PublicKey from solana.system_program import SYS_PROGRAM_ID +from solana.sysvar import SYSVAR_CLOCK_PUBKEY from solana.transaction import AccountMeta, TransactionInstruction from program_admin.types import ReferenceAuthorityPermissions @@ -16,6 +17,8 @@ COMMAND_ADD_PRICE = 4 COMMAND_ADD_PUBLISHER = 5 COMMAND_DEL_PUBLISHER = 6 +COMMAND_UPD_PRICE = 7 +COMMAND_INIT_PRICE = 9 COMMAND_MIN_PUBLISHERS = 12 COMMAND_RESIZE_PRICE_ACCOUNT = 14 COMMAND_DEL_PRICE = 15 @@ -297,6 +300,58 @@ def toggle_publisher( ) +def init_price( + program_key: PublicKey, + funding_key: PublicKey, + price_account_key: PublicKey, +) -> TransactionInstruction: + layout = Struct( + "version" / Int32ul, "command" / Int32sl, "exponent" / Int32sl, "type" / Int32ul + ) + data = layout.build( + dict(version=PROGRAM_VERSION, command=COMMAND_INIT_PRICE, exponent=8, type=PRICE_TYPE_PRICE) + ) + + permissions_account = get_permissions_account( + program_key, AUTHORITY_PERMISSIONS_PDA_SEED + ) + + return TransactionInstruction( + data=data, + keys=[ + AccountMeta(pubkey=funding_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=price_account_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=permissions_account, is_signer=False, is_writable=True), + ], + program_id=program_key, + ) + +def upd_price( + program_key: PublicKey, + funding_key: PublicKey, + price_account_key: PublicKey, + status: int, + price: int, + confidence: int, + publish_slot: int +) -> TransactionInstruction: + layout = Struct( + "version" / Int32ul, "command" / Int32sl, "status" / Int32sl, "unused" / Int32sl, "price" / Int64sl, "confidence" / Int64sl, "publish_slot" / Int64sl + ) + data = layout.build( + dict(version=PROGRAM_VERSION, command=COMMAND_UPD_PRICE, unused=0, status=status, price=price, confidence=confidence, publish_slot=publish_slot) + ) + + return TransactionInstruction( + data=data, + keys=[ + AccountMeta(pubkey=funding_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=price_account_key, is_signer=False, is_writable=True), + AccountMeta(pubkey=SYSVAR_CLOCK_PUBKEY, is_signer=False, is_writable=False), + ], + program_id=program_key, + ) + def upd_permissions( program_key: PublicKey, upgrade_authority: PublicKey, diff --git a/program_admin/keys.py b/program_admin/keys.py index 018c47f..87d3301 100644 --- a/program_admin/keys.py +++ b/program_admin/keys.py @@ -39,7 +39,6 @@ def load_keypair( return Keypair.from_secret_key(data) else: file_path = Path(key_dir) / f"{label_or_pubkey}.json" - if not file_path.exists(): if generate: return generate_keypair(label_or_pubkey, key_dir) diff --git a/program_admin/util.py b/program_admin/util.py index 3b7b515..bbbde43 100644 --- a/program_admin/util.py +++ b/program_admin/util.py @@ -14,7 +14,7 @@ ReferencePermissions, ) -MAPPING_ACCOUNT_SIZE = 20536 # https://github.com/pyth-network/pyth-client/blob/b49f73afe32ce8685a3d05e32d8f3bb51909b061/program/src/oracle/oracle.h#L88 +MAPPING_ACCOUNT_SIZE = 160056 # https://github.com/pyth-network/pyth-client/blob/main/program/c/src/oracle/oracle.h#L120 MAPPING_ACCOUNT_PRODUCT_LIMIT = 640 PRICE_ACCOUNT_V1_SIZE = 3312 PRICE_ACCOUNT_V2_SIZE = 12576