Refactor (#599)
This commit is contained in:
parent
234cac4261
commit
aba0390495
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -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]);
|
||||
}
|
|
@ -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),
|
||||
});
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue