message_buffer.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. import * as anchor from "@coral-xyz/anchor";
  2. import {
  3. IdlAccounts,
  4. IdlTypes,
  5. Program,
  6. BorshAccountsCoder,
  7. } from "@coral-xyz/anchor";
  8. import { MessageBuffer } from "../target/types/message_buffer";
  9. import { MockCpiCaller } from "../target/types/mock_cpi_caller";
  10. import lumina from "@lumina-dev/test";
  11. import { assert } from "chai";
  12. import { AccountMeta, ComputeBudgetProgram } from "@solana/web3.js";
  13. import bs58 from "bs58";
  14. import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
  15. // Enables tool that runs in local browser for easier debugging of
  16. // transactions in this test - https://lumina.fyi/debug
  17. // lumina();
  18. const messageBufferProgram = anchor.workspace
  19. .MessageBuffer as Program<MessageBuffer>;
  20. const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
  21. const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
  22. [Buffer.from("upd_price_write"), messageBufferProgram.programId.toBuffer()],
  23. mockCpiProg.programId,
  24. );
  25. const pythPriceAccountId = new anchor.BN(1);
  26. const addPriceParams = {
  27. id: pythPriceAccountId,
  28. price: new anchor.BN(2),
  29. priceExpo: new anchor.BN(3),
  30. ema: new anchor.BN(4),
  31. emaExpo: new anchor.BN(5),
  32. };
  33. const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
  34. [
  35. Buffer.from("pyth"),
  36. Buffer.from("price"),
  37. pythPriceAccountId.toArrayLike(Buffer, "le", 8),
  38. ],
  39. mockCpiProg.programId,
  40. );
  41. const MESSAGE = Buffer.from("message");
  42. const [messageBufferPda, messageBufferBump] =
  43. anchor.web3.PublicKey.findProgramAddressSync(
  44. [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
  45. messageBufferProgram.programId,
  46. );
  47. const pythPriceAccountId2 = new anchor.BN(2);
  48. const [pythPriceAccountPk2] = anchor.web3.PublicKey.findProgramAddressSync(
  49. [
  50. Buffer.from("pyth"),
  51. Buffer.from("price"),
  52. pythPriceAccountId2.toArrayLike(Buffer, "le", 8),
  53. ],
  54. mockCpiProg.programId,
  55. );
  56. const [messageBufferPda2, messageBufferBump2] =
  57. anchor.web3.PublicKey.findProgramAddressSync(
  58. [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk2.toBuffer()],
  59. messageBufferProgram.programId,
  60. );
  61. const messageBufferPdaMeta2 = {
  62. pubkey: messageBufferPda2,
  63. isSigner: false,
  64. isWritable: true,
  65. };
  66. const discriminator = BorshAccountsCoder.accountDiscriminator("MessageBuffer");
  67. const messageBufferDiscriminator = bs58.encode(discriminator);
  68. let provider = anchor.AnchorProvider.env();
  69. anchor.setProvider(provider);
  70. const payer = provider.wallet as NodeWallet;
  71. let whitelistAdmin = anchor.web3.Keypair.generate();
  72. const [whitelistPubkey, whitelistBump] =
  73. anchor.web3.PublicKey.findProgramAddressSync(
  74. [MESSAGE, Buffer.from("whitelist")],
  75. messageBufferProgram.programId,
  76. );
  77. describe("message_buffer", () => {
  78. it("Is initialized!", async () => {
  79. // Add your test here.
  80. const tx = await messageBufferProgram.methods
  81. .initialize()
  82. .accounts({
  83. admin: whitelistAdmin.publicKey,
  84. payer: payer.publicKey,
  85. })
  86. .signers([whitelistAdmin])
  87. .rpc();
  88. console.log("Your transaction signature", tx);
  89. const whitelist =
  90. await messageBufferProgram.account.whitelist.fetch(whitelistPubkey);
  91. assert.strictEqual(whitelist.bump, whitelistBump);
  92. assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
  93. console.info(`whitelist: ${JSON.stringify(whitelist)}`);
  94. });
  95. it("Sets allowed programs to the whitelist", async () => {
  96. const allowedProgramAuthorities = [mockCpiCallerAuth];
  97. await messageBufferProgram.methods
  98. .setAllowedPrograms(allowedProgramAuthorities)
  99. .accounts({
  100. admin: whitelistAdmin.publicKey,
  101. })
  102. .signers([whitelistAdmin])
  103. .rpc();
  104. const whitelist =
  105. await messageBufferProgram.account.whitelist.fetch(whitelistPubkey);
  106. console.info(`whitelist after add: ${JSON.stringify(whitelist)}`);
  107. const whitelistAllowedPrograms = whitelist.allowedPrograms.map((pk) =>
  108. pk.toString(),
  109. );
  110. assert.deepEqual(
  111. whitelistAllowedPrograms,
  112. allowedProgramAuthorities.map((p) => p.toString()),
  113. );
  114. });
  115. it("Creates a buffer", async () => {
  116. const msgBufferPdaMetas = [
  117. {
  118. pubkey: messageBufferPda,
  119. isSigner: false,
  120. isWritable: true,
  121. },
  122. ];
  123. await messageBufferProgram.methods
  124. .createBuffer(mockCpiCallerAuth, pythPriceAccountPk, 1024 * 8)
  125. .accounts({
  126. whitelist: whitelistPubkey,
  127. admin: whitelistAdmin.publicKey,
  128. payer: payer.publicKey,
  129. systemProgram: anchor.web3.SystemProgram.programId,
  130. })
  131. .signers([whitelistAdmin])
  132. .remainingAccounts(msgBufferPdaMetas)
  133. .rpc({ skipPreflight: true });
  134. const messageBufferAccountData = await getMessageBuffer(
  135. provider.connection,
  136. messageBufferPda,
  137. );
  138. const messageBufferHeader = deserializeMessageBufferHeader(
  139. messageBufferProgram,
  140. messageBufferAccountData,
  141. );
  142. assert.equal(messageBufferHeader.version, 1);
  143. assert.equal(messageBufferHeader.bump, messageBufferBump);
  144. });
  145. it("Creates a buffer even if the account already has lamports", async () => {
  146. const minimumEmptyRent =
  147. await provider.connection.getMinimumBalanceForRentExemption(0);
  148. await provider.sendAndConfirm(
  149. (() => {
  150. const tx = new anchor.web3.Transaction();
  151. tx.add(
  152. anchor.web3.SystemProgram.transfer({
  153. fromPubkey: provider.wallet.publicKey,
  154. toPubkey: messageBufferPda2,
  155. lamports: minimumEmptyRent,
  156. }),
  157. );
  158. return tx;
  159. })(),
  160. );
  161. const accumulatorPdaBalance =
  162. await provider.connection.getBalance(messageBufferPda2);
  163. console.log(`accumulatorPdaBalance: ${accumulatorPdaBalance}`);
  164. assert.isTrue(accumulatorPdaBalance === minimumEmptyRent);
  165. await messageBufferProgram.methods
  166. .createBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 1000)
  167. .accounts({
  168. whitelist: whitelistPubkey,
  169. admin: whitelistAdmin.publicKey,
  170. payer: payer.publicKey,
  171. systemProgram: anchor.web3.SystemProgram.programId,
  172. })
  173. .signers([whitelistAdmin])
  174. .remainingAccounts([messageBufferPdaMeta2])
  175. .rpc({ skipPreflight: true });
  176. const messageBufferAccountData = await getMessageBuffer(
  177. provider.connection,
  178. messageBufferPda2,
  179. );
  180. const minimumMessageBufferRent =
  181. await provider.connection.getMinimumBalanceForRentExemption(
  182. messageBufferAccountData.length,
  183. );
  184. const accumulatorPdaBalanceAfter =
  185. await provider.connection.getBalance(messageBufferPda2);
  186. assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
  187. const messageBufferHeader = deserializeMessageBufferHeader(
  188. messageBufferProgram,
  189. messageBufferAccountData,
  190. );
  191. console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
  192. assert.equal(messageBufferHeader.bump, messageBufferBump2);
  193. assert.equal(messageBufferAccountData[8], messageBufferBump2);
  194. assert.equal(messageBufferHeader.version, 1);
  195. });
  196. it("Updates the whitelist authority", async () => {
  197. const newWhitelistAdmin = anchor.web3.Keypair.generate();
  198. await messageBufferProgram.methods
  199. .updateWhitelistAdmin(newWhitelistAdmin.publicKey)
  200. .accounts({
  201. admin: whitelistAdmin.publicKey,
  202. })
  203. .signers([whitelistAdmin])
  204. .rpc();
  205. let whitelist =
  206. await messageBufferProgram.account.whitelist.fetch(whitelistPubkey);
  207. assert.isTrue(whitelist.admin.equals(newWhitelistAdmin.publicKey));
  208. // swap back to original authority
  209. await messageBufferProgram.methods
  210. .updateWhitelistAdmin(whitelistAdmin.publicKey)
  211. .accounts({
  212. admin: newWhitelistAdmin.publicKey,
  213. })
  214. .signers([newWhitelistAdmin])
  215. .rpc();
  216. whitelist =
  217. await messageBufferProgram.account.whitelist.fetch(whitelistPubkey);
  218. assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
  219. });
  220. it("Mock CPI program - AddPrice", async () => {
  221. const mockCpiCallerAddPriceTxPubkeys = await mockCpiProg.methods
  222. .addPrice(addPriceParams)
  223. .accounts({
  224. systemProgram: anchor.web3.SystemProgram.programId,
  225. auth: mockCpiCallerAuth,
  226. accumulatorWhitelist: whitelistPubkey,
  227. messageBufferProgram: messageBufferProgram.programId,
  228. })
  229. .pubkeys();
  230. const accumulatorPdaMetas = [
  231. {
  232. pubkey: messageBufferPda,
  233. isSigner: false,
  234. isWritable: true,
  235. },
  236. ];
  237. const mockCpiCallerAddPriceTxPrep = await mockCpiProg.methods
  238. .addPrice(addPriceParams)
  239. .accounts({
  240. ...mockCpiCallerAddPriceTxPubkeys,
  241. })
  242. .remainingAccounts(accumulatorPdaMetas)
  243. .prepare();
  244. console.log(
  245. `ix: ${JSON.stringify(
  246. mockCpiCallerAddPriceTxPrep.instruction,
  247. (k, v) => {
  248. if (k === "data") {
  249. return v.toString();
  250. } else {
  251. return v;
  252. }
  253. },
  254. 2,
  255. )}`,
  256. );
  257. for (const prop in mockCpiCallerAddPriceTxPrep.pubkeys) {
  258. console.log(
  259. `${prop}: ${mockCpiCallerAddPriceTxPrep.pubkeys[prop].toString()}`,
  260. );
  261. }
  262. const addPriceTx = await mockCpiProg.methods
  263. .addPrice(addPriceParams)
  264. .accounts({
  265. ...mockCpiCallerAddPriceTxPubkeys,
  266. })
  267. .remainingAccounts(accumulatorPdaMetas)
  268. .preInstructions([
  269. ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
  270. ])
  271. .rpc({
  272. skipPreflight: true,
  273. });
  274. console.log(`addPriceTx: ${addPriceTx}`);
  275. const pythPriceAccount = await provider.connection.getAccountInfo(
  276. mockCpiCallerAddPriceTxPubkeys.pythPriceAccount,
  277. );
  278. const messageBufferAccount =
  279. await provider.connection.getAccountInfo(messageBufferPda);
  280. const accumulatorPriceMessages = parseMessageBuffer(
  281. messageBufferProgram,
  282. messageBufferAccount.data,
  283. );
  284. console.log(
  285. `accumulatorPriceMessages: ${JSON.stringify(
  286. accumulatorPriceMessages,
  287. null,
  288. 2,
  289. )}`,
  290. );
  291. accumulatorPriceMessages.forEach((pm) => {
  292. assert.isTrue(pm.id.eq(addPriceParams.id));
  293. assert.isTrue(pm.price.eq(addPriceParams.price));
  294. assert.isTrue(pm.priceExpo.eq(addPriceParams.priceExpo));
  295. });
  296. });
  297. it("Fetches MessageBuffer using getProgramAccounts with discriminator", async () => {
  298. const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
  299. provider.connection,
  300. );
  301. const msgBufferAcctKeys = messageBufferAccounts.map((ai) =>
  302. ai.pubkey.toString(),
  303. );
  304. console.log(
  305. `messageBufferAccounts: ${JSON.stringify(msgBufferAcctKeys, null, 2)}`,
  306. );
  307. assert.isTrue(messageBufferAccounts.length === 2);
  308. msgBufferAcctKeys.includes(messageBufferPda.toString());
  309. });
  310. it("Mock CPI Program - UpdatePrice", async () => {
  311. const updatePriceParams = {
  312. price: new anchor.BN(5),
  313. priceExpo: new anchor.BN(6),
  314. ema: new anchor.BN(7),
  315. emaExpo: new anchor.BN(8),
  316. };
  317. let accumulatorPdaMeta = getAccumulatorPdaMeta(
  318. mockCpiCallerAuth,
  319. pythPriceAccountPk,
  320. );
  321. await mockCpiProg.methods
  322. .updatePrice(updatePriceParams)
  323. .accounts({
  324. pythPriceAccount: pythPriceAccountPk,
  325. auth: mockCpiCallerAuth,
  326. accumulatorWhitelist: whitelistPubkey,
  327. messageBufferProgram: messageBufferProgram.programId,
  328. })
  329. .remainingAccounts([accumulatorPdaMeta])
  330. .preInstructions([
  331. ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
  332. ])
  333. .rpc({
  334. skipPreflight: true,
  335. });
  336. const pythPriceAccount =
  337. await mockCpiProg.account.priceAccount.fetch(pythPriceAccountPk);
  338. assert.isTrue(pythPriceAccount.price.eq(updatePriceParams.price));
  339. assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
  340. assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
  341. assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
  342. const messageBufferAccountData = await getMessageBuffer(
  343. provider.connection,
  344. messageBufferPda,
  345. );
  346. const updatedAccumulatorPriceMessages = parseMessageBuffer(
  347. messageBufferProgram,
  348. messageBufferAccountData,
  349. );
  350. console.log(
  351. `updatedAccumulatorPriceMessages: ${JSON.stringify(
  352. updatedAccumulatorPriceMessages,
  353. null,
  354. 2,
  355. )}`,
  356. );
  357. updatedAccumulatorPriceMessages.forEach((pm) => {
  358. assert.isTrue(pm.id.eq(addPriceParams.id));
  359. assert.isTrue(pm.price.eq(updatePriceParams.price));
  360. assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
  361. });
  362. });
  363. it("Mock CPI Program - CPI Max Test", async () => {
  364. // with loosen CPI feature activated, max cpi instruction size len is 10KB
  365. let testCases = [[1024], [1024, 2048], [1024, 2048, 4096]];
  366. // for (let i = 1; i < 8; i++) {
  367. for (let i = 0; i < testCases.length; i++) {
  368. let testCase = testCases[i];
  369. console.info(`testCase: ${testCase}`);
  370. const updatePriceParams = {
  371. price: new anchor.BN(10 * (i + 5)),
  372. priceExpo: new anchor.BN(10 * (i + 6)),
  373. ema: new anchor.BN(10 * i + 7),
  374. emaExpo: new anchor.BN(10 * i + 8),
  375. };
  376. console.log(`updatePriceParams: ${JSON.stringify(updatePriceParams)}`);
  377. let accumulatorPdaMeta = getAccumulatorPdaMeta(
  378. mockCpiCallerAuth,
  379. pythPriceAccountPk,
  380. );
  381. await mockCpiProg.methods
  382. .cpiMaxTest(updatePriceParams, testCase)
  383. .accounts({
  384. pythPriceAccount: pythPriceAccountPk,
  385. auth: mockCpiCallerAuth,
  386. accumulatorWhitelist: whitelistPubkey,
  387. messageBufferProgram: messageBufferProgram.programId,
  388. })
  389. .remainingAccounts([accumulatorPdaMeta])
  390. .preInstructions([
  391. ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
  392. ])
  393. .rpc({
  394. skipPreflight: true,
  395. });
  396. const pythPriceAccount =
  397. await mockCpiProg.account.priceAccount.fetch(pythPriceAccountPk);
  398. assert.isTrue(pythPriceAccount.price.eq(updatePriceParams.price));
  399. assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
  400. assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
  401. assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
  402. const messageBufferAccountData = await getMessageBuffer(
  403. provider.connection,
  404. messageBufferPda,
  405. );
  406. const messageBufferHeader = deserializeMessageBufferHeader(
  407. messageBufferProgram,
  408. messageBufferAccountData,
  409. );
  410. console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
  411. let mockCpiMessageHeaderLen = 7;
  412. let currentExpectedOffset = 0;
  413. for (let j = 0; j < testCase.length; j++) {
  414. currentExpectedOffset += testCase[j];
  415. currentExpectedOffset += mockCpiMessageHeaderLen;
  416. console.log(`
  417. header.endOffsets[${j}]: ${messageBufferHeader.endOffsets[j]}
  418. currentExpectedOffset: ${currentExpectedOffset}
  419. `);
  420. assert.isTrue(
  421. messageBufferHeader.endOffsets[j] === currentExpectedOffset,
  422. );
  423. }
  424. }
  425. });
  426. it("Mock CPI Program - Exceed CPI Max Test ", async () => {
  427. // with loosen CPI feature activated, max cpi instruction size len is 10KB
  428. let testCases = [[1024, 2048, 4096, 8192]];
  429. // for (let i = 1; i < 8; i++) {
  430. for (let i = 0; i < testCases.length; i++) {
  431. let testCase = testCases[i];
  432. console.info(`testCase: ${testCase}`);
  433. const updatePriceParams = {
  434. price: new anchor.BN(10 * i + 5),
  435. priceExpo: new anchor.BN(10 * (i + 6)),
  436. ema: new anchor.BN(10 * i + 7),
  437. emaExpo: new anchor.BN(10 * i + 8),
  438. };
  439. let accumulatorPdaMeta = getAccumulatorPdaMeta(
  440. mockCpiCallerAuth,
  441. pythPriceAccountPk,
  442. );
  443. let errorThrown = false;
  444. try {
  445. await mockCpiProg.methods
  446. .cpiMaxTest(updatePriceParams, testCase)
  447. .accounts({
  448. pythPriceAccount: pythPriceAccountPk,
  449. auth: mockCpiCallerAuth,
  450. accumulatorWhitelist: whitelistPubkey,
  451. messageBufferProgram: messageBufferProgram.programId,
  452. })
  453. .remainingAccounts([accumulatorPdaMeta])
  454. .preInstructions([
  455. ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
  456. ])
  457. .rpc({
  458. skipPreflight: true,
  459. });
  460. } catch (_err) {
  461. errorThrown = true;
  462. }
  463. assert.ok(errorThrown);
  464. }
  465. });
  466. it("Resizes a buffer to a valid larger size", async () => {
  467. const messageBufferAccountDataBefore = await getMessageBuffer(
  468. provider.connection,
  469. messageBufferPda2,
  470. );
  471. const messageBufferAccountDataLenBefore =
  472. messageBufferAccountDataBefore.length;
  473. // check that header is still the same as before
  474. const messageBufferHeaderBefore = deserializeMessageBufferHeader(
  475. messageBufferProgram,
  476. messageBufferAccountDataBefore,
  477. );
  478. const payerBalanceBefore = await provider.connection.getBalance(
  479. payer.publicKey,
  480. );
  481. console.log(`payerBalanceBefore: ${payerBalanceBefore}`);
  482. const targetSize = 10 * 1024;
  483. await messageBufferProgram.methods
  484. .resizeBuffer(mockCpiCallerAuth, pythPriceAccountPk2, targetSize)
  485. .accounts({
  486. whitelist: whitelistPubkey,
  487. admin: whitelistAdmin.publicKey,
  488. payer: payer.publicKey,
  489. systemProgram: anchor.web3.SystemProgram.programId,
  490. })
  491. .signers([whitelistAdmin])
  492. .rpc({ skipPreflight: true });
  493. const payerBalanceAftger = await provider.connection.getBalance(
  494. payer.publicKey,
  495. );
  496. assert.isTrue(payerBalanceAftger < payerBalanceBefore);
  497. const messageBufferAccountData = await getMessageBuffer(
  498. provider.connection,
  499. messageBufferPda2,
  500. );
  501. assert.equal(messageBufferAccountData.length, targetSize);
  502. // check that header is still the same as before
  503. const messageBufferHeader = deserializeMessageBufferHeader(
  504. messageBufferProgram,
  505. messageBufferAccountData,
  506. );
  507. assert.deepEqual(
  508. messageBufferHeader.endOffsets,
  509. messageBufferHeaderBefore.endOffsets,
  510. );
  511. assert.deepEqual(
  512. messageBufferAccountData.subarray(0, messageBufferAccountDataLenBefore),
  513. messageBufferAccountDataBefore,
  514. );
  515. });
  516. it("Resizes a buffer to a smaller size", async () => {
  517. const targetSize = 4 * 1024;
  518. await messageBufferProgram.methods
  519. .resizeBuffer(mockCpiCallerAuth, pythPriceAccountPk2, targetSize)
  520. .accounts({
  521. whitelist: whitelistPubkey,
  522. admin: whitelistAdmin.publicKey,
  523. payer: payer.publicKey,
  524. systemProgram: anchor.web3.SystemProgram.programId,
  525. messageBuffer: messageBufferPda2,
  526. })
  527. .signers([whitelistAdmin])
  528. .rpc({ skipPreflight: true });
  529. const messageBufferAccountData = await getMessageBuffer(
  530. provider.connection,
  531. messageBufferPda2,
  532. );
  533. assert.equal(messageBufferAccountData.length, targetSize);
  534. });
  535. it("Fails to resize buffers to invalid sizes", async () => {
  536. // resize more than 10KB in one txn and less than header.header_len should be fail
  537. const testCases = [20 * 1024, 2];
  538. for (const testCase of testCases) {
  539. let errorThrown = false;
  540. try {
  541. await messageBufferProgram.methods
  542. .resizeBuffer(mockCpiCallerAuth, pythPriceAccountPk2, testCase)
  543. .accounts({
  544. whitelist: whitelistPubkey,
  545. admin: whitelistAdmin.publicKey,
  546. payer: payer.publicKey,
  547. systemProgram: anchor.web3.SystemProgram.programId,
  548. messageBuffer: messageBufferPda2,
  549. })
  550. .signers([whitelistAdmin])
  551. .rpc({ skipPreflight: true });
  552. } catch (_err) {
  553. errorThrown = true;
  554. }
  555. assert.ok(errorThrown);
  556. }
  557. });
  558. it("Deletes a buffer", async () => {
  559. await messageBufferProgram.methods
  560. .deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2)
  561. .accounts({
  562. whitelist: whitelistPubkey,
  563. admin: whitelistAdmin.publicKey,
  564. payer: payer.publicKey,
  565. messageBuffer: messageBufferPda2,
  566. })
  567. .signers([whitelistAdmin])
  568. .remainingAccounts([messageBufferPdaMeta2])
  569. .rpc({ skipPreflight: true });
  570. const messageBufferAccountData = await getMessageBuffer(
  571. provider.connection,
  572. messageBufferPda2,
  573. );
  574. if (messageBufferAccountData != null) {
  575. assert.fail("messageBufferAccountData should be null");
  576. }
  577. const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
  578. provider.connection,
  579. );
  580. assert.equal(messageBufferAccounts.length, 1);
  581. assert.isFalse(
  582. messageBufferAccounts
  583. .map((a) => a.pubkey.toString())
  584. .includes(messageBufferPda2.toString()),
  585. );
  586. });
  587. it("Can recreate a buffer after it's been deleted", async () => {
  588. await messageBufferProgram.methods
  589. .createBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 1000)
  590. .accounts({
  591. whitelist: whitelistPubkey,
  592. admin: whitelistAdmin.publicKey,
  593. payer: payer.publicKey,
  594. systemProgram: anchor.web3.SystemProgram.programId,
  595. })
  596. .signers([whitelistAdmin])
  597. .remainingAccounts([messageBufferPdaMeta2])
  598. .rpc({ skipPreflight: true });
  599. const messageBufferAccountData = await getMessageBuffer(
  600. provider.connection,
  601. messageBufferPda2,
  602. );
  603. const minimumMessageBufferRent =
  604. await provider.connection.getMinimumBalanceForRentExemption(
  605. messageBufferAccountData.length,
  606. );
  607. const accumulatorPdaBalanceAfter =
  608. await provider.connection.getBalance(messageBufferPda2);
  609. assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
  610. const messageBufferHeader = deserializeMessageBufferHeader(
  611. messageBufferProgram,
  612. messageBufferAccountData,
  613. );
  614. console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
  615. assert.equal(messageBufferHeader.bump, messageBufferBump2);
  616. assert.equal(messageBufferAccountData[8], messageBufferBump2);
  617. assert.equal(messageBufferHeader.version, 1);
  618. });
  619. });
  620. export const getAccumulatorPdaMeta = (
  621. cpiCallerAuth: anchor.web3.PublicKey,
  622. pythAccount: anchor.web3.PublicKey,
  623. ): AccountMeta => {
  624. const accumulatorPdaKey = anchor.web3.PublicKey.findProgramAddressSync(
  625. [cpiCallerAuth.toBuffer(), MESSAGE, pythAccount.toBuffer()],
  626. messageBufferProgram.programId,
  627. )[0];
  628. return {
  629. pubkey: accumulatorPdaKey,
  630. isSigner: false,
  631. isWritable: true,
  632. };
  633. };
  634. async function getMessageBuffer(
  635. connection: anchor.web3.Connection,
  636. accountKey: anchor.web3.PublicKey,
  637. ): Promise<Buffer | null> {
  638. let accountInfo = await connection.getAccountInfo(accountKey);
  639. return accountInfo ? accountInfo.data : null;
  640. }
  641. // Parses MessageBuffer.data into a PriceAccount or PriceOnly object based on the
  642. // accountType and accountSchema.
  643. function parseMessageBuffer(
  644. messageBufferProgram: Program<MessageBuffer>,
  645. accountData: Buffer,
  646. ): AccumulatorPriceMessage[] {
  647. const msgBufferHeader = deserializeMessageBufferHeader(
  648. messageBufferProgram,
  649. accountData,
  650. );
  651. const accumulatorMessages = [];
  652. // let dataBuffer = Buffer.from(messages);
  653. let dataBuffer = accountData.subarray(
  654. msgBufferHeader.headerLen,
  655. accountData.length,
  656. );
  657. let start = 0;
  658. for (let i = 0; i < msgBufferHeader.endOffsets.length; i++) {
  659. const endOffset = msgBufferHeader.endOffsets[i];
  660. if (endOffset == 0) {
  661. console.log(`endOffset = 0. breaking`);
  662. break;
  663. }
  664. const messageBytes = dataBuffer.subarray(start, endOffset);
  665. const { header: msgHeader, data: msgData } =
  666. parseMessageBytes(messageBytes);
  667. console.info(`header: ${JSON.stringify(msgHeader, null, 2)}`);
  668. if (msgHeader.schema == 0) {
  669. accumulatorMessages.push(parseFullPriceMessage(msgData));
  670. } else if (msgHeader.schema == 1) {
  671. accumulatorMessages.push(parseCompactPriceMessage(msgData));
  672. } else {
  673. console.warn("unknown msgHeader.schema: " + i);
  674. continue;
  675. }
  676. start = endOffset;
  677. }
  678. return accumulatorMessages;
  679. }
  680. type MessageHeader = {
  681. schema: number;
  682. version: number;
  683. size: number;
  684. };
  685. type MessageBufferType = {
  686. header: MessageHeader;
  687. data: Buffer;
  688. };
  689. function deserializeMessageBufferHeader(
  690. messageBufferProgram: Program<MessageBuffer>,
  691. accountData: Buffer,
  692. ): IdlAccounts<MessageBuffer>["messageBuffer"] {
  693. return messageBufferProgram.coder.accounts.decode(
  694. "MessageBuffer",
  695. accountData,
  696. );
  697. }
  698. function parseMessageBytes(data: Buffer): MessageBufferType {
  699. let offset = 0;
  700. const schema = data.readInt8(offset);
  701. offset += 1;
  702. const version = data.readInt16BE(offset);
  703. offset += 2;
  704. const size = data.readUInt32BE(offset);
  705. offset += 4;
  706. const messageHeader = {
  707. schema,
  708. version,
  709. size,
  710. };
  711. let messageData = data.subarray(offset, offset + size);
  712. return {
  713. header: messageHeader,
  714. data: messageData,
  715. };
  716. }
  717. type AccumulatorPriceMessage = FullPriceMessage | CompactPriceMessage;
  718. type FullPriceMessage = {
  719. id: anchor.BN;
  720. price: anchor.BN;
  721. priceExpo: anchor.BN;
  722. ema: anchor.BN;
  723. emaExpo: anchor.BN;
  724. };
  725. function parseFullPriceMessage(data: Uint8Array): FullPriceMessage {
  726. return {
  727. id: new anchor.BN(data.subarray(0, 8), "be"),
  728. price: new anchor.BN(data.subarray(8, 16), "be"),
  729. priceExpo: new anchor.BN(data.subarray(16, 24), "be"),
  730. ema: new anchor.BN(data.subarray(24, 32), "be"),
  731. emaExpo: new anchor.BN(data.subarray(32, 40), "be"),
  732. };
  733. }
  734. type CompactPriceMessage = {
  735. id: anchor.BN;
  736. price: anchor.BN;
  737. priceExpo: anchor.BN;
  738. };
  739. function parseCompactPriceMessage(data: Uint8Array): CompactPriceMessage {
  740. return {
  741. id: new anchor.BN(data.subarray(0, 8), "be"),
  742. price: new anchor.BN(data.subarray(8, 16), "be"),
  743. priceExpo: new anchor.BN(data.subarray(16, 24), "be"),
  744. };
  745. }
  746. // fetch MessageBuffer accounts using `getProgramAccounts` and memcmp filter
  747. async function getProgramAccountsForMessageBuffers(
  748. connection: anchor.web3.Connection,
  749. ) {
  750. return await connection.getProgramAccounts(messageBufferProgram.programId, {
  751. filters: [
  752. {
  753. memcmp: {
  754. offset: 0,
  755. bytes: messageBufferDiscriminator,
  756. },
  757. },
  758. ],
  759. });
  760. }