This commit is contained in:
chalda 2024-04-16 08:26:21 +02:00 committed by GitHub
commit d443d063ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 180 additions and 120 deletions

View File

@ -58,13 +58,27 @@ describe("Events", () => {
const ixData = anchor.utils.bytes.bs58.decode(
txResult.meta.innerInstructions[0].instructions[0].data
);
const eventData = anchor.utils.bytes.base64.encode(ixData.slice(8));
const event = program.coder.events.decode(eventData);
assertCpiEvent(event);
// ensure the coder works directly with cpiEvent
const cpiEvent = program.coder.events.decode(
txResult.meta.innerInstructions[0].instructions[0].data
);
assertCpiEvent(cpiEvent);
const cpiEvents = program.coder.events.parseCpiEvents(txResult);
assert(cpiEvents.length === 1);
assertCpiEvent(cpiEvents[0]);
});
function assertCpiEvent(event: any) {
assert.strictEqual(event.name, "myOtherEvent");
assert.strictEqual(event.data.label, "cpi");
assert.strictEqual((event.data.data as anchor.BN).toNumber(), 7);
});
}
it("Throws on unauthorized invocation", async () => {
const tx = new anchor.web3.Transaction();

View File

@ -1,11 +1,28 @@
import { Buffer } from "buffer";
import { Layout } from "buffer-layout";
import * as base64 from "../../utils/bytes/base64.js";
import * as bs58 from "../../utils/bytes/bs58.js";
import { Idl } from "../../idl.js";
import { IdlCoder } from "./idl.js";
import { EventCoder } from "../index.js";
import BN from "bn.js";
import {
CompiledInnerInstruction,
PublicKey,
Transaction,
TransactionResponse,
VersionedTransactionResponse,
} from "@solana/web3.js";
export class BorshEventCoder implements EventCoder {
/**
* CPI event discriminator.
* https://github.com/coral-xyz/anchor/blob/v0.29.0/lang/src/event.rs
*/
private static eventIxTag: BN = new BN("1d9acb512ea545e4", "hex");
public address: PublicKey;
/**
* Maps account type identifier to a layout.
*/
@ -42,12 +59,26 @@ export class BorshEventCoder implements EventCoder {
ev.name,
])
);
this.address = new PublicKey(idl.address);
}
/**
*
* @param log base 64 encoded log data from transaction message
* or base58 encoded transaction message or CPI encoded event data
* @returns decoded event object or null
*/
public decode(log: string): {
name: string;
data: any;
} | null {
const transactionCpiData = this.parseAsTransactionCpiData(log);
if (transactionCpiData !== null) {
// log parsed to be CPI data, recursive call stripped event data
return this.decode(transactionCpiData);
}
let logArr: Buffer;
// This will throw if log length is not a multiple of 4.
try {
@ -55,9 +86,9 @@ export class BorshEventCoder implements EventCoder {
} catch (e) {
return null;
}
const disc = base64.encode(logArr.slice(0, 8));
// Only deserialize if the discriminator implies a proper event.
const disc = base64.encode(logArr.slice(0, 8));
const eventName = this.discriminators.get(disc);
if (!eventName) {
return null;
@ -70,4 +101,59 @@ export class BorshEventCoder implements EventCoder {
const data = layout.decode(logArr.slice(8));
return { data, name: eventName };
}
/**
* Check the log data to be transaction CPI event:
* Expected data format:
* < cpi event discriminator | event name discriminator | event data >
* If matches cpi event discriminator
* < event name | event data> base64 formatted is returned
* otherwise null is returned.
*/
parseAsTransactionCpiData(log: string): string | null {
let encodedLog: Buffer;
try {
// verification if log is transaction cpi data encoded with base58
encodedLog = bs58.decode(log);
} catch (e) {
return null;
}
const disc = encodedLog.slice(0, 8);
if (disc.equals(BorshEventCoder.eventIxTag.toBuffer("le"))) {
// after CPI tag data follows in format of standard event
return base64.encode(encodedLog.slice(8));
} else {
return null;
}
}
public parseCpiEvents(
transactionResponse: VersionedTransactionResponse | TransactionResponse
): { name: string; data: any }[] {
const events: { name: string; data: any }[] = [];
const inner: CompiledInnerInstruction[] =
transactionResponse?.meta?.innerInstructions ?? [];
const idlProgramId = this.address;
for (let i = 0; i < inner.length; i++) {
for (let j = 0; j < inner[i].instructions.length; j++) {
const ix = inner[i].instructions[j];
const programPubkey =
transactionResponse?.transaction.message.staticAccountKeys[
ix.programIdIndex
];
if (
programPubkey === undefined ||
!programPubkey.equals(idlProgramId)
) {
// we are at instructions that does not match the linked program
continue;
}
const event = this.decode(ix.data);
if (event) {
events.push(event);
}
}
}
return events;
}
}

View File

@ -1,3 +1,4 @@
import { VersionedTransactionResponse } from "@solana/web3.js";
import { IdlEvent } from "../idl.js";
import { Event } from "../program/event.js";
@ -50,6 +51,24 @@ export interface EventCoder {
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
log: string
): Event<E, T> | null;
parseCpiEvents<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
transactionResponse: VersionedTransactionResponse
): Event<E, T>[];
}
export abstract class NoEventCoder implements EventCoder {
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error(`${this.constructor} program does not have events`);
}
parseCpiEvents<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_transactionResponse: VersionedTransactionResponse
): Event<E, T>[] {
throw new Error(`${this.constructor} program does not have CPI events`);
}
}
export interface TypesCoder<N extends string = string> {

View File

@ -2,6 +2,7 @@ import { EventCoder } from "../index.js";
import { Idl } from "../../idl.js";
import { Event } from "../../program/event";
import { IdlEvent } from "../../idl";
import { VersionedTransactionResponse } from "@solana/web3.js";
export class SystemEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
@ -9,6 +10,11 @@ export class SystemEventsCoder implements EventCoder {
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("System program does not have events");
throw new Error("SystemProgram does not have events.");
}
parseCpiEvents<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_transactionResponse: VersionedTransactionResponse
): Event<E, T>[] {
throw new Error("SystemProgram does not have CPI events.");
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplAssociatedTokenAccountEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplAssociatedTokenAccount program does not have events");
export class SplAssociatedTokenAccountEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplBinaryOptionEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplBinaryOption program does not have events");
export class SplBinaryOptionEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplBinaryOraclePairEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplBinaryOraclePair program does not have events");
export class SplBinaryOraclePairEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplFeatureProposalEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplFeatureProposal program does not have events");
export class SplFeatureProposalEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplGovernanceEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplGovernance program does not have events");
export class SplGovernanceEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplMemoEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplMemo program does not have events");
export class SplMemoEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplNameServiceEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplNameService program does not have events");
export class SplNameServiceEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplRecordEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplRecord program does not have events");
export class SplRecordEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplStakePoolEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplStakePool program does not have events");
export class SplStakePoolEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplStatelessAsksEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplStatelessAsks program does not have events");
export class SplStatelessAsksEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplTokenLendingEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplTokenLending program does not have events");
export class SplTokenLendingEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplTokenSwapEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplTokenSwap program does not have events");
export class SplTokenSwapEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}

View File

@ -1,12 +1,7 @@
import { Idl, Event, EventCoder } from "@coral-xyz/anchor";
import { IdlEvent } from "@coral-xyz/anchor/dist/cjs/idl";
import { Idl, EventCoder, NoEventCoder } from "@coral-xyz/anchor";
export class SplTokenEventsCoder implements EventCoder {
constructor(_idl: Idl) {}
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
_log: string
): Event<E, T> | null {
throw new Error("SplToken program does not have events");
export class SplTokenEventsCoder extends NoEventCoder implements EventCoder {
constructor(_idl: Idl) {
super();
}
}