message_buffer.ts 26 KB

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