ts: Add program.coder.types for encoding/decoding user-defined types (#1931)

This commit is contained in:
Vladimir Guguiev 2022-05-29 22:39:45 +02:00 committed by GitHub
parent b993854767
commit d83fcbf7bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 155 additions and 6 deletions

View File

@ -12,17 +12,18 @@ The minor version will be incremented upon a breaking change and the patch versi
### Features
* lang: Add `PartialEq` and `Eq` for `anchor_lang::Error` ([#1544](https://github.com/project-serum/anchor/pull/1544)).
* cli: Add `--skip-build` to `anchor publish` ([#1786](https://github.
com/project-serum/anchor/pull/1841)).
* cli: Add `--program-keypair` to `anchor deploy` ([#1786](https://github.com/project-serum/anchor/pull/1786)).
* spl: Add more derived traits to `TokenAccount` to `Mint` ([#1818](https://github.com/project-serum/anchor/pull/1818)).
* cli: Add compilation optimizations to cli template ([#1807](https://github.com/project-serum/anchor/pull/1807)).
* cli: `build` now adds docs to idl. This can be turned off with `--no-docs` ([#1561](https://github.com/project-serum/anchor/pull/1561)).
* lang: Add `PartialEq` and `Eq` for `anchor_lang::Error` ([#1544](https://github.com/project-serum/anchor/pull/1544)).
* cli: Add `b` and `t` aliases for `build` and `test` respectively ([#1823](https://github.com/project-serum/anchor/pull/1823)).
* spl: Add more derived traits to `TokenAccount` to `Mint` ([#1818](https://github.com/project-serum/anchor/pull/1818)).
* spl: Add `sync_native` token program CPI wrapper function ([#1833](https://github.com/project-serum/anchor/pull/1833)).
* ts: Implement a coder for system program ([#1920](https://github.com/project-serum/anchor/pull/1920)).
* client: Add send_with_spinner_and_config function to RequestBuilder ([#1926](https://github.com/project-serum/anchor/pull/1926))
* ts: Add `program.coder.types` for encoding/decoding user-defined types ([#1931](https://github.com/project-serum/anchor/pull/1931)).
* client: Add send_with_spinner_and_config function to RequestBuilder ([#1926](https://github.com/project-serum/anchor/pull/1926)).
### Fixes

View File

@ -3,6 +3,7 @@ import { BorshInstructionCoder } from "./instruction.js";
import { BorshAccountsCoder } from "./accounts.js";
import { BorshEventCoder } from "./event.js";
import { BorshStateCoder } from "./state.js";
import { BorshTypesCoder } from "./types.js";
import { Coder } from "../index.js";
export { BorshInstructionCoder } from "./instruction.js";
@ -14,7 +15,9 @@ export { BorshStateCoder, stateDiscriminator } from "./state.js";
* BorshCoder is the default Coder for Anchor programs implementing the
* borsh based serialization interface.
*/
export class BorshCoder<A extends string = string> implements Coder {
export class BorshCoder<A extends string = string, T extends string = string>
implements Coder
{
/**
* Instruction coder.
*/
@ -35,6 +38,11 @@ export class BorshCoder<A extends string = string> implements Coder {
*/
readonly events: BorshEventCoder;
/**
* Coder for user-defined types.
*/
readonly types: BorshTypesCoder<T>;
constructor(idl: Idl) {
this.instruction = new BorshInstructionCoder(idl);
this.accounts = new BorshAccountsCoder(idl);
@ -42,5 +50,6 @@ export class BorshCoder<A extends string = string> implements Coder {
if (idl.state) {
this.state = new BorshStateCoder(idl);
}
this.types = new BorshTypesCoder(idl);
}
}

View File

@ -0,0 +1,52 @@
import { Buffer } from "buffer";
import { Layout } from "buffer-layout";
import { Idl } from "../../idl.js";
import { IdlCoder } from "./idl.js";
import { TypesCoder } from "../index.js";
/**
* Encodes and decodes user-defined types.
*/
export class BorshTypesCoder<N extends string = string> implements TypesCoder {
/**
* Maps type name to a layout.
*/
private typeLayouts: Map<N, Layout>;
/**
* IDL whose types will be coded.
*/
private idl: Idl;
public constructor(idl: Idl) {
if (idl.types === undefined) {
this.typeLayouts = new Map();
return;
}
const layouts: [N, Layout][] = idl.types.map((acc) => {
return [acc.name as N, IdlCoder.typeDefLayout(acc, idl.types)];
});
this.typeLayouts = new Map(layouts);
this.idl = idl;
}
public encode<T = any>(typeName: N, type: T): Buffer {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const layout = this.typeLayouts.get(typeName);
if (!layout) {
throw new Error(`Unknown type: ${typeName}`);
}
const len = layout.encode(type, buffer);
return buffer.slice(0, len);
}
public decode<T = any>(typeName: N, typeData: Buffer): T {
const layout = this.typeLayouts.get(typeName);
if (!layout) {
throw new Error(`Unknown type: ${typeName}`);
}
return layout.decode(typeData);
}
}

View File

@ -8,7 +8,7 @@ export * from "./system/index.js";
/**
* Coder provides a facade for encoding and decoding all IDL related objects.
*/
export interface Coder {
export interface Coder<A extends string = string, T extends string = string> {
/**
* Instruction coder.
*/
@ -17,7 +17,7 @@ export interface Coder {
/**
* Account coder.
*/
readonly accounts: AccountsCoder;
readonly accounts: AccountsCoder<A>;
/**
* Coder for state structs.
@ -28,6 +28,11 @@ export interface Coder {
* Coder for events.
*/
readonly events: EventCoder;
/**
* Coder for user-defined types.
*/
readonly types: TypesCoder<T>;
}
export interface StateCoder {
@ -53,3 +58,8 @@ export interface EventCoder {
log: string
): Event<E, T> | null;
}
export interface TypesCoder<N extends string = string> {
encode<T = any>(typeName: N, type: T): Buffer;
decode<T = any>(typeName: N, typeData: Buffer): T;
}

View File

@ -4,6 +4,7 @@ import { SplTokenInstructionCoder } from "./instruction.js";
import { SplTokenStateCoder } from "./state.js";
import { SplTokenAccountsCoder } from "./accounts.js";
import { SplTokenEventsCoder } from "./events.js";
import { SplTokenTypesCoder } from "./types.js";
/**
* Coder for the SPL token program.
@ -13,11 +14,13 @@ export class SplTokenCoder implements Coder {
readonly accounts: SplTokenAccountsCoder;
readonly state: SplTokenStateCoder;
readonly events: SplTokenEventsCoder;
readonly types: SplTokenTypesCoder;
constructor(idl: Idl) {
this.instruction = new SplTokenInstructionCoder(idl);
this.accounts = new SplTokenAccountsCoder(idl);
this.events = new SplTokenEventsCoder(idl);
this.state = new SplTokenStateCoder(idl);
this.types = new SplTokenTypesCoder(idl);
}
}

View File

@ -0,0 +1,13 @@
import { TypesCoder } from "../index.js";
import { Idl } from "../../idl.js";
export class SplTokenTypesCoder implements TypesCoder {
constructor(_idl: Idl) {}
encode<T = any>(_name: string, _type: T): Buffer {
throw new Error("SPL token does not have user-defined types");
}
decode<T = any>(_name: string, _typeData: Buffer): T {
throw new Error("SPL token does not have user-defined types");
}
}

View File

@ -4,6 +4,7 @@ import { SystemInstructionCoder } from "./instruction.js";
import { SystemStateCoder } from "./state.js";
import { SystemAccountsCoder } from "./accounts.js";
import { SystemEventsCoder } from "./events.js";
import { SystemTypesCoder } from "./types.js";
/**
* Coder for the System program.
@ -13,11 +14,13 @@ export class SystemCoder implements Coder {
readonly accounts: SystemAccountsCoder;
readonly state: SystemStateCoder;
readonly events: SystemEventsCoder;
readonly types: SystemTypesCoder;
constructor(idl: Idl) {
this.instruction = new SystemInstructionCoder(idl);
this.accounts = new SystemAccountsCoder(idl);
this.events = new SystemEventsCoder(idl);
this.state = new SystemStateCoder(idl);
this.types = new SystemTypesCoder(idl);
}
}

View File

@ -0,0 +1,13 @@
import { TypesCoder } from "../index.js";
import { Idl } from "../../idl.js";
export class SystemTypesCoder implements TypesCoder {
constructor(_idl: Idl) {}
encode<T = any>(_name: string, _type: T): Buffer {
throw new Error("System does not have user-defined types");
}
decode<T = any>(_name: string, _typeData: Buffer): T {
throw new Error("System does not have user-defined types");
}
}

View File

@ -0,0 +1,45 @@
import * as assert from "assert";
import { BorshCoder } from "../src";
describe("coder.types", () => {
test("Can encode and decode user-defined types", () => {
const idl = {
version: "0.0.0",
name: "basic_0",
instructions: [
{
name: "initialize",
accounts: [],
args: [],
},
],
types: [
{
name: "MintInfo",
type: {
kind: "struct" as const,
fields: [
{
name: "minted",
type: "bool" as const,
},
{
name: "metadataUrl",
type: "string" as const,
},
],
},
},
],
};
const coder = new BorshCoder(idl);
const mintInfo = {
minted: true,
metadataUrl: "hello",
};
const encoded = coder.types.encode("MintInfo", mintInfo);
assert.deepEqual(coder.types.decode("MintInfo", encoded), mintInfo);
});
});