Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante committed Jun 10, 2021
1 parent f8cbc5d commit ea719bb
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 11 deletions.
156 changes: 145 additions & 11 deletions ts/src/coder/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import camelCase from "camelcase";
import { Layout } from "buffer-layout";
import * as borsh from "@project-serum/borsh";
import * as bs58 from "bs58";
import { Idl, IdlField, IdlStateMethod } from "../idl";
import { Idl, IdlField, IdlStateMethod, IdlType, IdlTypeDef } from "../idl";
import { IdlCoder } from "./idl";
import { sighash } from "./common";

Expand All @@ -20,40 +20,51 @@ export const SIGHASH_GLOBAL_NAMESPACE = "global";
* Encodes and decodes program instructions.
*/
export class InstructionCoder {
/**
* Instruction args layout. Maps namespaced method
*/
// Instruction args layout. Maps namespaced method
private ixLayout: Map<string, Layout>;

// Base58 encoded sighash to instruction layout.
private sighashLayouts: Map<string, Layout>;
private sighashLayouts: Map<string, { layout: Layout; name: string }>;

public constructor(idl: Idl) {
public constructor(private idl: Idl) {
this.ixLayout = InstructionCoder.parseIxLayout(idl);

const sighashLayouts = new Map<string, Layout>();
const sighashLayouts = new Map();
idl.instructions.forEach((ix) => {
const sh = sighash(SIGHASH_GLOBAL_NAMESPACE, ix.name);
sighashLayouts.set(bs58.encode(sh), this.ixLayout.get(ix.name));
sighashLayouts.set(bs58.encode(sh), {
layout: this.ixLayout.get(ix.name),
name: ix.name,
});
});

if (idl.state) {
idl.state.methods.map((ix) => {
const sh = sighash(SIGHASH_STATE_NAMESPACE, ix.name);
sighashLayouts.set(bs58.encode(sh), this.ixLayout.get(ix.name));
sighashLayouts.set(bs58.encode(sh), {
layout: this.ixLayout.get(ix.name) as Layout,
name: ix.name,
});
});
}

this.sighashLayouts = sighashLayouts;
}

public decode(ix: Buffer | string): Object | undefined {
public decode(ix: Buffer | string): Instruction | null {
if (typeof ix === "string") {
ix = bs58.decode(ix);
}
let sighash = bs58.encode(ix.slice(0, 8));
let data = ix.slice(8);
return this.sighashLayouts.get(sighash)?.decode(data);
const decoder = this.sighashLayouts.get(sighash);
if (!decoder) {
return null;
}
return {
data: decoder.layout.decode(data),
name: decoder.name,
};
}

/**
Expand Down Expand Up @@ -101,4 +112,127 @@ export class InstructionCoder {
// @ts-ignore
return new Map(ixLayouts);
}

public formatFields(
ix: Instruction
): { name: string; type: string; data: string }[] {
const idlIx = this.idl.instructions.filter((i) => ix.name === i.name)[0];
if (idlIx === undefined) {
throw new Error("Invalid instruction given");
}
return idlIx.args.map((idlField) => {
return {
name: idlField.name,
type: this.formatIdlType(idlField.type),
data: this.formatIdlData(
idlField,
ix.data[idlField.name],
this.idl.types
),
};
});
}

private formatIdlType(idlType: IdlType): string {
if (typeof idlType === "string") {
return idlType as string;
}

// @ts-ignore
if (idlType.vec) {
// @ts-ignore
return `vec<${this.formatIdlType(idlType.vec)}>`;
}
// @ts-ignore
if (idlType.option) {
// @ts-ignore
return `option<${this.formatIdlType(idlType.option)}>`;
}
// @ts-ignore
if (idlType.defined) {
// @ts-ignore
return idlType.defined;
}
}

private formatIdlData(
idlField: IdlField,
data: Object,
types?: IdlTypeDef[]
): string {
if (typeof idlField.type === "string") {
return data.toString();
}
// @ts-ignore
if (idlField.type.vec) {
// @ts-ignore
return (
"[" +
data
.map((d) =>
this.formatIdlData(
// @ts-ignore
{ name: "", type: idlField.type.vec },
d
)
)
.join(", ") +
"]"
);
}
// @ts-ignore
if (idlField.type.option) {
// @ts-ignore
return data === null
? "null"
: this.formatIdlData(
// @ts-ignore
{ name: "", type: idlField.type.option },
data
);
}
// @ts-ignore
if (idlField.type.defined) {
if (types === undefined) {
throw new Error("User defined types not provided");
}
// @ts-ignore
const filtered = types.filter((t) => t.name === idlField.type.defined);
if (filtered.length !== 1) {
// @ts-ignore
throw new Error(`Type not found: ${idlField.type.defined}`);
}
return this.formatIdlDataDefined(filtered[0], data, types);
}

return "unknown";
}

private formatIdlDataDefined(
typeDef: IdlTypeDef,
data: Object,
types: IdlTypeDef[]
): string {
console.log(data);
if (typeDef.type.kind === "struct") {
const fields = Object.keys(data)
.map((k) => {
const f = typeDef.type.fields.filter((f) => f.name === k)[0];
if (f === undefined) {
throw new Error("Unable to find type");
}
return k + ": " + this.formatIdlData(f, data[k], types);
})
.join(", ");
return "{ " + fields + " }";
} else {
// todo
return "{}";
}
}
}

export type Instruction = {
name: string;
data: Object;
};
2 changes: 2 additions & 0 deletions ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Coder, {
StateCoder,
TypesCoder,
} from "./coder";
import { Instruction } from "./coder/instruction";
import { Idl } from "./idl";
import workspace from "./workspace";
import * as utils from "./utils";
Expand Down Expand Up @@ -56,6 +57,7 @@ export {
StateCoder,
TypesCoder,
Event,
Instruction,
setProvider,
getProvider,
Provider,
Expand Down

0 comments on commit ea719bb

Please sign in to comment.