guibescos 2 anos atrás
pai
commit
aba0390495

+ 22 - 58
governance/xc_admin/packages/crank_executor/src/index.ts

@@ -11,6 +11,8 @@ import SquadsMesh, { DEFAULT_MULTISIG_PROGRAM_ID, getIxPDA } from "@sqds/mesh";
 import * as fs from "fs";
 import NodeWallet from "@project-serum/anchor/dist/cjs/nodewallet";
 import {
+  envOrErr,
+  getCreateAccountWithSeedInstruction,
   getProposals,
   MultisigParser,
   PythMultisigInstruction,
@@ -20,44 +22,29 @@ import BN from "bn.js";
 import { AnchorProvider } from "@project-serum/anchor";
 import {
   getPythClusterApiUrl,
-  getPythProgramKeyForCluster,
   PythCluster,
 } from "@pythnetwork/client/lib/cluster";
 import {
   deriveFeeCollectorKey,
   getWormholeBridgeData,
 } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
-import { parseProductData } from "@pythnetwork/client";
+import { AccountType, parseProductData } from "@pythnetwork/client";
 
-export function envOrErr(env: string): string {
-  const val = process.env[env];
-  if (!val) {
-    throw new Error(`environment variable "${env}" must be set`);
-  }
-  return String(process.env[env]);
-}
-
-const PRODUCT_ACCOUNT_SIZE = 512;
-const PRICE_ACCOUNT_SIZE = 3312;
-
-const CLUSTER: string = envOrErr("CLUSTER");
-const COMMITMENT: Commitment =
-  (process.env.COMMITMENT as Commitment) ?? "confirmed";
+const CLUSTER: PythCluster = envOrErr("CLUSTER") as PythCluster;
 const VAULT: PublicKey = new PublicKey(envOrErr("VAULT"));
 const KEYPAIR: Keypair = Keypair.fromSecretKey(
   Uint8Array.from(JSON.parse(fs.readFileSync(envOrErr("WALLET"), "ascii")))
 );
+const COMMITMENT: Commitment =
+  (process.env.COMMITMENT as Commitment) ?? "confirmed";
 
 async function run() {
   const squad = new SquadsMesh({
-    connection: new Connection(
-      getPythClusterApiUrl(CLUSTER as PythCluster),
-      COMMITMENT
-    ),
+    connection: new Connection(getPythClusterApiUrl(CLUSTER), COMMITMENT),
     wallet: new NodeWallet(KEYPAIR),
     multisigProgramId: DEFAULT_MULTISIG_PROGRAM_ID,
   });
-  const multisigParser = MultisigParser.fromCluster(CLUSTER as PythCluster);
+  const multisigParser = MultisigParser.fromCluster(CLUSTER);
 
   const wormholeFee = multisigParser.wormholeBridgeAddress
     ? (
@@ -111,25 +98,14 @@ async function run() {
           parsedInstruction.name == "addProduct"
         ) {
           /// Add product, fetch the symbol from the instruction
-          const productSeed = "product:" + parsedInstruction.args.symbol;
-          const productAddress = await PublicKey.createWithSeed(
-            squad.wallet.publicKey,
-            productSeed,
-            getPythProgramKeyForCluster(CLUSTER as PythCluster)
-          );
           transaction.add(
-            SystemProgram.createAccountWithSeed({
-              fromPubkey: squad.wallet.publicKey,
-              basePubkey: squad.wallet.publicKey,
-              newAccountPubkey: productAddress,
-              seed: productSeed,
-              space: PRODUCT_ACCOUNT_SIZE,
-              lamports:
-                await squad.connection.getMinimumBalanceForRentExemption(
-                  PRODUCT_ACCOUNT_SIZE
-                ),
-              programId: getPythProgramKeyForCluster(CLUSTER as PythCluster),
-            })
+            await getCreateAccountWithSeedInstruction(
+              squad.connection,
+              CLUSTER,
+              squad.wallet.publicKey,
+              parsedInstruction.args.symbol,
+              AccountType.Product
+            )
           );
         } else if (
           parsedInstruction instanceof PythMultisigInstruction &&
@@ -140,26 +116,14 @@ async function run() {
             parsedInstruction.accounts.named.productAccount.pubkey
           );
           if (productAccount) {
-            const priceSeed =
-              "price:" + parseProductData(productAccount.data).product.symbol;
-            const priceAddress = await PublicKey.createWithSeed(
-              squad.wallet.publicKey,
-              priceSeed,
-              getPythProgramKeyForCluster(CLUSTER as PythCluster)
-            );
             transaction.add(
-              SystemProgram.createAccountWithSeed({
-                fromPubkey: squad.wallet.publicKey,
-                basePubkey: squad.wallet.publicKey,
-                newAccountPubkey: priceAddress,
-                seed: priceSeed,
-                space: PRICE_ACCOUNT_SIZE,
-                lamports:
-                  await squad.connection.getMinimumBalanceForRentExemption(
-                    PRICE_ACCOUNT_SIZE
-                  ),
-                programId: getPythProgramKeyForCluster(CLUSTER as PythCluster),
-              })
+              await getCreateAccountWithSeedInstruction(
+                squad.connection,
+                CLUSTER,
+                squad.wallet.publicKey,
+                parseProductData(productAccount.data).product.symbol,
+                AccountType.Price
+              )
             );
           } else {
             throw Error("Product account not found");

+ 36 - 77
governance/xc_admin/packages/crank_pythnet_relayer/src/index.ts

@@ -1,15 +1,11 @@
-import { ParsedVaa, parseVaa, postVaaSolana } from "@certusone/wormhole-sdk";
+import { parseVaa, postVaaSolana } from "@certusone/wormhole-sdk";
 import { signTransactionFactory } from "@certusone/wormhole-sdk/lib/cjs/solana";
-import {
-  derivePostedVaaKey,
-  getPostedVaa,
-} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
+import { derivePostedVaaKey } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
 import { AnchorProvider, BN, Program } from "@coral-xyz/anchor";
 import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
-import { parseProductData } from "@pythnetwork/client";
+import { AccountType, parseProductData } from "@pythnetwork/client";
 import {
   getPythClusterApiUrl,
-  getPythProgramKeyForCluster,
   PythCluster,
 } from "@pythnetwork/client/lib/cluster";
 import {
@@ -18,43 +14,31 @@ import {
   Connection,
   Keypair,
   PublicKey,
-  SystemProgram,
   TransactionInstruction,
 } from "@solana/web3.js";
 import * as fs from "fs";
 import {
   decodeGovernancePayload,
   ExecutePostedVaa,
+  getCreateAccountWithSeedInstruction,
   MultisigParser,
   PythMultisigInstruction,
   WORMHOLE_ADDRESS,
   WORMHOLE_API_ENDPOINT,
+  CLAIM_RECORD_SEED,
+  mapKey,
+  REMOTE_EXECUTOR_ADDRESS,
+  envOrErr,
 } from "xc_admin_common";
 
-export function envOrErr(env: string): string {
-  const val = process.env[env];
-  if (!val) {
-    throw new Error(`environment variable "${env}" must be set`);
-  }
-  return String(process.env[env]);
-}
-
-const REMOTE_EXECUTOR_ADDRESS = new PublicKey(
-  "exe6S3AxPVNmy46L4Nj6HrnnAVQUhwyYzMSNcnRn3qq"
-);
-
-const PRODUCT_ACCOUNT_SIZE = 512;
-const PRICE_ACCOUNT_SIZE = 3312;
-const CLAIM_RECORD_SEED = "CLAIM_RECORD";
-const EXECUTOR_KEY_SEED = "EXECUTOR_KEY";
 const CLUSTER: PythCluster = envOrErr("CLUSTER") as PythCluster;
-const COMMITMENT: Commitment =
-  (process.env.COMMITMENT as Commitment) ?? "confirmed";
-const OFFSET: number = Number(process.env.OFFSET ?? "-1");
 const EMITTER: PublicKey = new PublicKey(envOrErr("EMITTER"));
 const KEYPAIR: Keypair = Keypair.fromSecretKey(
   Uint8Array.from(JSON.parse(fs.readFileSync(envOrErr("WALLET"), "ascii")))
 );
+const OFFSET: number = Number(process.env.OFFSET ?? "-1");
+const COMMITMENT: Commitment =
+  (process.env.COMMITMENT as Commitment) ?? "confirmed";
 
 async function run() {
   const provider = new AnchorProvider(
@@ -65,6 +49,7 @@ async function run() {
       preflightCommitment: COMMITMENT,
     }
   );
+  const multisigParser = MultisigParser.fromCluster(CLUSTER);
 
   const remoteExecutor = await Program.at(REMOTE_EXECUTOR_ADDRESS, provider);
 
@@ -72,10 +57,7 @@ async function run() {
     [Buffer.from(CLAIM_RECORD_SEED), EMITTER.toBuffer()],
     remoteExecutor.programId
   )[0];
-  const executorKey: PublicKey = PublicKey.findProgramAddressSync(
-    [Buffer.from(EXECUTOR_KEY_SEED), EMITTER.toBuffer()],
-    remoteExecutor.programId
-  )[0];
+  const executorKey: PublicKey = mapKey(EMITTER);
   const claimRecord = await remoteExecutor.account.claimRecord.fetchNullable(
     claimRecordAddress
   );
@@ -105,7 +87,6 @@ async function run() {
         governancePayload instanceof ExecutePostedVaa &&
         governancePayload.targetChainId == "pythnet"
       ) {
-        const multisigParser = MultisigParser.fromCluster(CLUSTER);
         const preInstructions: TransactionInstruction[] = [];
 
         console.log(`Found VAA ${lastSequenceNumber}, relaying ...`);
@@ -139,25 +120,14 @@ async function run() {
             parsedInstruction instanceof PythMultisigInstruction &&
             parsedInstruction.name == "addProduct"
           ) {
-            const productSeed = "product:" + parsedInstruction.args.symbol;
-            const productAddress = await PublicKey.createWithSeed(
-              provider.wallet.publicKey,
-              productSeed,
-              getPythProgramKeyForCluster(CLUSTER as PythCluster)
-            );
             preInstructions.push(
-              SystemProgram.createAccountWithSeed({
-                fromPubkey: provider.wallet.publicKey,
-                basePubkey: provider.wallet.publicKey,
-                newAccountPubkey: productAddress,
-                seed: productSeed,
-                space: PRODUCT_ACCOUNT_SIZE,
-                lamports:
-                  await provider.connection.getMinimumBalanceForRentExemption(
-                    PRODUCT_ACCOUNT_SIZE
-                  ),
-                programId: getPythProgramKeyForCluster(CLUSTER as PythCluster),
-              })
+              await getCreateAccountWithSeedInstruction(
+                provider.connection,
+                CLUSTER,
+                provider.wallet.publicKey,
+                parsedInstruction.args.symbol,
+                AccountType.Product
+              )
             );
           } else if (
             parsedInstruction instanceof PythMultisigInstruction &&
@@ -167,28 +137,14 @@ async function run() {
               parsedInstruction.accounts.named.productAccount.pubkey
             );
             if (productAccount) {
-              const priceSeed =
-                "price:" + parseProductData(productAccount.data).product.symbol;
-              const priceAddress = await PublicKey.createWithSeed(
-                provider.wallet.publicKey,
-                priceSeed,
-                getPythProgramKeyForCluster(CLUSTER as PythCluster)
-              );
               preInstructions.push(
-                SystemProgram.createAccountWithSeed({
-                  fromPubkey: provider.wallet.publicKey,
-                  basePubkey: provider.wallet.publicKey,
-                  newAccountPubkey: priceAddress,
-                  seed: priceSeed,
-                  space: PRICE_ACCOUNT_SIZE,
-                  lamports:
-                    await provider.connection.getMinimumBalanceForRentExemption(
-                      PRICE_ACCOUNT_SIZE
-                    ),
-                  programId: getPythProgramKeyForCluster(
-                    CLUSTER as PythCluster
-                  ),
-                })
+                await getCreateAccountWithSeedInstruction(
+                  provider.connection,
+                  CLUSTER,
+                  provider.wallet.publicKey,
+                  parseProductData(productAccount.data).product.symbol,
+                  AccountType.Price
+                )
               );
             } else {
               throw Error("Product account not found");
@@ -204,16 +160,14 @@ async function run() {
           })
           .remainingAccounts(extraAccountMetas)
           .preInstructions(preInstructions)
-          .rpc();
+          .rpc({ skipPreflight: true });
       }
     } else if (response.code == 5) {
-      console.log(`Wormhole API failure`);
-      console.log(
-        `${wormholeApi}/v1/signed_vaa/1/${EMITTER.toBuffer().toString(
+      throw new Error(
+        `Wormhole API failure :${wormholeApi}/v1/signed_vaa/1/${EMITTER.toBuffer().toString(
           "hex"
         )}/${lastSequenceNumber}`
       );
-      break;
     } else {
       throw new Error("Could not connect to wormhole api");
     }
@@ -221,5 +175,10 @@ async function run() {
 }
 
 (async () => {
-  await run();
+  try {
+    await run();
+  } catch (err) {
+    console.error(err);
+    throw new Error();
+  }
 })();

+ 7 - 0
governance/xc_admin/packages/xc_admin_common/src/cranks.ts

@@ -0,0 +1,7 @@
+export function envOrErr(env: string): string {
+  const val = process.env[env];
+  if (!val) {
+    throw new Error(`environment variable "${env}" must be set`);
+  }
+  return String(process.env[env]);
+}

+ 97 - 0
governance/xc_admin/packages/xc_admin_common/src/deterministic_oracle_accounts.ts

@@ -0,0 +1,97 @@
+import {
+  AccountType,
+  getPythProgramKeyForCluster,
+  PythCluster,
+} from "@pythnetwork/client";
+import {
+  Connection,
+  PublicKey,
+  SystemProgram,
+  TransactionInstruction,
+} from "@solana/web3.js";
+import { OPS_KEY } from "./multisig";
+
+/**
+ * Get seed for deterministic creation of a price/product account
+ * @param type Type of the account
+ * @param symbol Symbol of the price feed
+ * @returns
+ */
+function getSeed(accountType: AccountType, symbol: string): string {
+  switch (accountType) {
+    case AccountType.Price:
+      return "price:" + symbol;
+    case AccountType.Product:
+      return "product:" + symbol;
+    default:
+      throw new Error("Unimplemented");
+  }
+}
+
+/**
+ * Get required account size for a given oracle account type
+ * @param accountType Type of the account
+ * @returns
+ */
+function getAccountTypeSize(accountType: AccountType): number {
+  switch (accountType) {
+    case AccountType.Price:
+      return 3312;
+    case AccountType.Product:
+      return 512;
+    default:
+      throw new Error("Unimplemented");
+  }
+}
+
+/**
+ * Get the address and the seed of a deterministic price/product account
+ * @param type Type of the account
+ * @param symbol Symbol of the price feed
+ * @param cluster Cluster in which to create the deterministic account
+ * @returns
+ */
+export async function findDetermisticAccountAddress(
+  type: AccountType,
+  symbol: string,
+  cluster: PythCluster
+): Promise<[PublicKey, string]> {
+  const seed: string = getSeed(type, symbol);
+  const address: PublicKey = await PublicKey.createWithSeed(
+    OPS_KEY,
+    seed,
+    getPythProgramKeyForCluster(cluster)
+  );
+  return [address, seed];
+}
+
+/**
+ * Get instruction to create a determistic price/product account
+ * @param connection Connection used to compute rent, should be connected to `cluster`
+ * @param cluster Cluster in which to create the determistic account
+ * @param base Base for the determistic derivation
+ * @param symbol Symbol of the price feed
+ * @param accountType Type of the account
+ * @returns
+ */
+export async function getCreateAccountWithSeedInstruction(
+  connection: Connection,
+  cluster: PythCluster,
+  base: PublicKey,
+  symbol: string,
+  accountType: AccountType
+): Promise<TransactionInstruction> {
+  const [address, seed]: [PublicKey, string] =
+    await findDetermisticAccountAddress(accountType, symbol, cluster);
+  return SystemProgram.createAccountWithSeed({
+    fromPubkey: base,
+    basePubkey: base,
+    newAccountPubkey: address,
+    seed: seed,
+    space: getAccountTypeSize(accountType),
+    lamports: await connection.getMinimumBalanceForRentExemption(
+      getAccountTypeSize(accountType)
+    ),
+    programId: getPythProgramKeyForCluster(cluster),
+  });
+}

+ 2 - 0
governance/xc_admin/packages/xc_admin_common/src/index.ts

@@ -6,3 +6,5 @@ export * from "./multisig_transaction";
 export * from "./cluster";
 export * from "./remote_executor";
 export * from "./bpf_upgradable_loader";
+export * from "./deterministic_oracle_accounts";
+export * from "./cranks";

+ 12 - 2
governance/xc_admin/packages/xc_admin_common/src/remote_executor.ts

@@ -1,9 +1,19 @@
 import { PublicKey } from "@solana/web3.js";
 
+/**
+ * Seed for the claim PDA of the remote executor
+ */
+export const CLAIM_RECORD_SEED: string = "CLAIM_RECORD";
+
+/**
+ * Seed for the executor PDA of the remote executor
+ */
+const EXECUTOR_KEY_SEED: string = "EXECUTOR_KEY";
+
 /**
  * Address of the remote executor (same on all networks)
  */
-export const REMOTE_EXECUTOR_ADDRESS = new PublicKey(
+export const REMOTE_EXECUTOR_ADDRESS: PublicKey = new PublicKey(
   "exe6S3AxPVNmy46L4Nj6HrnnAVQUhwyYzMSNcnRn3qq"
 );
 
@@ -14,7 +24,7 @@ export const REMOTE_EXECUTOR_ADDRESS = new PublicKey(
  */
 export function mapKey(key: PublicKey): PublicKey {
   return PublicKey.findProgramAddressSync(
-    [Buffer.from("EXECUTOR_KEY"), key.toBytes()],
+    [Buffer.from(EXECUTOR_KEY_SEED), key.toBytes()],
     REMOTE_EXECUTOR_ADDRESS
   )[0];
 }

+ 16 - 11
governance/xc_admin/packages/xc_admin_frontend/components/tabs/General.tsx

@@ -1,5 +1,5 @@
 import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor'
-import { getPythProgramKeyForCluster } from '@pythnetwork/client'
+import { AccountType, getPythProgramKeyForCluster } from '@pythnetwork/client'
 import { PythOracle, pythOracleProgram } from '@pythnetwork/client/lib/anchor'
 import { useAnchorWallet, useWallet } from '@solana/wallet-adapter-react'
 import { WalletModalButton } from '@solana/wallet-adapter-react-ui'
@@ -7,6 +7,7 @@ import { Cluster, PublicKey, TransactionInstruction } from '@solana/web3.js'
 import { useCallback, useContext, useEffect, useState } from 'react'
 import toast from 'react-hot-toast'
 import {
+  findDetermisticAccountAddress,
   getMultisigCluster,
   isRemoteCluster,
   mapKey,
@@ -258,11 +259,13 @@ const General = () => {
         // if prev is undefined, it means that the symbol is new
         if (!prev) {
           // deterministically generate product account key
-          const productAccountKey = await PublicKey.createWithSeed(
-            OPS_KEY,
-            'product:' + symbol,
-            pythProgramClient.programId
-          )
+          const productAccountKey: PublicKey = (
+            await findDetermisticAccountAddress(
+              AccountType.Product,
+              symbol,
+              cluster
+            )
+          )[0]
           // create add product account instruction
           instructions.push(
             await pythProgramClient.methods
@@ -276,11 +279,13 @@ const General = () => {
           )
 
           // deterministically generate price account key
-          const priceAccountKey = await PublicKey.createWithSeed(
-            OPS_KEY,
-            'price:' + symbol,
-            pythProgramClient.programId
-          )
+          const priceAccountKey: PublicKey = (
+            await findDetermisticAccountAddress(
+              AccountType.Price,
+              symbol,
+              cluster
+            )
+          )[0]
           // create add price account instruction
           instructions.push(
             await pythProgramClient.methods