Browse Source

Add system instruction (#1046)

* Add system instruction

* Update string logic

* Fix comments
guibescos 2 năm trước cách đây
mục cha
commit
b91360ab6b

+ 62 - 0
governance/xc_admin/packages/xc_admin_common/src/__tests__/SystemProgramInstruction.test.ts

@@ -0,0 +1,62 @@
+import { PythCluster } from "@pythnetwork/client";
+import {
+  MultisigInstructionProgram,
+  MultisigParser,
+  UNRECOGNIZED_INSTRUCTION,
+} from "../multisig_transaction";
+import {
+  PublicKey,
+  SystemProgram,
+  TransactionInstruction,
+} from "@solana/web3.js";
+import { SystemProgramMultisigInstruction } from "../multisig_transaction/SystemProgramInstruction";
+
+test("System multisig instruction parse", (done) => {
+  jest.setTimeout(60000);
+
+  const cluster: PythCluster = "devnet";
+
+  const parser = MultisigParser.fromCluster(cluster);
+
+  const transferInstruction = SystemProgram.transfer({
+    fromPubkey: new PublicKey(1),
+    toPubkey: new PublicKey(2),
+    lamports: 100,
+  });
+
+  const parsedInstruction = parser.parseInstruction(transferInstruction);
+  if (parsedInstruction instanceof SystemProgramMultisigInstruction) {
+    expect(parsedInstruction.program).toBe(
+      MultisigInstructionProgram.SystemProgram
+    );
+    expect(parsedInstruction.name).toBe("Transfer");
+    expect(
+      parsedInstruction.args.fromPubkey.equals(new PublicKey(1))
+    ).toBeTruthy();
+    expect(
+      parsedInstruction.args.toPubkey.equals(new PublicKey(2))
+    ).toBeTruthy();
+    expect(parsedInstruction.args.lamports.toString()).toBe("100");
+  } else {
+    done("Not instance of SystemInstruction");
+  }
+
+  const badInstruction = new TransactionInstruction({
+    keys: [],
+    programId: new PublicKey(SystemProgram.programId),
+    data: Buffer.from([1, 2, 3, 4]),
+  });
+  const parsedBadInstruction = parser.parseInstruction(badInstruction);
+  if (parsedBadInstruction instanceof SystemProgramMultisigInstruction) {
+    expect(parsedBadInstruction.program).toBe(
+      MultisigInstructionProgram.SystemProgram
+    );
+    expect(parsedBadInstruction.name).toBe(UNRECOGNIZED_INSTRUCTION);
+    expect(
+      parsedBadInstruction.args.data.equals(Buffer.from([1, 2, 3, 4]))
+    ).toBeTruthy();
+    done();
+  } else {
+    done("Not instance of SystemInstruction");
+  }
+});

+ 84 - 0
governance/xc_admin/packages/xc_admin_common/src/multisig_transaction/SystemProgramInstruction.ts

@@ -0,0 +1,84 @@
+import { SystemInstruction, TransactionInstruction } from "@solana/web3.js";
+import {
+  MultisigInstruction,
+  MultisigInstructionProgram,
+  UNRECOGNIZED_INSTRUCTION,
+} from ".";
+import { AnchorAccounts } from "./anchor";
+
+export class SystemProgramMultisigInstruction implements MultisigInstruction {
+  readonly program = MultisigInstructionProgram.SystemProgram;
+  readonly name: string;
+  readonly args: { [key: string]: any };
+  readonly accounts: AnchorAccounts;
+
+  constructor(
+    name: string,
+    args: { [key: string]: any },
+    accounts: AnchorAccounts
+  ) {
+    this.name = name;
+    this.args = args;
+    this.accounts = accounts;
+  }
+
+  static fromTransactionInstruction(
+    instruction: TransactionInstruction
+  ): SystemProgramMultisigInstruction {
+    try {
+      const instructionType =
+        SystemInstruction.decodeInstructionType(instruction);
+      let data;
+      switch (instructionType) {
+        case "AdvanceNonceAccount":
+          data = SystemInstruction.decodeNonceAdvance(instruction);
+          break;
+        case "Allocate":
+          data = SystemInstruction.decodeAllocate(instruction);
+          break;
+        case "AllocateWithSeed":
+          data = SystemInstruction.decodeAllocateWithSeed(instruction);
+          break;
+        case "Assign":
+          data = SystemInstruction.decodeAssign(instruction);
+          break;
+        case "AssignWithSeed":
+          data = SystemInstruction.decodeAssignWithSeed(instruction);
+          break;
+        case "AuthorizeNonceAccount":
+          data = SystemInstruction.decodeNonceAuthorize(instruction);
+          break;
+        case "Create":
+          data = SystemInstruction.decodeCreateAccount(instruction);
+          break;
+        case "CreateWithSeed":
+          data = SystemInstruction.decodeCreateWithSeed(instruction);
+          break;
+        case "InitializeNonceAccount":
+          data = SystemInstruction.decodeNonceInitialize(instruction);
+          break;
+        case "Transfer":
+          data = SystemInstruction.decodeTransfer(instruction);
+          break;
+        case "TransferWithSeed":
+          data = SystemInstruction.decodeTransferWithSeed(instruction);
+          break;
+        case "WithdrawNonceAccount":
+          data = SystemInstruction.decodeNonceWithdraw(instruction);
+          break;
+        case "UpgradeNonceAccount": // I couldn't find the decode function for this
+          throw Error("UpgradeNonceAccount not implemented");
+      }
+      return new SystemProgramMultisigInstruction(instructionType, data, {
+        named: {},
+        remaining: [],
+      });
+    } catch {
+      return new SystemProgramMultisigInstruction(
+        UNRECOGNIZED_INSTRUCTION,
+        { data: instruction.data },
+        { named: {}, remaining: instruction.keys }
+      );
+    }
+  }
+}

+ 12 - 1
governance/xc_admin/packages/xc_admin_common/src/multisig_transaction/index.ts

@@ -2,18 +2,24 @@ import {
   getPythProgramKeyForCluster,
   PythCluster,
 } from "@pythnetwork/client/lib/cluster";
-import { PublicKey, TransactionInstruction } from "@solana/web3.js";
+import {
+  PublicKey,
+  SystemProgram,
+  TransactionInstruction,
+} from "@solana/web3.js";
 import { MESSAGE_BUFFER_PROGRAM_ID } from "../message_buffer";
 import { WORMHOLE_ADDRESS } from "../wormhole";
 import { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruction";
 import { PythMultisigInstruction } from "./PythMultisigInstruction";
 import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
+import { SystemProgramMultisigInstruction } from "./SystemProgramInstruction";
 
 export const UNRECOGNIZED_INSTRUCTION = "unrecognizedInstruction";
 export enum MultisigInstructionProgram {
   PythOracle,
   WormholeBridge,
   MessageBuffer,
+  SystemProgram,
   UnrecognizedProgram,
 }
 
@@ -67,6 +73,10 @@ export class MultisigParser {
       return MessageBufferMultisigInstruction.fromTransactionInstruction(
         instruction
       );
+    } else if (instruction.programId.equals(SystemProgram.programId)) {
+      return SystemProgramMultisigInstruction.fromTransactionInstruction(
+        instruction
+      );
     } else {
       return UnrecognizedProgram.fromTransactionInstruction(instruction);
     }
@@ -76,3 +86,4 @@ export class MultisigParser {
 export { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
 export { PythMultisigInstruction } from "./PythMultisigInstruction";
 export { MessageBufferMultisigInstruction } from "./MessageBufferMultisigInstruction";
+export { SystemProgramMultisigInstruction } from "./SystemProgramInstruction";

+ 53 - 25
governance/xc_admin/packages/xc_admin_frontend/components/InstructionViews/WormholeInstructionView.tsx

@@ -13,18 +13,17 @@ import {
   SetDataSources,
   SetFee,
   SetValidPeriod,
+  SystemProgramMultisigInstruction,
   UnrecognizedProgram,
   WormholeMultisigInstruction,
 } from 'xc_admin_common'
 import { AccountMeta, PublicKey } from '@solana/web3.js'
 import CopyPubkey from '../common/CopyPubkey'
-import { useContext } from 'react'
-import { ClusterContext } from '../../contexts/ClusterContext'
 import { ParsedAccountPubkeyRow, SignerTag, WritableTag } from './AccountUtils'
 import { usePythContext } from '../../contexts/PythContext'
-
 import { getMappingCluster, isPubkey } from './utils'
 import { PythCluster } from '@pythnetwork/client'
+import { lamportsToSol } from '../../utils/lamportsToSol'
 
 const GovernanceInstructionView = ({
   instruction,
@@ -93,6 +92,9 @@ export const WormholeInstructionView = ({
                     : parsedInstruction instanceof
                       MessageBufferMultisigInstruction
                     ? 'Message Buffer'
+                    : parsedInstruction instanceof
+                      SystemProgramMultisigInstruction
+                    ? 'System Program'
                     : 'Unknown'}
                 </div>
               </div>
@@ -104,7 +106,9 @@ export const WormholeInstructionView = ({
                 <div>
                   {parsedInstruction instanceof PythMultisigInstruction ||
                   parsedInstruction instanceof WormholeMultisigInstruction ||
-                  parsedInstruction instanceof MessageBufferMultisigInstruction
+                  parsedInstruction instanceof
+                    MessageBufferMultisigInstruction ||
+                  parsedInstruction instanceof SystemProgramMultisigInstruction
                     ? parsedInstruction.name
                     : 'Unknown'}
                 </div>
@@ -116,8 +120,9 @@ export const WormholeInstructionView = ({
                 <div>Arguments</div>
                 {parsedInstruction instanceof PythMultisigInstruction ||
                 parsedInstruction instanceof WormholeMultisigInstruction ||
+                parsedInstruction instanceof MessageBufferMultisigInstruction ||
                 parsedInstruction instanceof
-                  MessageBufferMultisigInstruction ? (
+                  SystemProgramMultisigInstruction ? (
                   Object.keys(parsedInstruction.args).length > 0 ? (
                     <div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0">
                       <div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
@@ -130,26 +135,48 @@ export const WormholeInstructionView = ({
                             key={index}
                             className="flex justify-between border-t border-beige-300 py-3"
                           >
-                            <div>{key}</div>
-                            {parsedInstruction.args[key] instanceof
-                            PublicKey ? (
-                              <CopyPubkey
-                                pubkey={parsedInstruction.args[key].toBase58()}
-                              />
-                            ) : typeof instruction.args[key] === 'string' &&
-                              isPubkey(instruction.args[key]) ? (
-                              <CopyPubkey
-                                pubkey={parsedInstruction.args[key]}
-                              />
+                            {key === 'lamports' &&
+                            typeof parsedInstruction.args[key] === 'bigint' ? (
+                              <>
+                                <div>{'◎'}</div>
+                                <div>
+                                  {lamportsToSol(parsedInstruction.args[key])}
+                                </div>
+                              </>
                             ) : (
-                              <div className="max-w-sm break-all">
-                                {typeof parsedInstruction.args[key] === 'string'
-                                  ? parsedInstruction.args[key]
-                                  : parsedInstruction.args[key] instanceof
-                                    Uint8Array
-                                  ? parsedInstruction.args[key].toString('hex')
-                                  : JSON.stringify(parsedInstruction.args[key])}
-                              </div>
+                              <>
+                                <div>{key}</div>
+                                {parsedInstruction.args[key] instanceof
+                                PublicKey ? (
+                                  <CopyPubkey
+                                    pubkey={parsedInstruction.args[
+                                      key
+                                    ].toBase58()}
+                                  />
+                                ) : typeof instruction.args[key] === 'string' &&
+                                  isPubkey(instruction.args[key]) ? (
+                                  <CopyPubkey
+                                    pubkey={parsedInstruction.args[key]}
+                                  />
+                                ) : (
+                                  <div className="max-w-sm break-all">
+                                    {typeof parsedInstruction.args[key] ===
+                                    'string'
+                                      ? parsedInstruction.args[key]
+                                      : parsedInstruction.args[key] instanceof
+                                        Uint8Array
+                                      ? parsedInstruction.args[key].toString(
+                                          'hex'
+                                        )
+                                      : typeof parsedInstruction.args[key] ===
+                                        'bigint'
+                                      ? parsedInstruction.args[key].toString()
+                                      : JSON.stringify(
+                                          parsedInstruction.args[key]
+                                        )}
+                                  </div>
+                                )}
+                              </>
                             )}
                           </div>
                           {key === 'pub' &&
@@ -176,7 +203,8 @@ export const WormholeInstructionView = ({
               </div>
               {parsedInstruction instanceof PythMultisigInstruction ||
               parsedInstruction instanceof WormholeMultisigInstruction ||
-              parsedInstruction instanceof MessageBufferMultisigInstruction ? (
+              parsedInstruction instanceof MessageBufferMultisigInstruction ||
+              parsedInstruction instanceof SystemProgramMultisigInstruction ? (
                 <div
                   key={`${index}_accounts`}
                   className="grid grid-cols-4 justify-between"

+ 11 - 21
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals.tsx

@@ -1,32 +1,21 @@
 import * as Tooltip from '@radix-ui/react-tooltip'
 import { useWallet } from '@solana/wallet-adapter-react'
-import { AccountMeta, Keypair, PublicKey } from '@solana/web3.js'
+import { AccountMeta, Keypair, PublicKey, SystemProgram } from '@solana/web3.js'
 import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
 import { useRouter } from 'next/router'
-import {
-  Dispatch,
-  Fragment,
-  SetStateAction,
-  useCallback,
-  useContext,
-  useEffect,
-  useState,
-} from 'react'
+import { Fragment, useCallback, useContext, useEffect, useState } from 'react'
 import toast from 'react-hot-toast'
 import {
   ExecutePostedVaa,
   getMultisigCluster,
-  getProposals,
   MultisigInstruction,
-  MultisigInstructionProgram,
   MultisigParser,
-  PRICE_FEED_MULTISIG,
   PythMultisigInstruction,
   MessageBufferMultisigInstruction,
   UnrecognizedProgram,
   WormholeMultisigInstruction,
   getManyProposalsInstructions,
-  UPGRADE_MULTISIG,
+  SystemProgramMultisigInstruction,
 } from 'xc_admin_common'
 import { ClusterContext } from '../../contexts/ClusterContext'
 import { useMultisigContext } from '../../contexts/MultisigContext'
@@ -274,7 +263,8 @@ const Proposal = ({
         for (const remoteCluster of remoteClusters) {
           if (
             multisigCluster === getMultisigCluster(remoteCluster) &&
-            ix.programId.equals(getPythProgramKeyForCluster(remoteCluster))
+            (ix.programId.equals(getPythProgramKeyForCluster(remoteCluster)) ||
+              ix.programId.equals(SystemProgram.programId))
           ) {
             targetClusters.push(remoteCluster)
           }
@@ -329,7 +319,9 @@ const Proposal = ({
             return (
               parsedRemoteInstruction instanceof PythMultisigInstruction ||
               parsedRemoteInstruction instanceof
-                MessageBufferMultisigInstruction
+                MessageBufferMultisigInstruction ||
+              parsedRemoteInstruction instanceof
+                SystemProgramMultisigInstruction
             )
           }) &&
           ix.governanceAction.targetChainId === 'pythnet')
@@ -432,12 +424,10 @@ const Proposal = ({
         </h4>
         <h4 className="h4 font-semibold">
           {uniqueTargetCluster
-            ? `Target Pyth oracle program: ${targetClusters[0]}`
+            ? `Target network: ${targetClusters[0]}`
             : targetClusters.length == 0
-            ? 'No target Pyth oracle program detected'
-            : `Multiple target Pyth oracle programs detected ${targetClusters.join(
-                ' '
-              )}`}
+            ? 'No target network detected'
+            : `Multiple target networks detected ${targetClusters.join(' ')}`}
         </h4>
       </div>
       <div className="col-span-3 my-2 space-y-4 bg-[#1E1B2F] p-4 lg:col-span-2">

+ 13 - 0
governance/xc_admin/packages/xc_admin_frontend/utils/lamportsToSol.ts

@@ -0,0 +1,13 @@
+const TRAILING_ZEROS = new RegExp(/\.?0+$/)
+const SOL_DECIMALS = 9
+
+export function lamportsToSol(lamports: bigint): string {
+  const padded = lamports.toString().padStart(SOL_DECIMALS + 1, '0')
+  return (
+    padded.slice(0, padded.length - SOL_DECIMALS) +
+    ('.' + padded.slice(padded.length - SOL_DECIMALS)).replace(
+      TRAILING_ZEROS,
+      ''
+    )
+  )
+}