Skip to content

Commit

Permalink
feat: support event parsing in shank (#31)
Browse files Browse the repository at this point in the history
* feat: support event parsing in shank

* docs(changeset): support shank event parsing

* build: bump up kinobi-lite
  • Loading branch information
doodoo-aihc authored May 25, 2024
1 parent 7a523e4 commit 30f749f
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 20 deletions.
6 changes: 6 additions & 0 deletions .changeset/three-moles-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solanafm/explorer-kit": patch
"@solanafm/explorer-kit-idls": patch
---

support shank event parsing
2 changes: 1 addition & 1 deletion packages/explorerkit-idls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"@coral-xyz/anchor": "^0.29.0",
"@coral-xyz/anchor-new": "npm:@coral-xyz/anchor@^0.30.0",
"@solanafm/kinobi-lite": "^0.12.0",
"@solanafm/kinobi-lite": "^0.12.3",
"axios": "^1.3.3"
}
}
2 changes: 1 addition & 1 deletion packages/explorerkit-translator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@metaplex-foundation/umi": "^0.8.6",
"@metaplex-foundation/umi-serializers": "^0.8.5",
"@solana/spl-type-length-value": "^0.1.0",
"@solanafm/kinobi-lite": "^0.12.0",
"@solanafm/kinobi-lite": "^0.12.3",
"@solanafm/utils": "^1.0.5"
}
}
25 changes: 25 additions & 0 deletions packages/explorerkit-translator/src/helpers/KinobiTreeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
EnumVariantTypeNode,
getAllAccounts,
getAllDefinedTypes,
getAllEvents,
getAllInstructions,
Idl,
isStructTypeNode,
Expand Down Expand Up @@ -322,6 +323,30 @@ export class KinobiTreeGenerator {

return typeLayout;

case KinobiTreeGeneratorType.EVENTS:
const eventNodes = getAllEvents(this.rootNode);
const eventsLayout = new Map<string | number, FMShankSerializer>();

eventNodes.forEach((eventNode, index) => {
if (isStructTypeNode(eventNode.dataArgs.struct)) {
const serializer = KinobiTreeGenerator.createSerializer(
eventNode.dataArgs.struct,
typeNodes,
eventNode.dataArgs.name,
treeGeneratorType
);
const fmShankSerializer: FMShankSerializer = {
serializer: serializer[1],
instructionName: serializer[0],
};

// TODO: Will try to strongly take reference from the IDL in the future
const eventDiscriminator = index;
eventsLayout.set(eventDiscriminator, fmShankSerializer);
}
});

return eventsLayout;
default:
return new Map();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BorshEventCoder, BorshInstructionCoder } from "@coral-xyz/anchor";

import { createAnchorEventParser } from "../parsers/v2/event";
import { createAnchorEventParser, createShankEventParser } from "../parsers/v2/event";
import { createBubblegumEventParser } from "../parsers/v2/event/anchor/bubblegum";
import { createSPLCompEventParser } from "../parsers/v2/event/anchor/spl-compression";
import { createTCompEventParser } from "../parsers/v2/event/anchor/tcomp";
Expand Down Expand Up @@ -42,6 +42,12 @@ export const createEventParser = (idlItem: IdlItem, programHash: string) => {
return null;
}

case "shank":
switch (programHash) {
default:
return createShankEventParser(idlItem);
}

default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { createAnchorEventParser } from "./anchor";
export { createShankEventParser } from "./shank";
60 changes: 60 additions & 0 deletions packages/explorerkit-translator/src/parsers/v2/event/shank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Idl } from "@solanafm/kinobi-lite";
import { convertBNToNumberInObject } from "@solanafm/utils";

import { EventParserInterface, ParserOutput, ParserType } from "../../..";
import { mapDataTypeToName } from "../../../helpers/idl";
import { KinobiTreeGenerator } from "../../../helpers/KinobiTreeGenerator";
import { IdlItem } from "../../../types/IdlItem";
import { FMShankSerializer, KinobiTreeGeneratorType } from "../../../types/KinobiTreeGenerator";

