ExecutePostedVaa.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { ChainId, ChainName } from "@certusone/wormhole-sdk";
  2. import * as BufferLayout from "@solana/buffer-layout";
  3. import {
  4. encodeHeader,
  5. governanceHeaderLayout,
  6. PythGovernanceHeader,
  7. verifyHeader,
  8. } from ".";
  9. import { Layout } from "@solana/buffer-layout";
  10. import {
  11. AccountMeta,
  12. PACKET_DATA_SIZE,
  13. PublicKey,
  14. TransactionInstruction,
  15. } from "@solana/web3.js";
  16. class Vector<T> extends Layout<T[]> {
  17. private element: Layout<T>;
  18. constructor(element: Layout<T>, property?: string) {
  19. super(-1, property);
  20. this.element = element;
  21. }
  22. decode(b: Uint8Array, offset?: number | undefined): T[] {
  23. const length = BufferLayout.u32().decode(b, offset);
  24. return BufferLayout.seq(this.element, length).decode(b, (offset || 0) + 4);
  25. }
  26. encode(src: T[], b: Uint8Array, offset?: number | undefined): number {
  27. return BufferLayout.struct<Readonly<{ length: number; elements: T[] }>>([
  28. BufferLayout.u32("length"),
  29. BufferLayout.seq(this.element, src.length, "elements"),
  30. ]).encode({ length: src.length, elements: src }, b, offset);
  31. }
  32. getSpan(b: Buffer, offset?: number): number {
  33. const length = BufferLayout.u32().decode(b, offset);
  34. return 4 + this.element.span * length;
  35. }
  36. }
  37. export type InstructionData = {
  38. programId: Uint8Array;
  39. accounts: AccountMetadata[];
  40. data: number[];
  41. };
  42. export type AccountMetadata = {
  43. pubkey: Uint8Array;
  44. isSigner: number;
  45. isWritable: number;
  46. };
  47. export const accountMetaLayout = BufferLayout.struct<AccountMetadata>([
  48. BufferLayout.blob(32, "pubkey"),
  49. BufferLayout.u8("isSigner"),
  50. BufferLayout.u8("isWritable"),
  51. ]);
  52. export const instructionDataLayout = BufferLayout.struct<InstructionData>([
  53. BufferLayout.blob(32, "programId"),
  54. new Vector<AccountMetadata>(accountMetaLayout, "accounts"),
  55. new Vector<number>(BufferLayout.u8(), "data"),
  56. ]);
  57. export const executePostedVaaLayout: BufferLayout.Structure<
  58. Readonly<{
  59. header: Readonly<{
  60. magicNumber: number;
  61. module: number;
  62. action: number;
  63. chain: ChainId;
  64. }>;
  65. instructions: InstructionData[];
  66. }>
  67. > = BufferLayout.struct([
  68. governanceHeaderLayout(),
  69. new Vector<InstructionData>(instructionDataLayout, "instructions"),
  70. ]);
  71. export type ExecutePostedVaaArgs = {
  72. targetChainId: ChainName;
  73. instructions: TransactionInstruction[];
  74. };
  75. /** Decode ExecutePostedVaaArgs and return undefined if it failed */
  76. export function decodeExecutePostedVaa(
  77. data: Buffer
  78. ): ExecutePostedVaaArgs | undefined {
  79. let deserialized = executePostedVaaLayout.decode(data);
  80. let header = verifyHeader(deserialized.header);
  81. if (!header) {
  82. return undefined;
  83. }
  84. let instructions: TransactionInstruction[] = deserialized.instructions.map(
  85. (ix) => {
  86. let programId: PublicKey = new PublicKey(ix.programId);
  87. let keys: AccountMeta[] = ix.accounts.map((acc) => {
  88. return {
  89. pubkey: new PublicKey(acc.pubkey),
  90. isSigner: Boolean(acc.isSigner),
  91. isWritable: Boolean(acc.isWritable),
  92. };
  93. });
  94. let data: Buffer = Buffer.from(ix.data);
  95. return { programId, keys, data };
  96. }
  97. );
  98. return { targetChainId: header.targetChainId, instructions };
  99. }
  100. /** Encode ExecutePostedVaaArgs */
  101. export function encodeExecutePostedVaa(src: ExecutePostedVaaArgs): Buffer {
  102. // PACKET_DATA_SIZE is the maximum transactin size of Solana, so our serialized payload will never be bigger than that
  103. const buffer = Buffer.alloc(PACKET_DATA_SIZE);
  104. const offset = encodeHeader(
  105. { action: "ExecutePostedVaa", targetChainId: src.targetChainId },
  106. buffer
  107. );
  108. let instructions: InstructionData[] = src.instructions.map((ix) => {
  109. let programId = ix.programId.toBytes();
  110. let accounts: AccountMetadata[] = ix.keys.map((acc) => {
  111. return {
  112. pubkey: acc.pubkey.toBytes(),
  113. isSigner: acc.isSigner ? 1 : 0,
  114. isWritable: acc.isWritable ? 1 : 0,
  115. };
  116. });
  117. let data = [...ix.data];
  118. return { programId, accounts, data };
  119. });
  120. const span =
  121. offset +
  122. new Vector<InstructionData>(instructionDataLayout, "instructions").encode(
  123. instructions,
  124. buffer,
  125. offset
  126. );
  127. return buffer.subarray(0, span);
  128. }