import { ChainName } from "../chains"; import * as BufferLayout from "@solana/buffer-layout"; import { PythGovernanceAction, PythGovernanceHeader, safeLayoutDecode, } from "."; import { Layout } from "@solana/buffer-layout"; import { AccountMeta, PACKET_DATA_SIZE, PublicKey, TransactionInstruction, } from "@solana/web3.js"; class Vector extends Layout { private element: Layout; constructor(element: Layout, property?: string) { super(-1, property); this.element = element; } decode(b: Uint8Array, offset?: number | undefined): T[] { const length = BufferLayout.u32().decode(b, offset); return BufferLayout.seq(this.element, length).decode(b, (offset || 0) + 4); } encode(src: T[], b: Uint8Array, offset?: number | undefined): number { return BufferLayout.struct>([ BufferLayout.u32("length"), BufferLayout.seq(this.element, src.length, "elements"), ]).encode({ length: src.length, elements: src }, b, offset); } getSpan(b: Buffer, offset?: number): number { const length = BufferLayout.u32().decode(b, offset); return 4 + this.element.span * length; } } export type InstructionData = { programId: Uint8Array; accounts: AccountMetadata[]; data: number[]; }; export type AccountMetadata = { pubkey: Uint8Array; isSigner: number; isWritable: number; }; export const accountMetaLayout = BufferLayout.struct([ BufferLayout.blob(32, "pubkey"), BufferLayout.u8("isSigner"), BufferLayout.u8("isWritable"), ]); export const instructionDataLayout = BufferLayout.struct([ BufferLayout.blob(32, "programId"), new Vector(accountMetaLayout, "accounts"), new Vector(BufferLayout.u8(), "data"), ]); export class ExecutePostedVaa implements PythGovernanceAction { readonly targetChainId: ChainName; readonly instructions: TransactionInstruction[]; static layout: Vector = new Vector( instructionDataLayout, "instructions" ); constructor( targetChainId: ChainName, instructions: TransactionInstruction[] ) { this.targetChainId = targetChainId; this.instructions = instructions; } /** Decode ExecutePostedVaa */ static decode(data: Buffer): ExecutePostedVaa | undefined { const header = PythGovernanceHeader.decode(data); if (!header) return undefined; const deserialized = safeLayoutDecode( this.layout, data.subarray(PythGovernanceHeader.span) ); if (!deserialized) return undefined; let instructions: TransactionInstruction[] = deserialized.map((ix) => { let programId: PublicKey = new PublicKey(ix.programId); let keys: AccountMeta[] = ix.accounts.map((acc) => { return { pubkey: new PublicKey(acc.pubkey), isSigner: Boolean(acc.isSigner), isWritable: Boolean(acc.isWritable), }; }); let data: Buffer = Buffer.from(ix.data); return { programId, keys, data }; }); return new ExecutePostedVaa(header.targetChainId, instructions); } /** Encode ExecutePostedVaa */ encode(): Buffer { const headerBuffer = new PythGovernanceHeader( this.targetChainId, "ExecutePostedVaa" ).encode(); // The code will crash if the payload is actually bigger than PACKET_DATA_SIZE. But PACKET_DATA_SIZE is the maximum transaction size of Solana, so our serialized payload should never be bigger than this anyway const buffer = Buffer.alloc(PACKET_DATA_SIZE); let instructions: InstructionData[] = this.instructions.map((ix) => { let programId = ix.programId.toBytes(); let accounts: AccountMetadata[] = ix.keys.map((acc) => { return { pubkey: acc.pubkey.toBytes(), isSigner: acc.isSigner ? 1 : 0, isWritable: acc.isWritable ? 1 : 0, }; }); let data = [...ix.data]; return { programId, accounts, data }; }); const span = ExecutePostedVaa.layout.encode(instructions, buffer); return Buffer.concat([headerBuffer, buffer.subarray(0, span)]); } }