export const createShankEventParser: (idlItem: IdlItem) => EventParserInterface = (idlItem: IdlItem) => {
const idl = idlItem.idl as Idl;
const eventsLayout = new KinobiTreeGenerator(idl).constructLayout(KinobiTreeGeneratorType.EVENTS);

const parseEvents = (eventData: string, mapTypes?: boolean): ParserOutput => {
try {
if (eventsLayout) {
let dataBuffer: Buffer = Buffer.from(eventData, "base64");
let eventSerializer: FMShankSerializer | undefined = undefined;
if (dataBuffer.byteLength > 0) {
// Let's assume all the events have enums as u8 for now, if we need to support other discriminants we will need to add them from Kinobi
const borshDiscriminant = Buffer.from(dataBuffer).readUint8(0);
eventSerializer = eventsLayout.get(borshDiscriminant);
}

if (eventSerializer) {
const decodedEventData = eventSerializer.serializer?.deserialize(dataBuffer);

if (decodedEventData && decodedEventData[0]) {
const filteredIdlEvent =
idl.events?.filter(
(event) => event.name.toLowerCase() === eventSerializer.instructionName.toLowerCase()
) ?? [];

if (mapTypes) {
decodedEventData[0] = mapDataTypeToName(decodedEventData[0], filteredIdlEvent[0]?.fields);
}

decodedEventData[0] = convertBNToNumberInObject(decodedEventData[0]);

return {
name: eventSerializer.instructionName,
data: convertBNToNumberInObject(decodedEventData[0]),
type: ParserType.EVENT,
};
}
}
}

return null;
} catch (error) {
console.error(error);
return null;
}
};

return {
eventsLayout,
parseEvents,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export enum KinobiTreeGeneratorType {
ACCOUNTS,
INSTRUCTIONS,
TYPES,
EVENTS,
}
54 changes: 54 additions & 0 deletions packages/explorerkit-translator/tests/v2/event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,57 @@ describe("createAnchorEventParser", () => {
}
});
});

describe("createShankEventParser", () => {
it("should construct an shank event parser for a given valid IDL", async () => {
const programId = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const idlItem = await getProgramIdl(programId);

if (idlItem) {
const parser = new SolanaFMParser(idlItem, programId);
const eventParser = parser.createParser(ParserType.EVENT);

expect(eventParser).not.toBeNull();
expect(checkIfInstructionParser(eventParser)).toBe(false);
expect(checkIfEventParser(eventParser)).toBe(true);
}
});

it("should construct an anchor event parser for a given valid IDL and parses the event data", async () => {
const programId = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const eventData = "A8Ce5gUAAAAAnhJhfQsAAAACAAAAAAAAAMCe5gUAAAAAeU9gpSYAAAD7ESaLIGIAAJn+yu8OAAAA";
const idlItem = await getProgramIdl(programId);

if (idlItem) {
const parser = new SolanaFMParser(idlItem, programId);
const eventParser = parser.createParser(ParserType.EVENT);

if (eventParser && checkIfEventParser(eventParser)) {
const decodedData = eventParser.parseEvents(eventData);
expect(decodedData).not.toBeNull();
expect(decodedData?.type).toBe("event");
expect(decodedData?.name).toBe("swapBaseIn");
}
}
});

it("should construct an anchor event parser for a given valid IDL and parses the event data and properly map the data type with the given IDL", async () => {
const programId = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const eventData = "A8Ce5gUAAAAAnhJhfQsAAAACAAAAAAAAAMCe5gUAAAAAeU9gpSYAAAD7ESaLIGIAAJn+yu8OAAAA";

const idlItem = await getProgramIdl(programId);

if (idlItem) {
const parser = new SolanaFMParser(idlItem, programId);
const eventParser = parser.createParser(ParserType.EVENT);

if (eventParser && checkIfEventParser(eventParser)) {
const decodedData = eventParser.parseEvents(eventData, true);
expect(decodedData).not.toBeNull();
expect(decodedData?.type).toBe("event");
expect(decodedData?.name).toBe("swapBaseIn");
expect(decodedData?.data["minimumAmountOut"].type).toBe("u64");
}
}
});
});
61 changes: 44 additions & 17 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 30f749f

Please sign in to comment.