|
|
@@ -0,0 +1,888 @@
|
|
|
+import * as anchor from "@coral-xyz/anchor";
|
|
|
+import {
|
|
|
+ IdlAccounts,
|
|
|
+ IdlTypes,
|
|
|
+ Program,
|
|
|
+ BorshAccountsCoder,
|
|
|
+} from "@coral-xyz/anchor";
|
|
|
+import { MessageBuffer } from "../target/types/message_buffer";
|
|
|
+import { MockCpiCaller } from "../target/types/mock_cpi_caller";
|
|
|
+import lumina from "@lumina-dev/test";
|
|
|
+import { assert } from "chai";
|
|
|
+import { AccountMeta, ComputeBudgetProgram } from "@solana/web3.js";
|
|
|
+import bs58 from "bs58";
|
|
|
+
|
|
|
+// Enables tool that runs in local browser for easier debugging of
|
|
|
+// transactions in this test - https://lumina.fyi/debug
|
|
|
+// lumina();
|
|
|
+
|
|
|
+const messageBufferProgram = anchor.workspace
|
|
|
+ .MessageBuffer as Program<MessageBuffer>;
|
|
|
+const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
|
|
|
+let whitelistAdmin = anchor.web3.Keypair.generate();
|
|
|
+
|
|
|
+const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [messageBufferProgram.programId.toBuffer(), Buffer.from("cpi")],
|
|
|
+ mockCpiProg.programId
|
|
|
+);
|
|
|
+
|
|
|
+const pythPriceAccountId = new anchor.BN(1);
|
|
|
+const addPriceParams = {
|
|
|
+ id: pythPriceAccountId,
|
|
|
+ price: new anchor.BN(2),
|
|
|
+ priceExpo: new anchor.BN(3),
|
|
|
+ ema: new anchor.BN(4),
|
|
|
+ emaExpo: new anchor.BN(5),
|
|
|
+};
|
|
|
+const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [
|
|
|
+ Buffer.from("pyth"),
|
|
|
+ Buffer.from("price"),
|
|
|
+ pythPriceAccountId.toArrayLike(Buffer, "le", 8),
|
|
|
+ ],
|
|
|
+ mockCpiProg.programId
|
|
|
+);
|
|
|
+const MESSAGE = Buffer.from("message");
|
|
|
+const [accumulatorPdaKey, accumulatorPdaBump] =
|
|
|
+ anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
|
|
|
+ messageBufferProgram.programId
|
|
|
+ );
|
|
|
+
|
|
|
+const pythPriceAccountId2 = new anchor.BN(2);
|
|
|
+const [pythPriceAccountPk2] = anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [
|
|
|
+ Buffer.from("pyth"),
|
|
|
+ Buffer.from("price"),
|
|
|
+ pythPriceAccountId2.toArrayLike(Buffer, "le", 8),
|
|
|
+ ],
|
|
|
+ mockCpiProg.programId
|
|
|
+);
|
|
|
+
|
|
|
+const [accumulatorPdaKey2, accumulatorPdaBump2] =
|
|
|
+ anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk2.toBuffer()],
|
|
|
+ messageBufferProgram.programId
|
|
|
+ );
|
|
|
+
|
|
|
+const accumulatorPdaMeta2 = {
|
|
|
+ pubkey: accumulatorPdaKey2,
|
|
|
+ isSigner: false,
|
|
|
+ isWritable: true,
|
|
|
+};
|
|
|
+
|
|
|
+console.log("3");
|
|
|
+
|
|
|
+let fundBalance = 100 * anchor.web3.LAMPORTS_PER_SOL;
|
|
|
+
|
|
|
+const discriminator = BorshAccountsCoder.accountDiscriminator("MessageBuffer");
|
|
|
+const messageBufferDiscriminator = bs58.encode(discriminator);
|
|
|
+
|
|
|
+describe("accumulator_updater", () => {
|
|
|
+ // Configure the client to use the local cluster.
|
|
|
+ let provider = anchor.AnchorProvider.env();
|
|
|
+ anchor.setProvider(provider);
|
|
|
+
|
|
|
+ const [whitelistPubkey, whitelistBump] =
|
|
|
+ anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [MESSAGE, Buffer.from("whitelist")],
|
|
|
+ messageBufferProgram.programId
|
|
|
+ );
|
|
|
+
|
|
|
+ before("transfer lamports to needed accounts", async () => {
|
|
|
+ const airdropTxnSig = await provider.connection.requestAirdrop(
|
|
|
+ whitelistAdmin.publicKey,
|
|
|
+ fundBalance
|
|
|
+ );
|
|
|
+
|
|
|
+ await provider.connection.confirmTransaction({
|
|
|
+ signature: airdropTxnSig,
|
|
|
+ ...(await provider.connection.getLatestBlockhash()),
|
|
|
+ });
|
|
|
+ const whitelistAuthorityBalance = await provider.connection.getBalance(
|
|
|
+ whitelistAdmin.publicKey
|
|
|
+ );
|
|
|
+ assert.isTrue(whitelistAuthorityBalance === fundBalance);
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Is initialized!", async () => {
|
|
|
+ // Add your test here.
|
|
|
+ const tx = await messageBufferProgram.methods
|
|
|
+ .initialize(whitelistAdmin.publicKey)
|
|
|
+ .accounts({})
|
|
|
+ .rpc();
|
|
|
+ console.log("Your transaction signature", tx);
|
|
|
+
|
|
|
+ const whitelist = await messageBufferProgram.account.whitelist.fetch(
|
|
|
+ whitelistPubkey
|
|
|
+ );
|
|
|
+ assert.strictEqual(whitelist.bump, whitelistBump);
|
|
|
+ assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
|
|
|
+ console.info(`whitelist: ${JSON.stringify(whitelist)}`);
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Sets allowed programs to the whitelist", async () => {
|
|
|
+ const allowedProgramAuthorities = [mockCpiCallerAuth];
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .setAllowedPrograms(allowedProgramAuthorities)
|
|
|
+ .accounts({
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .rpc();
|
|
|
+ const 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())
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Creates a buffer", async () => {
|
|
|
+ const accumulatorPdaMetas = [
|
|
|
+ {
|
|
|
+ pubkey: accumulatorPdaKey,
|
|
|
+ isSigner: false,
|
|
|
+ isWritable: true,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .createBuffer(mockCpiCallerAuth, pythPriceAccountPk, 1024 * 8)
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts(accumulatorPdaMetas)
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey
|
|
|
+ );
|
|
|
+ const messageBufferHeader = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountData
|
|
|
+ );
|
|
|
+ assert.equal(messageBufferHeader.version, 1);
|
|
|
+ assert.equal(messageBufferHeader.bump, accumulatorPdaBump);
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Creates a buffer even if the account already has lamports", async () => {
|
|
|
+ const minimumEmptyRent =
|
|
|
+ await provider.connection.getMinimumBalanceForRentExemption(0);
|
|
|
+ await provider.sendAndConfirm(
|
|
|
+ (() => {
|
|
|
+ const tx = new anchor.web3.Transaction();
|
|
|
+ tx.add(
|
|
|
+ anchor.web3.SystemProgram.transfer({
|
|
|
+ fromPubkey: provider.wallet.publicKey,
|
|
|
+ toPubkey: accumulatorPdaKey2,
|
|
|
+ lamports: minimumEmptyRent,
|
|
|
+ })
|
|
|
+ );
|
|
|
+ return tx;
|
|
|
+ })()
|
|
|
+ );
|
|
|
+
|
|
|
+ const accumulatorPdaBalance = await provider.connection.getBalance(
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+ console.log(`accumulatorPdaBalance: ${accumulatorPdaBalance}`);
|
|
|
+ assert.isTrue(accumulatorPdaBalance === minimumEmptyRent);
|
|
|
+
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .createBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 1000)
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts([accumulatorPdaMeta2])
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+
|
|
|
+ const minimumMessageBufferRent =
|
|
|
+ await provider.connection.getMinimumBalanceForRentExemption(
|
|
|
+ messageBufferAccountData.length
|
|
|
+ );
|
|
|
+ const accumulatorPdaBalanceAfter = await provider.connection.getBalance(
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+ assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
|
|
|
+ const messageBufferHeader = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountData
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
|
|
|
+ assert.equal(messageBufferHeader.bump, accumulatorPdaBump2);
|
|
|
+ assert.equal(messageBufferAccountData[8], accumulatorPdaBump2);
|
|
|
+
|
|
|
+ assert.equal(messageBufferHeader.version, 1);
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Updates the whitelist authority", async () => {
|
|
|
+ const newWhitelistAdmin = anchor.web3.Keypair.generate();
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .updateWhitelistAdmin(newWhitelistAdmin.publicKey)
|
|
|
+ .accounts({
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .rpc();
|
|
|
+
|
|
|
+ let whitelist = await messageBufferProgram.account.whitelist.fetch(
|
|
|
+ whitelistPubkey
|
|
|
+ );
|
|
|
+ assert.isTrue(whitelist.admin.equals(newWhitelistAdmin.publicKey));
|
|
|
+
|
|
|
+ // swap back to original authority
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .updateWhitelistAdmin(whitelistAdmin.publicKey)
|
|
|
+ .accounts({
|
|
|
+ admin: newWhitelistAdmin.publicKey,
|
|
|
+ })
|
|
|
+ .signers([newWhitelistAdmin])
|
|
|
+ .rpc();
|
|
|
+
|
|
|
+ whitelist = await messageBufferProgram.account.whitelist.fetch(
|
|
|
+ whitelistPubkey
|
|
|
+ );
|
|
|
+ assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Mock CPI program - AddPrice", async () => {
|
|
|
+ const mockCpiCallerAddPriceTxPubkeys = await mockCpiProg.methods
|
|
|
+ .addPrice(addPriceParams)
|
|
|
+ .accounts({
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ auth: mockCpiCallerAuth,
|
|
|
+ accumulatorWhitelist: whitelistPubkey,
|
|
|
+ messageBufferProgram: messageBufferProgram.programId,
|
|
|
+ })
|
|
|
+ .pubkeys();
|
|
|
+
|
|
|
+ const accumulatorPdaMetas = [
|
|
|
+ {
|
|
|
+ pubkey: accumulatorPdaKey,
|
|
|
+ isSigner: false,
|
|
|
+ isWritable: true,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const mockCpiCallerAddPriceTxPrep = await mockCpiProg.methods
|
|
|
+ .addPrice(addPriceParams)
|
|
|
+ .accounts({
|
|
|
+ ...mockCpiCallerAddPriceTxPubkeys,
|
|
|
+ })
|
|
|
+ .remainingAccounts(accumulatorPdaMetas)
|
|
|
+ .prepare();
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ `ix: ${JSON.stringify(
|
|
|
+ mockCpiCallerAddPriceTxPrep.instruction,
|
|
|
+ (k, v) => {
|
|
|
+ if (k === "data") {
|
|
|
+ return v.toString();
|
|
|
+ } else {
|
|
|
+ return v;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 2
|
|
|
+ )}`
|
|
|
+ );
|
|
|
+ for (const prop in mockCpiCallerAddPriceTxPrep.pubkeys) {
|
|
|
+ console.log(
|
|
|
+ `${prop}: ${mockCpiCallerAddPriceTxPrep.pubkeys[prop].toString()}`
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const addPriceTx = await mockCpiProg.methods
|
|
|
+ .addPrice(addPriceParams)
|
|
|
+ .accounts({
|
|
|
+ ...mockCpiCallerAddPriceTxPubkeys,
|
|
|
+ })
|
|
|
+ .remainingAccounts(accumulatorPdaMetas)
|
|
|
+ .preInstructions([
|
|
|
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
|
|
|
+ ])
|
|
|
+ .rpc({
|
|
|
+ skipPreflight: true,
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`addPriceTx: ${addPriceTx}`);
|
|
|
+ const pythPriceAccount = await provider.connection.getAccountInfo(
|
|
|
+ mockCpiCallerAddPriceTxPubkeys.pythPriceAccount
|
|
|
+ );
|
|
|
+
|
|
|
+ const messageBufferAccount = await provider.connection.getAccountInfo(
|
|
|
+ accumulatorPdaKey
|
|
|
+ );
|
|
|
+
|
|
|
+ const accumulatorPriceMessages = parseMessageBuffer(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccount.data
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ `accumulatorPriceMessages: ${JSON.stringify(
|
|
|
+ accumulatorPriceMessages,
|
|
|
+ null,
|
|
|
+ 2
|
|
|
+ )}`
|
|
|
+ );
|
|
|
+ accumulatorPriceMessages.forEach((pm) => {
|
|
|
+ assert.isTrue(pm.id.eq(addPriceParams.id));
|
|
|
+ assert.isTrue(pm.price.eq(addPriceParams.price));
|
|
|
+ assert.isTrue(pm.priceExpo.eq(addPriceParams.priceExpo));
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Fetches MessageBuffer using getProgramAccounts with discriminator", async () => {
|
|
|
+ const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
|
|
|
+ provider.connection
|
|
|
+ );
|
|
|
+ const msgBufferAcctKeys = messageBufferAccounts.map((ai) =>
|
|
|
+ ai.pubkey.toString()
|
|
|
+ );
|
|
|
+ console.log(
|
|
|
+ `messageBufferAccounts: ${JSON.stringify(msgBufferAcctKeys, null, 2)}`
|
|
|
+ );
|
|
|
+
|
|
|
+ assert.isTrue(messageBufferAccounts.length === 2);
|
|
|
+ msgBufferAcctKeys.includes(accumulatorPdaKey.toString());
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Mock CPI Program - UpdatePrice", async () => {
|
|
|
+ const updatePriceParams = {
|
|
|
+ price: new anchor.BN(5),
|
|
|
+ priceExpo: new anchor.BN(6),
|
|
|
+ ema: new anchor.BN(7),
|
|
|
+ emaExpo: new anchor.BN(8),
|
|
|
+ };
|
|
|
+
|
|
|
+ let accumulatorPdaMeta = getAccumulatorPdaMeta(
|
|
|
+ mockCpiCallerAuth,
|
|
|
+ pythPriceAccountPk
|
|
|
+ );
|
|
|
+ await mockCpiProg.methods
|
|
|
+ .updatePrice(updatePriceParams)
|
|
|
+ .accounts({
|
|
|
+ pythPriceAccount: pythPriceAccountPk,
|
|
|
+ auth: mockCpiCallerAuth,
|
|
|
+ accumulatorWhitelist: whitelistPubkey,
|
|
|
+ messageBufferProgram: messageBufferProgram.programId,
|
|
|
+ })
|
|
|
+ .remainingAccounts([accumulatorPdaMeta])
|
|
|
+ .preInstructions([
|
|
|
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
|
|
|
+ ])
|
|
|
+ .rpc({
|
|
|
+ skipPreflight: true,
|
|
|
+ });
|
|
|
+
|
|
|
+ const pythPriceAccount = await mockCpiProg.account.priceAccount.fetch(
|
|
|
+ pythPriceAccountPk
|
|
|
+ );
|
|
|
+ assert.isTrue(pythPriceAccount.price.eq(updatePriceParams.price));
|
|
|
+ assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
|
|
|
+ assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
|
|
|
+ assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey
|
|
|
+ );
|
|
|
+
|
|
|
+ const updatedAccumulatorPriceMessages = parseMessageBuffer(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountData
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ `updatedAccumulatorPriceMessages: ${JSON.stringify(
|
|
|
+ updatedAccumulatorPriceMessages,
|
|
|
+ null,
|
|
|
+ 2
|
|
|
+ )}`
|
|
|
+ );
|
|
|
+ updatedAccumulatorPriceMessages.forEach((pm) => {
|
|
|
+ assert.isTrue(pm.id.eq(addPriceParams.id));
|
|
|
+ assert.isTrue(pm.price.eq(updatePriceParams.price));
|
|
|
+ assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Mock CPI Program - CPI Max Test", async () => {
|
|
|
+ // with loosen CPI feature activated, max cpi instruction size len is 10KB
|
|
|
+ let testCases = [[1024], [1024, 2048], [1024, 2048, 4096]];
|
|
|
+ // for (let i = 1; i < 8; i++) {
|
|
|
+ for (let i = 0; i < testCases.length; i++) {
|
|
|
+ let testCase = testCases[i];
|
|
|
+ console.info(`testCase: ${testCase}`);
|
|
|
+ const updatePriceParams = {
|
|
|
+ price: new anchor.BN(10 * (i + 5)),
|
|
|
+ priceExpo: new anchor.BN(10 * (i + 6)),
|
|
|
+ ema: new anchor.BN(10 * i + 7),
|
|
|
+ emaExpo: new anchor.BN(10 * i + 8),
|
|
|
+ };
|
|
|
+ console.log(`updatePriceParams: ${JSON.stringify(updatePriceParams)}`);
|
|
|
+
|
|
|
+ let accumulatorPdaMeta = getAccumulatorPdaMeta(
|
|
|
+ mockCpiCallerAuth,
|
|
|
+ pythPriceAccountPk
|
|
|
+ );
|
|
|
+ await mockCpiProg.methods
|
|
|
+ .cpiMaxTest(updatePriceParams, testCase)
|
|
|
+ .accounts({
|
|
|
+ pythPriceAccount: pythPriceAccountPk,
|
|
|
+ auth: mockCpiCallerAuth,
|
|
|
+ accumulatorWhitelist: whitelistPubkey,
|
|
|
+ messageBufferProgram: messageBufferProgram.programId,
|
|
|
+ })
|
|
|
+ .remainingAccounts([accumulatorPdaMeta])
|
|
|
+ .preInstructions([
|
|
|
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
|
|
|
+ ])
|
|
|
+ .rpc({
|
|
|
+ skipPreflight: true,
|
|
|
+ });
|
|
|
+
|
|
|
+ const pythPriceAccount = await mockCpiProg.account.priceAccount.fetch(
|
|
|
+ pythPriceAccountPk
|
|
|
+ );
|
|
|
+ assert.isTrue(pythPriceAccount.price.eq(updatePriceParams.price));
|
|
|
+ assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
|
|
|
+ assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
|
|
|
+ assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey
|
|
|
+ );
|
|
|
+
|
|
|
+ const messageBufferHeader = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountData
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
|
|
|
+ let mockCpiMessageHeaderLen = 7;
|
|
|
+
|
|
|
+ let currentExpectedOffset = 0;
|
|
|
+ for (let j = 0; j < testCase.length; j++) {
|
|
|
+ currentExpectedOffset += testCase[j];
|
|
|
+ currentExpectedOffset += mockCpiMessageHeaderLen;
|
|
|
+ console.log(`
|
|
|
+ header.endOffsets[${j}]: ${messageBufferHeader.endOffsets[j]}
|
|
|
+ currentExpectedOffset: ${currentExpectedOffset}
|
|
|
+ `);
|
|
|
+ assert.isTrue(
|
|
|
+ messageBufferHeader.endOffsets[j] === currentExpectedOffset
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Mock CPI Program - Exceed CPI Max Test ", async () => {
|
|
|
+ // with loosen CPI feature activated, max cpi instruction size len is 10KB
|
|
|
+ let testCases = [[1024, 2048, 4096, 8192]];
|
|
|
+ // for (let i = 1; i < 8; i++) {
|
|
|
+ for (let i = 0; i < testCases.length; i++) {
|
|
|
+ let testCase = testCases[i];
|
|
|
+ console.info(`testCase: ${testCase}`);
|
|
|
+ const updatePriceParams = {
|
|
|
+ price: new anchor.BN(10 * i + 5),
|
|
|
+ priceExpo: new anchor.BN(10 * (i + 6)),
|
|
|
+ ema: new anchor.BN(10 * i + 7),
|
|
|
+ emaExpo: new anchor.BN(10 * i + 8),
|
|
|
+ };
|
|
|
+
|
|
|
+ let accumulatorPdaMeta = getAccumulatorPdaMeta(
|
|
|
+ mockCpiCallerAuth,
|
|
|
+ pythPriceAccountPk
|
|
|
+ );
|
|
|
+ let errorThrown = false;
|
|
|
+ try {
|
|
|
+ await mockCpiProg.methods
|
|
|
+ .cpiMaxTest(updatePriceParams, testCase)
|
|
|
+ .accounts({
|
|
|
+ pythPriceAccount: pythPriceAccountPk,
|
|
|
+ auth: mockCpiCallerAuth,
|
|
|
+ accumulatorWhitelist: whitelistPubkey,
|
|
|
+ messageBufferProgram: messageBufferProgram.programId,
|
|
|
+ })
|
|
|
+ .remainingAccounts([accumulatorPdaMeta])
|
|
|
+ .preInstructions([
|
|
|
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
|
|
|
+ ])
|
|
|
+ .rpc({
|
|
|
+ skipPreflight: true,
|
|
|
+ });
|
|
|
+ } catch (_err) {
|
|
|
+ errorThrown = true;
|
|
|
+ }
|
|
|
+ assert.ok(errorThrown);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Resizes a buffer to a valid larger size", async () => {
|
|
|
+ const messageBufferAccountDataBefore = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+ const messageBufferAccountDataLenBefore =
|
|
|
+ messageBufferAccountDataBefore.length;
|
|
|
+
|
|
|
+ // check that header is stil the same as before
|
|
|
+ const messageBufferHeaderBefore = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountDataBefore
|
|
|
+ );
|
|
|
+
|
|
|
+ const whitelistAuthorityBalanceBefore =
|
|
|
+ await provider.connection.getBalance(whitelistAdmin.publicKey);
|
|
|
+ console.log(
|
|
|
+ `whitelistAuthorityBalance: ${whitelistAuthorityBalanceBefore}`
|
|
|
+ );
|
|
|
+ const targetSize = 10 * 1024;
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .resizeBuffer(
|
|
|
+ mockCpiCallerAuth,
|
|
|
+ pythPriceAccountPk2,
|
|
|
+ accumulatorPdaBump2,
|
|
|
+ targetSize
|
|
|
+ )
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts([accumulatorPdaMeta2])
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+
|
|
|
+ const whitelistAuthorityBalanceAfter = await provider.connection.getBalance(
|
|
|
+ whitelistAdmin.publicKey
|
|
|
+ );
|
|
|
+ assert.isTrue(
|
|
|
+ whitelistAuthorityBalanceAfter < whitelistAuthorityBalanceBefore
|
|
|
+ );
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+ assert.equal(messageBufferAccountData.length, targetSize);
|
|
|
+
|
|
|
+ // check that header is still the same as before
|
|
|
+ const messageBufferHeader = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountData
|
|
|
+ );
|
|
|
+ assert.deepEqual(
|
|
|
+ messageBufferHeader.endOffsets,
|
|
|
+ messageBufferHeaderBefore.endOffsets
|
|
|
+ );
|
|
|
+ assert.deepEqual(
|
|
|
+ messageBufferAccountData.subarray(0, messageBufferAccountDataLenBefore),
|
|
|
+ messageBufferAccountDataBefore
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Resizes a buffer to a smaller size", async () => {
|
|
|
+ const targetSize = 4 * 1024;
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .resizeBuffer(
|
|
|
+ mockCpiCallerAuth,
|
|
|
+ pythPriceAccountPk2,
|
|
|
+ accumulatorPdaBump2,
|
|
|
+ targetSize
|
|
|
+ )
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts([accumulatorPdaMeta2])
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+ assert.equal(messageBufferAccountData.length, targetSize);
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Fails to resize buffers to invalid sizes", async () => {
|
|
|
+ // resize more than 10KB in one txn and less than header.header_len should be fail
|
|
|
+ const testCases = [20 * 1024, 2];
|
|
|
+ for (const testCase of testCases) {
|
|
|
+ let errorThrown = false;
|
|
|
+ try {
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .resizeBuffer(
|
|
|
+ mockCpiCallerAuth,
|
|
|
+ pythPriceAccountPk2,
|
|
|
+ accumulatorPdaBump2,
|
|
|
+ testCase
|
|
|
+ )
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts([accumulatorPdaMeta2])
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+ } catch (_err) {
|
|
|
+ errorThrown = true;
|
|
|
+ }
|
|
|
+ assert.ok(errorThrown);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Deletes a buffer", async () => {
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2, accumulatorPdaBump2)
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts([accumulatorPdaMeta2])
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+
|
|
|
+ if (messageBufferAccountData != null) {
|
|
|
+ assert.fail("messageBufferAccountData should be null");
|
|
|
+ }
|
|
|
+
|
|
|
+ const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
|
|
|
+ provider.connection
|
|
|
+ );
|
|
|
+ assert.equal(messageBufferAccounts.length, 1);
|
|
|
+
|
|
|
+ assert.isFalse(
|
|
|
+ messageBufferAccounts
|
|
|
+ .map((a) => a.pubkey.toString())
|
|
|
+ .includes(accumulatorPdaKey2.toString())
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it("Can recreate a buffer after it's been deleted", async () => {
|
|
|
+ await messageBufferProgram.methods
|
|
|
+ .createBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 1000)
|
|
|
+ .accounts({
|
|
|
+ whitelist: whitelistPubkey,
|
|
|
+ admin: whitelistAdmin.publicKey,
|
|
|
+ systemProgram: anchor.web3.SystemProgram.programId,
|
|
|
+ })
|
|
|
+ .signers([whitelistAdmin])
|
|
|
+ .remainingAccounts([accumulatorPdaMeta2])
|
|
|
+ .rpc({ skipPreflight: true });
|
|
|
+
|
|
|
+ const messageBufferAccountData = await getMessageBuffer(
|
|
|
+ provider.connection,
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+
|
|
|
+ const minimumMessageBufferRent =
|
|
|
+ await provider.connection.getMinimumBalanceForRentExemption(
|
|
|
+ messageBufferAccountData.length
|
|
|
+ );
|
|
|
+ const accumulatorPdaBalanceAfter = await provider.connection.getBalance(
|
|
|
+ accumulatorPdaKey2
|
|
|
+ );
|
|
|
+ assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
|
|
|
+ const messageBufferHeader = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ messageBufferAccountData
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
|
|
|
+ assert.equal(messageBufferHeader.bump, accumulatorPdaBump2);
|
|
|
+ assert.equal(messageBufferAccountData[8], accumulatorPdaBump2);
|
|
|
+
|
|
|
+ assert.equal(messageBufferHeader.version, 1);
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+export const getAccumulatorPdaMeta = (
|
|
|
+ cpiCallerAuth: anchor.web3.PublicKey,
|
|
|
+ pythAccount: anchor.web3.PublicKey
|
|
|
+): AccountMeta => {
|
|
|
+ const accumulatorPdaKey = anchor.web3.PublicKey.findProgramAddressSync(
|
|
|
+ [cpiCallerAuth.toBuffer(), MESSAGE, pythAccount.toBuffer()],
|
|
|
+ messageBufferProgram.programId
|
|
|
+ )[0];
|
|
|
+ return {
|
|
|
+ pubkey: accumulatorPdaKey,
|
|
|
+ isSigner: false,
|
|
|
+ isWritable: true,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+// Parses MessageBuffer.data into a PriceAccount or PriceOnly object based on the
|
|
|
+// accountType and accountSchema.
|
|
|
+function parseMessageBuffer(
|
|
|
+ messageBufferProgram: Program<MessageBuffer>,
|
|
|
+ accountData: Buffer
|
|
|
+): AccumulatorPriceMessage[] {
|
|
|
+ const msgBufferHeader = deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram,
|
|
|
+ accountData
|
|
|
+ );
|
|
|
+
|
|
|
+ const accumulatorMessages = [];
|
|
|
+ // let dataBuffer = Buffer.from(messages);
|
|
|
+
|
|
|
+ let dataBuffer = accountData.subarray(
|
|
|
+ msgBufferHeader.headerLen,
|
|
|
+ accountData.length
|
|
|
+ );
|
|
|
+ let start = 0;
|
|
|
+ for (let i = 0; i < msgBufferHeader.endOffsets.length; i++) {
|
|
|
+ const endOffset = msgBufferHeader.endOffsets[i];
|
|
|
+
|
|
|
+ if (endOffset == 0) {
|
|
|
+ console.log(`endOffset = 0. breaking`);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ const messageBytes = dataBuffer.subarray(start, endOffset);
|
|
|
+ const { header: msgHeader, data: msgData } =
|
|
|
+ parseMessageBytes(messageBytes);
|
|
|
+ console.info(`header: ${JSON.stringify(msgHeader, null, 2)}`);
|
|
|
+ if (msgHeader.schema == 0) {
|
|
|
+ accumulatorMessages.push(parseFullPriceMessage(msgData));
|
|
|
+ } else if (msgHeader.schema == 1) {
|
|
|
+ accumulatorMessages.push(parseCompactPriceMessage(msgData));
|
|
|
+ } else {
|
|
|
+ console.warn("unknown msgHeader.schema: " + i);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ start = endOffset;
|
|
|
+ }
|
|
|
+ return accumulatorMessages;
|
|
|
+}
|
|
|
+
|
|
|
+type MessageHeader = {
|
|
|
+ schema: number;
|
|
|
+ version: number;
|
|
|
+ size: number;
|
|
|
+};
|
|
|
+
|
|
|
+type MessageBufferType = {
|
|
|
+ header: MessageHeader;
|
|
|
+ data: Buffer;
|
|
|
+};
|
|
|
+
|
|
|
+function deserializeMessageBufferHeader(
|
|
|
+ messageBufferProgram: Program<MessageBuffer>,
|
|
|
+ accountData: Buffer
|
|
|
+): IdlAccounts<MessageBuffer>["messageBuffer"] {
|
|
|
+ return messageBufferProgram.coder.accounts.decode(
|
|
|
+ "MessageBuffer",
|
|
|
+ accountData
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function parseMessageBytes(data: Buffer): MessageBufferType {
|
|
|
+ let offset = 0;
|
|
|
+
|
|
|
+ const schema = data.readInt8(offset);
|
|
|
+ offset += 1;
|
|
|
+
|
|
|
+ const version = data.readInt16BE(offset);
|
|
|
+ offset += 2;
|
|
|
+
|
|
|
+ const size = data.readUInt32BE(offset);
|
|
|
+ offset += 4;
|
|
|
+
|
|
|
+ const messageHeader = {
|
|
|
+ schema,
|
|
|
+ version,
|
|
|
+ size,
|
|
|
+ };
|
|
|
+ let messageData = data.subarray(offset, offset + size);
|
|
|
+ return {
|
|
|
+ header: messageHeader,
|
|
|
+ data: messageData,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+type AccumulatorPriceMessage = FullPriceMessage | CompactPriceMessage;
|
|
|
+
|
|
|
+type FullPriceMessage = {
|
|
|
+ id: anchor.BN;
|
|
|
+ price: anchor.BN;
|
|
|
+ priceExpo: anchor.BN;
|
|
|
+ ema: anchor.BN;
|
|
|
+ emaExpo: anchor.BN;
|
|
|
+};
|
|
|
+function parseFullPriceMessage(data: Uint8Array): FullPriceMessage {
|
|
|
+ return {
|
|
|
+ id: new anchor.BN(data.subarray(0, 8), "be"),
|
|
|
+ price: new anchor.BN(data.subarray(8, 16), "be"),
|
|
|
+ priceExpo: new anchor.BN(data.subarray(16, 24), "be"),
|
|
|
+ ema: new anchor.BN(data.subarray(24, 32), "be"),
|
|
|
+ emaExpo: new anchor.BN(data.subarray(32, 40), "be"),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+type CompactPriceMessage = {
|
|
|
+ id: anchor.BN;
|
|
|
+ price: anchor.BN;
|
|
|
+ priceExpo: anchor.BN;
|
|
|
+};
|
|
|
+
|
|
|
+function parseCompactPriceMessage(data: Uint8Array): CompactPriceMessage {
|
|
|
+ return {
|
|
|
+ id: new anchor.BN(data.subarray(0, 8), "be"),
|
|
|
+ price: new anchor.BN(data.subarray(8, 16), "be"),
|
|
|
+ priceExpo: new anchor.BN(data.subarray(16, 24), "be"),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// fetch MessageBuffer accounts using `getProgramAccounts` and memcmp filter
|
|
|
+async function getProgramAccountsForMessageBuffers(
|
|
|
+ connection: anchor.web3.Connection
|
|
|
+) {
|
|
|
+ return await connection.getProgramAccounts(messageBufferProgram.programId, {
|
|
|
+ filters: [
|
|
|
+ {
|
|
|
+ memcmp: {
|
|
|
+ offset: 0,
|
|
|
+ bytes: messageBufferDiscriminator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+}
|