accumulator_updater.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import * as anchor from "@coral-xyz/anchor";
  2. import { IdlTypes, Program, IdlAccounts } from "@coral-xyz/anchor";
  3. import { AccumulatorUpdater } from "../target/types/accumulator_updater";
  4. import { MockCpiCaller } from "../target/types/mock_cpi_caller";
  5. import lumina from "@lumina-dev/test";
  6. import { assert } from "chai";
  7. import { ComputeBudgetProgram } from "@solana/web3.js";
  8. // Enables tool that runs in localbrowser for easier debugging of txns
  9. // in this test - https://lumina.fyi/debug
  10. lumina();
  11. const accumulatorUpdaterProgram = anchor.workspace
  12. .AccumulatorUpdater as Program<AccumulatorUpdater>;
  13. const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
  14. describe("accumulator_updater", () => {
  15. // Configure the client to use the local cluster.
  16. let provider = anchor.AnchorProvider.env();
  17. anchor.setProvider(provider);
  18. const [whitelistPda, whitelistBump] =
  19. anchor.web3.PublicKey.findProgramAddressSync(
  20. [Buffer.from("accumulator"), Buffer.from("whitelist")],
  21. accumulatorUpdaterProgram.programId
  22. );
  23. it("Is initialized!", async () => {
  24. // Add your test here.
  25. const tx = await accumulatorUpdaterProgram.methods
  26. .initialize()
  27. .accounts({})
  28. .rpc();
  29. console.log("Your transaction signature", tx);
  30. const whitelist = await accumulatorUpdaterProgram.account.whitelist.fetch(
  31. whitelistPda
  32. );
  33. assert.strictEqual(whitelist.bump, whitelistBump);
  34. console.info(`whitelist: ${JSON.stringify(whitelist)}`);
  35. });
  36. it("Adds a program to the whitelist", async () => {
  37. const addToWhitelistTx = await accumulatorUpdaterProgram.methods
  38. .addAllowedProgram(mockCpiProg.programId)
  39. .accounts({})
  40. .rpc();
  41. const whitelist = await accumulatorUpdaterProgram.account.whitelist.fetch(
  42. whitelistPda
  43. );
  44. console.info(`whitelist after add: ${JSON.stringify(whitelist)}`);
  45. assert.isTrue(
  46. whitelist.allowedPrograms
  47. .map((pk) => pk.toString())
  48. .includes(mockCpiProg.programId.toString())
  49. );
  50. });
  51. it("Mock CPI program - AddPrice", async () => {
  52. const addPriceParams = {
  53. id: new anchor.BN(1),
  54. price: new anchor.BN(2),
  55. priceExpo: new anchor.BN(3),
  56. ema: new anchor.BN(4),
  57. emaExpo: new anchor.BN(5),
  58. };
  59. const mockCpiCallerAddPriceTxPubkeys = await mockCpiProg.methods
  60. .addPrice(addPriceParams)
  61. .accounts({
  62. systemProgram: anchor.web3.SystemProgram.programId,
  63. ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
  64. accumulatorWhitelist: whitelistPda,
  65. accumulatorProgram: accumulatorUpdaterProgram.programId,
  66. })
  67. .pubkeys();
  68. const accumulatorPdas = [0, 1].map((pythSchema) => {
  69. const [pda] = anchor.web3.PublicKey.findProgramAddressSync(
  70. [
  71. mockCpiProg.programId.toBuffer(),
  72. Buffer.from("accumulator"),
  73. mockCpiCallerAddPriceTxPubkeys.pythPriceAccount.toBuffer(),
  74. new anchor.BN(pythSchema).toArrayLike(Buffer, "le", 1),
  75. ],
  76. accumulatorUpdaterProgram.programId
  77. );
  78. console.log(`pda for pyth schema ${pythSchema}: ${pda.toString()}`);
  79. return {
  80. pubkey: pda,
  81. isSigner: false,
  82. isWritable: true,
  83. };
  84. // return pda;
  85. });
  86. const mockCpiCallerAddPriceTxPrep = await mockCpiProg.methods
  87. .addPrice(addPriceParams)
  88. .accounts({
  89. ...mockCpiCallerAddPriceTxPubkeys,
  90. })
  91. .remainingAccounts(accumulatorPdas)
  92. .prepare();
  93. console.log(
  94. `ix: ${JSON.stringify(
  95. mockCpiCallerAddPriceTxPrep.instruction,
  96. (k, v) => {
  97. if (k === "data") {
  98. return v.toString();
  99. } else {
  100. return v;
  101. }
  102. },
  103. 2
  104. )}`
  105. );
  106. for (const prop in mockCpiCallerAddPriceTxPrep.pubkeys) {
  107. console.log(
  108. `${prop}: ${mockCpiCallerAddPriceTxPrep.pubkeys[prop].toString()}`
  109. );
  110. }
  111. const addPriceTx = await mockCpiProg.methods
  112. .addPrice(addPriceParams)
  113. .accounts({
  114. ...mockCpiCallerAddPriceTxPubkeys,
  115. })
  116. .remainingAccounts(accumulatorPdas)
  117. .preInstructions([
  118. ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
  119. ])
  120. .rpc({
  121. skipPreflight: true,
  122. });
  123. console.log(`addPriceTx: ${addPriceTx}`);
  124. const accumulatorInputkeys = accumulatorPdas.map((a) => a.pubkey);
  125. const accumulatorInputs =
  126. await accumulatorUpdaterProgram.account.accumulatorInput.fetchMultiple(
  127. accumulatorInputkeys
  128. );
  129. const accumulatorPriceAccounts = accumulatorInputs.map((ai) => {
  130. const { header, data } = ai;
  131. return parseAccumulatorInput(ai);
  132. });
  133. console.log(
  134. `accumulatorPriceAccounts: ${JSON.stringify(
  135. accumulatorPriceAccounts,
  136. null,
  137. 2
  138. )}`
  139. );
  140. accumulatorPriceAccounts.forEach((pa) => {
  141. assert.isTrue(pa.id.eq(addPriceParams.id));
  142. assert.isTrue(pa.price.eq(addPriceParams.price));
  143. assert.isTrue(pa.priceExpo.eq(addPriceParams.priceExpo));
  144. });
  145. });
  146. });
  147. type AccumulatorInputHeader = IdlTypes<AccumulatorUpdater>["AccumulatorHeader"];
  148. type AccumulatorInputPriceAccountTypes =
  149. | IdlAccounts<MockCpiCaller>["priceAccount"] // case-sensitive
  150. | IdlTypes<MockCpiCaller>["PriceOnly"];
  151. // Parses AccumulatorInput.data into a PriceAccount or PriceOnly object based on the
  152. // accountType and accountSchema.
  153. //
  154. // AccumulatorInput.data for AccumulatorInput<PriceAccount> will
  155. // have mockCpiCaller::PriceAccount.discriminator()
  156. // AccumulatorInput<PriceOnly> will not since its not an account
  157. function parseAccumulatorInput({
  158. header,
  159. data,
  160. }: {
  161. header: AccumulatorInputHeader;
  162. data: Buffer;
  163. }): AccumulatorInputPriceAccountTypes {
  164. console.log(`header: ${JSON.stringify(header)}`);
  165. assert.strictEqual(header.accountType, 3);
  166. if (header.accountSchema === 0) {
  167. console.log(`[full]data: ${data.toString("hex")}`);
  168. // case-sensitive. Note that "P" is capitalized here and not in
  169. // the AccumulatorInputPriceAccountTypes type alias.
  170. return mockCpiProg.coder.accounts.decode("PriceAccount", data);
  171. } else {
  172. console.log(`[compact]data: ${data.toString("hex")}`);
  173. return mockCpiProg.coder.types.decode("PriceOnly", data);
  174. }
  175. }