This commit is contained in:
guibescos 2023-02-15 12:13:51 +00:00 committed by GitHub
parent 234cac4261
commit aba0390495
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 192 additions and 148 deletions

View File

@ -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");

View File

@ -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 () => {
try {
await run();
} catch (err) {
console.error(err);
throw new Error();
}
})();

View File

@ -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]);
}

View File

@ -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),
});
}

View File

@ -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";

View File

@ -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];
}

View File

@ -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