浏览代码

Xc admin/propose instructions (#463)

* Add encoder

* Checkpoint

* Refactor

* Cleanup

* Add comment

* Cleanup
guibescos 2 年之前
父节点
当前提交
2a961d5853
共有 2 个文件被更改,包括 177 次插入0 次删除
  1. 1 0
      xc-admin/packages/xc-admin-common/src/index.ts
  2. 176 0
      xc-admin/packages/xc-admin-common/src/propose.ts

+ 1 - 0
xc-admin/packages/xc-admin-common/src/index.ts

@@ -1,2 +1,3 @@
 export * from "./multisig";
+export * from "./propose";
 export * from "./governance_payload";

+ 176 - 0
xc-admin/packages/xc-admin-common/src/propose.ts

@@ -0,0 +1,176 @@
+import Squads, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh";
+import {
+  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;
+};
+
+/**
+ * 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,
+  instructions: TransactionInstruction[],
+  remote: boolean,
+  wormholeAddress?: PublicKey
+): Promise<PublicKey> {
+  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 (remote) {
+    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
+          )
+        )
+      );
+    }
+  } else {
+    for (let i = 0; i < instructions.length; i++) {
+      txToSend.push(
+        new Transaction().add(
+          await squad.buildAddInstruction(
+            vault,
+            newProposalAddress,
+            instructions[i],
+            i
+          )
+        )
+      );
+    }
+  }
+
+  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;
+}
+
+/**
+ * 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,
+  proposalAddress: PublicKey,
+  instruction: TransactionInstruction,
+  instructionIndex: number,
+  wormholeAddress: PublicKey
+): Promise<SquadInstruction> {
+  const emitter = squad.getAuthorityPDA(vault, 0);
+
+  const [messagePDA, messagePdaBump] = getIxAuthorityPDA(
+    proposalAddress,
+    new BN(instructionIndex),
+    squad.multisigProgramId
+  );
+
+  const provider = new AnchorProvider(
+    squad.connection,
+    squad.wallet,
+    AnchorProvider.defaultOptions()
+  );
+  const wormholeProgram = createWormholeProgramInterface(
+    wormholeAddress,
+    provider
+  );
+
+  const buffer = encodeExecutePostedVaa({
+    targetChainId: "pythnet",
+    instructions: [instruction],
+  });
+
+  const accounts = getPostMessageAccounts(
+    wormholeAddress,
+    emitter,
+    emitter,
+    messagePDA
+  );
+
+  return {
+    instruction: await wormholeProgram.methods
+      .postMessage(0, buffer, 0)
+      .accounts(accounts)
+      .instruction(),
+    authorityIndex: instructionIndex,
+    authorityBump: messagePdaBump,
+    authorityType: "custom",
+  };
+}