ts: Add program.coder.types for encoding/decoding user-defined types (#1931)
This commit is contained in:
parent
b993854767
commit
d83fcbf7bc
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue