Преглед изворни кода

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

Vladimir Guguiev пре 3 година
родитељ
комит
d83fcbf7bc

+ 4 - 3
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
 

+ 10 - 1
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<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);
   }
 }

+ 52 - 0
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<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);
+  }
+}

+ 12 - 2
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<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;
+}

+ 3 - 0
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);
   }
 }

+ 13 - 0
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<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");
+  }
+}

+ 3 - 0
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);
   }
 }

+ 13 - 0
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<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");
+  }
+}

+ 45 - 0
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);
+  });
+});