From 832ef852fbab9de2395ed0654198e90936e559ae Mon Sep 17 00:00:00 2001 From: Noah Gundotra Date: Mon, 26 Aug 2024 17:13:33 -0400 Subject: [PATCH] Add support for showing self-cpi (#364) Fixes issue where events were not parsed for self-cpi's executed via `emit_cpi!` in Anchor. See example: https://explorer.solana.com/tx/2iofwLXfEDjEqvGZNMuCzZF8TEGyvfogCVf4oDnAATLpGkUbSfPe7ZBuqNoNXzaV2Xb33AT7PRYd9HztdbTvF3GR?cluster=devnet --- .../instruction/AnchorDetailsCard.tsx | 34 ++++++++++++++----- app/utils/anchor.tsx | 11 ++++++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/app/components/instruction/AnchorDetailsCard.tsx b/app/components/instruction/AnchorDetailsCard.tsx index af43bdd2..22186519 100644 --- a/app/components/instruction/AnchorDetailsCard.tsx +++ b/app/components/instruction/AnchorDetailsCard.tsx @@ -1,11 +1,12 @@ import { Address } from '@components/common/Address'; -import { BorshInstructionCoder, Idl, Instruction, Program } from '@project-serum/anchor'; -import { IdlInstruction } from '@project-serum/anchor/dist/cjs/idl'; +import { BorshEventCoder, BorshInstructionCoder, Idl, Instruction, Program } from '@project-serum/anchor'; +import { IdlEvent, IdlInstruction } from '@project-serum/anchor/dist/cjs/idl'; import { SignatureResult, TransactionInstruction } from '@solana/web3.js'; import { getAnchorAccountsFromInstruction, getAnchorNameForInstruction, getAnchorProgramName, + instructionIsSelfCPI, mapIxArgsToRows, } from '@utils/anchor'; import { camelToTitleCase } from '@utils/index'; @@ -48,12 +49,29 @@ function AnchorDetails({ ix, anchorProgram }: { ix: TransactionInstruction; anch let decodedIxData: Instruction | null = null; let ixDef: IdlInstruction | undefined; if (anchorProgram) { - const coder = new BorshInstructionCoder(anchorProgram.idl); - decodedIxData = coder.decode(ix.data); - if (decodedIxData) { - ixDef = anchorProgram.idl.instructions.find(ixDef => ixDef.name === decodedIxData?.name); - if (ixDef) { - ixAccounts = getAnchorAccountsFromInstruction(decodedIxData, anchorProgram); + let coder: BorshInstructionCoder | BorshEventCoder; + if (instructionIsSelfCPI(ix.data)) { + coder = new BorshEventCoder(anchorProgram.idl); + decodedIxData = coder.decode(ix.data.slice(8).toString('base64')); + const ixEventDef = anchorProgram.idl.events?.find( + ixDef => ixDef.name === decodedIxData?.name + ) as IdlEvent; + + // Remap the event definition to an instruction definition + ixDef = { ...ixEventDef, accounts: [], args: ixEventDef.fields }; + + // Self-CPI instructions have 1 account called the eventAuthority + // https://github.com/coral-xyz/anchor/blob/04985802587c693091f836e0083e4412148c0ca6/lang/attribute/event/src/lib.rs#L165 + ixAccounts = [{ isMut: false, isSigner: true, name: 'eventAuthority' }]; + } else { + coder = new BorshInstructionCoder(anchorProgram.idl); + decodedIxData = coder.decode(ix.data); + + if (decodedIxData) { + ixDef = anchorProgram.idl.instructions.find(ixDef => ixDef.name === decodedIxData?.name); + if (ixDef) { + ixAccounts = getAnchorAccountsFromInstruction(decodedIxData, anchorProgram); + } } } } diff --git a/app/utils/anchor.tsx b/app/utils/anchor.tsx index adc55ab6..076924ba 100644 --- a/app/utils/anchor.tsx +++ b/app/utils/anchor.tsx @@ -10,6 +10,13 @@ import React, { Fragment, ReactNode, useState } from 'react'; import { ChevronDown, ChevronUp, CornerDownRight } from 'react-feather'; import ReactJson from 'react-json-view'; +const ANCHOR_SELF_CPI_TAG = Buffer.from('1d9acb512ea545e4', 'hex').reverse(); +const ANCHOR_SELF_CPI_NAME = 'Anchor Self Invocation'; + +export function instructionIsSelfCPI(ixData: Buffer): boolean { + return ixData.slice(0, 8).equals(ANCHOR_SELF_CPI_TAG); +} + export function getAnchorProgramName(program: Program | null): string | undefined { return program && 'name' in program.idl ? snakeToTitleCase(program.idl.name) : undefined; } @@ -38,6 +45,10 @@ export function ProgramName({ programId, cluster, url }: { programId: PublicKey; } export function getAnchorNameForInstruction(ix: TransactionInstruction, program: Program): string | null { + if (instructionIsSelfCPI(ix.data)) { + return ANCHOR_SELF_CPI_NAME; + } + const coder = new BorshInstructionCoder(program.idl); const decodedIx = coder.decode(ix.data);