402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
import * as anchor from "@coral-xyz/anchor";
|
|
import { Program, Idl } from "@coral-xyz/anchor";
|
|
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");
|
|
|
|
function getPythClusterEndpoint(cluster: PythClusterOrIntegration): string {
|
|
if (cluster === "integration") {
|
|
return "http://pythnet:8899";
|
|
}
|
|
return getPythClusterApiUrl(cluster);
|
|
}
|
|
|
|
function getPythPidForCluster(
|
|
cluster: PythClusterOrIntegration
|
|
): anchor.web3.PublicKey {
|
|
if (cluster === "integration") {
|
|
return new anchor.web3.PublicKey(
|
|
"7th6GdMuo4u1zNLzFAyMY6psunHNsGjPjo8hXvcTgKei"
|
|
);
|
|
} else {
|
|
return getPythProgramKeyForCluster(cluster);
|
|
}
|
|
}
|
|
|
|
const getKeypairFromFile = (keypairPath: string): anchor.web3.Keypair => {
|
|
const keypairBuffer = fs.readFileSync(keypairPath);
|
|
return anchor.web3.Keypair.fromSecretKey(
|
|
Uint8Array.from(JSON.parse(keypairBuffer.toString()))
|
|
);
|
|
};
|
|
|
|
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];
|
|
}
|
|
|
|
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 cluster = process.env.CLUSTER as PythClusterOrIntegration;
|
|
|
|
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;
|
|
|
|
console.info(`
|
|
messageBufferPid: ${messageBufferPid.toString()}
|
|
pythOraclePid: ${pythOraclePid.toString()}
|
|
payer: ${payer.publicKey.toString()}
|
|
endpoint: ${endpoint}
|
|
whitelistAdmin: ${whitelistAdmin.publicKey.toString()}
|
|
initialSize: ${initialSize}
|
|
`);
|
|
|
|
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,
|
|
}
|
|
);
|
|
|
|
anchor.setProvider(provider);
|
|
|
|
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
|
|
);
|
|
|
|
const pythOracleCpiAuth = getPythOracleCpiAuth(
|
|
messageBufferProgram.programId,
|
|
pythOraclePid
|
|
);
|
|
|
|
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 whitelist & admin"
|
|
);
|
|
const initializeTxnSig = await messageBufferProgram.methods
|
|
.initialize()
|
|
.accounts({
|
|
admin: whitelistAdmin.publicKey,
|
|
payer: payer.publicKey,
|
|
})
|
|
.signers([whitelistAdmin, payer])
|
|
.rpc();
|
|
|
|
console.log(`initializeTxnSig: ${initializeTxnSig}`);
|
|
|
|
console.log("fetching & checking whitelist");
|
|
whitelist = await messageBufferProgram.account.whitelist.fetch(
|
|
whitelistPubkey
|
|
);
|
|
|
|
assert.strictEqual(whitelist.bump, whitelistBump);
|
|
assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
|
|
console.groupEnd();
|
|
} else {
|
|
console.log("Whitelist already initialized");
|
|
}
|
|
|
|
if (whitelist.allowedPrograms.length === 0) {
|
|
console.group("Setting Allowed Programs");
|
|
const allowedProgramAuthorities = [pythOracleCpiAuth];
|
|
let setAllowedProgramSig = await messageBufferProgram.methods
|
|
.setAllowedPrograms(allowedProgramAuthorities)
|
|
.accounts({
|
|
admin: whitelistAdmin.publicKey,
|
|
})
|
|
.signers([whitelistAdmin])
|
|
.rpc();
|
|
console.log(`setAllowedProgramSig: ${setAllowedProgramSig}`);
|
|
console.log("fetching & checking whitelist after add");
|
|
whitelist = await messageBufferProgram.account.whitelist.fetch(
|
|
whitelistPubkey
|
|
);
|
|
console.info(`whitelist after add: ${JSON.stringify(whitelist)}`);
|
|
const whitelistAllowedPrograms = whitelist.allowedPrograms.map((pk) =>
|
|
pk.toString()
|
|
);
|
|
assert.deepEqual(
|
|
whitelistAllowedPrograms,
|
|
allowedProgramAuthorities.map((p) => p.toString())
|
|
);
|
|
console.groupEnd();
|
|
} else {
|
|
console.log("Allowed Programs already set");
|
|
}
|
|
|
|
let priceIds = await getPriceAccountPubkeys(connection, pythOraclePid);
|
|
console.info(`fetched ${priceIds.length} priceAccountIds`);
|
|
let errorAccounts = [];
|
|
let alreadyInitializedAccounts = [];
|
|
let newlyInitializedAccounts = [];
|
|
|
|
const messageBufferKeys = priceIds.map((priceId) => {
|
|
return {
|
|
messageBuffer: getMessageBufferPubkey(
|
|
pythOracleCpiAuth,
|
|
priceId,
|
|
messageBufferPid
|
|
),
|
|
priceAccount: priceId,
|
|
};
|
|
});
|
|
|
|
let accounts = await messageBufferProgram.account.messageBuffer.fetchMultiple(
|
|
messageBufferKeys.map((k) => k.messageBuffer)
|
|
);
|
|
|
|
const msgBufferKeysAndData = messageBufferKeys.map((k, i) => {
|
|
return {
|
|
...k,
|
|
messageBufferData: accounts[i],
|
|
};
|
|
});
|
|
|
|
alreadyInitializedAccounts = msgBufferKeysAndData
|
|
.filter((idAndAccount) => {
|
|
return idAndAccount.messageBufferData !== null;
|
|
})
|
|
.map((v) => {
|
|
return {
|
|
priceAccount: v.priceAccount.toString(),
|
|
messageBuffer: v.messageBuffer.toString(),
|
|
};
|
|
});
|
|
|
|
console.log(`
|
|
${alreadyInitializedAccounts.length} message buffer accounts already initialized`);
|
|
console.table(alreadyInitializedAccounts);
|
|
|
|
const priceAccountPubkeysForNewlyInitializedMessageBuffers =
|
|
msgBufferKeysAndData.filter((idAndAccount) => {
|
|
return idAndAccount.messageBufferData === null;
|
|
});
|
|
|
|
if (priceAccountPubkeysForNewlyInitializedMessageBuffers.length === 0) {
|
|
console.info(`no new message buffers to initialize`);
|
|
}
|
|
// TODO: optimize with batching
|
|
await Promise.all(
|
|
priceAccountPubkeysForNewlyInitializedMessageBuffers.map(
|
|
async (idAndAccount) => {
|
|
const priceId = idAndAccount.priceAccount;
|
|
const messageBufferPda = idAndAccount.messageBuffer;
|
|
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.info(`Accounts with errors: ${JSON.stringify(errorAccounts)}`);
|
|
}
|
|
console.log(`Initialized ${newlyInitializedAccounts.length} accounts`);
|
|
console.table(newlyInitializedAccounts);
|
|
|
|
// 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();
|