message_buffer.ts 26 KB

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