|
|
@@ -4,99 +4,214 @@ import { MessageBuffer } from "../target/types/message_buffer";
|
|
|
import messageBuffer from "../target/idl/message_buffer.json";
|
|
|
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
|
|
|
import { assert } from "chai";
|
|
|
+import { Connection, PublicKey } from "@solana/web3.js";
|
|
|
+import {
|
|
|
+ getPythClusterApiUrl,
|
|
|
+ getPythProgramKeyForCluster,
|
|
|
+ PythCluster,
|
|
|
+ parseBaseData,
|
|
|
+ AccountType,
|
|
|
+ parseProductData,
|
|
|
+} from "@pythnetwork/client";
|
|
|
|
|
|
+import path from "path";
|
|
|
+import dotenv from "dotenv";
|
|
|
+import fs from "fs";
|
|
|
+
|
|
|
+type PythClusterOrIntegration = PythCluster | "integration";
|
|
|
/**
|
|
|
* Script to initialize the message buffer program and whitelist admin
|
|
|
* using the integration repo setup
|
|
|
+ *
|
|
|
+ * run using the following command:
|
|
|
+ * `NODE_ENV=<env> yarn ts-node scripts/setup_message_buffer.ts`
|
|
|
*/
|
|
|
+const MESSAGE = Buffer.from("message");
|
|
|
|
|
|
-const payer = anchor.web3.Keypair.fromSecretKey(
|
|
|
- // Keypair at keys/funding.json
|
|
|
- Uint8Array.from([
|
|
|
- 235, 245, 49, 124, 125, 91, 162, 107, 245, 83, 158, 7, 86, 181, 31, 252,
|
|
|
- 215, 200, 125, 25, 126, 55, 37, 240, 205, 171, 71, 196, 2, 11, 137, 229,
|
|
|
- 131, 30, 46, 220, 89, 75, 108, 173, 185, 146, 114, 253, 109, 67, 214, 133,
|
|
|
- 117, 79, 154, 107, 133, 193, 249, 251, 40, 171, 42, 191, 192, 60, 188, 78,
|
|
|
- ])
|
|
|
-);
|
|
|
-const endpoint = "http://pythnet:8899";
|
|
|
-console.log(`connecting to ${endpoint}`);
|
|
|
-const commitment = "finalized";
|
|
|
-const provider = new anchor.AnchorProvider(
|
|
|
- new anchor.web3.Connection(endpoint),
|
|
|
- new NodeWallet(payer),
|
|
|
- {
|
|
|
- commitment,
|
|
|
- preflightCommitment: commitment,
|
|
|
- skipPreflight: true,
|
|
|
+function getPythClusterEndpoint(cluster: PythClusterOrIntegration): string {
|
|
|
+ if (cluster === "integration") {
|
|
|
+ return "http://pythnet:8899";
|
|
|
}
|
|
|
-);
|
|
|
+ return getPythClusterApiUrl(cluster);
|
|
|
+}
|
|
|
|
|
|
-anchor.setProvider(provider);
|
|
|
-const messageBufferPid = new anchor.web3.PublicKey(
|
|
|
- "BZZFM8qzdWvv4ysy8dxpAzjs9WJ6iy9y1ph2YNqWYzrm"
|
|
|
-);
|
|
|
+function getPythPidForCluster(
|
|
|
+ cluster: PythClusterOrIntegration
|
|
|
+): anchor.web3.PublicKey {
|
|
|
+ if (cluster === "integration") {
|
|
|
+ return new anchor.web3.PublicKey(
|
|
|
+ "7th6GdMuo4u1zNLzFAyMY6psunHNsGjPjo8hXvcTgKei"
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ return getPythProgramKeyForCluster(cluster);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
-const messageBufferProgram = new Program(
|
|
|
- messageBuffer as Idl,
|
|
|
- messageBufferPid,
|
|
|
- provider
|
|
|
-) as unknown as Program<MessageBuffer>;
|
|
|
+const getKeypairFromFile = (keypairPath: string): anchor.web3.Keypair => {
|
|
|
+ const keypairBuffer = fs.readFileSync(keypairPath);
|
|
|
+ return anchor.web3.Keypair.fromSecretKey(
|
|
|
+ Uint8Array.from(JSON.parse(keypairBuffer.toString()))
|
|
|
+ );
|
|
|
+};
|
|
|
|
|
|
-const whitelistAdmin = payer;
|
|
|
+function getPythOracleCpiAuth(
|
|
|
+ messageBufferProgramId: anchor.web3.PublicKey,
|
|
|
+ pythOracleProgramId: anchor.web3.PublicKey
|
|
|
+): anchor.web3.PublicKey {
|
|
|
+ return anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [Buffer.from("upd_price_write"), messageBufferProgramId.toBuffer()],
|
|
|
+ pythOracleProgramId
|
|
|
+ )[0];
|
|
|
+}
|
|
|
|
|
|
-const MESSAGE = Buffer.from("message");
|
|
|
-const [whitelistPubkey, whitelistBump] =
|
|
|
- anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
- [MESSAGE, Buffer.from("whitelist")],
|
|
|
- messageBufferProgram.programId
|
|
|
+function getMessageBufferPubkey(
|
|
|
+ pythOracleCpiAuth: anchor.web3.PublicKey,
|
|
|
+ pythPriceAccountPk: anchor.web3.PublicKey,
|
|
|
+ messageBufferProgramId: anchor.web3.PublicKey
|
|
|
+): anchor.web3.PublicKey {
|
|
|
+ return anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [pythOracleCpiAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
|
|
|
+ messageBufferProgramId
|
|
|
+ )[0];
|
|
|
+}
|
|
|
+
|
|
|
+export async function getPriceAccountPubkeys(
|
|
|
+ connection: anchor.web3.Connection,
|
|
|
+ pythPublicKey: anchor.web3.PublicKey
|
|
|
+): Promise<PublicKey[]> {
|
|
|
+ const accountList = await connection.getProgramAccounts(
|
|
|
+ pythPublicKey,
|
|
|
+ connection.commitment
|
|
|
+ );
|
|
|
+ console.info(
|
|
|
+ `fetched ${
|
|
|
+ accountList.length
|
|
|
+ } programAccounts for pythProgram: ${pythPublicKey.toString()}`
|
|
|
);
|
|
|
+ const priceAccountIds: PublicKey[] = [];
|
|
|
+ accountList.forEach((singleAccount) => {
|
|
|
+ const base = parseBaseData(singleAccount.account.data);
|
|
|
+ if (base) {
|
|
|
+ switch (base.type) {
|
|
|
+ case AccountType.Product:
|
|
|
+ const productData = parseProductData(singleAccount.account.data);
|
|
|
+ priceAccountIds.push(productData.priceAccountKey);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return priceAccountIds;
|
|
|
+}
|
|
|
+
|
|
|
+async function main() {
|
|
|
+ let canAirdrop = false;
|
|
|
+ switch (process.env.NODE_ENV) {
|
|
|
+ case "local":
|
|
|
+ dotenv.config({ path: path.join(__dirname, ".env.local") });
|
|
|
+ canAirdrop = true;
|
|
|
+ break;
|
|
|
+ case "integration":
|
|
|
+ dotenv.config({ path: path.join(__dirname, ".env.integration") });
|
|
|
+ canAirdrop = true;
|
|
|
+ break;
|
|
|
+ case "pythtest":
|
|
|
+ dotenv.config({ path: path.join(__dirname, ".env.pythtest") });
|
|
|
+ break;
|
|
|
+ case "pythnet":
|
|
|
+ dotenv.config({ path: path.join(__dirname, ".env.pythnet") });
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ console.error(`Invalid NODE_ENV: ${process.env.NODE_ENV}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
|
|
|
-const pythOraclePid = new anchor.web3.PublicKey(
|
|
|
- "7th6GdMuo4u1zNLzFAyMY6psunHNsGjPjo8hXvcTgKei"
|
|
|
-);
|
|
|
+ const cluster = process.env.CLUSTER as PythClusterOrIntegration;
|
|
|
|
|
|
-const [pythOracleCpiAuth] = anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
- [Buffer.from("upd_price_write"), messageBufferProgram.programId.toBuffer()],
|
|
|
- pythOraclePid
|
|
|
-);
|
|
|
+ const messageBufferPid = new anchor.web3.PublicKey(
|
|
|
+ process.env.MESSAGE_BUFFER_PROGRAM_ID
|
|
|
+ );
|
|
|
+ const pythOraclePid = getPythPidForCluster(cluster);
|
|
|
+ const payer = getKeypairFromFile(
|
|
|
+ path.resolve(process.env.PAYER_KEYPAIR_PATH)
|
|
|
+ );
|
|
|
+ const endpoint = getPythClusterEndpoint(cluster);
|
|
|
+ const initialSize = parseInt(process.env.INITIAL_SIZE || "", 10);
|
|
|
+ let whitelistAdmin = payer;
|
|
|
|
|
|
-const pythPriceAccountPk = new anchor.web3.PublicKey(
|
|
|
- "tvNV74CEkyEhmzJYiXGgcTMLCSX8JDPVi3er5ZSTJn2"
|
|
|
-);
|
|
|
+ console.info(`
|
|
|
+ messageBufferPid: ${messageBufferPid.toString()}
|
|
|
+ pythOraclePid: ${pythOraclePid.toString()}
|
|
|
+ payer: ${payer.publicKey.toString()}
|
|
|
+ endpoint: ${endpoint}
|
|
|
+ whitelistAdmin: ${whitelistAdmin.publicKey.toString()}
|
|
|
+ initialSize: ${initialSize}
|
|
|
+ `);
|
|
|
|
|
|
-const [messageBufferPda, messageBufferBump] =
|
|
|
- anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
- [pythOracleCpiAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
|
|
|
- messageBufferProgram.programId
|
|
|
+ console.log(`connecting to ${endpoint}`);
|
|
|
+ const connection = new anchor.web3.Connection(endpoint);
|
|
|
+ const commitment = "finalized";
|
|
|
+
|
|
|
+ const provider = new anchor.AnchorProvider(
|
|
|
+ connection,
|
|
|
+ new NodeWallet(payer),
|
|
|
+ {
|
|
|
+ commitment,
|
|
|
+ preflightCommitment: commitment,
|
|
|
+ skipPreflight: true,
|
|
|
+ }
|
|
|
);
|
|
|
|
|
|
-async function main() {
|
|
|
- console.log("Initializing message buffer...");
|
|
|
+ anchor.setProvider(provider);
|
|
|
|
|
|
- console.group();
|
|
|
- console.log("Requesting airdrop");
|
|
|
+ const messageBufferProgram = new Program(
|
|
|
+ messageBuffer as Idl,
|
|
|
+ messageBufferPid,
|
|
|
+ provider
|
|
|
+ ) as unknown as Program<MessageBuffer>;
|
|
|
+
|
|
|
+ const [whitelistPubkey, whitelistBump] =
|
|
|
+ anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [MESSAGE, Buffer.from("whitelist")],
|
|
|
+ messageBufferProgram.programId
|
|
|
+ );
|
|
|
|
|
|
- let airdropSig = await provider.connection.requestAirdrop(
|
|
|
- payer.publicKey,
|
|
|
- 1 * anchor.web3.LAMPORTS_PER_SOL
|
|
|
+ const pythOracleCpiAuth = getPythOracleCpiAuth(
|
|
|
+ messageBufferProgram.programId,
|
|
|
+ pythOraclePid
|
|
|
);
|
|
|
- await provider.connection.confirmTransaction({
|
|
|
- signature: airdropSig,
|
|
|
- ...(await provider.connection.getLatestBlockhash()),
|
|
|
- });
|
|
|
|
|
|
- const payerBalance = await provider.connection.getBalance(payer.publicKey);
|
|
|
- console.log(`payerBalance: ${payerBalance}`);
|
|
|
- console.log("Airdrop complete");
|
|
|
- console.groupEnd();
|
|
|
+ if (canAirdrop) {
|
|
|
+ console.group("Requesting airdrop");
|
|
|
+
|
|
|
+ let airdropSig = await provider.connection.requestAirdrop(
|
|
|
+ payer.publicKey,
|
|
|
+ 1 * anchor.web3.LAMPORTS_PER_SOL
|
|
|
+ );
|
|
|
+ await provider.connection.confirmTransaction({
|
|
|
+ signature: airdropSig,
|
|
|
+ ...(await provider.connection.getLatestBlockhash()),
|
|
|
+ });
|
|
|
+
|
|
|
+ const payerBalance = await provider.connection.getBalance(payer.publicKey);
|
|
|
+ console.log(`payerBalance: ${payerBalance}`);
|
|
|
+ console.log("Airdrop complete");
|
|
|
+ console.groupEnd();
|
|
|
+ } else {
|
|
|
+ console.log("Skipping airdrop for non-local/integration environments");
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("Initializing message buffer whitelist admin...");
|
|
|
|
|
|
let whitelist = await messageBufferProgram.account.whitelist.fetchNullable(
|
|
|
whitelistPubkey
|
|
|
);
|
|
|
|
|
|
if (whitelist === null) {
|
|
|
- console.group("No whitelist detected. Initializing message buffer");
|
|
|
+ console.group(
|
|
|
+ "No whitelist detected. Initializing message buffer whitelist & admin"
|
|
|
+ );
|
|
|
const initializeTxnSig = await messageBufferProgram.methods
|
|
|
.initialize()
|
|
|
.accounts({
|
|
|
@@ -148,50 +263,134 @@ async function main() {
|
|
|
console.log("Allowed Programs already set");
|
|
|
}
|
|
|
|
|
|
- let messageBufferData = await getMessageBuffer(
|
|
|
- provider.connection,
|
|
|
- messageBufferPda
|
|
|
+ let priceIds = await getPriceAccountPubkeys(connection, pythOraclePid);
|
|
|
+ console.info(`fetched ${priceIds.length} priceAccountIds`);
|
|
|
+ let errorAccounts = [];
|
|
|
+ let alreadyInitializedAccounts = [];
|
|
|
+ let newlyInitializedAccounts = [];
|
|
|
+
|
|
|
+ const messageBufferKeys = priceIds.map((priceId) => {
|
|
|
+ return {
|
|
|
+ messageBufferKey: getMessageBufferPubkey(
|
|
|
+ pythOracleCpiAuth,
|
|
|
+ priceId,
|
|
|
+ messageBufferPid
|
|
|
+ ),
|
|
|
+ priceAccountKey: priceId,
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ let accounts = await messageBufferProgram.account.messageBuffer.fetchMultiple(
|
|
|
+ messageBufferKeys.map((k) => k.messageBufferKey)
|
|
|
);
|
|
|
- if (messageBufferData === null) {
|
|
|
- console.group("Creating Message Buffer");
|
|
|
- const msgBufferPdaMetas = [
|
|
|
- {
|
|
|
- pubkey: messageBufferPda,
|
|
|
- isSigner: false,
|
|
|
- isWritable: true,
|
|
|
- },
|
|
|
- ];
|
|
|
-
|
|
|
- await messageBufferProgram.methods
|
|
|
- .createBuffer(pythOracleCpiAuth, pythPriceAccountPk, 1024 * 8)
|
|
|
- .accounts({
|
|
|
- whitelist: whitelistPubkey,
|
|
|
- admin: whitelistAdmin.publicKey,
|
|
|
- systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
- })
|
|
|
- .signers([whitelistAdmin])
|
|
|
- .remainingAccounts(msgBufferPdaMetas)
|
|
|
- .rpc({ skipPreflight: true });
|
|
|
|
|
|
- console.log("fetching messageBuffer");
|
|
|
- const messageBufferData = await getMessageBuffer(
|
|
|
- provider.connection,
|
|
|
- messageBufferPda
|
|
|
+ const msgBufferKeysAndData = messageBufferKeys.map((k, i) => {
|
|
|
+ return {
|
|
|
+ ...k,
|
|
|
+ messageBufferData: accounts[i],
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ alreadyInitializedAccounts = msgBufferKeysAndData.filter((idAndAccount) => {
|
|
|
+ return idAndAccount.messageBufferData !== null;
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`
|
|
|
+ ${
|
|
|
+ alreadyInitializedAccounts.length
|
|
|
+ } message buffer accounts already initialized.
|
|
|
+ alreadyInitializedAccounts: ${JSON.stringify(alreadyInitializedAccounts)}`);
|
|
|
+
|
|
|
+ const priceAccountPubkeysForNewlyInitializedMessageBuffers =
|
|
|
+ msgBufferKeysAndData.filter((idAndAccount) => {
|
|
|
+ return idAndAccount.messageBufferData === null;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (priceAccountPubkeysForNewlyInitializedMessageBuffers.length === 0) {
|
|
|
+ console.info(`no new message buffers to initialize. exiting...`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+ // TODO: optimize with batching
|
|
|
+ await Promise.all(
|
|
|
+ priceAccountPubkeysForNewlyInitializedMessageBuffers.map(
|
|
|
+ async (idAndAccount) => {
|
|
|
+ const priceId = idAndAccount.priceAccountKey;
|
|
|
+ const messageBufferPda = idAndAccount.messageBufferKey;
|
|
|
+ const msgBufferPdaMetas = [
|
|
|
+ {
|
|
|
+ pubkey: messageBufferPda,
|
|
|
+ isSigner: false,
|
|
|
+ isWritable: true,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ try {
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .createBuffer(pythOracleCpiAuth, priceId, initialSize)
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts(msgBufferPdaMetas)
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+ newlyInitializedAccounts.push({
|
|
|
+ priceId: priceId.toString(),
|
|
|
+ messageBuffer: messageBufferPda.toString(),
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ console.error(
|
|
|
+ "Error creating message buffer for price account: ",
|
|
|
+ priceId.toString()
|
|
|
+ );
|
|
|
+ console.error(e);
|
|
|
+ errorAccounts.push({
|
|
|
+ priceId: priceId.toString(),
|
|
|
+ messageBuffer: messageBufferPda.toString(),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ );
|
|
|
+ if (errorAccounts.length !== 0) {
|
|
|
+ console.error(
|
|
|
+ `Ran into errors when initializing ${errorAccounts.length} accounts`
|
|
|
);
|
|
|
- console.log(`messageBufferData: ${messageBufferData.toString("utf-8")}`);
|
|
|
- console.groupEnd();
|
|
|
- } else {
|
|
|
- console.log("Message Buffer already created");
|
|
|
- console.log(`messageBufferData: ${messageBufferData.toString("utf-8")}`);
|
|
|
+ console.info(`Accounts with errors: ${JSON.stringify(errorAccounts)}`);
|
|
|
}
|
|
|
-}
|
|
|
+ console.log(`Initialized ${newlyInitializedAccounts.length} accounts`);
|
|
|
|
|
|
-async function getMessageBuffer(
|
|
|
- connection: anchor.web3.Connection,
|
|
|
- accountKey: anchor.web3.PublicKey
|
|
|
-): Promise<Buffer | null> {
|
|
|
- let accountInfo = await connection.getAccountInfo(accountKey);
|
|
|
- return accountInfo ? accountInfo.data : null;
|
|
|
+ // Update whitelist admin at the end otherwise all the message buffer PDAs
|
|
|
+ // will have to be initialized by the whitelist admin (which could be the multisig)
|
|
|
+ if (process.env.WHITELIST_ADMIN) {
|
|
|
+ whitelist = await messageBufferProgram.account.whitelist.fetchNullable(
|
|
|
+ whitelistPubkey
|
|
|
+ );
|
|
|
+ let newWhitelistAdmin = new anchor.web3.PublicKey(
|
|
|
+ process.env.WHITELIST_ADMIN
|
|
|
+ );
|
|
|
+ if (!whitelist.admin.equals(newWhitelistAdmin)) {
|
|
|
+ console.info(
|
|
|
+ `updating whitelist admin from ${whitelist.admin.toString()} to ${newWhitelistAdmin.toString()}`
|
|
|
+ );
|
|
|
+ try {
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .updateWhitelistAdmin(newWhitelistAdmin)
|
|
|
+ .accounts({
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .rpc();
|
|
|
+ } catch (e) {
|
|
|
+ console.error(`Error when attempting to update the admin: ${e}`);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.info(
|
|
|
+ `whitelist admin is already ${newWhitelistAdmin.toString()}`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void main();
|