From c967aa623276c13631ab23794ce21b9ead5710ee Mon Sep 17 00:00:00 2001
From: Yonghui Lin <homura.dev@gmail.com>
Date: Mon, 18 Jul 2022 14:30:56 +0800
Subject: [PATCH] feat(codec): molecule unpack works with BytesLike (#365)

* feat(codec): molecule unpack works with BytesLike

* refactor(codec): rm unnecessary XLike codec
---
 packages/codec/src/base.ts              | 17 ++++++-----------
 packages/codec/src/blockchain.ts        | 24 ++++++++++++++++++------
 packages/codec/src/molecule/helper.ts   | 15 ++++++++++-----
 packages/codec/src/molecule/layout.ts   | 25 ++++++++++++-------------
 packages/codec/src/number/uint.ts       |  4 ++--
 packages/codec/tests/blockchain.test.ts | 13 ++++++++++++-
 6 files changed, 60 insertions(+), 38 deletions(-)

diff --git a/packages/codec/src/base.ts b/packages/codec/src/base.ts
index ac9a17231..f7a4073e9 100644
--- a/packages/codec/src/base.ts
+++ b/packages/codec/src/base.ts
@@ -49,7 +49,7 @@ export type UnpackParam<T extends AnyCodec> = T extends Codec<
   ? Unpackable
   : never;
 
-export type BytesCodec<Unpacked = any, Packable = Unpacked> = Codec<
+export type Uint8ArrayCodec<Unpacked = any, Packable = Unpacked> = Codec<
   Uint8Array,
   Unpacked,
   Packable
@@ -57,7 +57,7 @@ export type BytesCodec<Unpacked = any, Packable = Unpacked> = Codec<
 
 export type BytesLike = ArrayLike<number> | ArrayBuffer | string;
 
-export type BytesLikeCodec<Unpacked = any, Packable = Unpacked> = Codec<
+export type BytesCodec<Unpacked = any, Packable = Unpacked> = Codec<
   Uint8Array,
   Unpacked,
   Packable,
@@ -69,8 +69,8 @@ export type BytesLikeCodec<Unpacked = any, Packable = Unpacked> = Codec<
  * @param codec
  */
 export function createBytesCodec<Unpacked, Packable = Unpacked>(
-  codec: BytesCodec<Unpacked, Packable>
-): BytesLikeCodec<Unpacked, Packable> {
+  codec: Uint8ArrayCodec<Unpacked, Packable>
+): BytesCodec<Unpacked, Packable> {
   return {
     pack: (unpacked) => codec.pack(unpacked),
     unpack: (bytesLike) => codec.unpack(bytify(bytesLike)),
@@ -88,11 +88,6 @@ export type FixedBytesCodec<Unpacked = any, Packable = Unpacked> = BytesCodec<
 > &
   Fixed;
 
-export type FixedBytesLikeCodec<
-  Unpacked = any,
-  Packable = Unpacked
-> = BytesLikeCodec<Unpacked, Packable> & Fixed;
-
 export function isFixedCodec<T>(
   codec: BytesCodec<T>
 ): codec is FixedBytesCodec<T> {
@@ -100,8 +95,8 @@ export function isFixedCodec<T>(
 }
 
 export function createFixedBytesCodec<Unpacked, Packable = Unpacked>(
-  codec: BytesCodec<Unpacked, Packable> & { byteLength: number }
-): FixedBytesLikeCodec<Unpacked, Packable> {
+  codec: Uint8ArrayCodec<Unpacked, Packable> & { byteLength: number }
+): FixedBytesCodec<Unpacked, Packable> {
   const byteLength = codec.byteLength;
   return {
     __isFixedCodec__: true,
diff --git a/packages/codec/src/blockchain.ts b/packages/codec/src/blockchain.ts
index 0f200de74..6ba7865c0 100644
--- a/packages/codec/src/blockchain.ts
+++ b/packages/codec/src/blockchain.ts
@@ -1,9 +1,11 @@
 import {
   AnyCodec,
   BytesCodec,
+  BytesLike,
   createBytesCodec,
   createFixedBytesCodec,
   FixedBytesCodec,
+  PackParam,
   UnpackResult,
 } from "./base";
 import { bytify, hexify } from "./bytes";
@@ -47,11 +49,18 @@ export function WitnessArgsOf<
   lock: LockCodec;
   input_type: InputTypeCodec;
   output_type: OutputTypeCodec;
-}): BytesCodec<{
-  lock?: UnpackResult<LockCodec>;
-  input_type?: UnpackResult<InputTypeCodec>;
-  output_type?: UnpackResult<OutputTypeCodec>;
-}> {
+}): BytesCodec<
+  {
+    lock?: UnpackResult<LockCodec>;
+    input_type?: UnpackResult<InputTypeCodec>;
+    output_type?: UnpackResult<OutputTypeCodec>;
+  },
+  {
+    lock?: PackParam<LockCodec>;
+    input_type?: PackParam<InputTypeCodec>;
+    output_type?: PackParam<OutputTypeCodec>;
+  }
+> {
   return table(
     {
       lock: option(byteVecOf(payload.lock)),
@@ -62,7 +71,10 @@ export function WitnessArgsOf<
   );
 }
 
-const HexifyCodec = createBytesCodec<string>({ pack: bytify, unpack: hexify });
+const HexifyCodec = createBytesCodec<string, BytesLike>({
+  pack: bytify,
+  unpack: hexify,
+});
 
 /**
  *
diff --git a/packages/codec/src/molecule/helper.ts b/packages/codec/src/molecule/helper.ts
index c3f8299ce..ea6d8bd51 100644
--- a/packages/codec/src/molecule/helper.ts
+++ b/packages/codec/src/molecule/helper.ts
@@ -1,6 +1,11 @@
 import { assertBufferLength, assertMinBufferLength } from "../utils";
 import { concat } from "../bytes";
-import { BytesCodec, createFixedBytesCodec, FixedBytesCodec } from "../base";
+import {
+  BytesCodec,
+  createBytesCodec,
+  createFixedBytesCodec,
+  FixedBytesCodec,
+} from "../base";
 import { Uint32LE } from "../number";
 
 /**
@@ -30,8 +35,8 @@ export function byteOf<T>(codec: BytesCodec<T>): FixedBytesCodec<T> {
  * a helper function to create custom codec of `vector Bytes <byte>`
  * @param codec
  */
-export function byteVecOf<T>(codec: BytesCodec<T>): BytesCodec<T> {
-  return {
+export const byteVecOf = <T>(codec: BytesCodec<T>): BytesCodec<T> => {
+  return createBytesCodec({
     pack(unpacked) {
       const payload = codec.pack(unpacked);
       const header = Uint32LE.pack(payload.byteLength);
@@ -44,5 +49,5 @@ export function byteVecOf<T>(codec: BytesCodec<T>): BytesCodec<T> {
       assertBufferLength(packed.slice(4), header);
       return codec.unpack(packed.slice(4));
     },
-  };
-}
+  });
+};
diff --git a/packages/codec/src/molecule/layout.ts b/packages/codec/src/molecule/layout.ts
index ae0215cbf..e12b95345 100644
--- a/packages/codec/src/molecule/layout.ts
+++ b/packages/codec/src/molecule/layout.ts
@@ -11,7 +11,7 @@
  */
 
 import type { BytesCodec, Fixed, FixedBytesCodec, UnpackResult } from "../base";
-import { createFixedBytesCodec, isFixedCodec } from "../base";
+import { createBytesCodec, createFixedBytesCodec, isFixedCodec } from "../base";
 import { Uint32LE } from "../number";
 import { concat } from "../bytes";
 
@@ -48,8 +48,7 @@ export function array<T extends FixedBytesCodec>(
   itemCodec: T,
   itemCount: number
 ): ArrayCodec<T> & Fixed {
-  return Object.freeze({
-    __isFixedCodec__: true,
+  return createFixedBytesCodec({
     byteLength: itemCodec.byteLength * itemCount,
     pack(items) {
       const itemsBuf = items.map((item) => itemCodec.pack(item));
@@ -123,7 +122,7 @@ export function struct<T extends Record<string, FixedBytesCodec>>(
 }
 
 export function fixvec<T extends FixedBytesCodec>(itemCodec: T): ArrayCodec<T> {
-  return {
+  return createBytesCodec({
     pack(items) {
       return concat(
         Uint32LE.pack(items.length),
@@ -142,11 +141,11 @@ export function fixvec<T extends FixedBytesCodec>(itemCodec: T): ArrayCodec<T> {
       const itemCount = Uint32LE.unpack(buf.slice(0, 4));
       return array(itemCodec, itemCount).unpack(buf.slice(4));
     },
-  };
+  });
 }
 
 export function dynvec<T extends BytesCodec>(itemCodec: T): ArrayCodec<T> {
-  return {
+  return createBytesCodec({
     pack(obj) {
       const packed = obj.reduce(
         (result, item) => {
@@ -198,7 +197,7 @@ export function dynvec<T extends BytesCodec>(itemCodec: T): ArrayCodec<T> {
         return result;
       }
     },
-  };
+  });
 }
 
 export function vector<T extends BytesCodec>(itemCodec: T): ArrayCodec<T> {
@@ -213,7 +212,7 @@ export function table<T extends Record<string, BytesCodec>>(
   fields: (keyof T)[]
 ): ObjectCodec<T> {
   checkShape(shape, fields);
-  return {
+  return createBytesCodec({
     pack(obj) {
       const headerLength = 4 + fields.length * 4;
       const packed = fields.reduce(
@@ -271,14 +270,14 @@ export function table<T extends Record<string, BytesCodec>>(
         >;
       }
     },
-  };
+  });
 }
 
 export function union<T extends Record<string, BytesCodec>>(
   itemCodec: T,
   fields: (keyof T)[]
 ): UnionCodec<T> {
-  return {
+  return createBytesCodec({
     pack(obj) {
       const type = obj.type;
       const fieldIndex = fields.indexOf(type);
@@ -294,11 +293,11 @@ export function union<T extends Record<string, BytesCodec>>(
       const type = fields[typeIndex];
       return { type, value: itemCodec[type].unpack(buf.slice(4)) };
     },
-  };
+  });
 }
 
 export function option<T extends BytesCodec>(itemCodec: T): OptionCodec<T> {
-  return {
+  return createBytesCodec({
     pack(obj?) {
       if (obj !== undefined && obj !== null) {
         return itemCodec.pack(obj);
@@ -312,5 +311,5 @@ export function option<T extends BytesCodec>(itemCodec: T): OptionCodec<T> {
       }
       return itemCodec.unpack(buf);
     },
-  };
+  });
 }
diff --git a/packages/codec/src/number/uint.ts b/packages/codec/src/number/uint.ts
index 47d788492..fd1dbd830 100644
--- a/packages/codec/src/number/uint.ts
+++ b/packages/codec/src/number/uint.ts
@@ -1,5 +1,5 @@
 import { BI, BIish } from "@ckb-lumos/bi";
-import { createFixedBytesCodec, FixedBytesLikeCodec } from "../base";
+import { createFixedBytesCodec, FixedBytesCodec } from "../base";
 
 function assertNumberRange(value: BIish, min: BIish, max: BIish): void {
   value = BI.from(value);
@@ -14,7 +14,7 @@ function assertNumberRange(value: BIish, min: BIish, max: BIish): void {
 function createUintNumberCodec(
   byteLength: number,
   littleEndian = false
-): FixedBytesLikeCodec<number, BIish> {
+): FixedBytesCodec<number, BIish> {
   const codec = createUintBICodec(byteLength, littleEndian);
   return {
     __isFixedCodec__: true,
diff --git a/packages/codec/tests/blockchain.test.ts b/packages/codec/tests/blockchain.test.ts
index 665a6fd4b..641ebfe97 100644
--- a/packages/codec/tests/blockchain.test.ts
+++ b/packages/codec/tests/blockchain.test.ts
@@ -8,7 +8,7 @@ const SECP256K1_SIGNATURE_LENGTH = 65;
 
 test("secp256k1 witness args", (t) => {
   const unsigned = WitnessArgs.pack({
-    lock: hexify(Buffer.alloc(SECP256K1_SIGNATURE_LENGTH)),
+    lock: Buffer.alloc(SECP256K1_SIGNATURE_LENGTH),
   });
 
   t.deepEqual(
@@ -18,6 +18,17 @@ test("secp256k1 witness args", (t) => {
     )
   );
 
+  t.deepEqual(
+    WitnessArgs.unpack(
+      "0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+    ),
+    {
+      lock: hexify(Buffer.alloc(SECP256K1_SIGNATURE_LENGTH)),
+      input_type: undefined,
+      output_type: undefined,
+    }
+  );
+
   const signature = bytify(randomBytes(SECP256K1_SIGNATURE_LENGTH));
   const signed = WitnessArgs.pack({ lock: hexify(signature) });