diff --git a/CHANGELOG.md b/CHANGELOG.md index 622d06b75..6b96be7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/ts/src/coder/borsh/index.ts b/ts/src/coder/borsh/index.ts index 4b84fd2ed..708ff2c03 100644 --- a/ts/src/coder/borsh/index.ts +++ b/ts/src/coder/borsh/index.ts @@ -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 implements Coder { +export class BorshCoder + implements Coder +{ /** * Instruction coder. */ @@ -35,6 +38,11 @@ export class BorshCoder implements Coder { */ readonly events: BorshEventCoder; + /** + * Coder for user-defined types. + */ + readonly types: BorshTypesCoder; + constructor(idl: Idl) { this.instruction = new BorshInstructionCoder(idl); this.accounts = new BorshAccountsCoder(idl); @@ -42,5 +50,6 @@ export class BorshCoder implements Coder { if (idl.state) { this.state = new BorshStateCoder(idl); } + this.types = new BorshTypesCoder(idl); } } diff --git a/ts/src/coder/borsh/types.ts b/ts/src/coder/borsh/types.ts new file mode 100644 index 000000000..848e4faa3 --- /dev/null +++ b/ts/src/coder/borsh/types.ts @@ -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 implements TypesCoder { + /** + * Maps type name to a layout. + */ + private typeLayouts: Map; + + /** + * 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(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(typeName: N, typeData: Buffer): T { + const layout = this.typeLayouts.get(typeName); + if (!layout) { + throw new Error(`Unknown type: ${typeName}`); + } + return layout.decode(typeData); + } +} diff --git a/ts/src/coder/index.ts b/ts/src/coder/index.ts index 29a15ce16..8dc630a6f 100644 --- a/ts/src/coder/index.ts +++ b/ts/src/coder/index.ts @@ -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 { /** * Instruction coder. */ @@ -17,7 +17,7 @@ export interface Coder { /** * Account coder. */ - readonly accounts: AccountsCoder; + readonly accounts: AccountsCoder; /** * 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; } export interface StateCoder { @@ -53,3 +58,8 @@ export interface EventCoder { log: string ): Event | null; } + +export interface TypesCoder { + encode(typeName: N, type: T): Buffer; + decode(typeName: N, typeData: Buffer): T; +} diff --git a/ts/src/coder/spl-token/index.ts b/ts/src/coder/spl-token/index.ts index 188b32b28..f7d33e798 100644 --- a/ts/src/coder/spl-token/index.ts +++ b/ts/src/coder/spl-token/index.ts @@ -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); } } diff --git a/ts/src/coder/spl-token/types.ts b/ts/src/coder/spl-token/types.ts new file mode 100644 index 000000000..fee1e00de --- /dev/null +++ b/ts/src/coder/spl-token/types.ts @@ -0,0 +1,13 @@ +import { TypesCoder } from "../index.js"; +import { Idl } from "../../idl.js"; + +export class SplTokenTypesCoder implements TypesCoder { + constructor(_idl: Idl) {} + + encode(_name: string, _type: T): Buffer { + throw new Error("SPL token does not have user-defined types"); + } + decode(_name: string, _typeData: Buffer): T { + throw new Error("SPL token does not have user-defined types"); + } +} diff --git a/ts/src/coder/system/index.ts b/ts/src/coder/system/index.ts index be8db86ad..f4efa131c 100644 --- a/ts/src/coder/system/index.ts +++ b/ts/src/coder/system/index.ts @@ -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); } } diff --git a/ts/src/coder/system/types.ts b/ts/src/coder/system/types.ts new file mode 100644 index 000000000..65d63b2ad --- /dev/null +++ b/ts/src/coder/system/types.ts @@ -0,0 +1,13 @@ +import { TypesCoder } from "../index.js"; +import { Idl } from "../../idl.js"; + +export class SystemTypesCoder implements TypesCoder { + constructor(_idl: Idl) {} + + encode(_name: string, _type: T): Buffer { + throw new Error("System does not have user-defined types"); + } + decode(_name: string, _typeData: Buffer): T { + throw new Error("System does not have user-defined types"); + } +} diff --git a/ts/tests/coder-types.spec.ts b/ts/tests/coder-types.spec.ts new file mode 100644 index 000000000..4e2b93c8f --- /dev/null +++ b/ts/tests/coder-types.spec.ts @@ -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); + }); +});