From 8dce9449e984ff8e245a019f711627f4e3dc1990 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Fri, 6 Jan 2023 16:16:41 -0600 Subject: [PATCH 1/6] Add encoder --- .../src/__tests__/GovernancePayload.test.ts | 25 +++++++++++++++++ .../governance_payload/ExecutePostedVaa.ts | 28 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts b/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts index 8dd8b90872..06c0f32d74 100644 --- a/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts +++ b/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts @@ -59,6 +59,12 @@ test("GovernancePayload ser/de", (done) => { expect(governanceHeader?.targetChainId).toBe("solana"); expect(governanceHeader?.action).toBe("SetFee"); + buffer = Buffer.alloc(1000); + span = encodeHeader(governanceHeader!, buffer); + expect( + buffer.subarray(0, span).equals(Buffer.from([80, 84, 71, 77, 1, 3, 0, 1])) + ).toBeTruthy(); + // Wrong magic number governanceHeader = decodeHeader( Buffer.from([0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0]) @@ -146,5 +152,24 @@ test("GovernancePayload ser/de", (done) => { ) ); + buffer = Buffer.alloc(1000); + span = encodeExecutePostedVaa(executePostedVaaArgs!, buffer); + expect( + buffer + .subarray(0, span) + .equals( + Buffer.from([ + 80, 84, 71, 77, 0, 0, 0, 26, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 141, 65, 8, 219, 216, 57, 229, 94, 74, 17, 138, 50, 121, 176, + 38, 178, 50, 229, 210, 103, 232, 253, 133, 66, 14, 47, 228, 224, 162, + 147, 232, 251, 1, 1, 252, 221, 21, 33, 156, 1, 72, 252, 246, 229, 150, + 218, 109, 165, 127, 11, 165, 252, 140, 6, 121, 57, 204, 91, 119, 165, + 106, 241, 234, 131, 75, 180, 0, 1, 12, 0, 0, 0, 2, 0, 0, 0, 0, 152, + 13, 0, 0, 0, 0, 0, + ]) + ) + ).toBeTruthy(); + done(); }); diff --git a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts index e89c0287de..3b5bfbae02 100644 --- a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts +++ b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts @@ -142,3 +142,31 @@ export function encodeExecutePostedVaa(src: ExecutePostedVaaArgs): Buffer { ); return buffer.subarray(0, span); } + +/** Encode ExecutePostedVaaArgs */ +export function encodeExecutePostedVaa( + src: ExecutePostedVaaArgs, + buffer: Buffer +): number { + const offset = encodeHeader(src.header, buffer); + let instructions: InstructionData[] = src.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 }; + }); + return ( + offset + + new Vector(instructionDataLayout, "instructions").encode( + instructions, + buffer, + offset + ) + ); +} From c676db19ba4c89caffa143016361048dfef05c1a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 9 Jan 2023 11:55:11 -0600 Subject: [PATCH 2/6] Checkpoint --- .../src/multisig_actions/index.ts | 1 + .../src/multisig_actions/propose.ts | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts create mode 100644 xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts diff --git a/xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts b/xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts new file mode 100644 index 0000000000..8829d363ee --- /dev/null +++ b/xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts @@ -0,0 +1 @@ +export * from "./propose"; diff --git a/xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts b/xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts new file mode 100644 index 0000000000..41e435c638 --- /dev/null +++ b/xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts @@ -0,0 +1,161 @@ +import Squads, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh"; +import { + PACKET_DATA_SIZE, + PublicKey, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { BN } from "bn.js"; +import { AnchorProvider } from "@project-serum/anchor"; +import { + createWormholeProgramInterface, + getPostMessageAccounts, +} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; +import { encodeExecutePostedVaa } from "../governance_payload/ExecutePostedVaa"; + +type SquadInstruction = { + instruction: TransactionInstruction; + authorityIndex?: number; + authorityBump?: number; + authorityType?: string; +}; + +export async function proposeInstructions( + squad: Squads, + vault: PublicKey, + chain: "solana", + instructions: TransactionInstruction[], + wormholeAddress?: PublicKey +): Promise { + const msAccount = await squad.getMultisig(vault); + let txToSend: Transaction[] = []; + + const createProposal = new Transaction().add( + await squad.buildCreateTransaction( + msAccount.publicKey, + msAccount.authorityIndex, + msAccount.transactionIndex + 1 + ) + ); + const newProposalAddress = getTxPDA( + vault, + new BN(msAccount.transactionIndex + 1), + squad.multisigProgramId + )[0]; + txToSend.push(createProposal); + + if (chain == "solana") { + for (let i = 0; i < instructions.length; i++) { + txToSend.push( + new Transaction().add( + await squad.buildAddInstruction( + vault, + newProposalAddress, + instructions[i], + i + ) + ) + ); + } + } else { + if (!wormholeAddress) { + throw new Error("Need wormhole address"); + } + for (let i = 0; i < instructions.length; i++) { + const squadIx = await wrapAsRemoteInstruction( + squad, + vault, + newProposalAddress, + instructions[i], + i, + wormholeAddress + ); + txToSend.push( + new Transaction().add( + await squad.buildAddInstruction( + vault, + newProposalAddress, + squadIx.instruction, + i, + squadIx.authorityIndex, + squadIx.authorityBump, + squadIx.authorityType + ) + ) + ); + } + } + + txToSend.push( + new Transaction().add( + await squad.buildActivateTransaction(vault, newProposalAddress) + ) + ); + txToSend.push( + new Transaction().add( + await squad.buildApproveTransaction(vault, newProposalAddress) + ) + ); + + await new AnchorProvider( + squad.connection, + squad.wallet, + AnchorProvider.defaultOptions() + ).sendAll( + txToSend.map((tx) => { + return { tx, signers: [] }; + }) + ); + return newProposalAddress; +} + +export async function wrapAsRemoteInstruction( + squad: Squads, + vault: PublicKey, + proposalAddress: PublicKey, + instruction: TransactionInstruction, + instructionIndex: number, + wormholeAddress: PublicKey +): Promise { + const [messagePDA, messagePdaBump] = getIxAuthorityPDA( + proposalAddress, + new BN(instructionIndex), + squad.multisigProgramId + ); + + const emitter = squad.getAuthorityPDA(vault, 0); + + const provider = new AnchorProvider( + squad.connection, + squad.wallet, + AnchorProvider.defaultOptions() + ); + const wormholeProgram = createWormholeProgramInterface( + wormholeAddress, + provider + ); + const buffer = Buffer.alloc(PACKET_DATA_SIZE); + const span = encodeExecutePostedVaa( + { + header: { action: "ExecutePostedVaa", targetChainId: "pythnet" }, + instructions: [instruction], + }, + buffer + ); + + const accounts = getPostMessageAccounts( + wormholeAddress, + emitter, + emitter, + messagePDA + ); + return { + instruction: await wormholeProgram.methods + .postMessage(0, buffer.subarray(0, span), 0) + .accounts(accounts) + .instruction(), + authorityIndex: instructionIndex, + authorityBump: messagePdaBump, + authorityType: "custom", + }; +} From 198d0bca5e3dbfd64776c07820da8e005e5ec8cd Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 9 Jan 2023 16:10:04 -0600 Subject: [PATCH 3/6] Refactor --- .../xc-admin-common/src/multisig_actions/index.ts | 1 - .../src/{multisig_actions => }/propose.ts | 15 ++++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts rename xc-admin/packages/xc-admin-common/src/{multisig_actions => }/propose.ts (92%) diff --git a/xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts b/xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts deleted file mode 100644 index 8829d363ee..0000000000 --- a/xc-admin/packages/xc-admin-common/src/multisig_actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./propose"; diff --git a/xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts b/xc-admin/packages/xc-admin-common/src/propose.ts similarity index 92% rename from xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts rename to xc-admin/packages/xc-admin-common/src/propose.ts index 41e435c638..0333d28fd2 100644 --- a/xc-admin/packages/xc-admin-common/src/multisig_actions/propose.ts +++ b/xc-admin/packages/xc-admin-common/src/propose.ts @@ -11,7 +11,7 @@ import { createWormholeProgramInterface, getPostMessageAccounts, } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; -import { encodeExecutePostedVaa } from "../governance_payload/ExecutePostedVaa"; +import { encodeExecutePostedVaa } from "./governance_payload/ExecutePostedVaa"; type SquadInstruction = { instruction: TransactionInstruction; @@ -135,13 +135,10 @@ export async function wrapAsRemoteInstruction( provider ); const buffer = Buffer.alloc(PACKET_DATA_SIZE); - const span = encodeExecutePostedVaa( - { - header: { action: "ExecutePostedVaa", targetChainId: "pythnet" }, - instructions: [instruction], - }, - buffer - ); + const span = encodeExecutePostedVaa({ + targetChainId: "pythnet", + instructions: [instruction], + }); const accounts = getPostMessageAccounts( wormholeAddress, @@ -151,7 +148,7 @@ export async function wrapAsRemoteInstruction( ); return { instruction: await wormholeProgram.methods - .postMessage(0, buffer.subarray(0, span), 0) + .postMessage(0, buffer, 0) .accounts(accounts) .instruction(), authorityIndex: instructionIndex, From 7ed8d0a051568daff6fa3deff6e4113ca168d9c1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 9 Jan 2023 16:12:21 -0600 Subject: [PATCH 4/6] Cleanup --- .../src/__tests__/GovernancePayload.test.ts | 25 ----------------- .../governance_payload/ExecutePostedVaa.ts | 28 ------------------- .../packages/xc-admin-common/src/index.ts | 1 + 3 files changed, 1 insertion(+), 53 deletions(-) diff --git a/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts b/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts index 06c0f32d74..8dd8b90872 100644 --- a/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts +++ b/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts @@ -59,12 +59,6 @@ test("GovernancePayload ser/de", (done) => { expect(governanceHeader?.targetChainId).toBe("solana"); expect(governanceHeader?.action).toBe("SetFee"); - buffer = Buffer.alloc(1000); - span = encodeHeader(governanceHeader!, buffer); - expect( - buffer.subarray(0, span).equals(Buffer.from([80, 84, 71, 77, 1, 3, 0, 1])) - ).toBeTruthy(); - // Wrong magic number governanceHeader = decodeHeader( Buffer.from([0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0]) @@ -152,24 +146,5 @@ test("GovernancePayload ser/de", (done) => { ) ); - buffer = Buffer.alloc(1000); - span = encodeExecutePostedVaa(executePostedVaaArgs!, buffer); - expect( - buffer - .subarray(0, span) - .equals( - Buffer.from([ - 80, 84, 71, 77, 0, 0, 0, 26, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - 0, 0, 0, 141, 65, 8, 219, 216, 57, 229, 94, 74, 17, 138, 50, 121, 176, - 38, 178, 50, 229, 210, 103, 232, 253, 133, 66, 14, 47, 228, 224, 162, - 147, 232, 251, 1, 1, 252, 221, 21, 33, 156, 1, 72, 252, 246, 229, 150, - 218, 109, 165, 127, 11, 165, 252, 140, 6, 121, 57, 204, 91, 119, 165, - 106, 241, 234, 131, 75, 180, 0, 1, 12, 0, 0, 0, 2, 0, 0, 0, 0, 152, - 13, 0, 0, 0, 0, 0, - ]) - ) - ).toBeTruthy(); - done(); }); diff --git a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts index 3b5bfbae02..e89c0287de 100644 --- a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts +++ b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts @@ -142,31 +142,3 @@ export function encodeExecutePostedVaa(src: ExecutePostedVaaArgs): Buffer { ); return buffer.subarray(0, span); } - -/** Encode ExecutePostedVaaArgs */ -export function encodeExecutePostedVaa( - src: ExecutePostedVaaArgs, - buffer: Buffer -): number { - const offset = encodeHeader(src.header, buffer); - let instructions: InstructionData[] = src.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 }; - }); - return ( - offset + - new Vector(instructionDataLayout, "instructions").encode( - instructions, - buffer, - offset - ) - ); -} diff --git a/xc-admin/packages/xc-admin-common/src/index.ts b/xc-admin/packages/xc-admin-common/src/index.ts index 45e1d0bff6..35fd45b2df 100644 --- a/xc-admin/packages/xc-admin-common/src/index.ts +++ b/xc-admin/packages/xc-admin-common/src/index.ts @@ -1,2 +1,3 @@ export * from "./multisig"; +export * from "./propose"; export * from "./governance_payload"; From 633c32ee80f21fe20fe615305cddbb31ec5d122a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 9 Jan 2023 16:43:04 -0600 Subject: [PATCH 5/6] Add comment --- .../packages/xc-admin-common/src/propose.ts | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/xc-admin/packages/xc-admin-common/src/propose.ts b/xc-admin/packages/xc-admin-common/src/propose.ts index 0333d28fd2..cebbd5d20b 100644 --- a/xc-admin/packages/xc-admin-common/src/propose.ts +++ b/xc-admin/packages/xc-admin-common/src/propose.ts @@ -20,11 +20,19 @@ type SquadInstruction = { authorityType?: string; }; +/** + * Propose an array of `TransactionInstructions` as a proposal + * @param squad Squads client + * @param vault vault public key (the id of the multisig where these instructions should be proposed) + * @param instructions instructions that will be proposed + * @param remote whether the instructions should be executed in the chain of the multisig or remotely on Pythnet + * @returns the newly created proposal's pubkey + */ export async function proposeInstructions( squad: Squads, vault: PublicKey, - chain: "solana", instructions: TransactionInstruction[], + remote: boolean, wormholeAddress?: PublicKey ): Promise { const msAccount = await squad.getMultisig(vault); @@ -44,20 +52,7 @@ export async function proposeInstructions( )[0]; txToSend.push(createProposal); - if (chain == "solana") { - for (let i = 0; i < instructions.length; i++) { - txToSend.push( - new Transaction().add( - await squad.buildAddInstruction( - vault, - newProposalAddress, - instructions[i], - i - ) - ) - ); - } - } else { + if (remote) { if (!wormholeAddress) { throw new Error("Need wormhole address"); } @@ -84,6 +79,19 @@ export async function proposeInstructions( ) ); } + } else { + for (let i = 0; i < instructions.length; i++) { + txToSend.push( + new Transaction().add( + await squad.buildAddInstruction( + vault, + newProposalAddress, + instructions[i], + i + ) + ) + ); + } } txToSend.push( @@ -134,8 +142,7 @@ export async function wrapAsRemoteInstruction( wormholeAddress, provider ); - const buffer = Buffer.alloc(PACKET_DATA_SIZE); - const span = encodeExecutePostedVaa({ + const buffer = encodeExecutePostedVaa({ targetChainId: "pythnet", instructions: [instruction], }); From 25f8270dfec82e8c2bd5d9bf2f12c82afa022aea Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Mon, 9 Jan 2023 16:50:08 -0600 Subject: [PATCH 6/6] Cleanup --- .../packages/xc-admin-common/src/propose.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/xc-admin/packages/xc-admin-common/src/propose.ts b/xc-admin/packages/xc-admin-common/src/propose.ts index cebbd5d20b..0aab74061d 100644 --- a/xc-admin/packages/xc-admin-common/src/propose.ts +++ b/xc-admin/packages/xc-admin-common/src/propose.ts @@ -1,6 +1,5 @@ import Squads, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh"; import { - PACKET_DATA_SIZE, PublicKey, Transaction, TransactionInstruction, @@ -117,6 +116,16 @@ export async function proposeInstructions( return newProposalAddress; } +/** + * Wrap `instruction` in a Wormhole message for remote execution + * @param squad Squads client + * @param vault vault public key (the id of the multisig where these instructions should be proposed) + * @param proposalAddress address of the proposal + * @param instruction instruction to be wrapped in a Wormhole message + * @param instructionIndex index of the instruction within the proposal + * @param wormholeAddress address of the Wormhole bridge + * @returns an instruction to be proposed + */ export async function wrapAsRemoteInstruction( squad: Squads, vault: PublicKey, @@ -125,14 +134,14 @@ export async function wrapAsRemoteInstruction( instructionIndex: number, wormholeAddress: PublicKey ): Promise { + const emitter = squad.getAuthorityPDA(vault, 0); + const [messagePDA, messagePdaBump] = getIxAuthorityPDA( proposalAddress, new BN(instructionIndex), squad.multisigProgramId ); - const emitter = squad.getAuthorityPDA(vault, 0); - const provider = new AnchorProvider( squad.connection, squad.wallet, @@ -142,6 +151,7 @@ export async function wrapAsRemoteInstruction( wormholeAddress, provider ); + const buffer = encodeExecutePostedVaa({ targetChainId: "pythnet", instructions: [instruction], @@ -153,6 +163,7 @@ export async function wrapAsRemoteInstruction( emitter, messagePDA ); + return { instruction: await wormholeProgram.methods .postMessage(0, buffer, 0)