From 8f0b620ffc8a02250839e567bcc42de16eefbaa0 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 14 Jul 2023 16:11:26 +0200 Subject: [PATCH 01/35] indexed by class name instead of the class itself --- borsh-ts/index.ts | 9 ++++++++- lib/index.d.ts | 2 +- lib/index.js | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 54e79468..5fa4ac0a 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -317,7 +317,14 @@ function serializeStruct(schema: Schema, obj: any, writer: BinaryWriter): void { return; } - const structSchema = schema.get(obj.constructor); + let structSchema = undefined; + for (const key of schema.keys()){ + if (key.name == obj.constructor.name) { + structSchema = schema.get(key); + break; + } + } + if (!structSchema) { throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); } diff --git a/lib/index.d.ts b/lib/index.d.ts index 326275a7..7cd65542 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -2,7 +2,7 @@ import BN from 'bn.js'; export declare function baseEncode(value: Uint8Array | string): string; export declare function baseDecode(value: string): Buffer; -export declare type Schema = Map; +export type Schema = Map; export declare class BorshError extends Error { originalMessage: string; fieldPath: string[]; diff --git a/lib/index.js b/lib/index.js index ec59321f..a5d4c0ad 100644 --- a/lib/index.js +++ b/lib/index.js @@ -327,7 +327,13 @@ function serializeStruct(schema, obj, writer) { obj.borshSerialize(writer); return; } - const structSchema = schema.get(obj.constructor); + let structSchema = undefined; + for (const key of schema.keys()) { + if (key.name == obj.constructor.name) { + structSchema = schema.get(key); + break; + } + } if (!structSchema) { throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); } From 6e514abbd23d7cb4fddc24831b4f4a1a6a7b5b5b Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 19 Jul 2023 18:26:49 +0200 Subject: [PATCH 02/35] serializer 1.0 --- README.md | 31 +- borsh-ts/buffer.ts | 91 ++++ borsh-ts/index.ts | 510 +----------------- borsh-ts/old.ts | 484 +++++++++++++++++ borsh-ts/serialize.ts | 154 ++++++ borsh-ts/test/fuzz/borsh-roundtrip.js | 19 - ...37181f6340b5b269b7faee05d97fe65b1ab91cd171 | Bin 160 -> 0 bytes ...06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7 | Bin 163 -> 0 bytes ...b3ffb19fcaf35deef41f8c084726c5aac5a4551696 | Bin 168 -> 0 bytes ...195c308c8af1a2006a76ba8fcfdca5ea5105ec9db1 | Bin 158 -> 0 bytes ...28d3381c64c61654579dc455a9e5ccaa54bc093721 | Bin 160 -> 0 bytes ...4ba237896a8ee4dffebd27b65c8101a1d8897d6409 | Bin 164 -> 0 bytes ...a2de056c34c40850bbe61b9ebc82cf48365484f4fc | Bin 155 -> 0 bytes ...38baae9bddbd620d018f803540e09ac1bc82ec3a6c | Bin 155 -> 0 bytes ...58a5c55d25830a8efffb9e8661a81e2db3584ca512 | Bin 155 -> 0 bytes ...137cf3e506f550fcfa19363050d1e4917250d83d32 | Bin 168 -> 0 bytes ...5107e67373646fe7c5370030819ccc13990375ca73 | Bin 160 -> 0 bytes ...bdaf44b17f2cd52501f1b5567ed05406163cf96fef | Bin 155 -> 0 bytes ...11e27c119009d0fcccbdddd5fe669c8c02bf04f7dd | Bin 170 -> 0 bytes ...58136c1f2dbd9ed3cad450556faf7b18db4184b42e | Bin 155 -> 0 bytes ...6d6e8ee36819bb22b7304f4e23f61d189a6c221f92 | Bin 164 -> 0 bytes ...2a1be308f7d25d21f81622a7ffc7da3733c03eb572 | Bin 160 -> 0 bytes ...f8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7 | Bin 109 -> 0 bytes ...b510fffc51f5c78078db2a2fb5cf19b0bd840291ee | Bin 155 -> 0 bytes ...f841050f0b2d78fa519ac76f178db0757d1bd3e96e | Bin 174 -> 0 bytes ...f981ab86d16876888bb7ba665637c5717c0a8916b2 | Bin 155 -> 0 bytes .../test/fuzz/transaction-example/enums.d.ts | 7 - .../test/fuzz/transaction-example/enums.js | 22 - .../fuzz/transaction-example/key_pair.d.ts | 61 --- .../test/fuzz/transaction-example/key_pair.js | 125 ----- .../fuzz/transaction-example/serialize.d.ts | 41 -- .../fuzz/transaction-example/serialize.js | 16 - .../test/fuzz/transaction-example/signer.d.ts | 63 --- .../test/fuzz/transaction-example/signer.js | 82 --- .../fuzz/transaction-example/transaction.d.ts | 106 ---- .../fuzz/transaction-example/transaction.js | 234 -------- borsh-ts/test/serialize.test.js | 240 +++------ borsh-ts/types.ts | 12 + borsh-ts/utils.ts | 35 ++ lib/buffer.d.ts | 11 + lib/buffer.js | 81 +++ lib/index.d.ts | 54 +- lib/index.js | 472 +--------------- lib/serialize.d.ts | 18 + lib/serialize.js | 171 ++++++ lib/types.d.ts | 28 + lib/types.js | 4 + lib/utils.d.ts | 4 + lib/utils.js | 37 ++ 49 files changed, 1256 insertions(+), 1957 deletions(-) create mode 100644 borsh-ts/buffer.ts create mode 100644 borsh-ts/old.ts create mode 100644 borsh-ts/serialize.ts delete mode 100644 borsh-ts/test/fuzz/borsh-roundtrip.js delete mode 100644 borsh-ts/test/fuzz/corpus/004b705c22403d1c22ceab37181f6340b5b269b7faee05d97fe65b1ab91cd171 delete mode 100644 borsh-ts/test/fuzz/corpus/25b2da453a8cb0d574d31a06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7 delete mode 100644 borsh-ts/test/fuzz/corpus/2f6f8117189cb82e111a13b3ffb19fcaf35deef41f8c084726c5aac5a4551696 delete mode 100644 borsh-ts/test/fuzz/corpus/4d97a7d362a8dc9da33040195c308c8af1a2006a76ba8fcfdca5ea5105ec9db1 delete mode 100644 borsh-ts/test/fuzz/corpus/4eb3f263ac0d9bac83d67e28d3381c64c61654579dc455a9e5ccaa54bc093721 delete mode 100644 borsh-ts/test/fuzz/corpus/5a0ac0f8936af102c2ea974ba237896a8ee4dffebd27b65c8101a1d8897d6409 delete mode 100644 borsh-ts/test/fuzz/corpus/67fc403d488908e0a27d74a2de056c34c40850bbe61b9ebc82cf48365484f4fc delete mode 100644 borsh-ts/test/fuzz/corpus/7fa19d2ffc291390ebe8a238baae9bddbd620d018f803540e09ac1bc82ec3a6c delete mode 100644 borsh-ts/test/fuzz/corpus/824d229c23c200a937bcd558a5c55d25830a8efffb9e8661a81e2db3584ca512 delete mode 100644 borsh-ts/test/fuzz/corpus/9d6a787b7d95c13bb67d02137cf3e506f550fcfa19363050d1e4917250d83d32 delete mode 100644 borsh-ts/test/fuzz/corpus/a330e2fd8d25278e3d28205107e67373646fe7c5370030819ccc13990375ca73 delete mode 100644 borsh-ts/test/fuzz/corpus/a53d5be4493c7cccab1d70bdaf44b17f2cd52501f1b5567ed05406163cf96fef delete mode 100644 borsh-ts/test/fuzz/corpus/b7361e727623b1cdafda1411e27c119009d0fcccbdddd5fe669c8c02bf04f7dd delete mode 100644 borsh-ts/test/fuzz/corpus/b98f80e8a618038000215158136c1f2dbd9ed3cad450556faf7b18db4184b42e delete mode 100644 borsh-ts/test/fuzz/corpus/bec080831059d96afd82176d6e8ee36819bb22b7304f4e23f61d189a6c221f92 delete mode 100644 borsh-ts/test/fuzz/corpus/cf94b13d93b7accf950d3d2a1be308f7d25d21f81622a7ffc7da3733c03eb572 delete mode 100644 borsh-ts/test/fuzz/corpus/d2897d4dc13f861fb5e7caf8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7 delete mode 100644 borsh-ts/test/fuzz/corpus/dfbc571f6a049f8f66f2fcb510fffc51f5c78078db2a2fb5cf19b0bd840291ee delete mode 100644 borsh-ts/test/fuzz/corpus/e08ccfae0c2fd6432c8e1bf841050f0b2d78fa519ac76f178db0757d1bd3e96e delete mode 100644 borsh-ts/test/fuzz/corpus/ecd71b60d1820dbbcb06d1f981ab86d16876888bb7ba665637c5717c0a8916b2 delete mode 100644 borsh-ts/test/fuzz/transaction-example/enums.d.ts delete mode 100644 borsh-ts/test/fuzz/transaction-example/enums.js delete mode 100644 borsh-ts/test/fuzz/transaction-example/key_pair.d.ts delete mode 100644 borsh-ts/test/fuzz/transaction-example/key_pair.js delete mode 100644 borsh-ts/test/fuzz/transaction-example/serialize.d.ts delete mode 100644 borsh-ts/test/fuzz/transaction-example/serialize.js delete mode 100644 borsh-ts/test/fuzz/transaction-example/signer.d.ts delete mode 100644 borsh-ts/test/fuzz/transaction-example/signer.js delete mode 100644 borsh-ts/test/fuzz/transaction-example/transaction.d.ts delete mode 100644 borsh-ts/test/fuzz/transaction-example/transaction.js create mode 100644 borsh-ts/types.ts create mode 100644 borsh-ts/utils.ts create mode 100644 lib/buffer.d.ts create mode 100644 lib/buffer.js create mode 100644 lib/serialize.d.ts create mode 100644 lib/serialize.js create mode 100644 lib/types.d.ts create mode 100644 lib/types.js create mode 100644 lib/utils.d.ts create mode 100644 lib/utils.js diff --git a/README.md b/README.md index fc3beb23..f49ad930 100644 --- a/README.md +++ b/README.md @@ -28,23 +28,20 @@ const newValue = borsh.deserialize(schema, Test, buffer); ## Type Mappings -| Borsh | TypeScript | -|-----------------------|----------------| -| `u8` integer | `number` | -| `u16` integer | `number` | -| `u32` integer | `number` | -| `u64` integer | `BN` | -| `u128` integer | `BN` | -| `u256` integer | `BN` | -| `u512` integer | `BN` | -| `f32` float | N/A | -| `f64` float | N/A | -| fixed-size byte array | `Uint8Array` | -| UTF-8 string | `string` | -| option | `null` or type | -| map | N/A | -| set | N/A | -| structs | `any` | +| Borsh | TypeScript | +|-----------------------------------|----------------| +| `u8` `u16` `u32` `i8` `i16` `i32` | `number` | +| `u64` `u128` `i64` `i128` | `bignum` | +| `f32` `f64` | `number` | +| `f32` `f64` | `number` | +| `bool` | `boolean` | +| UTF-8 string | `string` | +| fixed-size byte array | `Array` | +| dynamic sized array | `Array` | +| enum | N/A | +| HashMap | `Map` | +| HashSet | `Set` | +| Option | `null` or type | ## Contributing diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts new file mode 100644 index 00000000..34d1038c --- /dev/null +++ b/borsh-ts/buffer.ts @@ -0,0 +1,91 @@ +import { NumberType } from './types'; + +export class EncodeBuffer { + offset = 0; + buffer_size = 256; + buffer: ArrayBuffer = new ArrayBuffer(this.buffer_size); + view: DataView = new DataView(this.buffer); + + resize_if_necessary(needed_space: number): void { + if (this.buffer_size - this.offset < needed_space) { + this.buffer_size = Math.max(this.buffer_size * 2, this.buffer_size + needed_space); + + const new_buffer = new ArrayBuffer(this.buffer_size); + new Uint8Array(new_buffer).set(new Uint8Array(this.buffer)); + + this.buffer = new_buffer; + this.view = new DataView(new_buffer); + } + } + + get_used_buffer(): Uint8Array { + return new Uint8Array(this.buffer).slice(0, this.offset); + } + + store_value(value: number, type: NumberType): void { + const size = parseInt(type.substring(1)) / 8; + this.resize_if_necessary(size); + + switch (type) { + case 'u8': + this.view.setUint8(this.offset, value); + break; + case 'u16': + this.view.setUint16(this.offset, value, true); + break; + case 'u32': + this.view.setUint32(this.offset, value, true); + break; + case 'i8': + this.view.setInt8(this.offset, value); + break; + case 'i16': + this.view.setInt16(this.offset, value, true); + break; + case 'i32': + this.view.setInt32(this.offset, value, true); + break; + case 'f32': + this.view.setFloat32(this.offset, value, true); + break; + case 'f64': + this.view.setFloat64(this.offset, value, true); + break; + default: + throw new Error(`Unsupported integer type: ${type}`); + } + this.offset += size; + } + + store_bytes(from: Uint8Array): void { + this.resize_if_necessary(from.length); + new Uint8Array(this.buffer).set(new Uint8Array(from), this.offset); + this.offset += from.length; + } +} + +// export class DecodeBuffer { +// public offset: number = 0; +// public start: usize; + +// constructor(public arrBuffer: ArrayBuffer) { +// this.start = changetype(arrBuffer) +// } + +// consume(): T { +// const off = this.offset +// this.offset += sizeof() +// return load(this.start + off) +// } + +// consume_slice(length: number): ArrayBuffer { +// const off = this.offset +// this.offset += length +// return changetype(this.start).slice(off, off + length) +// } + +// consume_copy(dst: usize, length: number): void { +// memory.copy(dst, this.start + this.offset, length); +// this.offset += length; +// } +// } \ No newline at end of file diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 5fa4ac0a..7dfd7ea7 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -1,484 +1,26 @@ -import BN from 'bn.js'; -import bs58 from 'bs58'; - -// TODO: Make sure this polyfill not included when not required -import * as encoding from 'text-encoding-utf-8'; -const ResolvedTextDecoder = - typeof TextDecoder !== 'function' ? encoding.TextDecoder : TextDecoder; -const textDecoder = new ResolvedTextDecoder('utf-8', { fatal: true }); - -export function baseEncode(value: Uint8Array | string): string { - if (typeof value === 'string') { - value = Buffer.from(value, 'utf8'); - } - return bs58.encode(Buffer.from(value)); -} - -export function baseDecode(value: string): Buffer { - return Buffer.from(bs58.decode(value)); -} - -const INITIAL_LENGTH = 1024; - -export type Schema = Map - -export class BorshError extends Error { - originalMessage: string; - fieldPath: string[] = []; - - constructor(message: string) { - super(message); - this.originalMessage = message; - } - - addToFieldPath(fieldName: string): void { - this.fieldPath.splice(0, 0, fieldName); - // NOTE: Modifying message directly as jest doesn't use .toString() - this.message = this.originalMessage + ': ' + this.fieldPath.join('.'); - } -} - -/// Binary encoder. -export class BinaryWriter { - buf: Buffer; - length: number; - - public constructor() { - this.buf = Buffer.alloc(INITIAL_LENGTH); - this.length = 0; - } - - maybeResize(): void { - if (this.buf.length < 16 + this.length) { - this.buf = Buffer.concat([this.buf, Buffer.alloc(INITIAL_LENGTH)]); - } - } - - public writeU8(value: number): void { - this.maybeResize(); - this.buf.writeUInt8(value, this.length); - this.length += 1; - } - - public writeU16(value: number): void { - this.maybeResize(); - this.buf.writeUInt16LE(value, this.length); - this.length += 2; - } - - public writeU32(value: number): void { - this.maybeResize(); - this.buf.writeUInt32LE(value, this.length); - this.length += 4; - } - - public writeU64(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 8))); - } - - public writeU128(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 16))); - } - - public writeU256(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 32))); - } - - public writeU512(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 64))); - } - - private writeBuffer(buffer: Buffer): void { - // Buffer.from is needed as this.buf.subarray can return plain Uint8Array in browser - this.buf = Buffer.concat([ - Buffer.from(this.buf.subarray(0, this.length)), - buffer, - Buffer.alloc(INITIAL_LENGTH), - ]); - this.length += buffer.length; - } - - public writeString(str: string): void { - this.maybeResize(); - const b = Buffer.from(str, 'utf8'); - this.writeU32(b.length); - this.writeBuffer(b); - } - - public writeFixedArray(array: Uint8Array): void { - this.writeBuffer(Buffer.from(array)); - } - - public writeArray(array: any[], fn: any): void { - this.maybeResize(); - this.writeU32(array.length); - for (const elem of array) { - this.maybeResize(); - fn(elem); - } - } - - public toArray(): Uint8Array { - return this.buf.subarray(0, this.length); - } -} - -function handlingRangeError( - target: any, - propertyKey: string, - propertyDescriptor: PropertyDescriptor -): any { - const originalMethod = propertyDescriptor.value; - propertyDescriptor.value = function (...args: any[]): any { - try { - return originalMethod.apply(this, args); - } catch (e) { - if (e instanceof RangeError) { - const code = (e as any).code; - if ( - ['ERR_BUFFER_OUT_OF_BOUNDS', 'ERR_OUT_OF_RANGE'].indexOf(code) >= 0 - ) { - throw new BorshError('Reached the end of buffer when deserializing'); - } - } - throw e; - } - }; -} - -export class BinaryReader { - buf: Buffer; - offset: number; - - public constructor(buf: Buffer) { - this.buf = buf; - this.offset = 0; - } - - @handlingRangeError - readU8(): number { - const value = this.buf.readUInt8(this.offset); - this.offset += 1; - return value; - } - - @handlingRangeError - readU16(): number { - const value = this.buf.readUInt16LE(this.offset); - this.offset += 2; - return value; - } - - @handlingRangeError - readU32(): number { - const value = this.buf.readUInt32LE(this.offset); - this.offset += 4; - return value; - } - - @handlingRangeError - readU64(): BN { - const buf = this.readBuffer(8); - return new BN(buf, 'le'); - } - - @handlingRangeError - readU128(): BN { - const buf = this.readBuffer(16); - return new BN(buf, 'le'); - } - - @handlingRangeError - readU256(): BN { - const buf = this.readBuffer(32); - return new BN(buf, 'le'); - } - - @handlingRangeError - readU512(): BN { - const buf = this.readBuffer(64); - return new BN(buf, 'le'); - } - - private readBuffer(len: number): Buffer { - if (this.offset + len > this.buf.length) { - throw new BorshError(`Expected buffer length ${len} isn't within bounds`); - } - const result = this.buf.slice(this.offset, this.offset + len); - this.offset += len; - return result; - } - - @handlingRangeError - readString(): string { - const len = this.readU32(); - const buf = this.readBuffer(len); - try { - // NOTE: Using TextDecoder to fail on invalid UTF-8 - return textDecoder.decode(buf); - } catch (e) { - throw new BorshError(`Error decoding UTF-8 string: ${e}`); - } - } - - @handlingRangeError - readFixedArray(len: number): Uint8Array { - return new Uint8Array(this.readBuffer(len)); - } - - @handlingRangeError - readArray(fn: any): any[] { - const len = this.readU32(); - const result = Array(); - for (let i = 0; i < len; ++i) { - result.push(fn()); - } - return result; - } -} - -function capitalizeFirstLetter(string): string { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -function serializeField( - schema: Schema, - fieldName: string, - value: any, - fieldType: any, - writer: any -): void { - try { - // TODO: Handle missing values properly (make sure they never result in just skipped write) - if (typeof fieldType === 'string') { - writer[`write${capitalizeFirstLetter(fieldType)}`](value); - } else if (fieldType instanceof Array) { - if (typeof fieldType[0] === 'number') { - if (value.length !== fieldType[0]) { - throw new BorshError( - `Expecting byte array of length ${fieldType[0]}, but got ${value.length} bytes` - ); - } - writer.writeFixedArray(value); - } else if (fieldType.length === 2 && typeof fieldType[1] === 'number') { - if (value.length !== fieldType[1]) { - throw new BorshError( - `Expecting byte array of length ${fieldType[1]}, but got ${value.length} bytes` - ); - } - for (let i = 0; i < fieldType[1]; i++) { - serializeField(schema, null, value[i], fieldType[0], writer); - } - } else { - writer.writeArray(value, (item: any) => { - serializeField(schema, fieldName, item, fieldType[0], writer); - }); - } - } else if (fieldType.kind !== undefined) { - switch (fieldType.kind) { - case 'option': { - if (value === null || value === undefined) { - writer.writeU8(0); - } else { - writer.writeU8(1); - serializeField(schema, fieldName, value, fieldType.type, writer); - } - break; - } - case 'map': { - writer.writeU32(value.size); - value.forEach((val, key) => { - serializeField(schema, fieldName, key, fieldType.key, writer); - serializeField(schema, fieldName, val, fieldType.value, writer); - }); - break; - } - default: - throw new BorshError(`FieldType ${fieldType} unrecognized`); - } - } else { - serializeStruct(schema, value, writer); - } - } catch (error) { - if (error instanceof BorshError) { - error.addToFieldPath(fieldName); - } - throw error; - } -} - -function serializeStruct(schema: Schema, obj: any, writer: BinaryWriter): void { - if (typeof obj.borshSerialize === 'function') { - obj.borshSerialize(writer); - return; - } - - let structSchema = undefined; - for (const key of schema.keys()){ - if (key.name == obj.constructor.name) { - structSchema = schema.get(key); - break; - } - } - - if (!structSchema) { - throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); - } - - if (structSchema.kind === 'struct') { - structSchema.fields.map(([fieldName, fieldType]: [any, any]) => { - serializeField(schema, fieldName, obj[fieldName], fieldType, writer); - }); - } else if (structSchema.kind === 'enum') { - const name = obj[structSchema.field]; - for (let idx = 0; idx < structSchema.values.length; ++idx) { - const [fieldName, fieldType]: [any, any] = structSchema.values[idx]; - if (fieldName === name) { - writer.writeU8(idx); - serializeField(schema, fieldName, obj[fieldName], fieldType, writer); - break; - } - } - } else { - throw new BorshError( - `Unexpected schema kind: ${structSchema.kind} for ${obj.constructor.name}` - ); - } -} - -/// Serialize given object using schema of the form: -/// { class_name -> [ [field_name, field_type], .. ], .. } -export function serialize( - schema: Schema, - obj: any, - Writer = BinaryWriter -): Uint8Array { - const writer = new Writer(); - serializeStruct(schema, obj, writer); - return writer.toArray(); -} - -function deserializeField( - schema: Schema, - fieldName: string, - fieldType: any, - reader: BinaryReader -): any { - try { - if (typeof fieldType === 'string') { - return reader[`read${capitalizeFirstLetter(fieldType)}`](); - } - - if (fieldType instanceof Array) { - if (typeof fieldType[0] === 'number') { - return reader.readFixedArray(fieldType[0]); - } else if (typeof fieldType[1] === 'number') { - const arr = []; - for (let i = 0; i < fieldType[1]; i++) { - arr.push(deserializeField(schema, null, fieldType[0], reader)); - } - return arr; - } else { - return reader.readArray(() => - deserializeField(schema, fieldName, fieldType[0], reader) - ); - } - } - - if (fieldType.kind === 'option') { - const option = reader.readU8(); - if (option) { - return deserializeField(schema, fieldName, fieldType.type, reader); - } - - return undefined; - } - if (fieldType.kind === 'map') { - const map = new Map(); - const length = reader.readU32(); - for (let i = 0; i < length; i++) { - const key = deserializeField(schema, fieldName, fieldType.key, reader); - const val = deserializeField(schema, fieldName, fieldType.value, reader); - map.set(key, val); - } - return map; - } - - return deserializeStruct(schema, fieldType, reader); - } catch (error) { - if (error instanceof BorshError) { - error.addToFieldPath(fieldName); - } - throw error; - } -} - -function deserializeStruct( - schema: Schema, - classType: any, - reader: BinaryReader -): any { - if (typeof classType.borshDeserialize === 'function') { - return classType.borshDeserialize(reader); - } - - const structSchema = schema.get(classType); - if (!structSchema) { - throw new BorshError(`Class ${classType.name} is missing in schema`); - } - - if (structSchema.kind === 'struct') { - const result = {}; - for (const [fieldName, fieldType] of schema.get(classType).fields) { - result[fieldName] = deserializeField(schema, fieldName, fieldType, reader); - } - return new classType(result); - } - - if (structSchema.kind === 'enum') { - const idx = reader.readU8(); - if (idx >= structSchema.values.length) { - throw new BorshError(`Enum index: ${idx} is out of range`); - } - const [fieldName, fieldType] = structSchema.values[idx]; - const fieldValue = deserializeField(schema, fieldName, fieldType, reader); - return new classType({ [fieldName]: fieldValue }); - } - - throw new BorshError( - `Unexpected schema kind: ${structSchema.kind} for ${classType.constructor.name}` - ); -} - -/// Deserializes object from bytes using schema. -export function deserialize( - schema: Schema, - classType: { new(args: any): T }, - buffer: Buffer, - Reader = BinaryReader -): T { - const reader = new Reader(buffer); - const result = deserializeStruct(schema, classType, reader); - if (reader.offset < buffer.length) { - throw new BorshError( - `Unexpected ${buffer.length - reader.offset - } bytes after deserialized data` - ); - } - return result; -} - -/// Deserializes object from bytes using schema, without checking the length read -export function deserializeUnchecked( - schema: Schema, - classType: { new(args: any): T }, - buffer: Buffer, - Reader = BinaryReader -): T { - const reader = new Reader(buffer); - return deserializeStruct(schema, classType, reader); -} +import { Schema } from './types'; +import {BorshSerializer} from './serialize'; + +export function serialize(schema: Schema, value: unknown): Uint8Array { + const serializer = new BorshSerializer(); + return serializer.encode(value, schema); +} + + +// export function serialize( +// schema: Schema, +// obj: any, +// ): Uint8Array { +// const writer = new Writer(); +// serializeStruct(schema, obj, writer); +// return writer.toArray(); +// } + +// export function deserialize( +// schema: Schema, +// classType: { new(args: any): T }, +// buffer: Buffer, +// ): T { +// const reader = new Reader(buffer); +// return deserializeStruct(schema, classType, reader); +// } diff --git a/borsh-ts/old.ts b/borsh-ts/old.ts new file mode 100644 index 00000000..5fa4ac0a --- /dev/null +++ b/borsh-ts/old.ts @@ -0,0 +1,484 @@ +import BN from 'bn.js'; +import bs58 from 'bs58'; + +// TODO: Make sure this polyfill not included when not required +import * as encoding from 'text-encoding-utf-8'; +const ResolvedTextDecoder = + typeof TextDecoder !== 'function' ? encoding.TextDecoder : TextDecoder; +const textDecoder = new ResolvedTextDecoder('utf-8', { fatal: true }); + +export function baseEncode(value: Uint8Array | string): string { + if (typeof value === 'string') { + value = Buffer.from(value, 'utf8'); + } + return bs58.encode(Buffer.from(value)); +} + +export function baseDecode(value: string): Buffer { + return Buffer.from(bs58.decode(value)); +} + +const INITIAL_LENGTH = 1024; + +export type Schema = Map + +export class BorshError extends Error { + originalMessage: string; + fieldPath: string[] = []; + + constructor(message: string) { + super(message); + this.originalMessage = message; + } + + addToFieldPath(fieldName: string): void { + this.fieldPath.splice(0, 0, fieldName); + // NOTE: Modifying message directly as jest doesn't use .toString() + this.message = this.originalMessage + ': ' + this.fieldPath.join('.'); + } +} + +/// Binary encoder. +export class BinaryWriter { + buf: Buffer; + length: number; + + public constructor() { + this.buf = Buffer.alloc(INITIAL_LENGTH); + this.length = 0; + } + + maybeResize(): void { + if (this.buf.length < 16 + this.length) { + this.buf = Buffer.concat([this.buf, Buffer.alloc(INITIAL_LENGTH)]); + } + } + + public writeU8(value: number): void { + this.maybeResize(); + this.buf.writeUInt8(value, this.length); + this.length += 1; + } + + public writeU16(value: number): void { + this.maybeResize(); + this.buf.writeUInt16LE(value, this.length); + this.length += 2; + } + + public writeU32(value: number): void { + this.maybeResize(); + this.buf.writeUInt32LE(value, this.length); + this.length += 4; + } + + public writeU64(value: number | BN): void { + this.maybeResize(); + this.writeBuffer(Buffer.from(new BN(value).toArray('le', 8))); + } + + public writeU128(value: number | BN): void { + this.maybeResize(); + this.writeBuffer(Buffer.from(new BN(value).toArray('le', 16))); + } + + public writeU256(value: number | BN): void { + this.maybeResize(); + this.writeBuffer(Buffer.from(new BN(value).toArray('le', 32))); + } + + public writeU512(value: number | BN): void { + this.maybeResize(); + this.writeBuffer(Buffer.from(new BN(value).toArray('le', 64))); + } + + private writeBuffer(buffer: Buffer): void { + // Buffer.from is needed as this.buf.subarray can return plain Uint8Array in browser + this.buf = Buffer.concat([ + Buffer.from(this.buf.subarray(0, this.length)), + buffer, + Buffer.alloc(INITIAL_LENGTH), + ]); + this.length += buffer.length; + } + + public writeString(str: string): void { + this.maybeResize(); + const b = Buffer.from(str, 'utf8'); + this.writeU32(b.length); + this.writeBuffer(b); + } + + public writeFixedArray(array: Uint8Array): void { + this.writeBuffer(Buffer.from(array)); + } + + public writeArray(array: any[], fn: any): void { + this.maybeResize(); + this.writeU32(array.length); + for (const elem of array) { + this.maybeResize(); + fn(elem); + } + } + + public toArray(): Uint8Array { + return this.buf.subarray(0, this.length); + } +} + +function handlingRangeError( + target: any, + propertyKey: string, + propertyDescriptor: PropertyDescriptor +): any { + const originalMethod = propertyDescriptor.value; + propertyDescriptor.value = function (...args: any[]): any { + try { + return originalMethod.apply(this, args); + } catch (e) { + if (e instanceof RangeError) { + const code = (e as any).code; + if ( + ['ERR_BUFFER_OUT_OF_BOUNDS', 'ERR_OUT_OF_RANGE'].indexOf(code) >= 0 + ) { + throw new BorshError('Reached the end of buffer when deserializing'); + } + } + throw e; + } + }; +} + +export class BinaryReader { + buf: Buffer; + offset: number; + + public constructor(buf: Buffer) { + this.buf = buf; + this.offset = 0; + } + + @handlingRangeError + readU8(): number { + const value = this.buf.readUInt8(this.offset); + this.offset += 1; + return value; + } + + @handlingRangeError + readU16(): number { + const value = this.buf.readUInt16LE(this.offset); + this.offset += 2; + return value; + } + + @handlingRangeError + readU32(): number { + const value = this.buf.readUInt32LE(this.offset); + this.offset += 4; + return value; + } + + @handlingRangeError + readU64(): BN { + const buf = this.readBuffer(8); + return new BN(buf, 'le'); + } + + @handlingRangeError + readU128(): BN { + const buf = this.readBuffer(16); + return new BN(buf, 'le'); + } + + @handlingRangeError + readU256(): BN { + const buf = this.readBuffer(32); + return new BN(buf, 'le'); + } + + @handlingRangeError + readU512(): BN { + const buf = this.readBuffer(64); + return new BN(buf, 'le'); + } + + private readBuffer(len: number): Buffer { + if (this.offset + len > this.buf.length) { + throw new BorshError(`Expected buffer length ${len} isn't within bounds`); + } + const result = this.buf.slice(this.offset, this.offset + len); + this.offset += len; + return result; + } + + @handlingRangeError + readString(): string { + const len = this.readU32(); + const buf = this.readBuffer(len); + try { + // NOTE: Using TextDecoder to fail on invalid UTF-8 + return textDecoder.decode(buf); + } catch (e) { + throw new BorshError(`Error decoding UTF-8 string: ${e}`); + } + } + + @handlingRangeError + readFixedArray(len: number): Uint8Array { + return new Uint8Array(this.readBuffer(len)); + } + + @handlingRangeError + readArray(fn: any): any[] { + const len = this.readU32(); + const result = Array(); + for (let i = 0; i < len; ++i) { + result.push(fn()); + } + return result; + } +} + +function capitalizeFirstLetter(string): string { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +function serializeField( + schema: Schema, + fieldName: string, + value: any, + fieldType: any, + writer: any +): void { + try { + // TODO: Handle missing values properly (make sure they never result in just skipped write) + if (typeof fieldType === 'string') { + writer[`write${capitalizeFirstLetter(fieldType)}`](value); + } else if (fieldType instanceof Array) { + if (typeof fieldType[0] === 'number') { + if (value.length !== fieldType[0]) { + throw new BorshError( + `Expecting byte array of length ${fieldType[0]}, but got ${value.length} bytes` + ); + } + writer.writeFixedArray(value); + } else if (fieldType.length === 2 && typeof fieldType[1] === 'number') { + if (value.length !== fieldType[1]) { + throw new BorshError( + `Expecting byte array of length ${fieldType[1]}, but got ${value.length} bytes` + ); + } + for (let i = 0; i < fieldType[1]; i++) { + serializeField(schema, null, value[i], fieldType[0], writer); + } + } else { + writer.writeArray(value, (item: any) => { + serializeField(schema, fieldName, item, fieldType[0], writer); + }); + } + } else if (fieldType.kind !== undefined) { + switch (fieldType.kind) { + case 'option': { + if (value === null || value === undefined) { + writer.writeU8(0); + } else { + writer.writeU8(1); + serializeField(schema, fieldName, value, fieldType.type, writer); + } + break; + } + case 'map': { + writer.writeU32(value.size); + value.forEach((val, key) => { + serializeField(schema, fieldName, key, fieldType.key, writer); + serializeField(schema, fieldName, val, fieldType.value, writer); + }); + break; + } + default: + throw new BorshError(`FieldType ${fieldType} unrecognized`); + } + } else { + serializeStruct(schema, value, writer); + } + } catch (error) { + if (error instanceof BorshError) { + error.addToFieldPath(fieldName); + } + throw error; + } +} + +function serializeStruct(schema: Schema, obj: any, writer: BinaryWriter): void { + if (typeof obj.borshSerialize === 'function') { + obj.borshSerialize(writer); + return; + } + + let structSchema = undefined; + for (const key of schema.keys()){ + if (key.name == obj.constructor.name) { + structSchema = schema.get(key); + break; + } + } + + if (!structSchema) { + throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); + } + + if (structSchema.kind === 'struct') { + structSchema.fields.map(([fieldName, fieldType]: [any, any]) => { + serializeField(schema, fieldName, obj[fieldName], fieldType, writer); + }); + } else if (structSchema.kind === 'enum') { + const name = obj[structSchema.field]; + for (let idx = 0; idx < structSchema.values.length; ++idx) { + const [fieldName, fieldType]: [any, any] = structSchema.values[idx]; + if (fieldName === name) { + writer.writeU8(idx); + serializeField(schema, fieldName, obj[fieldName], fieldType, writer); + break; + } + } + } else { + throw new BorshError( + `Unexpected schema kind: ${structSchema.kind} for ${obj.constructor.name}` + ); + } +} + +/// Serialize given object using schema of the form: +/// { class_name -> [ [field_name, field_type], .. ], .. } +export function serialize( + schema: Schema, + obj: any, + Writer = BinaryWriter +): Uint8Array { + const writer = new Writer(); + serializeStruct(schema, obj, writer); + return writer.toArray(); +} + +function deserializeField( + schema: Schema, + fieldName: string, + fieldType: any, + reader: BinaryReader +): any { + try { + if (typeof fieldType === 'string') { + return reader[`read${capitalizeFirstLetter(fieldType)}`](); + } + + if (fieldType instanceof Array) { + if (typeof fieldType[0] === 'number') { + return reader.readFixedArray(fieldType[0]); + } else if (typeof fieldType[1] === 'number') { + const arr = []; + for (let i = 0; i < fieldType[1]; i++) { + arr.push(deserializeField(schema, null, fieldType[0], reader)); + } + return arr; + } else { + return reader.readArray(() => + deserializeField(schema, fieldName, fieldType[0], reader) + ); + } + } + + if (fieldType.kind === 'option') { + const option = reader.readU8(); + if (option) { + return deserializeField(schema, fieldName, fieldType.type, reader); + } + + return undefined; + } + if (fieldType.kind === 'map') { + const map = new Map(); + const length = reader.readU32(); + for (let i = 0; i < length; i++) { + const key = deserializeField(schema, fieldName, fieldType.key, reader); + const val = deserializeField(schema, fieldName, fieldType.value, reader); + map.set(key, val); + } + return map; + } + + return deserializeStruct(schema, fieldType, reader); + } catch (error) { + if (error instanceof BorshError) { + error.addToFieldPath(fieldName); + } + throw error; + } +} + +function deserializeStruct( + schema: Schema, + classType: any, + reader: BinaryReader +): any { + if (typeof classType.borshDeserialize === 'function') { + return classType.borshDeserialize(reader); + } + + const structSchema = schema.get(classType); + if (!structSchema) { + throw new BorshError(`Class ${classType.name} is missing in schema`); + } + + if (structSchema.kind === 'struct') { + const result = {}; + for (const [fieldName, fieldType] of schema.get(classType).fields) { + result[fieldName] = deserializeField(schema, fieldName, fieldType, reader); + } + return new classType(result); + } + + if (structSchema.kind === 'enum') { + const idx = reader.readU8(); + if (idx >= structSchema.values.length) { + throw new BorshError(`Enum index: ${idx} is out of range`); + } + const [fieldName, fieldType] = structSchema.values[idx]; + const fieldValue = deserializeField(schema, fieldName, fieldType, reader); + return new classType({ [fieldName]: fieldValue }); + } + + throw new BorshError( + `Unexpected schema kind: ${structSchema.kind} for ${classType.constructor.name}` + ); +} + +/// Deserializes object from bytes using schema. +export function deserialize( + schema: Schema, + classType: { new(args: any): T }, + buffer: Buffer, + Reader = BinaryReader +): T { + const reader = new Reader(buffer); + const result = deserializeStruct(schema, classType, reader); + if (reader.offset < buffer.length) { + throw new BorshError( + `Unexpected ${buffer.length - reader.offset + } bytes after deserialized data` + ); + } + return result; +} + +/// Deserializes object from bytes using schema, without checking the length read +export function deserializeUnchecked( + schema: Schema, + classType: { new(args: any): T }, + buffer: Buffer, + Reader = BinaryReader +): T { + const reader = new Reader(buffer); + return deserializeStruct(schema, classType, reader); +} diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts new file mode 100644 index 00000000..f7b52aa5 --- /dev/null +++ b/borsh-ts/serialize.ts @@ -0,0 +1,154 @@ +import { ArrayType, MapType, NumberType, OptionType, Schema, SetType, StructType, numbers } from './types'; +import { EncodeBuffer } from './buffer'; +import BN from 'bn.js'; +import * as utils from './utils'; + + +export class BorshSerializer { + encoded: EncodeBuffer = new EncodeBuffer(); + + encode(value: unknown, schema: Schema): Uint8Array { + if (typeof schema === 'string') { + if (numbers.includes(schema)) this.encode_integer(value, schema); + if (schema === 'string') this.encode_string(value); + if (schema === 'bool') this.encode_boolean(value); + } + + if (typeof schema === 'object') { + if ('option' in schema) this.encode_option(value, schema as OptionType); + if ('array' in schema) this.encode_array(value, schema); + if ('set' in schema) this.encode_set(value, schema as SetType); + if ('map' in schema) this.encode_map(value, schema as MapType); + if ('struct' in schema) this.encode_struct(value, schema as StructType); + } + return this.encoded.get_used_buffer(); + } + + encode_integer(value: unknown, schema: NumberType): void { + const size: number = parseInt(schema.substring(1)); + + if (size <= 32 || schema == 'f64') { + utils.expect_type(value, 'number'); + this.encoded.store_value(value as number, schema); + } else { + utils.expect_BN(value); + this.encode_bigint(value as BN, size); + } + } + + encode_bigint(value: BN, size: number): void { + const buffer_len = size / 8; + const buffer = value.toArray('le', buffer_len); + + if (value.lt(new BN(0))) { + // compute two's complement + let carry = 1; + for (let i = 0; i < buffer_len; i++) { + const v = (buffer[i] ^ 0xff) + carry; + this.encoded.store_value(v & 0xff, 'u8'); + carry = v >> 8; + } + } else { + this.encoded.store_bytes(new Uint8Array(buffer)); + } + } + + encode_string(value: unknown): void { + utils.expect_type(value, 'string'); + const _value = value as string; + + // 4 bytes for length + this.encoded.store_value(_value.length, 'u32'); + + // string bytes + for (let i = 0; i < _value.length; i++) { + this.encoded.store_value(_value.charCodeAt(i), 'u8'); + } + } + + encode_boolean(value: unknown): void { + utils.expect_type(value, 'boolean'); + this.encoded.store_value(value as boolean ? 1 : 0, 'u8'); + } + + encode_option(value: unknown, schema: OptionType): void { + if (value === null || value === undefined) { + this.encoded.store_value(0, 'u8'); + } else { + this.encoded.store_value(1, 'u8'); + this.encode(value, schema.option); + } + } + + encode_array(value: unknown, schema: Schema): void { + const _schema = schema as ArrayType; + if (utils.isArrayLike(value)) return this.encode_arraylike(value as ArrayLike, _schema); + if (value instanceof ArrayBuffer) return this.encode_buffer(value, _schema); + throw new Error(`Expected Array-like not ${typeof (value)}(${value})`); + } + + encode_arraylike(value: ArrayLike, schema: ArrayType): void { + if (schema.array.len) { + utils.expect_same_size(value.length, schema.array.len); + } else { + // 4 bytes for length + this.encoded.store_value(value.length, 'u32'); + } + + // array values + for (let i = 0; i < value.length; i++) { + this.encode(value[i], schema.array.type); + } + } + + encode_buffer(value: ArrayBuffer, schema: ArrayType): void { + if (schema.array.len) { + utils.expect_same_size(value.byteLength, schema.array.len); + } else { + // 4 bytes for length + this.encoded.store_value(value.byteLength, 'u32'); + } + + // array values + this.encoded.store_bytes(new Uint8Array(value)); + } + + encode_set(value: unknown, schema: SetType): void { + utils.expect_type(value, 'object'); + + const isSet = value instanceof Set; + const values = isSet ? Array.from(value.values()) : Object.values(value); + + // 4 bytes for length + this.encoded.store_value(values.length, 'u32'); + + // set values + for (const value of values) { + this.encode(value, schema.set); + } + } + + encode_map(value: unknown, schema: MapType): void { + utils.expect_type(value, 'object'); + + const isMap = value instanceof Map; + const keys = isMap ? Array.from(value.keys()) : Object.keys(value); + + // 4 bytes for length + this.encoded.store_value(keys.length, 'u32'); + + // store key/values + for (const key of keys) { + this.encode(key, schema.map.key); + this.encode(isMap ? value.get(key) : value[key], schema.map.value); + } + } + + encode_struct(value: unknown, schema: StructType): void { + utils.expect_type(value, 'object'); + + for (const key of Object.keys(value)) { + this.encode(value[key], schema.struct[key]); + } + } +} \ No newline at end of file diff --git a/borsh-ts/test/fuzz/borsh-roundtrip.js b/borsh-ts/test/fuzz/borsh-roundtrip.js deleted file mode 100644 index f790375d..00000000 --- a/borsh-ts/test/fuzz/borsh-roundtrip.js +++ /dev/null @@ -1,19 +0,0 @@ -const borsh = require('../../../lib/index.js'); -const transaction = require('./transaction-example/transaction'); - -exports.fuzz = input => { - try { - const deserialized = borsh.deserialize(transaction.SCHEMA, transaction.Transaction, input); - const serialized = borsh.serialize(transaction.SCHEMA, deserialized); - if (!serialized.equals(input)) { - console.log(`Mismatching output:\n${serialized.toString('hex')}\nand input:\n${input.toString('hex')}`); - throw new Error('Mismatching input and output'); - } - } catch (e) { - if (e instanceof borsh.BorshError) { - // Do nothing - } else { - throw e; - } - } -}; \ No newline at end of file diff --git a/borsh-ts/test/fuzz/corpus/004b705c22403d1c22ceab37181f6340b5b269b7faee05d97fe65b1ab91cd171 b/borsh-ts/test/fuzz/corpus/004b705c22403d1c22ceab37181f6340b5b269b7faee05d97fe65b1ab91cd171 deleted file mode 100644 index fcfea9a29697dc2d94ce838889c3553ef75f0a19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmZQz0D{Vx?OVSVnLc;ASUO?Sher&B3(jhP=9ABmZQx6v94FAh2vP(DVnAG6Qks&P zuUl50?3-F#oLFj^YGRn{`@Xrlxz%|lzj4F*J}nFJyWja=PGdCPwDwX|`fM%6w;%(U ixPU}rN(xw8dMY=NSFKc%T2Z28rKD8b&>{yEzytt67B2As diff --git a/borsh-ts/test/fuzz/corpus/25b2da453a8cb0d574d31a06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7 b/borsh-ts/test/fuzz/corpus/25b2da453a8cb0d574d31a06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7 deleted file mode 100644 index 3e55b9a7f45b59ea242c6cdbd0dee8f1d4104891..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn$iPsoRFYaz0#XbF z0zh0`Qks&PuUl50TxywWVwmguzPY-&)p;hral`sPEer9x-}zroV>I2g_EJ>(Y%RvO lKrMGh!{2>`26FGm0X diff --git a/borsh-ts/test/fuzz/corpus/2f6f8117189cb82e111a13b3ffb19fcaf35deef41f8c084726c5aac5a4551696 b/borsh-ts/test/fuzz/corpus/2f6f8117189cb82e111a13b3ffb19fcaf35deef41f8c084726c5aac5a4551696 deleted file mode 100644 index 9f09c84d6a89cc14a382a943328c8fd347767bd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn$iPsoRFYaz0#y3| zhy{SSxTG{CGher?Jh{{|)xgHDGnf%5L>-)4U#P5FRe>siObko}Z4C%AA7~e9o{{PR) s1=O3ElH!|MT%4Gm$_?aIE0v^Hlqgv#Db+T#$N>dFfbkat0}w$00NhV8CjbBd diff --git a/borsh-ts/test/fuzz/corpus/4eb3f263ac0d9bac83d67e28d3381c64c61654579dc455a9e5ccaa54bc093721 b/borsh-ts/test/fuzz/corpus/4eb3f263ac0d9bac83d67e28d3381c64c61654579dc455a9e5ccaa54bc093721 deleted file mode 100644 index 0ea79f1823ab375505fa9bf959a93ba6590bb23d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vWr0#vlMBi%UvV zGV^uI%9BeiQ*(XaH&>e&f~n?K=b8M*4eR@~OvLYg=YKhk(QMP&OHt{wwHV)m3}E5{ k5{W4(zNy8i3><1 nrlk0$78fU`!*!-|1BI)VN>VFIl&qALY8zVQfC5M$62b-mG&nH7 diff --git a/borsh-ts/test/fuzz/corpus/67fc403d488908e0a27d74a2de056c34c40850bbe61b9ebc82cf48365484f4fc b/borsh-ts/test/fuzz/corpus/67fc403d488908e0a27d74a2de056c34c40850bbe61b9ebc82cf48365484f4fc deleted file mode 100644 index 77435fee1f33b572c943a5939a934f1d0d8d127a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vP(D0zh0`Qks&P zuUl50TxywWVwmguzPY-&)p;hral`sPEer9x-}zroV>I2g_EJ>(Y%RvOAiYdnKq4_E g#W%IMI59ny8_26xDoL#bI0}5aQ01<&Ki3>=; cc(^b diff --git a/borsh-ts/test/fuzz/corpus/824d229c23c200a937bcd558a5c55d25830a8efffb9e8661a81e2db3584ca512 b/borsh-ts/test/fuzz/corpus/824d229c23c200a937bcd558a5c55d25830a8efffb9e8661a81e2db3584ca512 deleted file mode 100644 index f7c9ac58b4c75f59f733b9ea2d09e7f718b858af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vWr0#vlMBi%UvV zGV^uI%9BeiQ%wwWecv}%H@7;^i3><1 irlk0$78fU`r*Z>%)k-C)6(vemN=mg2Epk8sOaK5dAuTHa diff --git a/borsh-ts/test/fuzz/corpus/9d6a787b7d95c13bb67d02137cf3e506f550fcfa19363050d1e4917250d83d32 b/borsh-ts/test/fuzz/corpus/9d6a787b7d95c13bb67d02137cf3e506f550fcfa19363050d1e4917250d83d32 deleted file mode 100644 index 630b94ad0b64803456fe83b17806ed1c307dc17f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn$iPsoRFYaz0#XbF z0zh0`Qks&PuUl50TxywWVwjutzPY-&)p;hral`sPEer9x-}zroV>I2g_EJ>(Y%RvO oKre&f~n?K=b8M*4eR@~EX41A=YKhk(QMP&OHt{wwHV)m3}E5{ k5{W4(zNy8ecv}%H@7;^7e+ZXmB(sU)?cM9E4?skWg-4k!QyzZe*R2nqm>U@)}+ diff --git a/borsh-ts/test/fuzz/corpus/b7361e727623b1cdafda1411e27c119009d0fcccbdddd5fe669c8c02bf04f7dd b/borsh-ts/test/fuzz/corpus/b7361e727623b1cdafda1411e27c119009d0fcccbdddd5fe669c8c02bf04f7dd deleted file mode 100644 index d0f9ae0329c0ca85e30e55962b0d41ade18cc9cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vWr0#vlMBi%UvV zGF|F#&3%48U$?A0xzsY%#4y+QeRFkltMg2L2{g&QxxoaJ5oNYDI~Xm6B3zLyH_x00~4w*Z^FmG_L>v diff --git a/borsh-ts/test/fuzz/corpus/b98f80e8a618038000215158136c1f2dbd9ed3cad450556faf7b18db4184b42e b/borsh-ts/test/fuzz/corpus/b98f80e8a618038000215158136c1f2dbd9ed3cad450556faf7b18db4184b42e deleted file mode 100644 index 0181e35d13a2d5fe0a567dd7e7fa99296ad0245a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vWqr$RGeDi%UvV zGV^uI%9BeiQ%wwWecv}%H@7;^i3><1 irlk0$78fU`r*Z>%)k-C)6(vemN=mg2Epk8sOaK4|-YmHQ diff --git a/borsh-ts/test/fuzz/corpus/bec080831059d96afd82176d6e8ee36819bb22b7304f4e23f61d189a6c221f92 b/borsh-ts/test/fuzz/corpus/bec080831059d96afd82176d6e8ee36819bb22b7304f4e23f61d189a6c221f92 deleted file mode 100644 index b04b3f05854aa3d7637fae48b14de189d4a50cb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmZQz0D{Vx?OVSVnLc+)ES)gv!y|^m1!uKC^T}t(Ht?lSjuYr$1StXnF(583DNV`D z*DWhg_DwA=zF2CRYGRn{`@Xrlxz%|lzj4F*KCPG27)v*;y%d!`TZZv1NG}r?kVs5P o0qICgFHeSQO63L$S1XmIR+K1NDJj)9w8#MkV3q-ykw6Rt0QN{QR{#J2 diff --git a/borsh-ts/test/fuzz/corpus/cf94b13d93b7accf950d3d2a1be308f7d25d21f81622a7ffc7da3733c03eb572 b/borsh-ts/test/fuzz/corpus/cf94b13d93b7accf950d3d2a1be308f7d25d21f81622a7ffc7da3733c03eb572 deleted file mode 100644 index 5ade500ec2b0052071865b84132da79f2fe64e4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmZQz0D{Vx?OVSVnLc;ASUO?Sher&B3(jhP=9ABmZQx6v94FAh$WRMX#vlg7#U-UF znfbb9<;ibTi;ELWEmKVlbA8`8S2wph&*V35Sl_2*A%6Ee|I2BNrkmDYib|iY#pufj m)X2mIBob3nd_mgMQ@Me>YNe9YiV`I&B_)Q27CE2*CIA46HZJi1 diff --git a/borsh-ts/test/fuzz/corpus/d2897d4dc13f861fb5e7caf8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7 b/borsh-ts/test/fuzz/corpus/d2897d4dc13f861fb5e7caf8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7 deleted file mode 100644 index 86c116b9414d856cb4b88364dfc37699623ba442..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vP(D0zh0`Qks&P wuUl50TxywWVwmguzPY-&)p;hral`sPEer9x-}zroV>I2g_EJi3><1 lrlk0$78fU`r*Z>%)k-C)6(vemN=mg2Epk8sG>~dy2mt&aEnolu diff --git a/borsh-ts/test/fuzz/corpus/e08ccfae0c2fd6432c8e1bf841050f0b2d78fa519ac76f178db0757d1bd3e96e b/borsh-ts/test/fuzz/corpus/e08ccfae0c2fd6432c8e1bf841050f0b2d78fa519ac76f178db0757d1bd3e96e deleted file mode 100644 index 212ab60fe57278568a39376e388dee96da8129a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmZQz0D{Vx?OVSVDLr?(SUO?Sher&B3(jhP=9ABmZQx6v94FSn2vWr0#vlMBi%UvV zGV^uI%9BeiznB>2`o3?jZf2kA@&=}hGY3Rf$Yq*jzDSt%*iHnhkA1z-T-7NAHZL=*tFkT+%k diff --git a/borsh-ts/test/fuzz/corpus/ecd71b60d1820dbbcb06d1f981ab86d16876888bb7ba665637c5717c0a8916b2 b/borsh-ts/test/fuzz/corpus/ecd71b60d1820dbbcb06d1f981ab86d16876888bb7ba665637c5717c0a8916b2 deleted file mode 100644 index 82dc8f8e8159af961fe9392b5612129507348459..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZQz0D{Vx?OVSVDLr?(SUO>nz$1pj1!uKC^T}t(Ht?lSjuY!(1Sw*0V-Nt6#U-UF znfbb9<;kU%A508$ecv}%H@7;^i3><1 irlk0$78fU`r*Z>%)k-C)6(vemN=mg2Epk8sOaK7uJS^4# diff --git a/borsh-ts/test/fuzz/transaction-example/enums.d.ts b/borsh-ts/test/fuzz/transaction-example/enums.d.ts deleted file mode 100644 index 4d0a3dae..00000000 --- a/borsh-ts/test/fuzz/transaction-example/enums.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export declare abstract class Enum { - enum: string; - constructor(properties: any); -} -export declare abstract class Assignable { - constructor(properties: any); -} diff --git a/borsh-ts/test/fuzz/transaction-example/enums.js b/borsh-ts/test/fuzz/transaction-example/enums.js deleted file mode 100644 index 2351f87f..00000000 --- a/borsh-ts/test/fuzz/transaction-example/enums.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -class Enum { - constructor(properties) { - if (Object.keys(properties).length !== 1) { - throw new Error('Enum can only take single value'); - } - Object.keys(properties).map((key) => { - this[key] = properties[key]; - this.enum = key; - }); - } -} -exports.Enum = Enum; -class Assignable { - constructor(properties) { - Object.keys(properties).map((key) => { - this[key] = properties[key]; - }); - } -} -exports.Assignable = Assignable; diff --git a/borsh-ts/test/fuzz/transaction-example/key_pair.d.ts b/borsh-ts/test/fuzz/transaction-example/key_pair.d.ts deleted file mode 100644 index cf7a473d..00000000 --- a/borsh-ts/test/fuzz/transaction-example/key_pair.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Assignable } from './enums'; -export declare type Arrayish = string | ArrayLike; -export interface Signature { - signature: Uint8Array; - publicKey: PublicKey; -} -/** All supported key types */ -export declare enum KeyType { - ED25519 = 0 -} -/** - * PublicKey representation that has type and bytes of the key. - */ -export declare class PublicKey extends Assignable { - keyType: KeyType; - data: Uint8Array; - static from(value: string | PublicKey): PublicKey; - static fromString(encodedKey: string): PublicKey; - toString(): string; -} -export declare abstract class KeyPair { - abstract sign(message: Uint8Array): Signature; - abstract verify(message: Uint8Array, signature: Uint8Array): boolean; - abstract toString(): string; - abstract getPublicKey(): PublicKey; - /** - * @param curve Name of elliptical curve, case-insensitive - * @returns Random KeyPair based on the curve - */ - static fromRandom(curve: string): KeyPair; - static fromString(encodedKey: string): KeyPair; -} -/** - * This class provides key pair functionality for Ed25519 curve: - * generating key pairs, encoding key pairs, signing and verifying. - */ -export declare class KeyPairEd25519 extends KeyPair { - readonly publicKey: PublicKey; - readonly secretKey: string; - /** - * Construct an instance of key pair given a secret key. - * It's generally assumed that these are encoded in base58. - * @param {string} secretKey - */ - constructor(secretKey: string); - /** - * Generate a new random keypair. - * @example - * const keyRandom = KeyPair.fromRandom(); - * keyRandom.publicKey - * // returns [PUBLIC_KEY] - * - * keyRandom.secretKey - * // returns [SECRET_KEY] - */ - static fromRandom(): KeyPairEd25519; - sign(message: Uint8Array): Signature; - verify(message: Uint8Array, signature: Uint8Array): boolean; - toString(): string; - getPublicKey(): PublicKey; -} diff --git a/borsh-ts/test/fuzz/transaction-example/key_pair.js b/borsh-ts/test/fuzz/transaction-example/key_pair.js deleted file mode 100644 index c49f3890..00000000 --- a/borsh-ts/test/fuzz/transaction-example/key_pair.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { 'default': mod }; -}; -Object.defineProperty(exports, '__esModule', { value: true }); -const tweetnacl_1 = __importDefault(require('tweetnacl')); -const serialize_1 = require('./serialize'); -const enums_1 = require('./enums'); -/** All supported key types */ -var KeyType; -(function (KeyType) { - KeyType[KeyType['ED25519'] = 0] = 'ED25519'; -})(KeyType = exports.KeyType || (exports.KeyType = {})); -function key_type_to_str(keyType) { - switch (keyType) { - case KeyType.ED25519: return 'ed25519'; - default: throw new Error(`Unknown key type ${keyType}`); - } -} -function str_to_key_type(keyType) { - switch (keyType.toLowerCase()) { - case 'ed25519': return KeyType.ED25519; - default: throw new Error(`Unknown key type ${keyType}`); - } -} -/** - * PublicKey representation that has type and bytes of the key. - */ -class PublicKey extends enums_1.Assignable { - static from(value) { - if (typeof value === 'string') { - return PublicKey.fromString(value); - } - return value; - } - static fromString(encodedKey) { - const parts = encodedKey.split(':'); - if (parts.length === 1) { - return new PublicKey({ keyType: KeyType.ED25519, data: serialize_1.base_decode(parts[0]) }); - } - else if (parts.length === 2) { - return new PublicKey({ keyType: str_to_key_type(parts[0]), data: serialize_1.base_decode(parts[1]) }); - } - else { - throw new Error('Invalid encoded key format, must be :'); - } - } - toString() { - return `${key_type_to_str(this.keyType)}:${serialize_1.base_encode(this.data)}`; - } -} -exports.PublicKey = PublicKey; -class KeyPair { - /** - * @param curve Name of elliptical curve, case-insensitive - * @returns Random KeyPair based on the curve - */ - static fromRandom(curve) { - switch (curve.toUpperCase()) { - case 'ED25519': return KeyPairEd25519.fromRandom(); - default: throw new Error(`Unknown curve ${curve}`); - } - } - static fromString(encodedKey) { - const parts = encodedKey.split(':'); - if (parts.length === 1) { - return new KeyPairEd25519(parts[0]); - } - else if (parts.length === 2) { - switch (parts[0].toUpperCase()) { - case 'ED25519': return new KeyPairEd25519(parts[1]); - default: throw new Error(`Unknown curve: ${parts[0]}`); - } - } - else { - throw new Error('Invalid encoded key format, must be :'); - } - } -} -exports.KeyPair = KeyPair; -/** - * This class provides key pair functionality for Ed25519 curve: - * generating key pairs, encoding key pairs, signing and verifying. - */ -class KeyPairEd25519 extends KeyPair { - /** - * Construct an instance of key pair given a secret key. - * It's generally assumed that these are encoded in base58. - * @param {string} secretKey - */ - constructor(secretKey) { - super(); - const keyPair = tweetnacl_1.default.sign.keyPair.fromSecretKey(serialize_1.base_decode(secretKey)); - this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); - this.secretKey = secretKey; - } - /** - * Generate a new random keypair. - * @example - * const keyRandom = KeyPair.fromRandom(); - * keyRandom.publicKey - * // returns [PUBLIC_KEY] - * - * keyRandom.secretKey - * // returns [SECRET_KEY] - */ - static fromRandom() { - const newKeyPair = tweetnacl_1.default.sign.keyPair(); - return new KeyPairEd25519(serialize_1.base_encode(newKeyPair.secretKey)); - } - sign(message) { - const signature = tweetnacl_1.default.sign.detached(message, serialize_1.base_decode(this.secretKey)); - return { signature, publicKey: this.publicKey }; - } - verify(message, signature) { - return tweetnacl_1.default.sign.detached.verify(message, signature, this.publicKey.data); - } - toString() { - return `ed25519:${this.secretKey}`; - } - getPublicKey() { - return this.publicKey; - } -} -exports.KeyPairEd25519 = KeyPairEd25519; diff --git a/borsh-ts/test/fuzz/transaction-example/serialize.d.ts b/borsh-ts/test/fuzz/transaction-example/serialize.d.ts deleted file mode 100644 index 6db96b58..00000000 --- a/borsh-ts/test/fuzz/transaction-example/serialize.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/// -import BN from 'bn.js'; -export declare function base_encode(value: Uint8Array | string): string; -export declare function base_decode(value: string): Uint8Array; -export declare type Schema = Map; -export declare class BorshError extends Error { - originalMessage: string; - fieldPath: string[]; - constructor(message: string); - addToFieldPath(fieldName: string): void; -} -export declare class BinaryWriter { - buf: Buffer; - length: number; - constructor(); - maybe_resize(): void; - write_u8(value: number): void; - write_u32(value: number): void; - write_u64(value: BN): void; - write_u128(value: BN): void; - private write_buffer; - write_string(str: string): void; - write_fixed_array(array: Uint8Array): void; - write_array(array: any[], fn: any): void; - toArray(): Uint8Array; -} -export declare class BinaryReader { - buf: Buffer; - offset: number; - constructor(buf: Buffer); - read_u8(): number; - read_u32(): number; - read_u64(): BN; - read_u128(): BN; - private read_buffer; - read_string(): string; - read_fixed_array(len: number): Uint8Array; - read_array(fn: any): any[]; -} -export declare function serialize(schema: Schema, obj: any): Uint8Array; -export declare function deserialize(schema: Schema, classType: any, buffer: Buffer): any; diff --git a/borsh-ts/test/fuzz/transaction-example/serialize.js b/borsh-ts/test/fuzz/transaction-example/serialize.js deleted file mode 100644 index 2437c307..00000000 --- a/borsh-ts/test/fuzz/transaction-example/serialize.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { 'default': mod }; -}; -const bs58_1 = __importDefault(require('bs58')); -function base_encode(value) { - if (typeof (value) === 'string') { - value = Buffer.from(value, 'utf8'); - } - return bs58_1.default.encode(Buffer.from(value)); -} -exports.base_encode = base_encode; -function base_decode(value) { - return Buffer.from(bs58_1.default.decode(value)); -} -exports.base_decode = base_decode; diff --git a/borsh-ts/test/fuzz/transaction-example/signer.d.ts b/borsh-ts/test/fuzz/transaction-example/signer.d.ts deleted file mode 100644 index 36775b63..00000000 --- a/borsh-ts/test/fuzz/transaction-example/signer.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Signature, KeyPair, PublicKey } from './key_pair'; -import { KeyStore } from './key_stores'; -/** - * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. - */ -export declare abstract class Signer { - /** - * Creates new key and returns public key. - */ - abstract createKey(accountId: string, networkId?: string): Promise; - /** - * Returns public key for given account / network. - * @param accountId accountId to retrieve from. - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - abstract getPublicKey(accountId?: string, networkId?: string): Promise; - /** - * Signs given message, by first hashing with sha256. - * @param message message to sign. - * @param accountId accountId to use for signing. - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - abstract signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; -} -/** - * Signs using in memory key store. - */ -export declare class InMemorySigner extends Signer { - readonly keyStore: KeyStore; - constructor(keyStore: KeyStore); - /** - * Creates a single account Signer instance with account, network and keyPair provided. - * - * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). - * - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account to assign the key pair to - * @param keyPair The keyPair to use for signing - */ - static fromKeyPair(networkId: string, accountId: string, keyPair: KeyPair): Promise; - /** - * Creates a public key for the account given - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - createKey(accountId: string, networkId: string): Promise; - /** - * Gets the existing public key for a given account - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} Returns the public key or null if not found - */ - getPublicKey(accountId?: string, networkId?: string): Promise; - /** - * @param message A message to be signed, typically a serialized transaction - * @param accountId the NEAR account signing the message - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; - toString(): string; -} diff --git a/borsh-ts/test/fuzz/transaction-example/signer.js b/borsh-ts/test/fuzz/transaction-example/signer.js deleted file mode 100644 index 00eec265..00000000 --- a/borsh-ts/test/fuzz/transaction-example/signer.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { 'default': mod }; -}; -Object.defineProperty(exports, '__esModule', { value: true }); -const js_sha256_1 = __importDefault(require('js-sha256')); -const key_pair_1 = require('./key_pair'); -const key_stores_1 = require('./key_stores'); -/** - * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. - */ -class Signer { -} -exports.Signer = Signer; -/** - * Signs using in memory key store. - */ -class InMemorySigner extends Signer { - constructor(keyStore) { - super(); - this.keyStore = keyStore; - } - /** - * Creates a single account Signer instance with account, network and keyPair provided. - * - * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). - * - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account to assign the key pair to - * @param keyPair The keyPair to use for signing - */ - static async fromKeyPair(networkId, accountId, keyPair) { - const keyStore = new key_stores_1.InMemoryKeyStore(); - await keyStore.setKey(networkId, accountId, keyPair); - return new InMemorySigner(keyStore); - } - /** - * Creates a public key for the account given - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - async createKey(accountId, networkId) { - const keyPair = key_pair_1.KeyPair.fromRandom('ed25519'); - await this.keyStore.setKey(networkId, accountId, keyPair); - return keyPair.getPublicKey(); - } - /** - * Gets the existing public key for a given account - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} Returns the public key or null if not found - */ - async getPublicKey(accountId, networkId) { - const keyPair = await this.keyStore.getKey(networkId, accountId); - if (keyPair === null) { - return null; - } - return keyPair.getPublicKey(); - } - /** - * @param message A message to be signed, typically a serialized transaction - * @param accountId the NEAR account signing the message - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - async signMessage(message, accountId, networkId) { - const hash = new Uint8Array(js_sha256_1.default.sha256.array(message)); - if (!accountId) { - throw new Error('InMemorySigner requires provided account id'); - } - const keyPair = await this.keyStore.getKey(networkId, accountId); - if (keyPair === null) { - throw new Error(`Key for ${accountId} not found in ${networkId}`); - } - return keyPair.sign(hash); - } - toString() { - return `InMemorySigner(${this.keyStore})`; - } -} -exports.InMemorySigner = InMemorySigner; diff --git a/borsh-ts/test/fuzz/transaction-example/transaction.d.ts b/borsh-ts/test/fuzz/transaction-example/transaction.d.ts deleted file mode 100644 index bedf27f3..00000000 --- a/borsh-ts/test/fuzz/transaction-example/transaction.d.ts +++ /dev/null @@ -1,106 +0,0 @@ -/// -import BN from 'bn.js'; -import { Enum, Assignable } from './enums'; -import { KeyType, PublicKey } from './key_pair'; -import { Signer } from './signer'; -export declare class FunctionCallPermission extends Assignable { - allowance?: BN; - receiverId: string; - methodNames: string[]; -} -export declare class FullAccessPermission extends Assignable { -} -export declare class AccessKeyPermission extends Enum { - functionCall: FunctionCallPermission; - fullAccess: FullAccessPermission; -} -export declare class AccessKey extends Assignable { - nonce: number; - permission: AccessKeyPermission; -} -export declare function fullAccessKey(): AccessKey; -export declare function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey; -export declare class IAction extends Assignable { -} -export declare class CreateAccount extends IAction { -} -export declare class DeployContract extends IAction { - code: Uint8Array; -} -export declare class FunctionCall extends IAction { - methodName: string; - args: Uint8Array; - gas: BN; - deposit: BN; -} -export declare class Transfer extends IAction { - deposit: BN; -} -export declare class Stake extends IAction { - stake: BN; - publicKey: PublicKey; -} -export declare class AddKey extends IAction { - publicKey: PublicKey; - accessKey: AccessKey; -} -export declare class DeleteKey extends IAction { - publicKey: PublicKey; -} -export declare class DeleteAccount extends IAction { - beneficiaryId: string; -} -export declare function createAccount(): Action; -export declare function deployContract(code: Uint8Array): Action; -/** - * Constructs {@link Action} instance representing contract method call. - * - * @param methodName the name of the method to call - * @param args arguments to pass to method. Can be either plain JS object which gets serialized as JSON automatically - * or `Uint8Array` instance which represents bytes passed as is. - * @param gas max amount of gas that method call can use - * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call - */ -export declare function functionCall(methodName: string, args: Uint8Array | object, gas: BN, deposit: BN): Action; -export declare function transfer(deposit: BN): Action; -export declare function stake(stake: BN, publicKey: PublicKey): Action; -export declare function addKey(publicKey: PublicKey, accessKey: AccessKey): Action; -export declare function deleteKey(publicKey: PublicKey): Action; -export declare function deleteAccount(beneficiaryId: string): Action; -export declare class Signature extends Assignable { - keyType: KeyType; - data: Uint8Array; -} -export declare class Transaction extends Assignable { - signerId: string; - publicKey: PublicKey; - nonce: number; - receiverId: string; - actions: Action[]; - blockHash: Uint8Array; - encode(): Uint8Array; - static decode(bytes: Buffer): Transaction; -} -export declare class SignedTransaction extends Assignable { - transaction: Transaction; - signature: Signature; - encode(): Uint8Array; - static decode(bytes: Buffer): SignedTransaction; -} -/** - * Contains a list of the valid transaction Actions available with this API - */ -export declare class Action extends Enum { - createAccount: CreateAccount; - deployContract: DeployContract; - functionCall: FunctionCall; - transfer: Transfer; - stake: Stake; - addKey: AddKey; - deleteKey: DeleteKey; - deleteAccount: DeleteAccount; -} -export declare const SCHEMA: Map; -export declare function createTransaction(signerId: string, publicKey: PublicKey, receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array): Transaction; -export declare function signTransaction(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; -export declare function signTransaction(receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; diff --git a/borsh-ts/test/fuzz/transaction-example/transaction.js b/borsh-ts/test/fuzz/transaction-example/transaction.js deleted file mode 100644 index 50aa7c8b..00000000 --- a/borsh-ts/test/fuzz/transaction-example/transaction.js +++ /dev/null @@ -1,234 +0,0 @@ -'use strict'; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { 'default': mod }; -}; -Object.defineProperty(exports, '__esModule', { value: true }); -const js_sha256_1 = __importDefault(require('js-sha256')); -const enums_1 = require('./enums'); -const serialize_1 = require('./serialize'); -const key_pair_1 = require('./key_pair'); -class FunctionCallPermission extends enums_1.Assignable { -} -exports.FunctionCallPermission = FunctionCallPermission; -class FullAccessPermission extends enums_1.Assignable { -} -exports.FullAccessPermission = FullAccessPermission; -class AccessKeyPermission extends enums_1.Enum { -} -exports.AccessKeyPermission = AccessKeyPermission; -class AccessKey extends enums_1.Assignable { -} -exports.AccessKey = AccessKey; -function fullAccessKey() { - return new AccessKey({ nonce: 0, permission: new AccessKeyPermission({ fullAccess: new FullAccessPermission({}) }) }); -} -exports.fullAccessKey = fullAccessKey; -function functionCallAccessKey(receiverId, methodNames, allowance) { - return new AccessKey({ nonce: 0, permission: new AccessKeyPermission({ functionCall: new FunctionCallPermission({ receiverId, allowance, methodNames }) }) }); -} -exports.functionCallAccessKey = functionCallAccessKey; -class IAction extends enums_1.Assignable { -} -exports.IAction = IAction; -class CreateAccount extends IAction { -} -exports.CreateAccount = CreateAccount; -class DeployContract extends IAction { -} -exports.DeployContract = DeployContract; -class FunctionCall extends IAction { -} -exports.FunctionCall = FunctionCall; -class Transfer extends IAction { -} -exports.Transfer = Transfer; -class Stake extends IAction { -} -exports.Stake = Stake; -class AddKey extends IAction { -} -exports.AddKey = AddKey; -class DeleteKey extends IAction { -} -exports.DeleteKey = DeleteKey; -class DeleteAccount extends IAction { -} -exports.DeleteAccount = DeleteAccount; -function createAccount() { - return new Action({ createAccount: new CreateAccount({}) }); -} -exports.createAccount = createAccount; -function deployContract(code) { - return new Action({ deployContract: new DeployContract({ code }) }); -} -exports.deployContract = deployContract; -/** - * Constructs {@link Action} instance representing contract method call. - * - * @param methodName the name of the method to call - * @param args arguments to pass to method. Can be either plain JS object which gets serialized as JSON automatically - * or `Uint8Array` instance which represents bytes passed as is. - * @param gas max amount of gas that method call can use - * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call - */ -function functionCall(methodName, args, gas, deposit) { - const anyArgs = args; - const isUint8Array = anyArgs.byteLength !== undefined && anyArgs.byteLength === anyArgs.length; - const serializedArgs = isUint8Array ? args : Buffer.from(JSON.stringify(args)); - return new Action({ functionCall: new FunctionCall({ methodName, args: serializedArgs, gas, deposit }) }); -} -exports.functionCall = functionCall; -function transfer(deposit) { - return new Action({ transfer: new Transfer({ deposit }) }); -} -exports.transfer = transfer; -function stake(stake, publicKey) { - return new Action({ stake: new Stake({ stake, publicKey }) }); -} -exports.stake = stake; -function addKey(publicKey, accessKey) { - return new Action({ addKey: new AddKey({ publicKey, accessKey }) }); -} -exports.addKey = addKey; -function deleteKey(publicKey) { - return new Action({ deleteKey: new DeleteKey({ publicKey }) }); -} -exports.deleteKey = deleteKey; -function deleteAccount(beneficiaryId) { - return new Action({ deleteAccount: new DeleteAccount({ beneficiaryId }) }); -} -exports.deleteAccount = deleteAccount; -class Signature extends enums_1.Assignable { -} -exports.Signature = Signature; -class Transaction extends enums_1.Assignable { - encode() { - return serialize_1.serialize(exports.SCHEMA, this); - } - static decode(bytes) { - return serialize_1.deserialize(exports.SCHEMA, Transaction, bytes); - } -} -exports.Transaction = Transaction; -class SignedTransaction extends enums_1.Assignable { - encode() { - return serialize_1.serialize(exports.SCHEMA, this); - } - static decode(bytes) { - return serialize_1.deserialize(exports.SCHEMA, SignedTransaction, bytes); - } -} -exports.SignedTransaction = SignedTransaction; -/** - * Contains a list of the valid transaction Actions available with this API - */ -class Action extends enums_1.Enum { -} -exports.Action = Action; -exports.SCHEMA = new Map([ - [Signature, { kind: 'struct', fields: [ - ['keyType', 'u8'], - ['data', [64]] - ] }], - [SignedTransaction, { kind: 'struct', fields: [ - ['transaction', Transaction], - ['signature', Signature] - ] }], - [Transaction, { kind: 'struct', fields: [ - ['signerId', 'string'], - ['publicKey', key_pair_1.PublicKey], - ['nonce', 'u64'], - ['receiverId', 'string'], - ['blockHash', [32]], - ['actions', [Action]] - ] }], - [key_pair_1.PublicKey, { kind: 'struct', fields: [ - ['keyType', 'u8'], - ['data', [32]] - ] }], - [AccessKey, { kind: 'struct', fields: [ - ['nonce', 'u64'], - ['permission', AccessKeyPermission], - ] }], - [AccessKeyPermission, { kind: 'enum', field: 'enum', values: [ - ['functionCall', FunctionCallPermission], - ['fullAccess', FullAccessPermission], - ] }], - [FunctionCallPermission, { kind: 'struct', fields: [ - ['allowance', { kind: 'option', type: 'u128' }], - ['receiverId', 'string'], - ['methodNames', ['string']], - ] }], - [FullAccessPermission, { kind: 'struct', fields: [] }], - [Action, { kind: 'enum', field: 'enum', values: [ - ['createAccount', CreateAccount], - ['deployContract', DeployContract], - ['functionCall', FunctionCall], - ['transfer', Transfer], - ['stake', Stake], - ['addKey', AddKey], - ['deleteKey', DeleteKey], - ['deleteAccount', DeleteAccount], - ] }], - [CreateAccount, { kind: 'struct', fields: [] }], - [DeployContract, { kind: 'struct', fields: [ - ['code', ['u8']] - ] }], - [FunctionCall, { kind: 'struct', fields: [ - ['methodName', 'string'], - ['args', ['u8']], - ['gas', 'u64'], - ['deposit', 'u128'] - ] }], - [Transfer, { kind: 'struct', fields: [ - ['deposit', 'u128'] - ] }], - [Stake, { kind: 'struct', fields: [ - ['stake', 'u128'], - ['publicKey', key_pair_1.PublicKey] - ] }], - [AddKey, { kind: 'struct', fields: [ - ['publicKey', key_pair_1.PublicKey], - ['accessKey', AccessKey] - ] }], - [DeleteKey, { kind: 'struct', fields: [ - ['publicKey', key_pair_1.PublicKey] - ] }], - [DeleteAccount, { kind: 'struct', fields: [ - ['beneficiaryId', 'string'] - ] }], -]); -function createTransaction(signerId, publicKey, receiverId, nonce, actions, blockHash) { - return new Transaction({ signerId, publicKey, nonce, receiverId, actions, blockHash }); -} -exports.createTransaction = createTransaction; -/** - * Signs a given transaction from an account with given keys, applied to the given network - * @param transaction The Transaction object to sign - * @param signer The {Signer} object that assists with signing keys - * @param accountId The human-readable NEAR account name - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ -async function signTransactionObject(transaction, signer, accountId, networkId) { - const message = serialize_1.serialize(exports.SCHEMA, transaction); - const hash = new Uint8Array(js_sha256_1.default.sha256.array(message)); - const signature = await signer.signMessage(message, accountId, networkId); - const signedTx = new SignedTransaction({ - transaction, - signature: new Signature({ keyType: transaction.publicKey.keyType, data: signature.signature }) - }); - return [hash, signedTx]; -} -async function signTransaction(...args) { - if (args[0].constructor === Transaction) { - const [transaction, signer, accountId, networkId] = args; - return signTransactionObject(transaction, signer, accountId, networkId); - } - else { - const [receiverId, nonce, actions, blockHash, signer, accountId, networkId] = args; - const publicKey = await signer.getPublicKey(accountId, networkId); - const transaction = createTransaction(accountId, publicKey, receiverId, nonce, actions, blockHash); - return signTransactionObject(transaction, signer, accountId, networkId); - } -} -exports.signTransaction = signTransaction; diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index 39419ab0..4c262164 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -1,204 +1,90 @@ const borsh = require('../../lib/index'); const BN = require('bn.js'); -class Assignable { - constructor(properties) { - Object.keys(properties).map((key) => { - this[key] = properties[key]; - }); - } +function check_encode(value, schema, expected) { + const encoded = borsh.serialize(schema, value); + expect(encoded).toEqual(Uint8Array.from(expected)); } -class Test extends Assignable { } - -class Serializable { - constructor(data) { - this.data = data; - } - - static borshDeserialize(reader) { - return new Serializable(reader.readU8()); - } - - borshSerialize(writer) { - writer.writeU8(this.data); - } -} - -test('serialize object', async () => { - const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] }); - const schema = new Map([[Test, { kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]); - const buf = borsh.serialize(schema, value); - const newValue = borsh.deserialize(schema, Test, buf); - expect(newValue.x).toEqual(255); - expect(newValue.y.toString()).toEqual('20'); - expect(newValue.z).toEqual('123'); - expect(newValue.q).toEqual(new Uint8Array([1, 2, 3])); +test('serialize integers', async () => { + check_encode(100, 'u8', [100]); + check_encode(258, 'u16', [2, 1]); + check_encode(102, 'u32', [102, 0, 0, 0]); + check_encode(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); + check_encode(new BN(104), 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + check_encode(-100, 'i8', [156]); + check_encode(-258, 'i16', [254, 254]); + check_encode(-102, 'i32', [154, 255, 255, 255]); + check_encode(new BN(-103), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); + check_encode(new BN(-104), 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); }); -test('serialize optional field', async () => { - const schema = new Map([[Test, { kind: 'struct', fields: [['x', { kind: 'option', type: 'string' }]] }]]); - - let buf = borsh.serialize(schema, new Test({ x: '123', })); - let newValue = borsh.deserialize(schema, Test, buf); - expect(newValue.x).toEqual('123'); - - buf = borsh.serialize(schema, new Test({})); - newValue = borsh.deserialize(schema, Test, buf); - expect(newValue.x).toEqual(undefined); +test('serialize booleans', async () => { + check_encode(true, 'bool', [1]); + check_encode(false, 'bool', [0]); }); -test('serialize max uint', async () => { - const u64MaxHex = 'ffffffffffffffff'; - const value = new Test({ - x: 255, - y: 65535, - z: 4294967295, - q: new BN(u64MaxHex, 16), - r: new BN(u64MaxHex.repeat(2), 16), - s: new BN(u64MaxHex.repeat(4), 16), - t: new BN(u64MaxHex.repeat(8), 16) - }); - const schema = new Map([[Test, { - kind: 'struct', - fields: [ - ['x', 'u8'], - ['y', 'u16'], - ['z', 'u32'], - ['q', 'u64'], - ['r', 'u128'], - ['s', 'u256'], - ['t', 'u512'] - ] - }]]); - const buf = borsh.serialize(schema, value); - const newValue = borsh.deserialize(schema, Test, buf); - expect(newValue.x).toEqual(255); - expect(newValue.y).toEqual(65535); - expect(newValue.z).toEqual(4294967295); - expect(newValue.q.toString()).toEqual('18446744073709551615'); - expect(newValue.r.toString()).toEqual('340282366920938463463374607431768211455'); - expect(newValue.s.toString()).toEqual('115792089237316195423570985008687907853269984665640564039457584007913129639935'); - expect(newValue.t.toString()).toEqual('13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095'); +test('serialize strings', async () => { + check_encode('h"i', 'string', [3, 0, 0, 0, 104, 34, 105]); }); -test('serialize/deserialize with class methods', () => { - const item = new Serializable(10); - - const buf = borsh.serialize(null, item); - const newValue = borsh.deserialize(null, Serializable, buf); - - expect(newValue).toEqual(item); +test('serialize floats', async () => { + check_encode(7.23, 'f64', [236, 81, 184, 30, 133, 235, 28, 64]); + check_encode(7.23, 'f32', [41, 92, 231, 64]); + check_encode(10e2, 'f32', [0, 0, 122, 68]); + check_encode(10e2, 'f64', [0, 0, 0, 0, 0, 64, 143, 64]); }); -test('serialize/deserialize fixed array', () => { - const value = new Test({ - a: ['hello', 'world'] - }); - const schema = new Map([[Test, { - kind: 'struct', - fields: [ - ['a', ['string', 2]] - ] - }]]); - - const buf = borsh.serialize(schema, value); - const deserializedValue = borsh.deserialize(schema, Test, buf); - - expect(buf).toEqual(Buffer.from([5, 0, 0, 0, 104, 101, 108, 108, 111, 5, 0, 0, 0, 119, 111, 114, 108, 100])); - expect(deserializedValue.a).toEqual(['hello', 'world']); +test('serialize arrays', async () => { + check_encode([true, false], { array: { type: 'bool' } }, [2, 0, 0, 0, 1, 0]); + check_encode([true, false], { array: { type: 'bool', len: 2 } }, [1, 0]); + check_encode(new ArrayBuffer(2), { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); + check_encode(new ArrayBuffer(2), { array: { type: 'u8', len: 2 } }, [0, 0]); }); -test('errors serializing fixed array of wrong size', () => { - const value = new Test({ - a: ['hello', 'world', 'you'] - }); - const schema = new Map([[Test, { - kind: 'struct', - fields: [ - ['a', ['string', 2]] - ] - }]]); - - expect(() => borsh.serialize(schema, value)).toThrow('Expecting byte array of length 2, but got 3 bytes'); +test('serialize options', async () => { + check_encode(null, { option: 'u8' }, [0]); + check_encode(1, { option: 'u32' }, [1, 1, 0, 0, 0]); }); -test('errors serializing fixed array of wrong type', () => { - const value = new Test({ - a: [244, 34] - }); - const schema = new Map([[Test, { - kind: 'struct', - fields: [ - ['a', ['string', 2]] - ] - }]]); +test('serialize maps', async () => { + check_encode(new Map(), { map: { key: 'u8', value: 'u8' } }, [0, 0, 0, 0]); + check_encode({ 'a': 1, 'b': 2 }, { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); - expect(() => borsh.serialize(schema, value)).toThrow('The first argument must be of type string'); + const map = new Map(); + map.set('testing', 1); + check_encode(map, { map: { key: 'string', value: 'u32' } }, [1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0]); }); -test('baseEncode string test', async () => { - const encodedValue = borsh.baseEncode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); - const expectedValue = 'HKk9gqNj4xb4rLdJuzT5zzJbLa4vHBdYCxQT9H99csQh6nz3Hfpqn4jtWA92'; - expect(encodedValue).toEqual(expectedValue); +test('serialize sets', async () => { + check_encode(new Set(), { set: 'u8' }, [0, 0, 0, 0]); + check_encode(new Set([1, 2]), { set: 'u8' }, [2, 0, 0, 0, 1, 2]); }); -test('baseEncode array test', async () => { - expect(borsh.baseEncode([1, 2, 3, 4, 5])).toEqual('7bWpTW'); -}); - -test('baseDecode test', async () => { - const value = 'HKk9gqNj4xb4rLdJu'; - const expectedDecodedArray = [3, 96, 254, 84, 10, 240, 93, 199, 52, 244, 164, 240, 6]; - const expectedBuffer = Buffer.from(expectedDecodedArray); - expect(borsh.baseDecode(value)).toEqual(expectedBuffer); -}); - -test('base encode and decode test', async () => { - const value = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; - expect(borsh.baseEncode(borsh.baseDecode(value))).toEqual(value); -}); - -test('serialize with custom writer/reader', async () => { - class ExtendedWriter extends borsh.BinaryWriter { - writeDate(value) { - this.writeU64(value.getTime()); +test('serialize struct', async () => { + const numbers = new Numbers(); + const schema = { + struct: { + u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', i128: 'i128', f32: 'f32', f64: 'f64' } - } + }; + check_encode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]); - class ExtendedReader extends borsh.BinaryReader { - readDate() { - const value = this.readU64(); - return new Date(value.toNumber()); - } - } - - const time = 'Aug 12, 2021 12:00:00 UTC+00:00'; - const value = new Test({ x: new Date(time) }); - const schema = new Map([[Test, { kind: 'struct', fields: [['x', 'date']] }]]); - - const buf = borsh.serialize(schema, value, ExtendedWriter); - const newValue = borsh.deserialize(schema, Test, buf, ExtendedReader); - expect(newValue.x).toEqual(new Date(time)); }); -test('serialize map', async () => { - let map = new Map(); - for (let i = 0; i < 10; i++) { - map.set(new BN(i * 10), 'some string ' + i.toString()); - } - const value = new Test({ x: map }); - const schema = new Map([[Test, { - kind: 'struct', - fields: [ - ['x', { kind: 'map', key: 'u64', value: 'string' }], - ], - }]]); - const buf = borsh.serialize(schema, value); - const deserialized = borsh.deserialize(schema, Test, buf); - expect(deserialized.x.size).toEqual(10); - deserialized.x.forEach((value, key) => { - expect(value).toEqual('some string ' + (key.toNumber() / 10).toString()); - }); -}); + +// Aux structures +class Numbers { + u8 = 1; + u16 = 2; + u32 = 3; + u64 = new BN(4); + u128 = new BN(5); + i8 = -1; + i16 = -2; + i32 = -3; + i64 = new BN(-4); + f32 = 6.0; + f64 = 7.1; +} \ No newline at end of file diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts new file mode 100644 index 00000000..ab0c8ac5 --- /dev/null +++ b/borsh-ts/types.ts @@ -0,0 +1,12 @@ +export const numbers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; + +export type NumberType = typeof numbers[number]; +export type BoolType = 'bool'; +export type StringType = 'string'; + +export type OptionType = { option: Schema }; +export type ArrayType = { array: { type: Schema, len?: number } }; +export type SetType = { set: Schema }; +export type MapType = { map: { key: Schema, value: Schema } }; +export type StructType = { struct: { [key: string]: Schema } }; +export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; \ No newline at end of file diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts new file mode 100644 index 00000000..91765f75 --- /dev/null +++ b/borsh-ts/utils.ts @@ -0,0 +1,35 @@ +import BN from 'bn.js'; + +export function isArrayLike(value: unknown): boolean { + // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like + return ( + Array.isArray(value) || + (!!value && + typeof value === 'object' && + 'length' in value && + typeof (value.length) === 'number' && + (value.length === 0 || + (value.length > 0 && + (value.length - 1) in value) + ) + ) + ); +} + +export function expect_type(value: unknown, type: string): void { + if (typeof (value) !== type) { + throw new Error(`Expected ${type} not ${typeof (value)}(${value})`); + } +} + +export function expect_BN(value: unknown): void { + if (!(value instanceof BN)) { + throw new Error(`Expected BN not ${typeof (value)}(${value})`); + } +} + +export function expect_same_size(length: number, expected: number): void { + if (length !== expected) { + throw new Error(`Array length ${length} does not match schema length ${expected}`); + } +} \ No newline at end of file diff --git a/lib/buffer.d.ts b/lib/buffer.d.ts new file mode 100644 index 00000000..5d05cfb3 --- /dev/null +++ b/lib/buffer.d.ts @@ -0,0 +1,11 @@ +import { NumberType } from './types'; +export declare class EncodeBuffer { + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + resize_if_necessary(needed_space: number): void; + get_used_buffer(): Uint8Array; + store_value(value: number, type: NumberType): void; + store_bytes(from: Uint8Array): void; +} diff --git a/lib/buffer.js b/lib/buffer.js new file mode 100644 index 00000000..22cac8e9 --- /dev/null +++ b/lib/buffer.js @@ -0,0 +1,81 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EncodeBuffer = void 0; +class EncodeBuffer { + offset = 0; + buffer_size = 256; + buffer = new ArrayBuffer(this.buffer_size); + view = new DataView(this.buffer); + resize_if_necessary(needed_space) { + if (this.buffer_size - this.offset < needed_space) { + this.buffer_size = Math.max(this.buffer_size * 2, this.buffer_size + needed_space); + const new_buffer = new ArrayBuffer(this.buffer_size); + new Uint8Array(new_buffer).set(new Uint8Array(this.buffer)); + this.buffer = new_buffer; + this.view = new DataView(new_buffer); + } + } + get_used_buffer() { + return new Uint8Array(this.buffer).slice(0, this.offset); + } + store_value(value, type) { + const size = parseInt(type.substring(1)) / 8; + this.resize_if_necessary(size); + switch (type) { + case 'u8': + this.view.setUint8(this.offset, value); + break; + case 'u16': + this.view.setUint16(this.offset, value, true); + break; + case 'u32': + this.view.setUint32(this.offset, value, true); + break; + case 'i8': + this.view.setInt8(this.offset, value); + break; + case 'i16': + this.view.setInt16(this.offset, value, true); + break; + case 'i32': + this.view.setInt32(this.offset, value, true); + break; + case 'f32': + this.view.setFloat32(this.offset, value, true); + break; + case 'f64': + this.view.setFloat64(this.offset, value, true); + break; + default: + throw new Error(`Unsupported integer type: ${type}`); + } + this.offset += size; + } + store_bytes(from) { + this.resize_if_necessary(from.length); + new Uint8Array(this.buffer).set(new Uint8Array(from), this.offset); + this.offset += from.length; + } +} +exports.EncodeBuffer = EncodeBuffer; +// export class DecodeBuffer { +// public offset: number = 0; +// public start: usize; +// constructor(public arrBuffer: ArrayBuffer) { +// this.start = changetype(arrBuffer) +// } +// consume(): T { +// const off = this.offset +// this.offset += sizeof() +// return load(this.start + off) +// } +// consume_slice(length: number): ArrayBuffer { +// const off = this.offset +// this.offset += length +// return changetype(this.start).slice(off, off + length) +// } +// consume_copy(dst: usize, length: number): void { +// memory.copy(dst, this.start + this.offset, length); +// this.offset += length; +// } +// } diff --git a/lib/index.d.ts b/lib/index.d.ts index 7cd65542..a9763d55 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,52 +1,2 @@ -/// -import BN from 'bn.js'; -export declare function baseEncode(value: Uint8Array | string): string; -export declare function baseDecode(value: string): Buffer; -export type Schema = Map; -export declare class BorshError extends Error { - originalMessage: string; - fieldPath: string[]; - constructor(message: string); - addToFieldPath(fieldName: string): void; -} -export declare class BinaryWriter { - buf: Buffer; - length: number; - constructor(); - maybeResize(): void; - writeU8(value: number): void; - writeU16(value: number): void; - writeU32(value: number): void; - writeU64(value: number | BN): void; - writeU128(value: number | BN): void; - writeU256(value: number | BN): void; - writeU512(value: number | BN): void; - private writeBuffer; - writeString(str: string): void; - writeFixedArray(array: Uint8Array): void; - writeArray(array: any[], fn: any): void; - toArray(): Uint8Array; -} -export declare class BinaryReader { - buf: Buffer; - offset: number; - constructor(buf: Buffer); - readU8(): number; - readU16(): number; - readU32(): number; - readU64(): BN; - readU128(): BN; - readU256(): BN; - readU512(): BN; - private readBuffer; - readString(): string; - readFixedArray(len: number): Uint8Array; - readArray(fn: any): any[]; -} -export declare function serialize(schema: Schema, obj: any, Writer?: typeof BinaryWriter): Uint8Array; -export declare function deserialize(schema: Schema, classType: { - new (args: any): T; -}, buffer: Buffer, Reader?: typeof BinaryReader): T; -export declare function deserializeUnchecked(schema: Schema, classType: { - new (args: any): T; -}, buffer: Buffer, Reader?: typeof BinaryReader): T; +import { Schema } from './types'; +export declare function serialize(schema: Schema, value: unknown): Uint8Array; diff --git a/lib/index.js b/lib/index.js index a5d4c0ad..c62e75ec 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,455 +1,25 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.deserializeUnchecked = exports.deserialize = exports.serialize = exports.BinaryReader = exports.BinaryWriter = exports.BorshError = exports.baseDecode = exports.baseEncode = void 0; -const bn_js_1 = __importDefault(require("bn.js")); -const bs58_1 = __importDefault(require("bs58")); -// TODO: Make sure this polyfill not included when not required -const encoding = __importStar(require("text-encoding-utf-8")); -const ResolvedTextDecoder = typeof TextDecoder !== 'function' ? encoding.TextDecoder : TextDecoder; -const textDecoder = new ResolvedTextDecoder('utf-8', { fatal: true }); -function baseEncode(value) { - if (typeof value === 'string') { - value = Buffer.from(value, 'utf8'); - } - return bs58_1.default.encode(Buffer.from(value)); -} -exports.baseEncode = baseEncode; -function baseDecode(value) { - return Buffer.from(bs58_1.default.decode(value)); -} -exports.baseDecode = baseDecode; -const INITIAL_LENGTH = 1024; -class BorshError extends Error { - originalMessage; - fieldPath = []; - constructor(message) { - super(message); - this.originalMessage = message; - } - addToFieldPath(fieldName) { - this.fieldPath.splice(0, 0, fieldName); - // NOTE: Modifying message directly as jest doesn't use .toString() - this.message = this.originalMessage + ': ' + this.fieldPath.join('.'); - } -} -exports.BorshError = BorshError; -/// Binary encoder. -class BinaryWriter { - buf; - length; - constructor() { - this.buf = Buffer.alloc(INITIAL_LENGTH); - this.length = 0; - } - maybeResize() { - if (this.buf.length < 16 + this.length) { - this.buf = Buffer.concat([this.buf, Buffer.alloc(INITIAL_LENGTH)]); - } - } - writeU8(value) { - this.maybeResize(); - this.buf.writeUInt8(value, this.length); - this.length += 1; - } - writeU16(value) { - this.maybeResize(); - this.buf.writeUInt16LE(value, this.length); - this.length += 2; - } - writeU32(value) { - this.maybeResize(); - this.buf.writeUInt32LE(value, this.length); - this.length += 4; - } - writeU64(value) { - this.maybeResize(); - this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray('le', 8))); - } - writeU128(value) { - this.maybeResize(); - this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray('le', 16))); - } - writeU256(value) { - this.maybeResize(); - this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray('le', 32))); - } - writeU512(value) { - this.maybeResize(); - this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray('le', 64))); - } - writeBuffer(buffer) { - // Buffer.from is needed as this.buf.subarray can return plain Uint8Array in browser - this.buf = Buffer.concat([ - Buffer.from(this.buf.subarray(0, this.length)), - buffer, - Buffer.alloc(INITIAL_LENGTH), - ]); - this.length += buffer.length; - } - writeString(str) { - this.maybeResize(); - const b = Buffer.from(str, 'utf8'); - this.writeU32(b.length); - this.writeBuffer(b); - } - writeFixedArray(array) { - this.writeBuffer(Buffer.from(array)); - } - writeArray(array, fn) { - this.maybeResize(); - this.writeU32(array.length); - for (const elem of array) { - this.maybeResize(); - fn(elem); - } - } - toArray() { - return this.buf.subarray(0, this.length); - } -} -exports.BinaryWriter = BinaryWriter; -function handlingRangeError(target, propertyKey, propertyDescriptor) { - const originalMethod = propertyDescriptor.value; - propertyDescriptor.value = function (...args) { - try { - return originalMethod.apply(this, args); - } - catch (e) { - if (e instanceof RangeError) { - const code = e.code; - if (['ERR_BUFFER_OUT_OF_BOUNDS', 'ERR_OUT_OF_RANGE'].indexOf(code) >= 0) { - throw new BorshError('Reached the end of buffer when deserializing'); - } - } - throw e; - } - }; -} -class BinaryReader { - buf; - offset; - constructor(buf) { - this.buf = buf; - this.offset = 0; - } - readU8() { - const value = this.buf.readUInt8(this.offset); - this.offset += 1; - return value; - } - readU16() { - const value = this.buf.readUInt16LE(this.offset); - this.offset += 2; - return value; - } - readU32() { - const value = this.buf.readUInt32LE(this.offset); - this.offset += 4; - return value; - } - readU64() { - const buf = this.readBuffer(8); - return new bn_js_1.default(buf, 'le'); - } - readU128() { - const buf = this.readBuffer(16); - return new bn_js_1.default(buf, 'le'); - } - readU256() { - const buf = this.readBuffer(32); - return new bn_js_1.default(buf, 'le'); - } - readU512() { - const buf = this.readBuffer(64); - return new bn_js_1.default(buf, 'le'); - } - readBuffer(len) { - if (this.offset + len > this.buf.length) { - throw new BorshError(`Expected buffer length ${len} isn't within bounds`); - } - const result = this.buf.slice(this.offset, this.offset + len); - this.offset += len; - return result; - } - readString() { - const len = this.readU32(); - const buf = this.readBuffer(len); - try { - // NOTE: Using TextDecoder to fail on invalid UTF-8 - return textDecoder.decode(buf); - } - catch (e) { - throw new BorshError(`Error decoding UTF-8 string: ${e}`); - } - } - readFixedArray(len) { - return new Uint8Array(this.readBuffer(len)); - } - readArray(fn) { - const len = this.readU32(); - const result = Array(); - for (let i = 0; i < len; ++i) { - result.push(fn()); - } - return result; - } -} -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU8", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU16", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU32", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU64", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU128", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU256", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readU512", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readString", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readFixedArray", null); -__decorate([ - handlingRangeError -], BinaryReader.prototype, "readArray", null); -exports.BinaryReader = BinaryReader; -function capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} -function serializeField(schema, fieldName, value, fieldType, writer) { - try { - // TODO: Handle missing values properly (make sure they never result in just skipped write) - if (typeof fieldType === 'string') { - writer[`write${capitalizeFirstLetter(fieldType)}`](value); - } - else if (fieldType instanceof Array) { - if (typeof fieldType[0] === 'number') { - if (value.length !== fieldType[0]) { - throw new BorshError(`Expecting byte array of length ${fieldType[0]}, but got ${value.length} bytes`); - } - writer.writeFixedArray(value); - } - else if (fieldType.length === 2 && typeof fieldType[1] === 'number') { - if (value.length !== fieldType[1]) { - throw new BorshError(`Expecting byte array of length ${fieldType[1]}, but got ${value.length} bytes`); - } - for (let i = 0; i < fieldType[1]; i++) { - serializeField(schema, null, value[i], fieldType[0], writer); - } - } - else { - writer.writeArray(value, (item) => { - serializeField(schema, fieldName, item, fieldType[0], writer); - }); - } - } - else if (fieldType.kind !== undefined) { - switch (fieldType.kind) { - case 'option': { - if (value === null || value === undefined) { - writer.writeU8(0); - } - else { - writer.writeU8(1); - serializeField(schema, fieldName, value, fieldType.type, writer); - } - break; - } - case 'map': { - writer.writeU32(value.size); - value.forEach((val, key) => { - serializeField(schema, fieldName, key, fieldType.key, writer); - serializeField(schema, fieldName, val, fieldType.value, writer); - }); - break; - } - default: - throw new BorshError(`FieldType ${fieldType} unrecognized`); - } - } - else { - serializeStruct(schema, value, writer); - } - } - catch (error) { - if (error instanceof BorshError) { - error.addToFieldPath(fieldName); - } - throw error; - } -} -function serializeStruct(schema, obj, writer) { - if (typeof obj.borshSerialize === 'function') { - obj.borshSerialize(writer); - return; - } - let structSchema = undefined; - for (const key of schema.keys()) { - if (key.name == obj.constructor.name) { - structSchema = schema.get(key); - break; - } - } - if (!structSchema) { - throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); - } - if (structSchema.kind === 'struct') { - structSchema.fields.map(([fieldName, fieldType]) => { - serializeField(schema, fieldName, obj[fieldName], fieldType, writer); - }); - } - else if (structSchema.kind === 'enum') { - const name = obj[structSchema.field]; - for (let idx = 0; idx < structSchema.values.length; ++idx) { - const [fieldName, fieldType] = structSchema.values[idx]; - if (fieldName === name) { - writer.writeU8(idx); - serializeField(schema, fieldName, obj[fieldName], fieldType, writer); - break; - } - } - } - else { - throw new BorshError(`Unexpected schema kind: ${structSchema.kind} for ${obj.constructor.name}`); - } -} -/// Serialize given object using schema of the form: -/// { class_name -> [ [field_name, field_type], .. ], .. } -function serialize(schema, obj, Writer = BinaryWriter) { - const writer = new Writer(); - serializeStruct(schema, obj, writer); - return writer.toArray(); +exports.serialize = void 0; +const serialize_1 = require("./serialize"); +function serialize(schema, value) { + const serializer = new serialize_1.BorshSerializer(); + return serializer.encode(value, schema); } exports.serialize = serialize; -function deserializeField(schema, fieldName, fieldType, reader) { - try { - if (typeof fieldType === 'string') { - return reader[`read${capitalizeFirstLetter(fieldType)}`](); - } - if (fieldType instanceof Array) { - if (typeof fieldType[0] === 'number') { - return reader.readFixedArray(fieldType[0]); - } - else if (typeof fieldType[1] === 'number') { - const arr = []; - for (let i = 0; i < fieldType[1]; i++) { - arr.push(deserializeField(schema, null, fieldType[0], reader)); - } - return arr; - } - else { - return reader.readArray(() => deserializeField(schema, fieldName, fieldType[0], reader)); - } - } - if (fieldType.kind === 'option') { - const option = reader.readU8(); - if (option) { - return deserializeField(schema, fieldName, fieldType.type, reader); - } - return undefined; - } - if (fieldType.kind === 'map') { - const map = new Map(); - const length = reader.readU32(); - for (let i = 0; i < length; i++) { - const key = deserializeField(schema, fieldName, fieldType.key, reader); - const val = deserializeField(schema, fieldName, fieldType.value, reader); - map.set(key, val); - } - return map; - } - return deserializeStruct(schema, fieldType, reader); - } - catch (error) { - if (error instanceof BorshError) { - error.addToFieldPath(fieldName); - } - throw error; - } -} -function deserializeStruct(schema, classType, reader) { - if (typeof classType.borshDeserialize === 'function') { - return classType.borshDeserialize(reader); - } - const structSchema = schema.get(classType); - if (!structSchema) { - throw new BorshError(`Class ${classType.name} is missing in schema`); - } - if (structSchema.kind === 'struct') { - const result = {}; - for (const [fieldName, fieldType] of schema.get(classType).fields) { - result[fieldName] = deserializeField(schema, fieldName, fieldType, reader); - } - return new classType(result); - } - if (structSchema.kind === 'enum') { - const idx = reader.readU8(); - if (idx >= structSchema.values.length) { - throw new BorshError(`Enum index: ${idx} is out of range`); - } - const [fieldName, fieldType] = structSchema.values[idx]; - const fieldValue = deserializeField(schema, fieldName, fieldType, reader); - return new classType({ [fieldName]: fieldValue }); - } - throw new BorshError(`Unexpected schema kind: ${structSchema.kind} for ${classType.constructor.name}`); -} -/// Deserializes object from bytes using schema. -function deserialize(schema, classType, buffer, Reader = BinaryReader) { - const reader = new Reader(buffer); - const result = deserializeStruct(schema, classType, reader); - if (reader.offset < buffer.length) { - throw new BorshError(`Unexpected ${buffer.length - reader.offset} bytes after deserialized data`); - } - return result; -} -exports.deserialize = deserialize; -/// Deserializes object from bytes using schema, without checking the length read -function deserializeUnchecked(schema, classType, buffer, Reader = BinaryReader) { - const reader = new Reader(buffer); - return deserializeStruct(schema, classType, reader); -} -exports.deserializeUnchecked = deserializeUnchecked; +// export function serialize( +// schema: Schema, +// obj: any, +// ): Uint8Array { +// const writer = new Writer(); +// serializeStruct(schema, obj, writer); +// return writer.toArray(); +// } +// export function deserialize( +// schema: Schema, +// classType: { new(args: any): T }, +// buffer: Buffer, +// ): T { +// const reader = new Reader(buffer); +// return deserializeStruct(schema, classType, reader); +// } diff --git a/lib/serialize.d.ts b/lib/serialize.d.ts new file mode 100644 index 00000000..b1d15a99 --- /dev/null +++ b/lib/serialize.d.ts @@ -0,0 +1,18 @@ +import { ArrayType, MapType, NumberType, OptionType, Schema, SetType, StructType } from './types'; +import { EncodeBuffer } from './buffer'; +import BN from 'bn.js'; +export declare class BorshSerializer { + encoded: EncodeBuffer; + encode(value: unknown, schema: Schema): Uint8Array; + encode_integer(value: unknown, schema: NumberType): void; + encode_bigint(value: BN, size: number): void; + encode_string(value: unknown): void; + encode_boolean(value: unknown): void; + encode_option(value: unknown, schema: OptionType): void; + encode_array(value: unknown, schema: Schema): void; + encode_arraylike(value: ArrayLike, schema: ArrayType): void; + encode_buffer(value: ArrayBuffer, schema: ArrayType): void; + encode_set(value: unknown, schema: SetType): void; + encode_map(value: unknown, schema: MapType): void; + encode_struct(value: unknown, schema: StructType): void; +} diff --git a/lib/serialize.js b/lib/serialize.js new file mode 100644 index 00000000..d4e515c8 --- /dev/null +++ b/lib/serialize.js @@ -0,0 +1,171 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BorshSerializer = void 0; +const types_1 = require("./types"); +const buffer_1 = require("./buffer"); +const bn_js_1 = __importDefault(require("bn.js")); +const utils = __importStar(require("./utils")); +class BorshSerializer { + encoded = new buffer_1.EncodeBuffer(); + encode(value, schema) { + if (typeof schema === 'string') { + if (types_1.numbers.includes(schema)) + this.encode_integer(value, schema); + if (schema === 'string') + this.encode_string(value); + if (schema === 'bool') + this.encode_boolean(value); + } + if (typeof schema === 'object') { + if ('option' in schema) + this.encode_option(value, schema); + if ('array' in schema) + this.encode_array(value, schema); + if ('set' in schema) + this.encode_set(value, schema); + if ('map' in schema) + this.encode_map(value, schema); + if ('struct' in schema) + this.encode_struct(value, schema); + } + return this.encoded.get_used_buffer(); + } + encode_integer(value, schema) { + const size = parseInt(schema.substring(1)); + if (size <= 32 || schema == 'f64') { + utils.expect_type(value, 'number'); + this.encoded.store_value(value, schema); + } + else { + utils.expect_BN(value); + this.encode_bigint(value, size); + } + } + encode_bigint(value, size) { + const buffer_len = size / 8; + const buffer = value.toArray('le', buffer_len); + if (value.lt(new bn_js_1.default(0))) { + // compute two's complement + let carry = 1; + for (let i = 0; i < buffer_len; i++) { + const v = (buffer[i] ^ 0xff) + carry; + this.encoded.store_value(v & 0xff, 'u8'); + carry = v >> 8; + } + } + else { + this.encoded.store_bytes(new Uint8Array(buffer)); + } + } + encode_string(value) { + utils.expect_type(value, 'string'); + const _value = value; + // 4 bytes for length + this.encoded.store_value(_value.length, 'u32'); + // string bytes + for (let i = 0; i < _value.length; i++) { + this.encoded.store_value(_value.charCodeAt(i), 'u8'); + } + } + encode_boolean(value) { + utils.expect_type(value, 'boolean'); + this.encoded.store_value(value ? 1 : 0, 'u8'); + } + encode_option(value, schema) { + if (value === null || value === undefined) { + this.encoded.store_value(0, 'u8'); + } + else { + this.encoded.store_value(1, 'u8'); + this.encode(value, schema.option); + } + } + encode_array(value, schema) { + const _schema = schema; + if (utils.isArrayLike(value)) + return this.encode_arraylike(value, _schema); + if (value instanceof ArrayBuffer) + return this.encode_buffer(value, _schema); + throw new Error(`Expected Array-like not ${typeof (value)}(${value})`); + } + encode_arraylike(value, schema) { + if (schema.array.len) { + utils.expect_same_size(value.length, schema.array.len); + } + else { + // 4 bytes for length + this.encoded.store_value(value.length, 'u32'); + } + // array values + for (let i = 0; i < value.length; i++) { + this.encode(value[i], schema.array.type); + } + } + encode_buffer(value, schema) { + if (schema.array.len) { + utils.expect_same_size(value.byteLength, schema.array.len); + } + else { + // 4 bytes for length + this.encoded.store_value(value.byteLength, 'u32'); + } + // array values + this.encoded.store_bytes(new Uint8Array(value)); + } + encode_set(value, schema) { + utils.expect_type(value, 'object'); + const isSet = value instanceof Set; + const values = isSet ? Array.from(value.values()) : Object.values(value); + // 4 bytes for length + this.encoded.store_value(values.length, 'u32'); + // set values + for (const value of values) { + this.encode(value, schema.set); + } + } + encode_map(value, schema) { + utils.expect_type(value, 'object'); + const isMap = value instanceof Map; + const keys = isMap ? Array.from(value.keys()) : Object.keys(value); + // 4 bytes for length + this.encoded.store_value(keys.length, 'u32'); + // store key/values + for (const key of keys) { + this.encode(key, schema.map.key); + this.encode(isMap ? value.get(key) : value[key], schema.map.value); + } + } + encode_struct(value, schema) { + utils.expect_type(value, 'object'); + for (const key of Object.keys(value)) { + this.encode(value[key], schema.struct[key]); + } + } +} +exports.BorshSerializer = BorshSerializer; diff --git a/lib/types.d.ts b/lib/types.d.ts new file mode 100644 index 00000000..2b1e00d8 --- /dev/null +++ b/lib/types.d.ts @@ -0,0 +1,28 @@ +export declare const numbers: string[]; +export type NumberType = typeof numbers[number]; +export type BoolType = 'bool'; +export type StringType = 'string'; +export type OptionType = { + option: Schema; +}; +export type ArrayType = { + array: { + type: Schema; + len?: number; + }; +}; +export type SetType = { + set: Schema; +}; +export type MapType = { + map: { + key: Schema; + value: Schema; + }; +}; +export type StructType = { + struct: { + [key: string]: Schema; + }; +}; +export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 00000000..6f99ea3e --- /dev/null +++ b/lib/types.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.numbers = void 0; +exports.numbers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; diff --git a/lib/utils.d.ts b/lib/utils.d.ts new file mode 100644 index 00000000..5aee9548 --- /dev/null +++ b/lib/utils.d.ts @@ -0,0 +1,4 @@ +export declare function isArrayLike(value: unknown): boolean; +export declare function expect_type(value: unknown, type: string): void; +export declare function expect_BN(value: unknown): void; +export declare function expect_same_size(length: number, expected: number): void; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..1a831d47 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,37 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; +const bn_js_1 = __importDefault(require("bn.js")); +function isArrayLike(value) { + // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like + return (Array.isArray(value) || + (!!value && + typeof value === 'object' && + 'length' in value && + typeof (value.length) === 'number' && + (value.length === 0 || + (value.length > 0 && + (value.length - 1) in value)))); +} +exports.isArrayLike = isArrayLike; +function expect_type(value, type) { + if (typeof (value) !== type) { + throw new Error(`Expected ${type} not ${typeof (value)}(${value})`); + } +} +exports.expect_type = expect_type; +function expect_BN(value) { + if (!(value instanceof bn_js_1.default)) { + throw new Error(`Expected BN not ${typeof (value)}(${value})`); + } +} +exports.expect_BN = expect_BN; +function expect_same_size(length, expected) { + if (length !== expected) { + throw new Error(`Array length ${length} does not match schema length ${expected}`); + } +} +exports.expect_same_size = expect_same_size; From 7b3fe19b35f422e5fd11fe67368f47654a9998a8 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Thu, 20 Jul 2023 19:50:13 +0200 Subject: [PATCH 03/35] Implemented deserializer --- borsh-ts/buffer.ts | 54 ++++ borsh-ts/deserialize.ts | 119 ++++++++ borsh-ts/index.ts | 27 +- borsh-ts/old.ts | 484 ------------------------------ borsh-ts/serialize.ts | 11 +- borsh-ts/test/deserialize.test.js | 98 ++++++ borsh-ts/test/serialize.test.js | 8 +- borsh-ts/types.ts | 7 +- lib/buffer.d.ts | 9 + lib/buffer.js | 53 +++- lib/deserialize.d.ts | 17 ++ lib/deserialize.js | 114 +++++++ lib/index.d.ts | 3 +- lib/index.js | 24 +- lib/serialize.d.ts | 2 +- lib/serialize.js | 5 +- lib/types.d.ts | 2 + 17 files changed, 500 insertions(+), 537 deletions(-) create mode 100644 borsh-ts/deserialize.ts delete mode 100644 borsh-ts/old.ts create mode 100644 borsh-ts/test/deserialize.test.js create mode 100644 lib/deserialize.d.ts create mode 100644 lib/deserialize.js diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index 34d1038c..7e6ef394 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -64,6 +64,60 @@ export class EncodeBuffer { } } +export class DecodeBuffer { + offset = 0; + buffer_size = 256; + buffer: ArrayBuffer; + view: DataView; + + constructor(buf: Uint8Array) { + this.buffer = new ArrayBuffer(buf.length); + new Uint8Array(this.buffer).set(buf); + this.view = new DataView(this.buffer); + } + + consume_value(type: NumberType): number { + const size = parseInt(type.substring(1)) / 8; + let ret: number; + + switch (type) { + case 'u8': + ret = this.view.getUint8(this.offset); + break; + case 'u16': + ret = this.view.getUint16(this.offset, true); + break; + case 'u32': + ret = this.view.getUint32(this.offset, true); + break; + case 'i8': + ret = this.view.getInt8(this.offset); + break; + case 'i16': + ret = this.view.getInt16(this.offset, true); + break; + case 'i32': + ret = this.view.getInt32(this.offset, true); + break; + case 'f32': + ret = this.view.getFloat32(this.offset, true); + break; + case 'f64': + ret = this.view.getFloat64(this.offset, true); + break; + default: + throw new Error(`Unsupported integer type: ${type}`); + } + this.offset += size; + return ret; + } + + consume_bytes(size: number): ArrayBuffer { + const ret = this.buffer.slice(this.offset, this.offset + size); + this.offset += size; + return ret; + } +} // export class DecodeBuffer { // public offset: number = 0; // public start: usize; diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts new file mode 100644 index 00000000..8ff75a35 --- /dev/null +++ b/borsh-ts/deserialize.ts @@ -0,0 +1,119 @@ +import { ArrayType, DecodeTypes, MapType, NumberType, OptionType, Schema, SetType, StructType, numbers } from './types'; +import { DecodeBuffer } from './buffer'; +import BN from 'bn.js'; + +export class BorshDeserializer { + buffer: DecodeBuffer; + + constructor(bufferArray: Uint8Array) { + this.buffer = new DecodeBuffer(bufferArray); + } + + decode(schema: Schema, classType?: ObjectConstructor): DecodeTypes { + + if (typeof schema === 'string') { + if (numbers.includes(schema)) return this.decode_integer(schema); + if (schema === 'string') return this.decode_string(); + if (schema === 'bool') return this.decode_boolean(); + } + + if (typeof schema === 'object') { + if ('option' in schema) return this.decode_option(schema as OptionType); + if ('array' in schema) return this.decode_array(schema as ArrayType); + if ('set' in schema) return this.decode_set(schema as SetType); + if ('map' in schema) return this.decode_map(schema as MapType); + if ('struct' in schema) return this.decode_struct(schema as StructType, classType); + } + + throw new Error(`Unsupported type: ${schema}`); + } + + decode_integer(schema: NumberType): number | BN { + const size: number = parseInt(schema.substring(1)); + + if (size <= 32 || schema == 'f64') { + return this.buffer.consume_value(schema); + } + return this.decode_bigint(size); + } + + decode_bigint(size: number): BN { + const buffer_len = size / 8; + const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + const value = new BN(buffer, 'le'); + + if (value.testn(buffer_len * 8 - 1)) { + // negative number + const buffer = value.toArray('le', buffer_len + 1); + let carry = 1; + for (let i = 0; i < buffer_len; i++) { + const v = (buffer[i] ^ 0xff) + carry; + buffer[i] = v & 0xff; + carry = v >> 8; + } + return new BN(buffer, 'le').mul(new BN(-1)); + } + + return value; + } + + decode_string(): string { + const len: number = this.decode_integer('u32') as number; + const buffer = new Uint8Array(this.buffer.consume_bytes(len)); + return String.fromCharCode.apply(null, buffer); + } + + decode_boolean(): boolean { + return this.buffer.consume_value('u8') > 0; + } + + decode_option(schema: OptionType): DecodeTypes { + const option = this.buffer.consume_value('u8'); + if (option === 1) { + return this.decode(schema.option); + } + if (option !== 0) { + throw new Error(`Invalid option ${option}`); + } + return null; + } + + decode_array(schema: ArrayType): Array { + const result = []; + const len = schema.array.len? schema.array.len : this.decode_integer('u32') as number; + + for (let i = 0; i < len; ++i) { + result.push(this.decode(schema.array.type)); + } + + return result; + } + + decode_set(schema: SetType): Set { + const len = this.decode_integer('u32') as number; + const result = new Set(); + for (let i = 0; i < len; ++i) { + result.add(this.decode(schema.set)); + } + return result; + } + + decode_map(schema: MapType): Map { + const len = this.decode_integer('u32') as number; + const result = new Map(); + for (let i = 0; i < len; ++i) { + const key = this.decode(schema.map.key); + const value = this.decode(schema.map.value); + result.set(key, value); + } + return result; + } + + decode_struct(schema: StructType, classType?: ObjectConstructor): object { + const result = {}; + for (const key in schema.struct) { + result[key] = this.decode(schema.struct[key]); + } + return classType? new classType(result) : result; + } +} \ No newline at end of file diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 7dfd7ea7..296346a0 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -1,26 +1,13 @@ -import { Schema } from './types'; -import {BorshSerializer} from './serialize'; +import { Schema, DecodeTypes } from './types'; +import { BorshSerializer } from './serialize'; +import { BorshDeserializer } from './deserialize'; export function serialize(schema: Schema, value: unknown): Uint8Array { const serializer = new BorshSerializer(); return serializer.encode(value, schema); } - -// export function serialize( -// schema: Schema, -// obj: any, -// ): Uint8Array { -// const writer = new Writer(); -// serializeStruct(schema, obj, writer); -// return writer.toArray(); -// } - -// export function deserialize( -// schema: Schema, -// classType: { new(args: any): T }, -// buffer: Buffer, -// ): T { -// const reader = new Reader(buffer); -// return deserializeStruct(schema, classType, reader); -// } +export function deserialize(schema: Schema, buffer: Uint8Array, classType?: ObjectConstructor): DecodeTypes { + const deserializer = new BorshDeserializer(buffer); + return deserializer.decode(schema, classType); +} diff --git a/borsh-ts/old.ts b/borsh-ts/old.ts deleted file mode 100644 index 5fa4ac0a..00000000 --- a/borsh-ts/old.ts +++ /dev/null @@ -1,484 +0,0 @@ -import BN from 'bn.js'; -import bs58 from 'bs58'; - -// TODO: Make sure this polyfill not included when not required -import * as encoding from 'text-encoding-utf-8'; -const ResolvedTextDecoder = - typeof TextDecoder !== 'function' ? encoding.TextDecoder : TextDecoder; -const textDecoder = new ResolvedTextDecoder('utf-8', { fatal: true }); - -export function baseEncode(value: Uint8Array | string): string { - if (typeof value === 'string') { - value = Buffer.from(value, 'utf8'); - } - return bs58.encode(Buffer.from(value)); -} - -export function baseDecode(value: string): Buffer { - return Buffer.from(bs58.decode(value)); -} - -const INITIAL_LENGTH = 1024; - -export type Schema = Map - -export class BorshError extends Error { - originalMessage: string; - fieldPath: string[] = []; - - constructor(message: string) { - super(message); - this.originalMessage = message; - } - - addToFieldPath(fieldName: string): void { - this.fieldPath.splice(0, 0, fieldName); - // NOTE: Modifying message directly as jest doesn't use .toString() - this.message = this.originalMessage + ': ' + this.fieldPath.join('.'); - } -} - -/// Binary encoder. -export class BinaryWriter { - buf: Buffer; - length: number; - - public constructor() { - this.buf = Buffer.alloc(INITIAL_LENGTH); - this.length = 0; - } - - maybeResize(): void { - if (this.buf.length < 16 + this.length) { - this.buf = Buffer.concat([this.buf, Buffer.alloc(INITIAL_LENGTH)]); - } - } - - public writeU8(value: number): void { - this.maybeResize(); - this.buf.writeUInt8(value, this.length); - this.length += 1; - } - - public writeU16(value: number): void { - this.maybeResize(); - this.buf.writeUInt16LE(value, this.length); - this.length += 2; - } - - public writeU32(value: number): void { - this.maybeResize(); - this.buf.writeUInt32LE(value, this.length); - this.length += 4; - } - - public writeU64(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 8))); - } - - public writeU128(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 16))); - } - - public writeU256(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 32))); - } - - public writeU512(value: number | BN): void { - this.maybeResize(); - this.writeBuffer(Buffer.from(new BN(value).toArray('le', 64))); - } - - private writeBuffer(buffer: Buffer): void { - // Buffer.from is needed as this.buf.subarray can return plain Uint8Array in browser - this.buf = Buffer.concat([ - Buffer.from(this.buf.subarray(0, this.length)), - buffer, - Buffer.alloc(INITIAL_LENGTH), - ]); - this.length += buffer.length; - } - - public writeString(str: string): void { - this.maybeResize(); - const b = Buffer.from(str, 'utf8'); - this.writeU32(b.length); - this.writeBuffer(b); - } - - public writeFixedArray(array: Uint8Array): void { - this.writeBuffer(Buffer.from(array)); - } - - public writeArray(array: any[], fn: any): void { - this.maybeResize(); - this.writeU32(array.length); - for (const elem of array) { - this.maybeResize(); - fn(elem); - } - } - - public toArray(): Uint8Array { - return this.buf.subarray(0, this.length); - } -} - -function handlingRangeError( - target: any, - propertyKey: string, - propertyDescriptor: PropertyDescriptor -): any { - const originalMethod = propertyDescriptor.value; - propertyDescriptor.value = function (...args: any[]): any { - try { - return originalMethod.apply(this, args); - } catch (e) { - if (e instanceof RangeError) { - const code = (e as any).code; - if ( - ['ERR_BUFFER_OUT_OF_BOUNDS', 'ERR_OUT_OF_RANGE'].indexOf(code) >= 0 - ) { - throw new BorshError('Reached the end of buffer when deserializing'); - } - } - throw e; - } - }; -} - -export class BinaryReader { - buf: Buffer; - offset: number; - - public constructor(buf: Buffer) { - this.buf = buf; - this.offset = 0; - } - - @handlingRangeError - readU8(): number { - const value = this.buf.readUInt8(this.offset); - this.offset += 1; - return value; - } - - @handlingRangeError - readU16(): number { - const value = this.buf.readUInt16LE(this.offset); - this.offset += 2; - return value; - } - - @handlingRangeError - readU32(): number { - const value = this.buf.readUInt32LE(this.offset); - this.offset += 4; - return value; - } - - @handlingRangeError - readU64(): BN { - const buf = this.readBuffer(8); - return new BN(buf, 'le'); - } - - @handlingRangeError - readU128(): BN { - const buf = this.readBuffer(16); - return new BN(buf, 'le'); - } - - @handlingRangeError - readU256(): BN { - const buf = this.readBuffer(32); - return new BN(buf, 'le'); - } - - @handlingRangeError - readU512(): BN { - const buf = this.readBuffer(64); - return new BN(buf, 'le'); - } - - private readBuffer(len: number): Buffer { - if (this.offset + len > this.buf.length) { - throw new BorshError(`Expected buffer length ${len} isn't within bounds`); - } - const result = this.buf.slice(this.offset, this.offset + len); - this.offset += len; - return result; - } - - @handlingRangeError - readString(): string { - const len = this.readU32(); - const buf = this.readBuffer(len); - try { - // NOTE: Using TextDecoder to fail on invalid UTF-8 - return textDecoder.decode(buf); - } catch (e) { - throw new BorshError(`Error decoding UTF-8 string: ${e}`); - } - } - - @handlingRangeError - readFixedArray(len: number): Uint8Array { - return new Uint8Array(this.readBuffer(len)); - } - - @handlingRangeError - readArray(fn: any): any[] { - const len = this.readU32(); - const result = Array(); - for (let i = 0; i < len; ++i) { - result.push(fn()); - } - return result; - } -} - -function capitalizeFirstLetter(string): string { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -function serializeField( - schema: Schema, - fieldName: string, - value: any, - fieldType: any, - writer: any -): void { - try { - // TODO: Handle missing values properly (make sure they never result in just skipped write) - if (typeof fieldType === 'string') { - writer[`write${capitalizeFirstLetter(fieldType)}`](value); - } else if (fieldType instanceof Array) { - if (typeof fieldType[0] === 'number') { - if (value.length !== fieldType[0]) { - throw new BorshError( - `Expecting byte array of length ${fieldType[0]}, but got ${value.length} bytes` - ); - } - writer.writeFixedArray(value); - } else if (fieldType.length === 2 && typeof fieldType[1] === 'number') { - if (value.length !== fieldType[1]) { - throw new BorshError( - `Expecting byte array of length ${fieldType[1]}, but got ${value.length} bytes` - ); - } - for (let i = 0; i < fieldType[1]; i++) { - serializeField(schema, null, value[i], fieldType[0], writer); - } - } else { - writer.writeArray(value, (item: any) => { - serializeField(schema, fieldName, item, fieldType[0], writer); - }); - } - } else if (fieldType.kind !== undefined) { - switch (fieldType.kind) { - case 'option': { - if (value === null || value === undefined) { - writer.writeU8(0); - } else { - writer.writeU8(1); - serializeField(schema, fieldName, value, fieldType.type, writer); - } - break; - } - case 'map': { - writer.writeU32(value.size); - value.forEach((val, key) => { - serializeField(schema, fieldName, key, fieldType.key, writer); - serializeField(schema, fieldName, val, fieldType.value, writer); - }); - break; - } - default: - throw new BorshError(`FieldType ${fieldType} unrecognized`); - } - } else { - serializeStruct(schema, value, writer); - } - } catch (error) { - if (error instanceof BorshError) { - error.addToFieldPath(fieldName); - } - throw error; - } -} - -function serializeStruct(schema: Schema, obj: any, writer: BinaryWriter): void { - if (typeof obj.borshSerialize === 'function') { - obj.borshSerialize(writer); - return; - } - - let structSchema = undefined; - for (const key of schema.keys()){ - if (key.name == obj.constructor.name) { - structSchema = schema.get(key); - break; - } - } - - if (!structSchema) { - throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); - } - - if (structSchema.kind === 'struct') { - structSchema.fields.map(([fieldName, fieldType]: [any, any]) => { - serializeField(schema, fieldName, obj[fieldName], fieldType, writer); - }); - } else if (structSchema.kind === 'enum') { - const name = obj[structSchema.field]; - for (let idx = 0; idx < structSchema.values.length; ++idx) { - const [fieldName, fieldType]: [any, any] = structSchema.values[idx]; - if (fieldName === name) { - writer.writeU8(idx); - serializeField(schema, fieldName, obj[fieldName], fieldType, writer); - break; - } - } - } else { - throw new BorshError( - `Unexpected schema kind: ${structSchema.kind} for ${obj.constructor.name}` - ); - } -} - -/// Serialize given object using schema of the form: -/// { class_name -> [ [field_name, field_type], .. ], .. } -export function serialize( - schema: Schema, - obj: any, - Writer = BinaryWriter -): Uint8Array { - const writer = new Writer(); - serializeStruct(schema, obj, writer); - return writer.toArray(); -} - -function deserializeField( - schema: Schema, - fieldName: string, - fieldType: any, - reader: BinaryReader -): any { - try { - if (typeof fieldType === 'string') { - return reader[`read${capitalizeFirstLetter(fieldType)}`](); - } - - if (fieldType instanceof Array) { - if (typeof fieldType[0] === 'number') { - return reader.readFixedArray(fieldType[0]); - } else if (typeof fieldType[1] === 'number') { - const arr = []; - for (let i = 0; i < fieldType[1]; i++) { - arr.push(deserializeField(schema, null, fieldType[0], reader)); - } - return arr; - } else { - return reader.readArray(() => - deserializeField(schema, fieldName, fieldType[0], reader) - ); - } - } - - if (fieldType.kind === 'option') { - const option = reader.readU8(); - if (option) { - return deserializeField(schema, fieldName, fieldType.type, reader); - } - - return undefined; - } - if (fieldType.kind === 'map') { - const map = new Map(); - const length = reader.readU32(); - for (let i = 0; i < length; i++) { - const key = deserializeField(schema, fieldName, fieldType.key, reader); - const val = deserializeField(schema, fieldName, fieldType.value, reader); - map.set(key, val); - } - return map; - } - - return deserializeStruct(schema, fieldType, reader); - } catch (error) { - if (error instanceof BorshError) { - error.addToFieldPath(fieldName); - } - throw error; - } -} - -function deserializeStruct( - schema: Schema, - classType: any, - reader: BinaryReader -): any { - if (typeof classType.borshDeserialize === 'function') { - return classType.borshDeserialize(reader); - } - - const structSchema = schema.get(classType); - if (!structSchema) { - throw new BorshError(`Class ${classType.name} is missing in schema`); - } - - if (structSchema.kind === 'struct') { - const result = {}; - for (const [fieldName, fieldType] of schema.get(classType).fields) { - result[fieldName] = deserializeField(schema, fieldName, fieldType, reader); - } - return new classType(result); - } - - if (structSchema.kind === 'enum') { - const idx = reader.readU8(); - if (idx >= structSchema.values.length) { - throw new BorshError(`Enum index: ${idx} is out of range`); - } - const [fieldName, fieldType] = structSchema.values[idx]; - const fieldValue = deserializeField(schema, fieldName, fieldType, reader); - return new classType({ [fieldName]: fieldValue }); - } - - throw new BorshError( - `Unexpected schema kind: ${structSchema.kind} for ${classType.constructor.name}` - ); -} - -/// Deserializes object from bytes using schema. -export function deserialize( - schema: Schema, - classType: { new(args: any): T }, - buffer: Buffer, - Reader = BinaryReader -): T { - const reader = new Reader(buffer); - const result = deserializeStruct(schema, classType, reader); - if (reader.offset < buffer.length) { - throw new BorshError( - `Unexpected ${buffer.length - reader.offset - } bytes after deserialized data` - ); - } - return result; -} - -/// Deserializes object from bytes using schema, without checking the length read -export function deserializeUnchecked( - schema: Schema, - classType: { new(args: any): T }, - buffer: Buffer, - Reader = BinaryReader -): T { - const reader = new Reader(buffer); - return deserializeStruct(schema, classType, reader); -} diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index f7b52aa5..f0a2f254 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -3,7 +3,6 @@ import { EncodeBuffer } from './buffer'; import BN from 'bn.js'; import * as utils from './utils'; - export class BorshSerializer { encoded: EncodeBuffer = new EncodeBuffer(); @@ -16,11 +15,12 @@ export class BorshSerializer { if (typeof schema === 'object') { if ('option' in schema) this.encode_option(value, schema as OptionType); - if ('array' in schema) this.encode_array(value, schema); + if ('array' in schema) this.encode_array(value, schema as ArrayType); if ('set' in schema) this.encode_set(value, schema as SetType); if ('map' in schema) this.encode_map(value, schema as MapType); if ('struct' in schema) this.encode_struct(value, schema as StructType); } + return this.encoded.get_used_buffer(); } @@ -80,10 +80,9 @@ export class BorshSerializer { } } - encode_array(value: unknown, schema: Schema): void { - const _schema = schema as ArrayType; - if (utils.isArrayLike(value)) return this.encode_arraylike(value as ArrayLike, _schema); - if (value instanceof ArrayBuffer) return this.encode_buffer(value, _schema); + encode_array(value: unknown, schema: ArrayType): void { + if (utils.isArrayLike(value)) return this.encode_arraylike(value as ArrayLike, schema); + if (value instanceof ArrayBuffer) return this.encode_buffer(value, schema); throw new Error(`Expected Array-like not ${typeof (value)}(${value})`); } diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js new file mode 100644 index 00000000..e7a94204 --- /dev/null +++ b/borsh-ts/test/deserialize.test.js @@ -0,0 +1,98 @@ +const borsh = require('../../lib/index'); +const BN = require('bn.js'); + +function check_decode(expected, schema, buffer, classType) { + const decoded = borsh.deserialize(schema, buffer, classType); + + if (expected && typeof (expected) === 'object' && 'eq' in expected) { + expect(expected.eq(decoded)).toBe(true); + } else { + if(schema === 'f32'){ + expect(decoded).toBeCloseTo(expected); + }else{ + expect(decoded).toEqual(expected); + } + } +} + +test('deserialize integers', async () => { + check_decode(100, 'u8', [100]); + check_decode(258, 'u16', [2, 1]); + check_decode(102, 'u32', [102, 0, 0, 0]); + check_decode(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); + check_decode(new BN(104), 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + check_decode(-100, 'i8', [156]); + check_decode(-258, 'i16', [254, 254]); + check_decode(-102, 'i32', [154, 255, 255, 255]); + check_decode(new BN(-103), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); + check_decode(new BN(-104), 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); +}); + +test('deserialize booleans', async () => { + check_decode(true, 'bool', [1]); + check_decode(false, 'bool', [0]); +}); + +test('deserialize strings', async () => { + check_decode('h"i', 'string', [3, 0, 0, 0, 104, 34, 105]); +}); + +test('deserialize floats', async () => { + check_decode(7.23, 'f64', [236, 81, 184, 30, 133, 235, 28, 64]); + check_decode(7.23, 'f32', [41, 92, 231, 64]); + check_decode(10e2, 'f32', [0, 0, 122, 68]); + check_decode(10e2, 'f64', [0, 0, 0, 0, 0, 64, 143, 64]); +}); + +test('deserialize arrays', async () => { + check_decode([true, false], { array: { type: 'bool' } }, [2, 0, 0, 0, 1, 0]); + check_decode([true, false], { array: { type: 'bool', len: 2 } }, [1, 0]); + check_decode([0, 0], { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); + check_decode([1, 2, 3], { array: { type: 'u8', len: 3 } }, [1, 2, 3]); +}); + +test('deserialize options', async () => { + check_decode(null, { option: 'u8' }, [0]); + check_decode(1, { option: 'u32' }, [1, 1, 0, 0, 0]); +}); + +test('deserialize maps', async () => { + check_decode(new Map(), { map: { key: 'u8', value: 'u8' } }, [0, 0, 0, 0]); + + const map = new Map(); + map.set('testing', 1); + check_decode(map, { map: { key: 'string', value: 'u32' } }, [1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0]); +}); + +test('deserialize sets', async () => { + check_decode(new Set(), { set: 'u8' }, [0, 0, 0, 0]); + check_decode(new Set([1, 2]), { set: 'u8' }, [2, 0, 0, 0, 1, 2]); +}); + +test('deserialize struct', async () => { + const numbers = new Numbers(); + const schema = { + struct: { + u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' + } + }; + check_decode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64], Numbers); + + check_decode(new Map([['a', 1], ['b', 2]]), { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); +}); + + +// Aux structures +class Numbers { + u8 = 1; + u16 = 2; + u32 = 3; + u64 = new BN(4); + u128 = new BN(5); + i8 = -1; + i16 = -2; + i32 = -3; + i64 = new BN(-4); + f32 = 6.0; + f64 = 7.1; +} \ No newline at end of file diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index 4c262164..ecc19c71 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -39,7 +39,10 @@ test('serialize arrays', async () => { check_encode([true, false], { array: { type: 'bool' } }, [2, 0, 0, 0, 1, 0]); check_encode([true, false], { array: { type: 'bool', len: 2 } }, [1, 0]); check_encode(new ArrayBuffer(2), { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); - check_encode(new ArrayBuffer(2), { array: { type: 'u8', len: 2 } }, [0, 0]); + + const buffer = new ArrayBuffer(2); + new Uint8Array(buffer).set([1,2]); + check_encode(buffer, { array: { type: 'u8', len: 2 } }, [1, 2]); }); test('serialize options', async () => { @@ -65,7 +68,7 @@ test('serialize struct', async () => { const numbers = new Numbers(); const schema = { struct: { - u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', i128: 'i128', f32: 'f32', f64: 'f64' + u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' } }; check_encode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]); @@ -73,7 +76,6 @@ test('serialize struct', async () => { }); - // Aux structures class Numbers { u8 = 1; diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts index ab0c8ac5..c06bf018 100644 --- a/borsh-ts/types.ts +++ b/borsh-ts/types.ts @@ -1,3 +1,5 @@ +import BN from 'bn.js'; + export const numbers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; export type NumberType = typeof numbers[number]; @@ -9,4 +11,7 @@ export type ArrayType = { array: { type: Schema, len?: number } }; export type SetType = { set: Schema }; export type MapType = { map: { key: Schema, value: Schema } }; export type StructType = { struct: { [key: string]: Schema } }; -export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; \ No newline at end of file +export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; + +// returned +export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; diff --git a/lib/buffer.d.ts b/lib/buffer.d.ts index 5d05cfb3..20a434c8 100644 --- a/lib/buffer.d.ts +++ b/lib/buffer.d.ts @@ -9,3 +9,12 @@ export declare class EncodeBuffer { store_value(value: number, type: NumberType): void; store_bytes(from: Uint8Array): void; } +export declare class DecodeBuffer { + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + constructor(buf: Uint8Array); + consume_value(type: NumberType): number; + consume_bytes(size: number): ArrayBuffer; +} diff --git a/lib/buffer.js b/lib/buffer.js index 22cac8e9..5d2e10ac 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.EncodeBuffer = void 0; +exports.DecodeBuffer = exports.EncodeBuffer = void 0; class EncodeBuffer { offset = 0; buffer_size = 256; @@ -58,6 +58,57 @@ class EncodeBuffer { } } exports.EncodeBuffer = EncodeBuffer; +class DecodeBuffer { + offset = 0; + buffer_size = 256; + buffer; + view; + constructor(buf) { + this.buffer = new ArrayBuffer(buf.length); + new Uint8Array(this.buffer).set(buf); + this.view = new DataView(this.buffer); + } + consume_value(type) { + const size = parseInt(type.substring(1)) / 8; + let ret; + switch (type) { + case 'u8': + ret = this.view.getUint8(this.offset); + break; + case 'u16': + ret = this.view.getUint16(this.offset, true); + break; + case 'u32': + ret = this.view.getUint32(this.offset, true); + break; + case 'i8': + ret = this.view.getInt8(this.offset); + break; + case 'i16': + ret = this.view.getInt16(this.offset, true); + break; + case 'i32': + ret = this.view.getInt32(this.offset, true); + break; + case 'f32': + ret = this.view.getFloat32(this.offset, true); + break; + case 'f64': + ret = this.view.getFloat64(this.offset, true); + break; + default: + throw new Error(`Unsupported integer type: ${type}`); + } + this.offset += size; + return ret; + } + consume_bytes(size) { + const ret = this.buffer.slice(this.offset, this.offset + size); + this.offset += size; + return ret; + } +} +exports.DecodeBuffer = DecodeBuffer; // export class DecodeBuffer { // public offset: number = 0; // public start: usize; diff --git a/lib/deserialize.d.ts b/lib/deserialize.d.ts new file mode 100644 index 00000000..5e4d00b5 --- /dev/null +++ b/lib/deserialize.d.ts @@ -0,0 +1,17 @@ +import { ArrayType, DecodeTypes, MapType, NumberType, OptionType, Schema, SetType, StructType } from './types'; +import { DecodeBuffer } from './buffer'; +import BN from 'bn.js'; +export declare class BorshDeserializer { + buffer: DecodeBuffer; + constructor(bufferArray: Uint8Array); + decode(schema: Schema, classType?: ObjectConstructor): DecodeTypes; + decode_integer(schema: NumberType): number | BN; + decode_bigint(size: number): BN; + decode_string(): string; + decode_boolean(): boolean; + decode_option(schema: OptionType): DecodeTypes; + decode_array(schema: ArrayType): Array; + decode_set(schema: SetType): Set; + decode_map(schema: MapType): Map; + decode_struct(schema: StructType, classType?: ObjectConstructor): object; +} diff --git a/lib/deserialize.js b/lib/deserialize.js new file mode 100644 index 00000000..7ace53d4 --- /dev/null +++ b/lib/deserialize.js @@ -0,0 +1,114 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BorshDeserializer = void 0; +const types_1 = require("./types"); +const buffer_1 = require("./buffer"); +const bn_js_1 = __importDefault(require("bn.js")); +class BorshDeserializer { + buffer; + constructor(bufferArray) { + this.buffer = new buffer_1.DecodeBuffer(bufferArray); + } + decode(schema, classType) { + if (typeof schema === 'string') { + if (types_1.numbers.includes(schema)) + return this.decode_integer(schema); + if (schema === 'string') + return this.decode_string(); + if (schema === 'bool') + return this.decode_boolean(); + } + if (typeof schema === 'object') { + if ('option' in schema) + return this.decode_option(schema); + if ('array' in schema) + return this.decode_array(schema); + if ('set' in schema) + return this.decode_set(schema); + if ('map' in schema) + return this.decode_map(schema); + if ('struct' in schema) + return this.decode_struct(schema, classType); + } + throw new Error(`Unsupported type: ${schema}`); + } + decode_integer(schema) { + const size = parseInt(schema.substring(1)); + if (size <= 32 || schema == 'f64') { + return this.buffer.consume_value(schema); + } + return this.decode_bigint(size); + } + decode_bigint(size) { + const buffer_len = size / 8; + const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + const value = new bn_js_1.default(buffer, 'le'); + if (value.testn(buffer_len * 8 - 1)) { + // negative number + const buffer = value.toArray('le', buffer_len + 1); + let carry = 1; + for (let i = 0; i < buffer_len; i++) { + const v = (buffer[i] ^ 0xff) + carry; + buffer[i] = v & 0xff; + carry = v >> 8; + } + return new bn_js_1.default(buffer, 'le').mul(new bn_js_1.default(-1)); + } + return value; + } + decode_string() { + const len = this.decode_integer('u32'); + const buffer = new Uint8Array(this.buffer.consume_bytes(len)); + return String.fromCharCode.apply(null, buffer); + } + decode_boolean() { + return this.buffer.consume_value('u8') > 0; + } + decode_option(schema) { + const option = this.buffer.consume_value('u8'); + if (option === 1) { + return this.decode(schema.option); + } + if (option !== 0) { + throw new Error(`Invalid option ${option}`); + } + return null; + } + decode_array(schema) { + const result = []; + const len = schema.array.len ? schema.array.len : this.decode_integer('u32'); + for (let i = 0; i < len; ++i) { + result.push(this.decode(schema.array.type)); + } + return result; + } + decode_set(schema) { + const len = this.decode_integer('u32'); + const result = new Set(); + for (let i = 0; i < len; ++i) { + result.add(this.decode(schema.set)); + } + return result; + } + decode_map(schema) { + const len = this.decode_integer('u32'); + const result = new Map(); + for (let i = 0; i < len; ++i) { + const key = this.decode(schema.map.key); + const value = this.decode(schema.map.value); + result.set(key, value); + } + return result; + } + decode_struct(schema, classType) { + const result = {}; + for (const key in schema.struct) { + result[key] = this.decode(schema.struct[key]); + } + return classType ? new classType(result) : result; + } +} +exports.BorshDeserializer = BorshDeserializer; diff --git a/lib/index.d.ts b/lib/index.d.ts index a9763d55..0d9944c1 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,2 +1,3 @@ -import { Schema } from './types'; +import { Schema, DecodeTypes } from './types'; export declare function serialize(schema: Schema, value: unknown): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, classType?: ObjectConstructor): DecodeTypes; diff --git a/lib/index.js b/lib/index.js index c62e75ec..43be0e37 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,25 +1,15 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.serialize = void 0; +exports.deserialize = exports.serialize = void 0; const serialize_1 = require("./serialize"); +const deserialize_1 = require("./deserialize"); function serialize(schema, value) { const serializer = new serialize_1.BorshSerializer(); return serializer.encode(value, schema); } exports.serialize = serialize; -// export function serialize( -// schema: Schema, -// obj: any, -// ): Uint8Array { -// const writer = new Writer(); -// serializeStruct(schema, obj, writer); -// return writer.toArray(); -// } -// export function deserialize( -// schema: Schema, -// classType: { new(args: any): T }, -// buffer: Buffer, -// ): T { -// const reader = new Reader(buffer); -// return deserializeStruct(schema, classType, reader); -// } +function deserialize(schema, buffer, classType) { + const deserializer = new deserialize_1.BorshDeserializer(buffer); + return deserializer.decode(schema, classType); +} +exports.deserialize = deserialize; diff --git a/lib/serialize.d.ts b/lib/serialize.d.ts index b1d15a99..5452856e 100644 --- a/lib/serialize.d.ts +++ b/lib/serialize.d.ts @@ -9,7 +9,7 @@ export declare class BorshSerializer { encode_string(value: unknown): void; encode_boolean(value: unknown): void; encode_option(value: unknown, schema: OptionType): void; - encode_array(value: unknown, schema: Schema): void; + encode_array(value: unknown, schema: ArrayType): void; encode_arraylike(value: ArrayLike, schema: ArrayType): void; encode_buffer(value: ArrayBuffer, schema: ArrayType): void; encode_set(value: unknown, schema: SetType): void; diff --git a/lib/serialize.js b/lib/serialize.js index d4e515c8..a1e7f56e 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -107,11 +107,10 @@ class BorshSerializer { } } encode_array(value, schema) { - const _schema = schema; if (utils.isArrayLike(value)) - return this.encode_arraylike(value, _schema); + return this.encode_arraylike(value, schema); if (value instanceof ArrayBuffer) - return this.encode_buffer(value, _schema); + return this.encode_buffer(value, schema); throw new Error(`Expected Array-like not ${typeof (value)}(${value})`); } encode_arraylike(value, schema) { diff --git a/lib/types.d.ts b/lib/types.d.ts index 2b1e00d8..96405821 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -1,3 +1,4 @@ +import BN from 'bn.js'; export declare const numbers: string[]; export type NumberType = typeof numbers[number]; export type BoolType = 'bool'; @@ -26,3 +27,4 @@ export type StructType = { }; }; export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; +export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; From 8fba23d6e3b8dcf6f47b900f0395ec764336f3a4 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Thu, 20 Jul 2023 19:51:42 +0200 Subject: [PATCH 04/35] Fixed indentation --- borsh-ts/buffer.ts | 2 +- borsh-ts/deserialize.ts | 4 ++-- borsh-ts/test/deserialize.test.js | 4 ++-- borsh-ts/test/serialize.test.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index 7e6ef394..e2e73eb7 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -109,7 +109,7 @@ export class DecodeBuffer { throw new Error(`Unsupported integer type: ${type}`); } this.offset += size; - return ret; + return ret; } consume_bytes(size: number): ArrayBuffer { diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index 8ff75a35..62ee1553 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -80,7 +80,7 @@ export class BorshDeserializer { decode_array(schema: ArrayType): Array { const result = []; - const len = schema.array.len? schema.array.len : this.decode_integer('u32') as number; + const len = schema.array.len ? schema.array.len : this.decode_integer('u32') as number; for (let i = 0; i < len; ++i) { result.push(this.decode(schema.array.type)); @@ -114,6 +114,6 @@ export class BorshDeserializer { for (const key in schema.struct) { result[key] = this.decode(schema.struct[key]); } - return classType? new classType(result) : result; + return classType ? new classType(result) : result; } } \ No newline at end of file diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js index e7a94204..cf2442a7 100644 --- a/borsh-ts/test/deserialize.test.js +++ b/borsh-ts/test/deserialize.test.js @@ -7,9 +7,9 @@ function check_decode(expected, schema, buffer, classType) { if (expected && typeof (expected) === 'object' && 'eq' in expected) { expect(expected.eq(decoded)).toBe(true); } else { - if(schema === 'f32'){ + if (schema === 'f32') { expect(decoded).toBeCloseTo(expected); - }else{ + } else { expect(decoded).toEqual(expected); } } diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index ecc19c71..f7fac1aa 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -41,7 +41,7 @@ test('serialize arrays', async () => { check_encode(new ArrayBuffer(2), { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); const buffer = new ArrayBuffer(2); - new Uint8Array(buffer).set([1,2]); + new Uint8Array(buffer).set([1, 2]); check_encode(buffer, { array: { type: 'u8', len: 2 } }, [1, 2]); }); From 9cd1a01732894ca1a8376d5460733a4d018373c1 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 12:37:35 +0200 Subject: [PATCH 05/35] Added schema validation --- borsh-ts/buffer.ts | 6 +++--- borsh-ts/deserialize.ts | 23 ++++++++++++-------- borsh-ts/serialize.ts | 40 +++++++++++++++++++---------------- borsh-ts/types.ts | 6 +++--- borsh-ts/utils.ts | 47 +++++++++++++++++++++++++++++++++++++++++ lib/buffer.d.ts | 6 +++--- lib/deserialize.d.ts | 5 +++-- lib/deserialize.js | 42 ++++++++++++++++++++++++++++++------ lib/serialize.d.ts | 5 +++-- lib/serialize.js | 36 +++++++++++++++++-------------- lib/types.d.ts | 6 +++--- lib/types.js | 4 ++-- lib/utils.d.ts | 2 ++ lib/utils.js | 46 +++++++++++++++++++++++++++++++++++++++- 14 files changed, 205 insertions(+), 69 deletions(-) diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index e2e73eb7..88f3b17d 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -1,4 +1,4 @@ -import { NumberType } from './types'; +import { IntegerType } from './types'; export class EncodeBuffer { offset = 0; @@ -22,7 +22,7 @@ export class EncodeBuffer { return new Uint8Array(this.buffer).slice(0, this.offset); } - store_value(value: number, type: NumberType): void { + store_value(value: number, type: IntegerType): void { const size = parseInt(type.substring(1)) / 8; this.resize_if_necessary(size); @@ -76,7 +76,7 @@ export class DecodeBuffer { this.view = new DataView(this.buffer); } - consume_value(type: NumberType): number { + consume_value(type: IntegerType): number { const size = parseInt(type.substring(1)) / 8; let ret: number; diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index 62ee1553..2c78dbe5 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -1,4 +1,5 @@ -import { ArrayType, DecodeTypes, MapType, NumberType, OptionType, Schema, SetType, StructType, numbers } from './types'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers } from './types'; +import * as utils from './utils'; import { DecodeBuffer } from './buffer'; import BN from 'bn.js'; @@ -10,9 +11,13 @@ export class BorshDeserializer { } decode(schema: Schema, classType?: ObjectConstructor): DecodeTypes { + utils.validate_schema(schema); + return this.decode_value(schema, classType); + } + decode_value(schema: Schema, classType?: ObjectConstructor): DecodeTypes { if (typeof schema === 'string') { - if (numbers.includes(schema)) return this.decode_integer(schema); + if (integers.includes(schema)) return this.decode_integer(schema); if (schema === 'string') return this.decode_string(); if (schema === 'bool') return this.decode_boolean(); } @@ -28,7 +33,7 @@ export class BorshDeserializer { throw new Error(`Unsupported type: ${schema}`); } - decode_integer(schema: NumberType): number | BN { + decode_integer(schema: IntegerType): number | BN { const size: number = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { @@ -70,7 +75,7 @@ export class BorshDeserializer { decode_option(schema: OptionType): DecodeTypes { const option = this.buffer.consume_value('u8'); if (option === 1) { - return this.decode(schema.option); + return this.decode_value(schema.option); } if (option !== 0) { throw new Error(`Invalid option ${option}`); @@ -83,7 +88,7 @@ export class BorshDeserializer { const len = schema.array.len ? schema.array.len : this.decode_integer('u32') as number; for (let i = 0; i < len; ++i) { - result.push(this.decode(schema.array.type)); + result.push(this.decode_value(schema.array.type)); } return result; @@ -93,7 +98,7 @@ export class BorshDeserializer { const len = this.decode_integer('u32') as number; const result = new Set(); for (let i = 0; i < len; ++i) { - result.add(this.decode(schema.set)); + result.add(this.decode_value(schema.set)); } return result; } @@ -102,8 +107,8 @@ export class BorshDeserializer { const len = this.decode_integer('u32') as number; const result = new Map(); for (let i = 0; i < len; ++i) { - const key = this.decode(schema.map.key); - const value = this.decode(schema.map.value); + const key = this.decode_value(schema.map.key); + const value = this.decode_value(schema.map.value); result.set(key, value); } return result; @@ -112,7 +117,7 @@ export class BorshDeserializer { decode_struct(schema: StructType, classType?: ObjectConstructor): object { const result = {}; for (const key in schema.struct) { - result[key] = this.decode(schema.struct[key]); + result[key] = this.decode_value(schema.struct[key]); } return classType ? new classType(result) : result; } diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index f0a2f254..b4920f8c 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -1,4 +1,4 @@ -import { ArrayType, MapType, NumberType, OptionType, Schema, SetType, StructType, numbers } from './types'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers } from './types'; import { EncodeBuffer } from './buffer'; import BN from 'bn.js'; import * as utils from './utils'; @@ -7,24 +7,28 @@ export class BorshSerializer { encoded: EncodeBuffer = new EncodeBuffer(); encode(value: unknown, schema: Schema): Uint8Array { + utils.validate_schema(schema); + this.encode_value(value, schema); + return this.encoded.get_used_buffer(); + } + + encode_value(value: unknown, schema: Schema): void { if (typeof schema === 'string') { - if (numbers.includes(schema)) this.encode_integer(value, schema); - if (schema === 'string') this.encode_string(value); - if (schema === 'bool') this.encode_boolean(value); + if (integers.includes(schema)) return this.encode_integer(value, schema); + if (schema === 'string') return this.encode_string(value); + if (schema === 'bool') return this.encode_boolean(value); } if (typeof schema === 'object') { - if ('option' in schema) this.encode_option(value, schema as OptionType); - if ('array' in schema) this.encode_array(value, schema as ArrayType); - if ('set' in schema) this.encode_set(value, schema as SetType); - if ('map' in schema) this.encode_map(value, schema as MapType); - if ('struct' in schema) this.encode_struct(value, schema as StructType); + if ('option' in schema) return this.encode_option(value, schema as OptionType); + if ('array' in schema) return this.encode_array(value, schema as ArrayType); + if ('set' in schema) return this.encode_set(value, schema as SetType); + if ('map' in schema) return this.encode_map(value, schema as MapType); + if ('struct' in schema) return this.encode_struct(value, schema as StructType); } - - return this.encoded.get_used_buffer(); } - encode_integer(value: unknown, schema: NumberType): void { + encode_integer(value: unknown, schema: IntegerType): void { const size: number = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { @@ -76,7 +80,7 @@ export class BorshSerializer { this.encoded.store_value(0, 'u8'); } else { this.encoded.store_value(1, 'u8'); - this.encode(value, schema.option); + this.encode_value(value, schema.option); } } @@ -96,7 +100,7 @@ export class BorshSerializer { // array values for (let i = 0; i < value.length; i++) { - this.encode(value[i], schema.array.type); + this.encode_value(value[i], schema.array.type); } } @@ -123,7 +127,7 @@ export class BorshSerializer { // set values for (const value of values) { - this.encode(value, schema.set); + this.encode_value(value, schema.set); } } @@ -138,8 +142,8 @@ export class BorshSerializer { // store key/values for (const key of keys) { - this.encode(key, schema.map.key); - this.encode(isMap ? value.get(key) : value[key], schema.map.value); + this.encode_value(key, schema.map.key); + this.encode_value(isMap ? value.get(key) : value[key], schema.map.value); } } @@ -147,7 +151,7 @@ export class BorshSerializer { utils.expect_type(value, 'object'); for (const key of Object.keys(value)) { - this.encode(value[key], schema.struct[key]); + this.encode_value(value[key], schema.struct[key]); } } } \ No newline at end of file diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts index c06bf018..e949b8e7 100644 --- a/borsh-ts/types.ts +++ b/borsh-ts/types.ts @@ -1,8 +1,8 @@ import BN from 'bn.js'; -export const numbers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; +export const integers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; -export type NumberType = typeof numbers[number]; +export type IntegerType = typeof integers[number]; export type BoolType = 'bool'; export type StringType = 'string'; @@ -11,7 +11,7 @@ export type ArrayType = { array: { type: Schema, len?: number } }; export type SetType = { set: Schema }; export type MapType = { map: { key: Schema, value: Schema } }; export type StructType = { struct: { [key: string]: Schema } }; -export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; +export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; // returned export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 91765f75..1f6bb68b 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -1,4 +1,5 @@ import BN from 'bn.js'; +import { Schema, integers } from './types'; export function isArrayLike(value: unknown): boolean { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like @@ -32,4 +33,50 @@ export function expect_same_size(length: number, expected: number): void { if (length !== expected) { throw new Error(`Array length ${length} does not match schema length ${expected}`); } +} + +// Validate Schema +const VALID_STRING_TYPES = integers.concat(['bool', 'string']); +const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; + +export function validate_schema(schema: Schema): void { + if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) { + return; + } + + if (typeof (schema) === 'object') { + const keys = Object.keys(schema); + + if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { + const key = keys[0]; + + if(key === 'option') return validate_schema(schema[key]); + if(key === 'array') return validate_array_schema(schema[key]); + if(key === 'set') return validate_schema(schema[key]); + if(key === 'map') return validate_map_schema(schema[key]); + if(key === 'struct') return validate_struct_schema(schema[key]); + } + } + throw new Error(`Invalid schema: ${schema}`); +} + + +function validate_array_schema(schema: {type: Schema, len?: number}): void { + if (schema.len && typeof schema.len !== 'number') { + throw new Error(`Invalid schema: ${schema}`); + } + return validate_schema(schema.type); +} + +function validate_map_schema(schema: {key: Schema, value: Schema}): void { + validate_schema(schema.key); + validate_schema(schema.value); +} + +function validate_struct_schema(schema: {[key: string]: Schema}): void { + if(typeof schema !== 'object') throw new Error(`Invalid schema: ${schema}`); + + for (const key in schema) { + validate_schema(schema[key]); + } } \ No newline at end of file diff --git a/lib/buffer.d.ts b/lib/buffer.d.ts index 20a434c8..577feb81 100644 --- a/lib/buffer.d.ts +++ b/lib/buffer.d.ts @@ -1,4 +1,4 @@ -import { NumberType } from './types'; +import { IntegerType } from './types'; export declare class EncodeBuffer { offset: number; buffer_size: number; @@ -6,7 +6,7 @@ export declare class EncodeBuffer { view: DataView; resize_if_necessary(needed_space: number): void; get_used_buffer(): Uint8Array; - store_value(value: number, type: NumberType): void; + store_value(value: number, type: IntegerType): void; store_bytes(from: Uint8Array): void; } export declare class DecodeBuffer { @@ -15,6 +15,6 @@ export declare class DecodeBuffer { buffer: ArrayBuffer; view: DataView; constructor(buf: Uint8Array); - consume_value(type: NumberType): number; + consume_value(type: IntegerType): number; consume_bytes(size: number): ArrayBuffer; } diff --git a/lib/deserialize.d.ts b/lib/deserialize.d.ts index 5e4d00b5..d6e69ac2 100644 --- a/lib/deserialize.d.ts +++ b/lib/deserialize.d.ts @@ -1,11 +1,12 @@ -import { ArrayType, DecodeTypes, MapType, NumberType, OptionType, Schema, SetType, StructType } from './types'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType } from './types'; import { DecodeBuffer } from './buffer'; import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; constructor(bufferArray: Uint8Array); decode(schema: Schema, classType?: ObjectConstructor): DecodeTypes; - decode_integer(schema: NumberType): number | BN; + decode_value(schema: Schema, classType?: ObjectConstructor): DecodeTypes; + decode_integer(schema: IntegerType): number | BN; decode_bigint(size: number): BN; decode_string(): string; decode_boolean(): boolean; diff --git a/lib/deserialize.js b/lib/deserialize.js index 7ace53d4..51e0dc45 100644 --- a/lib/deserialize.js +++ b/lib/deserialize.js @@ -1,10 +1,34 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BorshDeserializer = void 0; const types_1 = require("./types"); +const utils = __importStar(require("./utils")); const buffer_1 = require("./buffer"); const bn_js_1 = __importDefault(require("bn.js")); class BorshDeserializer { @@ -13,8 +37,12 @@ class BorshDeserializer { this.buffer = new buffer_1.DecodeBuffer(bufferArray); } decode(schema, classType) { + utils.validate_schema(schema); + return this.decode_value(schema, classType); + } + decode_value(schema, classType) { if (typeof schema === 'string') { - if (types_1.numbers.includes(schema)) + if (types_1.integers.includes(schema)) return this.decode_integer(schema); if (schema === 'string') return this.decode_string(); @@ -70,7 +98,7 @@ class BorshDeserializer { decode_option(schema) { const option = this.buffer.consume_value('u8'); if (option === 1) { - return this.decode(schema.option); + return this.decode_value(schema.option); } if (option !== 0) { throw new Error(`Invalid option ${option}`); @@ -81,7 +109,7 @@ class BorshDeserializer { const result = []; const len = schema.array.len ? schema.array.len : this.decode_integer('u32'); for (let i = 0; i < len; ++i) { - result.push(this.decode(schema.array.type)); + result.push(this.decode_value(schema.array.type)); } return result; } @@ -89,7 +117,7 @@ class BorshDeserializer { const len = this.decode_integer('u32'); const result = new Set(); for (let i = 0; i < len; ++i) { - result.add(this.decode(schema.set)); + result.add(this.decode_value(schema.set)); } return result; } @@ -97,8 +125,8 @@ class BorshDeserializer { const len = this.decode_integer('u32'); const result = new Map(); for (let i = 0; i < len; ++i) { - const key = this.decode(schema.map.key); - const value = this.decode(schema.map.value); + const key = this.decode_value(schema.map.key); + const value = this.decode_value(schema.map.value); result.set(key, value); } return result; @@ -106,7 +134,7 @@ class BorshDeserializer { decode_struct(schema, classType) { const result = {}; for (const key in schema.struct) { - result[key] = this.decode(schema.struct[key]); + result[key] = this.decode_value(schema.struct[key]); } return classType ? new classType(result) : result; } diff --git a/lib/serialize.d.ts b/lib/serialize.d.ts index 5452856e..1467ba2c 100644 --- a/lib/serialize.d.ts +++ b/lib/serialize.d.ts @@ -1,10 +1,11 @@ -import { ArrayType, MapType, NumberType, OptionType, Schema, SetType, StructType } from './types'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType } from './types'; import { EncodeBuffer } from './buffer'; import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; encode(value: unknown, schema: Schema): Uint8Array; - encode_integer(value: unknown, schema: NumberType): void; + encode_value(value: unknown, schema: Schema): void; + encode_integer(value: unknown, schema: IntegerType): void; encode_bigint(value: BN, size: number): void; encode_string(value: unknown): void; encode_boolean(value: unknown): void; diff --git a/lib/serialize.js b/lib/serialize.js index a1e7f56e..34c2a1bc 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -34,27 +34,31 @@ const utils = __importStar(require("./utils")); class BorshSerializer { encoded = new buffer_1.EncodeBuffer(); encode(value, schema) { + utils.validate_schema(schema); + this.encode_value(value, schema); + return this.encoded.get_used_buffer(); + } + encode_value(value, schema) { if (typeof schema === 'string') { - if (types_1.numbers.includes(schema)) - this.encode_integer(value, schema); + if (types_1.integers.includes(schema)) + return this.encode_integer(value, schema); if (schema === 'string') - this.encode_string(value); + return this.encode_string(value); if (schema === 'bool') - this.encode_boolean(value); + return this.encode_boolean(value); } if (typeof schema === 'object') { if ('option' in schema) - this.encode_option(value, schema); + return this.encode_option(value, schema); if ('array' in schema) - this.encode_array(value, schema); + return this.encode_array(value, schema); if ('set' in schema) - this.encode_set(value, schema); + return this.encode_set(value, schema); if ('map' in schema) - this.encode_map(value, schema); + return this.encode_map(value, schema); if ('struct' in schema) - this.encode_struct(value, schema); + return this.encode_struct(value, schema); } - return this.encoded.get_used_buffer(); } encode_integer(value, schema) { const size = parseInt(schema.substring(1)); @@ -103,7 +107,7 @@ class BorshSerializer { } else { this.encoded.store_value(1, 'u8'); - this.encode(value, schema.option); + this.encode_value(value, schema.option); } } encode_array(value, schema) { @@ -123,7 +127,7 @@ class BorshSerializer { } // array values for (let i = 0; i < value.length; i++) { - this.encode(value[i], schema.array.type); + this.encode_value(value[i], schema.array.type); } } encode_buffer(value, schema) { @@ -145,7 +149,7 @@ class BorshSerializer { this.encoded.store_value(values.length, 'u32'); // set values for (const value of values) { - this.encode(value, schema.set); + this.encode_value(value, schema.set); } } encode_map(value, schema) { @@ -156,14 +160,14 @@ class BorshSerializer { this.encoded.store_value(keys.length, 'u32'); // store key/values for (const key of keys) { - this.encode(key, schema.map.key); - this.encode(isMap ? value.get(key) : value[key], schema.map.value); + this.encode_value(key, schema.map.key); + this.encode_value(isMap ? value.get(key) : value[key], schema.map.value); } } encode_struct(value, schema) { utils.expect_type(value, 'object'); for (const key of Object.keys(value)) { - this.encode(value[key], schema.struct[key]); + this.encode_value(value[key], schema.struct[key]); } } } diff --git a/lib/types.d.ts b/lib/types.d.ts index 96405821..6e383286 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -1,6 +1,6 @@ import BN from 'bn.js'; -export declare const numbers: string[]; -export type NumberType = typeof numbers[number]; +export declare const integers: string[]; +export type IntegerType = typeof integers[number]; export type BoolType = 'bool'; export type StringType = 'string'; export type OptionType = { @@ -26,5 +26,5 @@ export type StructType = { [key: string]: Schema; }; }; -export type Schema = NumberType | StringType | ArrayType | SetType | MapType | StructType; +export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; diff --git a/lib/types.js b/lib/types.js index 6f99ea3e..77e609f1 100644 --- a/lib/types.js +++ b/lib/types.js @@ -1,4 +1,4 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.numbers = void 0; -exports.numbers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; +exports.integers = void 0; +exports.integers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; diff --git a/lib/utils.d.ts b/lib/utils.d.ts index 5aee9548..e45175c2 100644 --- a/lib/utils.d.ts +++ b/lib/utils.d.ts @@ -1,4 +1,6 @@ +import { Schema } from './types'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string): void; export declare function expect_BN(value: unknown): void; export declare function expect_same_size(length: number, expected: number): void; +export declare function validate_schema(schema: Schema): void; diff --git a/lib/utils.js b/lib/utils.js index 1a831d47..c6ab42ec 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; +exports.validate_schema = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; const bn_js_1 = __importDefault(require("bn.js")); +const types_1 = require("./types"); function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like return (Array.isArray(value) || @@ -35,3 +36,46 @@ function expect_same_size(length, expected) { } } exports.expect_same_size = expect_same_size; +// Validate Schema +const VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']); +const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; +function validate_schema(schema) { + if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) { + return; + } + if (typeof (schema) === 'object') { + const keys = Object.keys(schema); + if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { + const key = keys[0]; + if (key === 'option') + return validate_schema(schema[key]); + if (key === 'array') + return validate_array_schema(schema[key]); + if (key === 'set') + return validate_schema(schema[key]); + if (key === 'map') + return validate_map_schema(schema[key]); + if (key === 'struct') + return validate_struct_schema(schema[key]); + } + } + throw new Error(`Invalid schema: ${schema}`); +} +exports.validate_schema = validate_schema; +function validate_array_schema(schema) { + if (schema.len && typeof schema.len !== 'number') { + throw new Error(`Invalid schema: ${schema}`); + } + return validate_schema(schema.type); +} +function validate_map_schema(schema) { + validate_schema(schema.key); + validate_schema(schema.value); +} +function validate_struct_schema(schema) { + if (typeof schema !== 'object') + throw new Error(`Invalid schema: ${schema}`); + for (const key in schema) { + validate_schema(schema[key]); + } +} From 6137793fe3e4e23cb693871e59dee5f13af10624 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 13:02:23 +0200 Subject: [PATCH 06/35] minor improvements --- borsh-ts/deserialize.ts | 6 ++---- borsh-ts/serialize.ts | 6 +++--- lib/deserialize.js | 6 ++---- lib/serialize.js | 6 ++---- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index 2c78dbe5..0a39e4c5 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -45,11 +45,9 @@ export class BorshDeserializer { decode_bigint(size: number): BN { const buffer_len = size / 8; const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); - const value = new BN(buffer, 'le'); - if (value.testn(buffer_len * 8 - 1)) { + if (buffer[buffer_len - 1]) { // negative number - const buffer = value.toArray('le', buffer_len + 1); let carry = 1; for (let i = 0; i < buffer_len; i++) { const v = (buffer[i] ^ 0xff) + carry; @@ -59,7 +57,7 @@ export class BorshDeserializer { return new BN(buffer, 'le').mul(new BN(-1)); } - return value; + return new BN(buffer, 'le'); } decode_string(): string { diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index b4920f8c..e490a9f7 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -49,12 +49,12 @@ export class BorshSerializer { let carry = 1; for (let i = 0; i < buffer_len; i++) { const v = (buffer[i] ^ 0xff) + carry; - this.encoded.store_value(v & 0xff, 'u8'); carry = v >> 8; + buffer[i] = v & 0xff; } - } else { - this.encoded.store_bytes(new Uint8Array(buffer)); } + + this.encoded.store_bytes(new Uint8Array(buffer)); } encode_string(value: unknown): void { diff --git a/lib/deserialize.js b/lib/deserialize.js index 51e0dc45..ad59e49a 100644 --- a/lib/deserialize.js +++ b/lib/deserialize.js @@ -73,10 +73,8 @@ class BorshDeserializer { decode_bigint(size) { const buffer_len = size / 8; const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); - const value = new bn_js_1.default(buffer, 'le'); - if (value.testn(buffer_len * 8 - 1)) { + if (buffer[buffer_len - 1]) { // negative number - const buffer = value.toArray('le', buffer_len + 1); let carry = 1; for (let i = 0; i < buffer_len; i++) { const v = (buffer[i] ^ 0xff) + carry; @@ -85,7 +83,7 @@ class BorshDeserializer { } return new bn_js_1.default(buffer, 'le').mul(new bn_js_1.default(-1)); } - return value; + return new bn_js_1.default(buffer, 'le'); } decode_string() { const len = this.decode_integer('u32'); diff --git a/lib/serialize.js b/lib/serialize.js index 34c2a1bc..b398d512 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -79,13 +79,11 @@ class BorshSerializer { let carry = 1; for (let i = 0; i < buffer_len; i++) { const v = (buffer[i] ^ 0xff) + carry; - this.encoded.store_value(v & 0xff, 'u8'); carry = v >> 8; + buffer[i] = v & 0xff; } } - else { - this.encoded.store_bytes(new Uint8Array(buffer)); - } + this.encoded.store_bytes(new Uint8Array(buffer)); } encode_string(value) { utils.expect_type(value, 'string'); From a8a0e83bc164cbe6e3a9bce7e5a00a3312b81437 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 14:58:58 +0200 Subject: [PATCH 07/35] added more tests --- borsh-ts/buffer.ts | 36 ++++++--------------- borsh-ts/test/deserialize.test.js | 50 ++++++++++++++++++++++++++++- borsh-ts/test/serialize.test.js | 52 ++++++++++++++++++++++++++++++- borsh-ts/test/utils.test.js | 37 ++++++++++++++++++++++ borsh-ts/utils.ts | 43 ++++++++++++++++--------- lib/buffer.d.ts | 1 + lib/buffer.js | 28 +++++------------ lib/utils.d.ts | 3 ++ lib/utils.js | 30 +++++++++++++----- 9 files changed, 210 insertions(+), 70 deletions(-) create mode 100644 borsh-ts/test/utils.test.js diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index 88f3b17d..80af6dec 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -76,8 +76,16 @@ export class DecodeBuffer { this.view = new DataView(this.buffer); } + assert_enough_buffer(size: number): void { + if (this.offset + size > this.buffer.byteLength) { + throw new Error('Error in schema, the buffer is smaller than expected'); + } + } + consume_value(type: IntegerType): number { const size = parseInt(type.substring(1)) / 8; + this.assert_enough_buffer(size); + let ret: number; switch (type) { @@ -113,33 +121,9 @@ export class DecodeBuffer { } consume_bytes(size: number): ArrayBuffer { + this.assert_enough_buffer(size); const ret = this.buffer.slice(this.offset, this.offset + size); this.offset += size; return ret; } -} -// export class DecodeBuffer { -// public offset: number = 0; -// public start: usize; - -// constructor(public arrBuffer: ArrayBuffer) { -// this.start = changetype(arrBuffer) -// } - -// consume(): T { -// const off = this.offset -// this.offset += sizeof() -// return load(this.start + off) -// } - -// consume_slice(length: number): ArrayBuffer { -// const off = this.offset -// this.offset += length -// return changetype(this.start).slice(off, off + length) -// } - -// consume_copy(dst: usize, length: number): void { -// memory.copy(dst, this.start + this.offset, length); -// this.offset += length; -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js index cf2442a7..f5f9dccc 100644 --- a/borsh-ts/test/deserialize.test.js +++ b/borsh-ts/test/deserialize.test.js @@ -81,6 +81,38 @@ test('deserialize struct', async () => { check_decode(new Map([['a', 1], ['b', 2]]), { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); }); +test('deserialize complex structures', async () => { + // computed by hand i32, u32, u64val, i64val, B, + const buffer = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, + // string, u8array, + 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, + // Array> + 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, + // u32Arr, i32Arr, u128, + 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // Array, // u64Arr + 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; + + const schema = { + struct: { + foo: 'u32', + bar: 'i32', + u64Val: 'u64', + i64Val: 'i64', + flag: 'bool', + baz: 'string', + uint8array: { array: { type: 'u8', len: 2 } }, + arr: { array: { type: { array: { type: 'string' } } } }, + u32Arr: { array: { type: 'u32' } }, + i32Arr: { array: { type: 'i32' } }, + u128Val: 'u128', + uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, + u64Arr: { array: { type: 'u64' } }, + } + }; + + check_decode(new MixtureTwo(), schema, buffer, MixtureTwo); +}); // Aux structures class Numbers { @@ -95,4 +127,20 @@ class Numbers { i64 = new BN(-4); f32 = 6.0; f64 = 7.1; -} \ No newline at end of file +} + +class MixtureTwo { + foo = 321; + bar = 123; + u64Val = new BN('4294967297'); + i64Val = new BN(-64); + flag = true; + baz = 'testing'; + uint8array = new Uint8Array([240, 241]); + arr = [['testing'], ['testing']]; + u32Arr = [21, 11]; + i32Arr = []; + u128Val = new BN(128); + uint8arrays = [this.uint8array, this.uint8array]; + u64Arr = [new BN('10000000000'), new BN('100000000000')]; +} diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index f7fac1aa..143e0a7c 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -72,7 +72,41 @@ test('serialize struct', async () => { } }; check_encode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]); +}); + +test('serialize complex structures', async () => { + // computed by hand i32, u32, u64val, i64val, B, + const expected = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, + // string, u8array, + 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, + // Array> + 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, + // u32Arr, i32Arr, u128, + 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // Array, // u64Arr + 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; + + const mixture = new MixtureTwo(); + const schema = { + struct: { + foo: 'u32', + bar: 'i32', + u64Val: 'u64', + i64Val: 'i64', + flag: 'bool', + baz: 'string', + uint8array: { array: { type: 'u8', len: 2 } }, + arr: { array: { type: { array: { type: 'string' } } } }, + u32Arr: { array: { type: 'u32' } }, + i32Arr: { array: { type: 'i32' } }, + u128Val: 'u128', + uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, + u64Arr: { array: { type: 'u64' } }, + } + }; + + check_encode(mixture, schema, expected); }); @@ -89,4 +123,20 @@ class Numbers { i64 = new BN(-4); f32 = 6.0; f64 = 7.1; -} \ No newline at end of file +} + +class MixtureTwo { + foo = 321; + bar = 123; + u64Val = new BN('4294967297'); + i64Val = new BN(-64); + flag = true; + baz = 'testing'; + uint8array = new Uint8Array([240, 241]); + arr = [['testing'], ['testing']]; + u32Arr = [21, 11]; + i32Arr = []; + u128Val = new BN(128); + uint8arrays = [this.uint8array, this.uint8array]; + u64Arr = [new BN('10000000000'), new BN('100000000000')]; +} diff --git a/borsh-ts/test/utils.test.js b/borsh-ts/test/utils.test.js new file mode 100644 index 00000000..a744691b --- /dev/null +++ b/borsh-ts/test/utils.test.js @@ -0,0 +1,37 @@ +const utils = require('../../lib/utils'); + +test('accept valid schemes', async () => { + const array = { array: { type: 'u8' } }; + const arrayFixed = { array: { type: 'u8', len: 2 } }; + const set = { set: 'u8' }; + const map = { map: { key: 'u8', value: 'u8' } }; + const option = { option: 'u8' }; + const struct = { struct: { u8: 'u8' } }; + + const valid = [array, arrayFixed, set, map, option, struct, 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'bool', 'string', 'f32', 'f64']; + + for (const schema of valid) { + expect(() => utils.validate_schema(schema)).not.toThrow(); + } +}); + +test('rejects invalid schemes', async () => { + const array = { array: 'u8' }; + const arrayFixed = { array: { len: 2 } }; + const set = { set: { type: 'u8' } }; + const map = { map: { value: 'u8' } }; + const option = { option: null }; + const struct = { struct: arrayFixed }; + const noStruct = { u8: 'u8' }; + + expect(() => utils.validate_schema(array)).toThrow('Invalid schema: "u8" expected { type, len? }'); + expect(() => utils.validate_schema(arrayFixed)).toThrow('Invalid schema: {"len":2} expected { type, len? }'); + expect(() => utils.validate_schema(set)).toThrow('Invalid schema: {"type":"u8"} expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(map)).toThrow('Invalid schema: {"value":"u8"} expected { key, value }'); + expect(() => utils.validate_schema(option)).toThrow('Invalid schema: null expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(struct)).toThrow('Invalid schema: {"len":2} expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(noStruct)).toThrow('Invalid schema: {"u8":"u8"} expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema('u7')).toThrow('Invalid schema: "u7" expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(Array)).toThrow(); + expect(() => utils.validate_schema(Map)).toThrow(); +}); \ No newline at end of file diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 1f6bb68b..9becb46f 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -39,42 +39,57 @@ export function expect_same_size(length: number, expected: number): void { const VALID_STRING_TYPES = integers.concat(['bool', 'string']); const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; +export class ErrorSchema extends Error { + constructor(schema: Schema, expected: string) { + const message = `Invalid schema: ${JSON.stringify(schema)} expected ${expected}` + super(message); + } +} + export function validate_schema(schema: Schema): void { if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) { return; } - if (typeof (schema) === 'object') { + if (schema && typeof (schema) === 'object') { const keys = Object.keys(schema); if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { const key = keys[0]; - if(key === 'option') return validate_schema(schema[key]); - if(key === 'array') return validate_array_schema(schema[key]); - if(key === 'set') return validate_schema(schema[key]); - if(key === 'map') return validate_map_schema(schema[key]); - if(key === 'struct') return validate_struct_schema(schema[key]); + if (key === 'option') return validate_schema(schema[key]); + if (key === 'array') return validate_array_schema(schema[key]); + if (key === 'set') return validate_schema(schema[key]); + if (key === 'map') return validate_map_schema(schema[key]); + if (key === 'struct') return validate_struct_schema(schema[key]); } } - throw new Error(`Invalid schema: ${schema}`); + throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(', ') + ' or ' + VALID_STRING_TYPES.join(', ')); } +function validate_array_schema(schema: { type: Schema, len?: number }): void { + if (typeof schema !== 'object') throw new ErrorSchema(schema, '{ type, len? }'); -function validate_array_schema(schema: {type: Schema, len?: number}): void { if (schema.len && typeof schema.len !== 'number') { throw new Error(`Invalid schema: ${schema}`); } - return validate_schema(schema.type); + + if ('type' in schema) return validate_schema(schema.type); + + throw new ErrorSchema(schema, '{ type, len? }'); } -function validate_map_schema(schema: {key: Schema, value: Schema}): void { - validate_schema(schema.key); - validate_schema(schema.value); +function validate_map_schema(schema: { key: Schema, value: Schema }): void { + if (typeof schema === 'object' && 'key' in schema && 'value' in schema) { + validate_schema(schema.key); + validate_schema(schema.value); + } else { + throw new ErrorSchema(schema, '{ key, value }'); + } } -function validate_struct_schema(schema: {[key: string]: Schema}): void { - if(typeof schema !== 'object') throw new Error(`Invalid schema: ${schema}`); +function validate_struct_schema(schema: { [key: string]: Schema }): void { + if (typeof schema !== 'object') throw new ErrorSchema(schema, 'object'); for (const key in schema) { validate_schema(schema[key]); diff --git a/lib/buffer.d.ts b/lib/buffer.d.ts index 577feb81..5673a7f2 100644 --- a/lib/buffer.d.ts +++ b/lib/buffer.d.ts @@ -15,6 +15,7 @@ export declare class DecodeBuffer { buffer: ArrayBuffer; view: DataView; constructor(buf: Uint8Array); + assert_enough_buffer(size: number): void; consume_value(type: IntegerType): number; consume_bytes(size: number): ArrayBuffer; } diff --git a/lib/buffer.js b/lib/buffer.js index 5d2e10ac..ebea8fbc 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -68,8 +68,14 @@ class DecodeBuffer { new Uint8Array(this.buffer).set(buf); this.view = new DataView(this.buffer); } + assert_enough_buffer(size) { + if (this.offset + size > this.buffer.byteLength) { + throw new Error('Error in schema, the buffer is smaller than expected'); + } + } consume_value(type) { const size = parseInt(type.substring(1)) / 8; + this.assert_enough_buffer(size); let ret; switch (type) { case 'u8': @@ -103,30 +109,10 @@ class DecodeBuffer { return ret; } consume_bytes(size) { + this.assert_enough_buffer(size); const ret = this.buffer.slice(this.offset, this.offset + size); this.offset += size; return ret; } } exports.DecodeBuffer = DecodeBuffer; -// export class DecodeBuffer { -// public offset: number = 0; -// public start: usize; -// constructor(public arrBuffer: ArrayBuffer) { -// this.start = changetype(arrBuffer) -// } -// consume(): T { -// const off = this.offset -// this.offset += sizeof() -// return load(this.start + off) -// } -// consume_slice(length: number): ArrayBuffer { -// const off = this.offset -// this.offset += length -// return changetype(this.start).slice(off, off + length) -// } -// consume_copy(dst: usize, length: number): void { -// memory.copy(dst, this.start + this.offset, length); -// this.offset += length; -// } -// } diff --git a/lib/utils.d.ts b/lib/utils.d.ts index e45175c2..2e3d10ed 100644 --- a/lib/utils.d.ts +++ b/lib/utils.d.ts @@ -3,4 +3,7 @@ export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string): void; export declare function expect_BN(value: unknown): void; export declare function expect_same_size(length: number, expected: number): void; +export declare class ErrorSchema extends Error { + constructor(schema: Schema, expected: string); +} export declare function validate_schema(schema: Schema): void; diff --git a/lib/utils.js b/lib/utils.js index c6ab42ec..a4fa13a1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.validate_schema = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; +exports.validate_schema = exports.ErrorSchema = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; const bn_js_1 = __importDefault(require("bn.js")); const types_1 = require("./types"); function isArrayLike(value) { @@ -39,11 +39,18 @@ exports.expect_same_size = expect_same_size; // Validate Schema const VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']); const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; +class ErrorSchema extends Error { + constructor(schema, expected) { + const message = `Invalid schema: ${JSON.stringify(schema)} expected ${expected}`; + super(message); + } +} +exports.ErrorSchema = ErrorSchema; function validate_schema(schema) { if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) { return; } - if (typeof (schema) === 'object') { + if (schema && typeof (schema) === 'object') { const keys = Object.keys(schema); if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { const key = keys[0]; @@ -59,22 +66,31 @@ function validate_schema(schema) { return validate_struct_schema(schema[key]); } } - throw new Error(`Invalid schema: ${schema}`); + throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(', ') + ' or ' + VALID_STRING_TYPES.join(', ')); } exports.validate_schema = validate_schema; function validate_array_schema(schema) { + if (typeof schema !== 'object') + throw new ErrorSchema(schema, '{ type, len? }'); if (schema.len && typeof schema.len !== 'number') { throw new Error(`Invalid schema: ${schema}`); } - return validate_schema(schema.type); + if ('type' in schema) + return validate_schema(schema.type); + throw new ErrorSchema(schema, '{ type, len? }'); } function validate_map_schema(schema) { - validate_schema(schema.key); - validate_schema(schema.value); + if (typeof schema === 'object' && 'key' in schema && 'value' in schema) { + validate_schema(schema.key); + validate_schema(schema.value); + } + else { + throw new ErrorSchema(schema, '{ key, value }'); + } } function validate_struct_schema(schema) { if (typeof schema !== 'object') - throw new Error(`Invalid schema: ${schema}`); + throw new ErrorSchema(schema, 'object'); for (const key in schema) { validate_schema(schema[key]); } From 7d46f8b17e4ca5d876880d4fa7b6262b4643effb Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 15:16:44 +0200 Subject: [PATCH 08/35] added more tests --- borsh-ts/test/deserialize.test.js | 29 +++++++++++++++++++++++++++++ borsh-ts/test/serialize.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js index f5f9dccc..92217c19 100644 --- a/borsh-ts/test/deserialize.test.js +++ b/borsh-ts/test/deserialize.test.js @@ -114,6 +114,21 @@ test('deserialize complex structures', async () => { check_decode(new MixtureTwo(), schema, buffer, MixtureTwo); }); +test('deserializes big structs', async () => { + const bigStruct = new BigStruct(); + const schema = { + struct: { + u64: 'u64', + u128: 'u128', + arr: { array: { type: 'u8', len: 1000 } } + } + }; + + const buffer = Array(24).fill(255).concat([...Array(1000).keys()].map(x => x*2)); + + check_decode(bigStruct, schema, buffer, BigStruct); +}); + // Aux structures class Numbers { u8 = 1; @@ -144,3 +159,17 @@ class MixtureTwo { uint8arrays = [this.uint8array, this.uint8array]; u64Arr = [new BN('10000000000'), new BN('100000000000')]; } + +const u64MaxHex = 'ffffffffffffffff'; + +class BigStruct { + u64 = new BN(u64MaxHex, 16); + u128 = new BN(u64MaxHex.repeat(2), 16); + arr = []; + + constructor() { + for (let i = 0; i < 1000; i++) { + this.arr.push(i*2); + } + } +} diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index 143e0a7c..ddf5ea90 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -109,6 +109,20 @@ test('serialize complex structures', async () => { check_encode(mixture, schema, expected); }); +test('serializes big structs', async () => { + const bigStruct = new BigStruct(); + const schema = { + struct: { + u64: 'u64', + u128: 'u128', + arr: { array: { type: 'u8', len: 1000 } } + } + }; + + const expected = Array(24).fill(255).concat([...Array(1000).keys()].map(x => x*2)); + + check_encode(bigStruct, schema, expected); +}); // Aux structures class Numbers { @@ -140,3 +154,17 @@ class MixtureTwo { uint8arrays = [this.uint8array, this.uint8array]; u64Arr = [new BN('10000000000'), new BN('100000000000')]; } + +const u64MaxHex = 'ffffffffffffffff'; + +class BigStruct { + u64 = new BN(u64MaxHex, 16); + u128 = new BN(u64MaxHex.repeat(2), 16); + arr = []; + + constructor() { + for (let i = 0; i < 1000; i++) { + this.arr.push(i*2); + } + } +} From 5e89593683ae2cbaac0b1a09c36e95534cf1f33a Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 15:26:55 +0200 Subject: [PATCH 09/35] added more tests --- borsh-ts/test/deserialize.test.js | 28 ++++++++++++++++++++-------- borsh-ts/test/serialize.test.js | 18 ++++++++++++++++-- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js index 92217c19..c194e917 100644 --- a/borsh-ts/test/deserialize.test.js +++ b/borsh-ts/test/deserialize.test.js @@ -5,14 +5,12 @@ function check_decode(expected, schema, buffer, classType) { const decoded = borsh.deserialize(schema, buffer, classType); if (expected && typeof (expected) === 'object' && 'eq' in expected) { - expect(expected.eq(decoded)).toBe(true); - } else { - if (schema === 'f32') { - expect(decoded).toBeCloseTo(expected); - } else { - expect(decoded).toEqual(expected); - } - } + return expect(expected.eq(decoded)).toBe(true); + } + + if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); + + expect(decoded).toEqual(expected); } test('deserialize integers', async () => { @@ -79,6 +77,14 @@ test('deserialize struct', async () => { check_decode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64], Numbers); check_decode(new Map([['a', 1], ['b', 2]]), { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); + + const options = new Options(); + const schemaOpt = { + struct: { + u32: {option: 'u32'}, option: {option: 'string'}, u8: {option: 'u8'} + } + }; + check_decode(options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1], Options); }); test('deserialize complex structures', async () => { @@ -144,6 +150,12 @@ class Numbers { f64 = 7.1; } +class Options { + u32 = 2; + option = null; + u8 = 1; +} + class MixtureTwo { foo = 321; bar = 123; diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index ddf5ea90..fcc61da6 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -72,6 +72,14 @@ test('serialize struct', async () => { } }; check_encode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]); + + const options = new Options(); + const schemaOpt = { + struct: { + u32: {option: 'u32'}, option: {option: 'string'}, u8: {option: 'u8'} + } + }; + check_encode(options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1]); }); test('serialize complex structures', async () => { @@ -119,7 +127,7 @@ test('serializes big structs', async () => { } }; - const expected = Array(24).fill(255).concat([...Array(1000).keys()].map(x => x*2)); + const expected = Array(24).fill(255).concat([...Array(1000).keys()].map(x => x * 2)); check_encode(bigStruct, schema, expected); }); @@ -139,6 +147,12 @@ class Numbers { f64 = 7.1; } +class Options { + u32 = 2; + option = null; + u8 = 1; +} + class MixtureTwo { foo = 321; bar = 123; @@ -164,7 +178,7 @@ class BigStruct { constructor() { for (let i = 0; i < 1000; i++) { - this.arr.push(i*2); + this.arr.push(i * 2); } } } From c64fd8116df71c367937a733ff28a82729a73f2f Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:07:06 +0200 Subject: [PATCH 10/35] updated readme --- README.md | 88 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f49ad930..56c3fd45 100644 --- a/README.md +++ b/README.md @@ -14,34 +14,82 @@ Borsh stands for _Binary Object Representation Serializer for Hashing_. It is me safety, speed, and comes with a strict specification. ## Examples -### Serializing an object + +### (De)serializing a Value +```javascript +import * as borsh from 'borsh'; +const encoded = borsh.serialize('u8', 2); +const decoded = borsh.deserialize('u8', encoded); +``` + +### (De)serializing an Object ```javascript -const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] }); -const schema = new Map([[Test, { kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]); -const buffer = borsh.serialize(schema, value); +import * as borsh from 'borsh'; + +const value = { x: 255, y: 20, z: '123', arr: [1, 2, 3] }; +const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; + +const encoded = borsh.serialize(schema, value); +const decoded = borsh.deserialize(schema, encoded); ``` -### Deserializing an object +### (De)serializing a Class Instance ```javascript -const newValue = borsh.deserialize(schema, Test, buffer); +import * as borsh from 'borsh'; + +class Test{ constructor({x, y, z, arr}){ Object.assign(this, {x, y, z, arr}) }} +const value = new Test({x: 255, y: 20, z: '123', arr: [1, 2, 3]}); +const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; + +const encoded = borsh.serialize(schema, value); +const decoded = borsh.deserialize(schema, encoded, Test); // decoded is an instance of Test ``` ## Type Mappings -| Borsh | TypeScript | -|-----------------------------------|----------------| -| `u8` `u16` `u32` `i8` `i16` `i32` | `number` | -| `u64` `u128` `i64` `i128` | `bignum` | -| `f32` `f64` | `number` | -| `f32` `f64` | `number` | -| `bool` | `boolean` | -| UTF-8 string | `string` | -| fixed-size byte array | `Array` | -| dynamic sized array | `Array` | -| enum | N/A | -| HashMap | `Map` | -| HashSet | `Set` | -| Option | `null` or type | +| Javascript | Borsh | +|-------------------------------------------|-----------------------------------| +| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | +| [`BN`](https://github.com/indutny/bn.js/) | `u64` `u128` `i64` `i128` | +| `number` | `f32` `f64` | +| `number` | `f32` `f64` | +| `boolean` | `bool` | +| `string` | UTF-8 string | +| `Array` | fixed-size byte array | +| `Array` | dynamic sized array | +| N/A | enum | +| `Map` | HashMap | +| `Set` | HashSet | +| `null` or type | Option | + +## API +The package exposes the following functions: +- `serialize(schema: Schema, obj: any): Uint8Array` - serializes an object `obj` according to the schema `schema`. +- `deserialize(schema: Schema, buffer: Uint8Array, class?: Class): any` - deserializes an object according to the schema `schema` from the buffer `buffer`. If the optional parameter `class` is present, the deserialized object will be an of `class`. + +## Schemas +Schemas are used to describe the structure of the data being serialized or deserialized. They are used to +validate the data and to determine the order of the fields in the serialized data. + +### Basic Types +Basic types are described by a string. The following types are supported: +- `u8`, `u16`, `u32`, `u64`, `u128` - unsigned integers of 8, 16, 32, 64, and 128 bits respectively. +- `i8`, `i16`, `i32`, `i64`, `i128` - signed integers of 8, 16, 32, 64, and 128 bits respectively. +- `f32`, `f64` - IEEE 754 floating point numbers of 32 and 64 bits respectively. +- `bool` - boolean value. +- `string` - UTF-8 string. + +### Arrays, Options, Maps, Sets, and Structs +More complex objects are described by a JSON object. The following types are supported: +- `{ array: { type: Schema, len?: number } }` - an array of objects of the same type. The type of the array elements is described by the `type` field. If the field `len` is present, the array is fixed-size and the length of the array is `len`. Otherwise, the array is dynamic-sized and the length of the array is serialized before the elements. +- `{ option: Schema }` - an optional object. The type of the object is described by the `type` field. +- `{ map: { key: Schema, value: Schema }}` - a map. The type of the keys and values are described by the `key` and `value` fields respectively. +- `{ set: Schema }` - a set. The type of the elements is described by the `type` field. +- `{ struct: { field1: Schema1, field2: Schema2, ... } }` - a struct. The fields of the struct are described by the `field1`, `field2`, etc. fields. + +> NOTE: You can find examples of valid in the [test](./borsh-ts/test/utils.test.js) folder. + +--- ## Contributing From fb64d67b13ad9e047b08d1fdfbbf018d4c69ae07 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:12:02 +0200 Subject: [PATCH 11/35] minor fix to examples --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 56c3fd45..07d45436 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,20 @@ safety, speed, and comes with a strict specification. ### (De)serializing a Value ```javascript import * as borsh from 'borsh'; -const encoded = borsh.serialize('u8', 2); -const decoded = borsh.deserialize('u8', encoded); + +const encodedU16 = borsh.serialize('u16', 2); +const decodedU16 = borsh.deserialize('u16', encodedU16); + +const encodedStr = borsh.serialize('string', 'testing'); +const decodedStr = borsh.deserialize('string', encodedStr); ``` ### (De)serializing an Object ```javascript import * as borsh from 'borsh'; +import BN from 'bn.js'; -const value = { x: 255, y: 20, z: '123', arr: [1, 2, 3] }; +const value = new Test({x: 255, y: new BN(20), z: '123', arr: [1, 2, 3]}); const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; const encoded = borsh.serialize(schema, value); @@ -36,9 +41,10 @@ const decoded = borsh.deserialize(schema, encoded); ### (De)serializing a Class Instance ```javascript import * as borsh from 'borsh'; +import BN from 'bn.js'; class Test{ constructor({x, y, z, arr}){ Object.assign(this, {x, y, z, arr}) }} -const value = new Test({x: 255, y: 20, z: '123', arr: [1, 2, 3]}); +const value = new Test({x: 255, y: new BN(20), z: '123', arr: [1, 2, 3]}); const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; const encoded = borsh.serialize(schema, value); From da7ef6b2a402c96a1e3580c6538de4df32d1bcaf Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:14:32 +0200 Subject: [PATCH 12/35] bump in version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0662955f..b43b1f4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "borsh", - "version": "0.7.0", + "version": "1.0.0", "description": "Binary Object Representation Serializer for Hashing", "main": "lib/index.js", "types": "lib/index.d.ts", From 67c75f44b57ef40ace906bf669ed2502b82418d7 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:23:24 +0200 Subject: [PATCH 13/35] minor update to README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 07d45436..0e2b1b00 100644 --- a/README.md +++ b/README.md @@ -53,20 +53,20 @@ const decoded = borsh.deserialize(schema, encoded, Test); // decoded is an insta ## Type Mappings -| Javascript | Borsh | -|-------------------------------------------|-----------------------------------| -| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | -| [`BN`](https://github.com/indutny/bn.js/) | `u64` `u128` `i64` `i128` | -| `number` | `f32` `f64` | -| `number` | `f32` `f64` | -| `boolean` | `bool` | -| `string` | UTF-8 string | -| `Array` | fixed-size byte array | -| `Array` | dynamic sized array | -| N/A | enum | -| `Map` | HashMap | -| `Set` | HashSet | -| `null` or type | Option | +| Javascript | Borsh | +|--------------------------------------------|-----------------------------------| +| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | +| [`BN`](https://github.com/indutny/bn.js/) | `u64` `u128` `i64` `i128` | +| `number` | `f32` `f64` | +| `number` | `f32` `f64` | +| `boolean` | `bool` | +| `string` | UTF-8 string | +| `type[]` | fixed-size byte array | +| `type[]` | dynamic sized array | +| N/A | enum | +| `Map` | HashMap | +| `Set` | HashSet | +| `null` or `type` | Option | ## API The package exposes the following functions: From 03672480228ce363fd45fe7e2a2d25f850c9e715 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:24:17 +0200 Subject: [PATCH 14/35] minor update to README.md --- README.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0e2b1b00..f4024b34 100644 --- a/README.md +++ b/README.md @@ -51,23 +51,6 @@ const encoded = borsh.serialize(schema, value); const decoded = borsh.deserialize(schema, encoded, Test); // decoded is an instance of Test ``` -## Type Mappings - -| Javascript | Borsh | -|--------------------------------------------|-----------------------------------| -| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | -| [`BN`](https://github.com/indutny/bn.js/) | `u64` `u128` `i64` `i128` | -| `number` | `f32` `f64` | -| `number` | `f32` `f64` | -| `boolean` | `bool` | -| `string` | UTF-8 string | -| `type[]` | fixed-size byte array | -| `type[]` | dynamic sized array | -| N/A | enum | -| `Map` | HashMap | -| `Set` | HashSet | -| `null` or `type` | Option | - ## API The package exposes the following functions: - `serialize(schema: Schema, obj: any): Uint8Array` - serializes an object `obj` according to the schema `schema`. @@ -77,6 +60,8 @@ The package exposes the following functions: Schemas are used to describe the structure of the data being serialized or deserialized. They are used to validate the data and to determine the order of the fields in the serialized data. +> NOTE: You can find examples of valid in the [test](./borsh-ts/test/utils.test.js) folder. + ### Basic Types Basic types are described by a string. The following types are supported: - `u8`, `u16`, `u32`, `u64`, `u128` - unsigned integers of 8, 16, 32, 64, and 128 bits respectively. @@ -93,7 +78,23 @@ More complex objects are described by a JSON object. The following types are sup - `{ set: Schema }` - a set. The type of the elements is described by the `type` field. - `{ struct: { field1: Schema1, field2: Schema2, ... } }` - a struct. The fields of the struct are described by the `field1`, `field2`, etc. fields. -> NOTE: You can find examples of valid in the [test](./borsh-ts/test/utils.test.js) folder. +### Type Mappings + +| Javascript | Borsh | +|--------------------------------------------|-----------------------------------| +| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | +| [`BN`](https://github.com/indutny/bn.js/) | `u64` `u128` `i64` `i128` | +| `number` | `f32` `f64` | +| `number` | `f32` `f64` | +| `boolean` | `bool` | +| `string` | UTF-8 string | +| `type[]` | fixed-size byte array | +| `type[]` | dynamic sized array | +| N/A | enum | +| `Map` | HashMap | +| `Set` | HashSet | +| `null` or `type` | Option | + --- From 04b13b331f62f93da48ca0b2a1f221aeace9b48c Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:29:06 +0200 Subject: [PATCH 15/35] trigger actions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f4024b34..e228845f 100644 --- a/README.md +++ b/README.md @@ -132,4 +132,4 @@ When publishing to npm use [np](https://github.com/sindresorhus/np). This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-MIT](LICENSE-MIT.txt) and [LICENSE-APACHE](LICENSE-APACHE) for details. -[Borsh]: https://borsh.io +[Borsh]: https://borsh.io \ No newline at end of file From ad6eeb0103b3348a58bc340cb2a78ae47dbe5441 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:36:49 +0200 Subject: [PATCH 16/35] Removed unnecesary packages + fixed lint --- borsh-ts/.eslintrc.yml | 1 - borsh-ts/deserialize.ts | 8 ++++---- borsh-ts/types.ts | 2 +- borsh-ts/utils.ts | 2 +- lib/deserialize.d.ts | 6 +++--- lib/types.d.ts | 2 +- package.json | 10 +--------- 7 files changed, 11 insertions(+), 20 deletions(-) diff --git a/borsh-ts/.eslintrc.yml b/borsh-ts/.eslintrc.yml index 82aa9c4d..ea4ae567 100644 --- a/borsh-ts/.eslintrc.yml +++ b/borsh-ts/.eslintrc.yml @@ -11,7 +11,6 @@ rules: '@typescript-eslint/no-explicit-any': 1 '@typescript-eslint/ban-types': 1 '@typescript-eslint/explicit-function-return-type': 1 - '@typescript-eslint/no-use-before-define': 1 parserOptions: ecmaVersion: 2018 diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index 0a39e4c5..8d4864f7 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -81,7 +81,7 @@ export class BorshDeserializer { return null; } - decode_array(schema: ArrayType): Array { + decode_array(schema: ArrayType): Array { const result = []; const len = schema.array.len ? schema.array.len : this.decode_integer('u32') as number; @@ -92,16 +92,16 @@ export class BorshDeserializer { return result; } - decode_set(schema: SetType): Set { + decode_set(schema: SetType): Set { const len = this.decode_integer('u32') as number; - const result = new Set(); + const result = new Set(); for (let i = 0; i < len; ++i) { result.add(this.decode_value(schema.set)); } return result; } - decode_map(schema: MapType): Map { + decode_map(schema: MapType): Map { const len = this.decode_integer('u32') as number; const result = new Map(); for (let i = 0; i < len; ++i) { diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts index e949b8e7..81c682fc 100644 --- a/borsh-ts/types.ts +++ b/borsh-ts/types.ts @@ -14,4 +14,4 @@ export type StructType = { struct: { [key: string]: Schema } }; export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; // returned -export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 9becb46f..387a6bc0 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -41,7 +41,7 @@ const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; export class ErrorSchema extends Error { constructor(schema: Schema, expected: string) { - const message = `Invalid schema: ${JSON.stringify(schema)} expected ${expected}` + const message = `Invalid schema: ${JSON.stringify(schema)} expected ${expected}`; super(message); } } diff --git a/lib/deserialize.d.ts b/lib/deserialize.d.ts index d6e69ac2..5bb4da68 100644 --- a/lib/deserialize.d.ts +++ b/lib/deserialize.d.ts @@ -11,8 +11,8 @@ export declare class BorshDeserializer { decode_string(): string; decode_boolean(): boolean; decode_option(schema: OptionType): DecodeTypes; - decode_array(schema: ArrayType): Array; - decode_set(schema: SetType): Set; - decode_map(schema: MapType): Map; + decode_array(schema: ArrayType): Array; + decode_set(schema: SetType): Set; + decode_map(schema: MapType): Map; decode_struct(schema: StructType, classType?: ObjectConstructor): object; } diff --git a/lib/types.d.ts b/lib/types.d.ts index 6e383286..7e98641f 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -27,4 +27,4 @@ export type StructType = { }; }; export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; -export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; diff --git a/package.json b/package.json index b43b1f4e..c5a2446a 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,6 @@ "scripts": { "build": "tsc -p ./tsconfig.json", "test": "jest test --runInBand", - "fuzz": "jsfuzz borsh-ts/test/fuzz/borsh-roundtrip.js borsh-ts/test/fuzz/corpus/", - "dev": "yarn build -w", "pretest": "yarn build", "lint": "eslint borsh-ts/**/*.ts", "fix": "eslint borsh-ts/**/*.ts --fix" @@ -43,17 +41,11 @@ "@types/node": "^12.7.3", "@typescript-eslint/eslint-plugin": "^5.28.0", "@typescript-eslint/parser": "^5.28.0", - "bs58": "^4.0.0", "eslint": "^8.17.0", "jest": "^26.0.1", - "js-sha256": "^0.9.0", - "jsfuzz": "^1.0.14", "typescript": "^4" }, "dependencies": { - "bn.js": "^5.2.0", - "bs58": "^4.0.0", - "buffer": "^6.0.3", - "text-encoding-utf-8": "^1.0.2" + "bn.js": "^5.2.0" } } From f0ee708cad2190dd897672e92093c948dbf60975 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 16:50:46 +0200 Subject: [PATCH 17/35] simplified buffer --- borsh-ts/buffer.ts | 67 ++++++---------------------------------------- lib/buffer.js | 67 ++++++---------------------------------------- 2 files changed, 16 insertions(+), 118 deletions(-) diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index 80af6dec..3d1b635d 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -23,37 +23,12 @@ export class EncodeBuffer { } store_value(value: number, type: IntegerType): void { - const size = parseInt(type.substring(1)) / 8; + const bSize = type.substring(1); + const size = parseInt(bSize) / 8; this.resize_if_necessary(size); - switch (type) { - case 'u8': - this.view.setUint8(this.offset, value); - break; - case 'u16': - this.view.setUint16(this.offset, value, true); - break; - case 'u32': - this.view.setUint32(this.offset, value, true); - break; - case 'i8': - this.view.setInt8(this.offset, value); - break; - case 'i16': - this.view.setInt16(this.offset, value, true); - break; - case 'i32': - this.view.setInt32(this.offset, value, true); - break; - case 'f32': - this.view.setFloat32(this.offset, value, true); - break; - case 'f64': - this.view.setFloat64(this.offset, value, true); - break; - default: - throw new Error(`Unsupported integer type: ${type}`); - } + const toCall = type[0] === 'f'? `setFloat${bSize}`: type[0] === 'i'? `setInt${bSize}` : `setUint${bSize}`; + this.view[toCall](this.offset, value, true); this.offset += size; } @@ -83,39 +58,13 @@ export class DecodeBuffer { } consume_value(type: IntegerType): number { - const size = parseInt(type.substring(1)) / 8; + const bSize = type.substring(1); + const size = parseInt(bSize) / 8; this.assert_enough_buffer(size); - let ret: number; + const toCall = type[0] === 'f'? `getFloat${bSize}`: type[0] === 'i'? `getInt${bSize}` : `getUint${bSize}`; + const ret = this.view[toCall](this.offset, true); - switch (type) { - case 'u8': - ret = this.view.getUint8(this.offset); - break; - case 'u16': - ret = this.view.getUint16(this.offset, true); - break; - case 'u32': - ret = this.view.getUint32(this.offset, true); - break; - case 'i8': - ret = this.view.getInt8(this.offset); - break; - case 'i16': - ret = this.view.getInt16(this.offset, true); - break; - case 'i32': - ret = this.view.getInt32(this.offset, true); - break; - case 'f32': - ret = this.view.getFloat32(this.offset, true); - break; - case 'f64': - ret = this.view.getFloat64(this.offset, true); - break; - default: - throw new Error(`Unsupported integer type: ${type}`); - } this.offset += size; return ret; } diff --git a/lib/buffer.js b/lib/buffer.js index ebea8fbc..f80c17af 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -19,36 +19,11 @@ class EncodeBuffer { return new Uint8Array(this.buffer).slice(0, this.offset); } store_value(value, type) { - const size = parseInt(type.substring(1)) / 8; + const bSize = type.substring(1); + const size = parseInt(bSize) / 8; this.resize_if_necessary(size); - switch (type) { - case 'u8': - this.view.setUint8(this.offset, value); - break; - case 'u16': - this.view.setUint16(this.offset, value, true); - break; - case 'u32': - this.view.setUint32(this.offset, value, true); - break; - case 'i8': - this.view.setInt8(this.offset, value); - break; - case 'i16': - this.view.setInt16(this.offset, value, true); - break; - case 'i32': - this.view.setInt32(this.offset, value, true); - break; - case 'f32': - this.view.setFloat32(this.offset, value, true); - break; - case 'f64': - this.view.setFloat64(this.offset, value, true); - break; - default: - throw new Error(`Unsupported integer type: ${type}`); - } + const toCall = type[0] === 'f' ? `setFloat${bSize}` : type[0] === 'i' ? `setInt${bSize}` : `setUint${bSize}`; + this.view[toCall](this.offset, value, true); this.offset += size; } store_bytes(from) { @@ -74,37 +49,11 @@ class DecodeBuffer { } } consume_value(type) { - const size = parseInt(type.substring(1)) / 8; + const bSize = type.substring(1); + const size = parseInt(bSize) / 8; this.assert_enough_buffer(size); - let ret; - switch (type) { - case 'u8': - ret = this.view.getUint8(this.offset); - break; - case 'u16': - ret = this.view.getUint16(this.offset, true); - break; - case 'u32': - ret = this.view.getUint32(this.offset, true); - break; - case 'i8': - ret = this.view.getInt8(this.offset); - break; - case 'i16': - ret = this.view.getInt16(this.offset, true); - break; - case 'i32': - ret = this.view.getInt32(this.offset, true); - break; - case 'f32': - ret = this.view.getFloat32(this.offset, true); - break; - case 'f64': - ret = this.view.getFloat64(this.offset, true); - break; - default: - throw new Error(`Unsupported integer type: ${type}`); - } + const toCall = type[0] === 'f' ? `getFloat${bSize}` : type[0] === 'i' ? `getInt${bSize}` : `getUint${bSize}`; + const ret = this.view[toCall](this.offset, true); this.offset += size; return ret; } From e2a83100a8213a49d5ccee4eeb6a95a0d89bebc8 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 21 Jul 2023 23:45:57 +0200 Subject: [PATCH 18/35] added base encode/decode --- borsh-ts/index.ts | 14 ++++++++++++++ borsh-ts/test/base_decode.test.js | 23 +++++++++++++++++++++++ lib/index.d.ts | 2 ++ lib/index.js | 19 ++++++++++++++++++- package.json | 3 ++- 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 borsh-ts/test/base_decode.test.js diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 296346a0..d0799655 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -1,6 +1,7 @@ import { Schema, DecodeTypes } from './types'; import { BorshSerializer } from './serialize'; import { BorshDeserializer } from './deserialize'; +import bs58 from 'bs58'; export function serialize(schema: Schema, value: unknown): Uint8Array { const serializer = new BorshSerializer(); @@ -11,3 +12,16 @@ export function deserialize(schema: Schema, buffer: Uint8Array, classType?: Obje const deserializer = new BorshDeserializer(buffer); return deserializer.decode(schema, classType); } + +// Keeping this for compatibility reasons with the old borsh-js +export function baseEncode(value: Uint8Array | string): string { + if (typeof value === 'string') { + // create a uint8array from string decoding utf-8 without using Buffer + value = new Uint8Array([...value].map((c) => c.charCodeAt(0))); + } + return bs58.encode(value); +} + +export function baseDecode(value: string): Uint8Array { + return new Uint8Array(bs58.decode(value)); +} diff --git a/borsh-ts/test/base_decode.test.js b/borsh-ts/test/base_decode.test.js new file mode 100644 index 00000000..85ef6f11 --- /dev/null +++ b/borsh-ts/test/base_decode.test.js @@ -0,0 +1,23 @@ +const borsh = require('../../lib/index'); + +test('baseEncode string test', async () => { + const encodedValue = borsh.baseEncode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + const expectedValue = 'HKk9gqNj4xb4rLdJuzT5zzJbLa4vHBdYCxQT9H99csQh6nz3Hfpqn4jtWA92'; + expect(encodedValue).toEqual(expectedValue); +}); + +test('baseEncode array test', async () => { + expect(borsh.baseEncode([1, 2, 3, 4, 5])).toEqual('7bWpTW'); +}); + +test('baseDecode test', async () => { + const value = 'HKk9gqNj4xb4rLdJu'; + const expectedDecodedArray = [3, 96, 254, 84, 10, 240, 93, 199, 52, 244, 164, 240, 6]; + const expectedBuffer = new Uint8Array(Buffer.from(expectedDecodedArray)); + expect(borsh.baseDecode(value)).toEqual(expectedBuffer); +}); + +test('base encode and decode test', async () => { + const value = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; + expect(borsh.baseEncode(borsh.baseDecode(value))).toEqual(value); +}); \ No newline at end of file diff --git a/lib/index.d.ts b/lib/index.d.ts index 0d9944c1..c6656d85 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,3 +1,5 @@ import { Schema, DecodeTypes } from './types'; export declare function serialize(schema: Schema, value: unknown): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, classType?: ObjectConstructor): DecodeTypes; +export declare function baseEncode(value: Uint8Array | string): string; +export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/index.js b/lib/index.js index 43be0e37..932083a3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,8 +1,12 @@ "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.deserialize = exports.serialize = void 0; +exports.baseDecode = exports.baseEncode = exports.deserialize = exports.serialize = void 0; const serialize_1 = require("./serialize"); const deserialize_1 = require("./deserialize"); +const bs58_1 = __importDefault(require("bs58")); function serialize(schema, value) { const serializer = new serialize_1.BorshSerializer(); return serializer.encode(value, schema); @@ -13,3 +17,16 @@ function deserialize(schema, buffer, classType) { return deserializer.decode(schema, classType); } exports.deserialize = deserialize; +// Keeping this for compatibility reasons with the old borsh-js +function baseEncode(value) { + if (typeof value === 'string') { + // create a uint8array from string decoding utf-8 without using Buffer + value = new Uint8Array([...value].map((c) => c.charCodeAt(0))); + } + return bs58_1.default.encode(value); +} +exports.baseEncode = baseEncode; +function baseDecode(value) { + return new Uint8Array(bs58_1.default.decode(value)); +} +exports.baseDecode = baseDecode; diff --git a/package.json b/package.json index c5a2446a..dea38087 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "typescript": "^4" }, "dependencies": { - "bn.js": "^5.2.0" + "bn.js": "^5.2.0", + "bs58": "^4.0.0" } } From 019d58ec441bb796d95cf9653f103b6ca46ba585 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 00:10:53 +0200 Subject: [PATCH 19/35] implemented enums and removed deserializing of classes --- borsh-ts/deserialize.ts | 33 +++-- borsh-ts/index.ts | 4 +- borsh-ts/serialize.ts | 19 ++- borsh-ts/test/deserialize.test.js | 220 ++++++++++++++++-------------- borsh-ts/test/serialize.test.js | 109 ++++++++------- borsh-ts/test/utils.test.js | 10 +- borsh-ts/types.ts | 3 +- borsh-ts/utils.ts | 27 +++- lib/deserialize.d.ts | 11 +- lib/deserialize.js | 29 ++-- lib/index.d.ts | 2 +- lib/index.js | 4 +- lib/serialize.d.ts | 3 +- lib/serialize.js | 14 ++ lib/types.d.ts | 5 +- lib/utils.d.ts | 1 + lib/utils.js | 25 +++- 17 files changed, 331 insertions(+), 188 deletions(-) diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index 8d4864f7..f183a1d8 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -1,4 +1,4 @@ -import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers } from './types'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types'; import * as utils from './utils'; import { DecodeBuffer } from './buffer'; import BN from 'bn.js'; @@ -10,12 +10,12 @@ export class BorshDeserializer { this.buffer = new DecodeBuffer(bufferArray); } - decode(schema: Schema, classType?: ObjectConstructor): DecodeTypes { + decode(schema: Schema): DecodeTypes { utils.validate_schema(schema); - return this.decode_value(schema, classType); + return this.decode_value(schema); } - decode_value(schema: Schema, classType?: ObjectConstructor): DecodeTypes { + decode_value(schema: Schema): DecodeTypes { if (typeof schema === 'string') { if (integers.includes(schema)) return this.decode_integer(schema); if (schema === 'string') return this.decode_string(); @@ -24,10 +24,11 @@ export class BorshDeserializer { if (typeof schema === 'object') { if ('option' in schema) return this.decode_option(schema as OptionType); + if ('enum' in schema) return this.decode_enum(schema as EnumType); if ('array' in schema) return this.decode_array(schema as ArrayType); if ('set' in schema) return this.decode_set(schema as SetType); if ('map' in schema) return this.decode_map(schema as MapType); - if ('struct' in schema) return this.decode_struct(schema as StructType, classType); + if ('struct' in schema) return this.decode_struct(schema as StructType); } throw new Error(`Unsupported type: ${schema}`); @@ -39,14 +40,14 @@ export class BorshDeserializer { if (size <= 32 || schema == 'f64') { return this.buffer.consume_value(schema); } - return this.decode_bigint(size); + return this.decode_bigint(size, schema.startsWith('i')); } - decode_bigint(size: number): BN { + decode_bigint(size: number, signed = false): BN { const buffer_len = size / 8; const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); - if (buffer[buffer_len - 1]) { + if (signed && buffer[buffer_len - 1]) { // negative number let carry = 1; for (let i = 0; i < buffer_len; i++) { @@ -81,6 +82,18 @@ export class BorshDeserializer { return null; } + decode_enum(schema: EnumType): DecodeTypes { + const valueIndex = this.buffer.consume_value('u8'); + + if (valueIndex > schema.enum.length) { + throw new Error(`Enum option ${valueIndex} is not available`); + } + + const struct = schema.enum[valueIndex].struct; + const key = Object.keys(struct)[0]; + return { [key]: this.decode_value(struct[key]) }; + } + decode_array(schema: ArrayType): Array { const result = []; const len = schema.array.len ? schema.array.len : this.decode_integer('u32') as number; @@ -112,11 +125,11 @@ export class BorshDeserializer { return result; } - decode_struct(schema: StructType, classType?: ObjectConstructor): object { + decode_struct(schema: StructType): object { const result = {}; for (const key in schema.struct) { result[key] = this.decode_value(schema.struct[key]); } - return classType ? new classType(result) : result; + return result; } } \ No newline at end of file diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index d0799655..02db4f27 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -8,9 +8,9 @@ export function serialize(schema: Schema, value: unknown): Uint8Array { return serializer.encode(value, schema); } -export function deserialize(schema: Schema, buffer: Uint8Array, classType?: ObjectConstructor): DecodeTypes { +export function deserialize(schema: Schema, buffer: Uint8Array): DecodeTypes { const deserializer = new BorshDeserializer(buffer); - return deserializer.decode(schema, classType); + return deserializer.decode(schema); } // Keeping this for compatibility reasons with the old borsh-js diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index e490a9f7..6a7af256 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -1,4 +1,4 @@ -import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers } from './types'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types'; import { EncodeBuffer } from './buffer'; import BN from 'bn.js'; import * as utils from './utils'; @@ -21,6 +21,7 @@ export class BorshSerializer { if (typeof schema === 'object') { if ('option' in schema) return this.encode_option(value, schema as OptionType); + if ('enum' in schema) return this.encode_enum(value, schema as EnumType); if ('array' in schema) return this.encode_array(value, schema as ArrayType); if ('set' in schema) return this.encode_set(value, schema as SetType); if ('map' in schema) return this.encode_map(value, schema as MapType); @@ -84,6 +85,22 @@ export class BorshSerializer { } } + encode_enum(value: unknown, schema: EnumType): void { + utils.expect_enum(value); + + const valueKey = Object.keys(value)[0]; + + for (let i = 0; i < schema.enum.length; i++) { + const valueSchema = schema.enum[i] as StructType; + + if (valueKey === Object.keys(valueSchema.struct)[0]) { + this.encoded.store_value(i, 'u8'); + return this.encode_struct(value, valueSchema as StructType); + } + } + throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)}`); + } + encode_array(value: unknown, schema: ArrayType): void { if (utils.isArrayLike(value)) return this.encode_arraylike(value as ArrayLike, schema); if (value instanceof ArrayBuffer) return this.encode_buffer(value, schema); diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js index c194e917..89742292 100644 --- a/borsh-ts/test/deserialize.test.js +++ b/borsh-ts/test/deserialize.test.js @@ -1,16 +1,16 @@ const borsh = require('../../lib/index'); const BN = require('bn.js'); -function check_decode(expected, schema, buffer, classType) { - const decoded = borsh.deserialize(schema, buffer, classType); +function check_decode(expected, schema, buffer) { + const decoded = borsh.deserialize(schema, buffer); if (expected && typeof (expected) === 'object' && 'eq' in expected) { return expect(expected.eq(decoded)).toBe(true); - } + } if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); - expect(decoded).toEqual(expected); + expect(JSON.stringify(decoded)).toEqual(JSON.stringify(expected)); } test('deserialize integers', async () => { @@ -67,121 +67,141 @@ test('deserialize sets', async () => { check_decode(new Set([1, 2]), { set: 'u8' }, [2, 0, 0, 0, 1, 2]); }); -test('deserialize struct', async () => { - const numbers = new Numbers(); - const schema = { - struct: { - u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' - } - }; - check_decode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64], Numbers); - - check_decode(new Map([['a', 1], ['b', 2]]), { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); +test('serialize struct', async () => { + check_decode(Numbers, numberSchema, expectedNumbers); - const options = new Options(); const schemaOpt = { struct: { - u32: {option: 'u32'}, option: {option: 'string'}, u8: {option: 'u8'} + u32: { option: 'u32' }, option: { option: 'string' }, u8: { option: 'u8' } } }; - check_decode(options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1], Options); -}); + check_decode(Options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1]); -test('deserialize complex structures', async () => { - // computed by hand i32, u32, u64val, i64val, B, - const buffer = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, - // string, u8array, - 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, - // Array> - 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, - // u32Arr, i32Arr, u128, - 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // Array, // u64Arr - 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; - - const schema = { - struct: { - foo: 'u32', - bar: 'i32', - u64Val: 'u64', - i64Val: 'i64', - flag: 'bool', - baz: 'string', - uint8array: { array: { type: 'u8', len: 2 } }, - arr: { array: { type: { array: { type: 'string' } } } }, - u32Arr: { array: { type: 'u32' } }, - i32Arr: { array: { type: 'i32' } }, - u128Val: 'u128', - uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, - u64Arr: { array: { type: 'u64' } }, - } - }; + check_decode(Mixture, mixtureSchema, expectedMixture); +}); - check_decode(new MixtureTwo(), schema, buffer, MixtureTwo); +test('serialize enums', async () => { + const enumSchema = { enum: [{ struct: { numbers: numberSchema }}, { struct: { mixture: mixtureSchema } }] }; + check_decode(MyEnumNumbers, enumSchema, [0].concat(expectedNumbers)); + check_decode(MyEnumMixture, enumSchema, [1].concat(expectedMixture)); }); -test('deserializes big structs', async () => { - const bigStruct = new BigStruct(); +test('serializes big structs', async () => { const schema = { struct: { u64: 'u64', u128: 'u128', - arr: { array: { type: 'u8', len: 1000 } } + arr: { array: { type: 'u8', len: 254 } } } }; - const buffer = Array(24).fill(255).concat([...Array(1000).keys()].map(x => x*2)); + const buffer = (Array(24).fill(255)).concat([...Array(254).keys()]); - check_decode(bigStruct, schema, buffer, BigStruct); + check_decode(BigStruct, schema, buffer); }); -// Aux structures -class Numbers { - u8 = 1; - u16 = 2; - u32 = 3; - u64 = new BN(4); - u128 = new BN(5); - i8 = -1; - i16 = -2; - i32 = -3; - i64 = new BN(-4); - f32 = 6.0; - f64 = 7.1; -} - -class Options { - u32 = 2; - option = null; - u8 = 1; -} - -class MixtureTwo { - foo = 321; - bar = 123; - u64Val = new BN('4294967297'); - i64Val = new BN(-64); - flag = true; - baz = 'testing'; - uint8array = new Uint8Array([240, 241]); - arr = [['testing'], ['testing']]; - u32Arr = [21, 11]; - i32Arr = []; - u128Val = new BN(128); - uint8arrays = [this.uint8array, this.uint8array]; - u64Arr = [new BN('10000000000'), new BN('100000000000')]; -} - -const u64MaxHex = 'ffffffffffffffff'; +test('serializes nested structs', async () => { + const Nested = { + a: { sa: { n: 1 } }, + b: 2, + c: 3, + }; + + const schema = { + struct: { a: { struct: { sa: { struct: { n: 'u8' } } } }, b: 'u8', c: 'u8' } + }; -class BigStruct { - u64 = new BN(u64MaxHex, 16); - u128 = new BN(u64MaxHex.repeat(2), 16); - arr = []; + const buffer = [1, 2, 3]; + check_decode(Nested, schema, buffer); +}); - constructor() { - for (let i = 0; i < 1000; i++) { - this.arr.push(i*2); - } +// Complex number structure +const Numbers = { + u8: 1, + u16: 2, + u32: 3, + u64: new BN(4), + u128: new BN(5), + i8: -1, + i16: -2, + i32: -3, + i64: new BN(-4), + f32: 6.0, + f64: 7.1, +}; + +const numberSchema = { + struct: { + u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' } -} +}; + +const expectedNumbers = [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]; + +// Options +const Options = { + u32: 2, + option: null, + u8: 1, +}; + +// Complex mixture of types +const Mixture = { + foo: 321, + bar: 123, + u64Val: new BN('4294967297'), + i64Val: new BN(-64), + flag: true, + baz: 'testing', + uint8array: [240, 241], + arr: [['testing'], ['testing']], + u32Arr: [21, 11], + i32Arr: [], + u128Val: new BN(128), + uint8arrays: [[240, 241], [240, 241]], + u64Arr: [new BN('10000000000'), new BN('100000000000')], +}; + +const mixtureSchema = { + struct: { + foo: 'u32', + bar: 'i32', + u64Val: 'u64', + i64Val: 'i64', + flag: 'bool', + baz: 'string', + uint8array: { array: { type: 'u8', len: 2 } }, + arr: { array: { type: { array: { type: 'string' } } } }, + u32Arr: { array: { type: 'u32' } }, + i32Arr: { array: { type: 'i32' } }, + u128Val: 'u128', + uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, + u64Arr: { array: { type: 'u64' } }, + } +}; + +// computed by hand i32, u32, u64val, i64val, B, +const expectedMixture = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, + // string, u8array, + 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, + // Array> + 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, + // u32Arr, i32Arr, u128, + 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // Array, // u64Arr + 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; + +// A structure of big nums +const BigStruct = { + u64: new BN('ffffffffffffffff', 'hex'), + u128: new BN('ffffffffffffffff'.repeat(2), 'hex'), + arr: [...Array(254).keys()], +}; + +const MyEnumNumbers = { + numbers: Numbers +}; + +const MyEnumMixture = { + mixture: Mixture +}; \ No newline at end of file diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js index fcc61da6..be3ba12c 100644 --- a/borsh-ts/test/serialize.test.js +++ b/borsh-ts/test/serialize.test.js @@ -66,55 +66,24 @@ test('serialize sets', async () => { test('serialize struct', async () => { const numbers = new Numbers(); - const schema = { - struct: { - u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' - } - }; - check_encode(numbers, schema, [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]); + check_encode(numbers, numberSchema, expectedNumbers); const options = new Options(); const schemaOpt = { struct: { - u32: {option: 'u32'}, option: {option: 'string'}, u8: {option: 'u8'} + u32: { option: 'u32' }, option: { option: 'string' }, u8: { option: 'u8' } } }; check_encode(options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1]); -}); - -test('serialize complex structures', async () => { - // computed by hand i32, u32, u64val, i64val, B, - const expected = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, - // string, u8array, - 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, - // Array> - 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, - // u32Arr, i32Arr, u128, - 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // Array, // u64Arr - 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; - const mixture = new MixtureTwo(); - - const schema = { - struct: { - foo: 'u32', - bar: 'i32', - u64Val: 'u64', - i64Val: 'i64', - flag: 'bool', - baz: 'string', - uint8array: { array: { type: 'u8', len: 2 } }, - arr: { array: { type: { array: { type: 'string' } } } }, - u32Arr: { array: { type: 'u32' } }, - i32Arr: { array: { type: 'i32' } }, - u128Val: 'u128', - uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, - u64Arr: { array: { type: 'u64' } }, - } - }; + const mixture = new Mixture(); + check_encode(mixture, mixtureSchema, expectedMixture); +}); - check_encode(mixture, schema, expected); +test('serialize enums', async () => { + const enumSchema = { enum: [{ struct: { Numbers: numberSchema }, construct: Numbers }, { struct: { Mixture: mixtureSchema }, construct: Mixture }] }; + check_encode(new MyEnum({ numbers: new Numbers() }), enumSchema, [0].concat(expectedNumbers)); + check_encode(new MyEnum({ mixture: new Mixture() }), enumSchema, [1].concat(expectedMixture)); }); test('serializes big structs', async () => { @@ -123,16 +92,16 @@ test('serializes big structs', async () => { struct: { u64: 'u64', u128: 'u128', - arr: { array: { type: 'u8', len: 1000 } } + arr: { array: { type: 'u8', len: 254 } } } }; - const expected = Array(24).fill(255).concat([...Array(1000).keys()].map(x => x * 2)); + const expected = Array(24).fill(255).concat([...Array(254).keys()]); check_encode(bigStruct, schema, expected); }); -// Aux structures +// Complex number structure class Numbers { u8 = 1; u16 = 2; @@ -147,13 +116,23 @@ class Numbers { f64 = 7.1; } +const numberSchema = { + struct: { + u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' + } +}; + +const expectedNumbers = [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]; + +// Options class Options { u32 = 2; option = null; u8 = 1; } -class MixtureTwo { +// Complex mixture of types +class Mixture { foo = 321; bar = 123; u64Val = new BN('4294967297'); @@ -169,6 +148,37 @@ class MixtureTwo { u64Arr = [new BN('10000000000'), new BN('100000000000')]; } +const mixtureSchema = { + struct: { + foo: 'u32', + bar: 'i32', + u64Val: 'u64', + i64Val: 'i64', + flag: 'bool', + baz: 'string', + uint8array: { array: { type: 'u8', len: 2 } }, + arr: { array: { type: { array: { type: 'string' } } } }, + u32Arr: { array: { type: 'u32' } }, + i32Arr: { array: { type: 'i32' } }, + u128Val: 'u128', + uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, + u64Arr: { array: { type: 'u64' } }, + } +}; + +// computed by hand i32, u32, u64val, i64val, B, +const expectedMixture = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, + // string, u8array, + 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, + // Array> + 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, + // u32Arr, i32Arr, u128, + 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // Array, // u64Arr + 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; + + +// A structure of big nums const u64MaxHex = 'ffffffffffffffff'; class BigStruct { @@ -177,8 +187,15 @@ class BigStruct { arr = []; constructor() { - for (let i = 0; i < 1000; i++) { - this.arr.push(i * 2); + for (let i = 0; i < 254; i++) { + this.arr.push(i); } } } + +class MyEnum { + constructor({ numbers, mixture }) { + if (numbers) this.Numbers = numbers; + if (mixture) this.Mixture = mixture; + } +} \ No newline at end of file diff --git a/borsh-ts/test/utils.test.js b/borsh-ts/test/utils.test.js index a744691b..64892c0b 100644 --- a/borsh-ts/test/utils.test.js +++ b/borsh-ts/test/utils.test.js @@ -26,12 +26,12 @@ test('rejects invalid schemes', async () => { expect(() => utils.validate_schema(array)).toThrow('Invalid schema: "u8" expected { type, len? }'); expect(() => utils.validate_schema(arrayFixed)).toThrow('Invalid schema: {"len":2} expected { type, len? }'); - expect(() => utils.validate_schema(set)).toThrow('Invalid schema: {"type":"u8"} expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(set)).toThrow('Invalid schema: {"type":"u8"} expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); expect(() => utils.validate_schema(map)).toThrow('Invalid schema: {"value":"u8"} expected { key, value }'); - expect(() => utils.validate_schema(option)).toThrow('Invalid schema: null expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); - expect(() => utils.validate_schema(struct)).toThrow('Invalid schema: {"len":2} expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); - expect(() => utils.validate_schema(noStruct)).toThrow('Invalid schema: {"u8":"u8"} expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); - expect(() => utils.validate_schema('u7')).toThrow('Invalid schema: "u7" expected option, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(option)).toThrow('Invalid schema: null expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(struct)).toThrow('Invalid schema: {"len":2} expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(noStruct)).toThrow('Invalid schema: {"u8":"u8"} expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema('u7')).toThrow('Invalid schema: "u7" expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); expect(() => utils.validate_schema(Array)).toThrow(); expect(() => utils.validate_schema(Map)).toThrow(); }); \ No newline at end of file diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts index 81c682fc..e4d663ad 100644 --- a/borsh-ts/types.ts +++ b/borsh-ts/types.ts @@ -8,10 +8,11 @@ export type StringType = 'string'; export type OptionType = { option: Schema }; export type ArrayType = { array: { type: Schema, len?: number } }; +export type EnumType = { enum: Array }; export type SetType = { set: Schema }; export type MapType = { map: { key: Schema, value: Schema } }; export type StructType = { struct: { [key: string]: Schema } }; export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; // returned -export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 387a6bc0..b00af77c 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -1,5 +1,5 @@ import BN from 'bn.js'; -import { Schema, integers } from './types'; +import { Schema, StructType, integers } from './types'; export function isArrayLike(value: unknown): boolean { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like @@ -35,9 +35,15 @@ export function expect_same_size(length: number, expected: number): void { } } +export function expect_enum(value: unknown): void { + if(typeof (value) !== 'object' || value === null ) { + throw new Error(`Expected object not ${typeof (value)}(${value})`); + } +} + // Validate Schema const VALID_STRING_TYPES = integers.concat(['bool', 'string']); -const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; +const VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct']; export class ErrorSchema extends Error { constructor(schema: Schema, expected: string) { @@ -58,6 +64,7 @@ export function validate_schema(schema: Schema): void { const key = keys[0]; if (key === 'option') return validate_schema(schema[key]); + if (key === 'enum') return validate_enum_schema(schema[key]); if (key === 'array') return validate_array_schema(schema[key]); if (key === 'set') return validate_schema(schema[key]); if (key === 'map') return validate_map_schema(schema[key]); @@ -67,6 +74,22 @@ export function validate_schema(schema: Schema): void { throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(', ') + ' or ' + VALID_STRING_TYPES.join(', ')); } +function validate_enum_schema(schema: Array): void { + if (!Array.isArray(schema)) throw new ErrorSchema(schema, 'Array'); + + for (const sch of schema) { + if (typeof sch !== 'object' || !('struct' in sch)) { + throw new Error('Missing "struct" key in enum schema'); + } + + if (typeof sch.struct !== 'object' || Object.keys(sch.struct).length !== 1) { + throw new Error('The "struct" key in enum schema must be an object with a single key'); + } + + validate_schema({struct: sch.struct}); + } +} + function validate_array_schema(schema: { type: Schema, len?: number }): void { if (typeof schema !== 'object') throw new ErrorSchema(schema, '{ type, len? }'); diff --git a/lib/deserialize.d.ts b/lib/deserialize.d.ts index 5bb4da68..bcc1e07f 100644 --- a/lib/deserialize.d.ts +++ b/lib/deserialize.d.ts @@ -1,18 +1,19 @@ -import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType } from './types'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; import { DecodeBuffer } from './buffer'; import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; constructor(bufferArray: Uint8Array); - decode(schema: Schema, classType?: ObjectConstructor): DecodeTypes; - decode_value(schema: Schema, classType?: ObjectConstructor): DecodeTypes; + decode(schema: Schema): DecodeTypes; + decode_value(schema: Schema): DecodeTypes; decode_integer(schema: IntegerType): number | BN; - decode_bigint(size: number): BN; + decode_bigint(size: number, signed?: boolean): BN; decode_string(): string; decode_boolean(): boolean; decode_option(schema: OptionType): DecodeTypes; + decode_enum(schema: EnumType): DecodeTypes; decode_array(schema: ArrayType): Array; decode_set(schema: SetType): Set; decode_map(schema: MapType): Map; - decode_struct(schema: StructType, classType?: ObjectConstructor): object; + decode_struct(schema: StructType): object; } diff --git a/lib/deserialize.js b/lib/deserialize.js index ad59e49a..91c0f623 100644 --- a/lib/deserialize.js +++ b/lib/deserialize.js @@ -36,11 +36,11 @@ class BorshDeserializer { constructor(bufferArray) { this.buffer = new buffer_1.DecodeBuffer(bufferArray); } - decode(schema, classType) { + decode(schema) { utils.validate_schema(schema); - return this.decode_value(schema, classType); + return this.decode_value(schema); } - decode_value(schema, classType) { + decode_value(schema) { if (typeof schema === 'string') { if (types_1.integers.includes(schema)) return this.decode_integer(schema); @@ -52,6 +52,8 @@ class BorshDeserializer { if (typeof schema === 'object') { if ('option' in schema) return this.decode_option(schema); + if ('enum' in schema) + return this.decode_enum(schema); if ('array' in schema) return this.decode_array(schema); if ('set' in schema) @@ -59,7 +61,7 @@ class BorshDeserializer { if ('map' in schema) return this.decode_map(schema); if ('struct' in schema) - return this.decode_struct(schema, classType); + return this.decode_struct(schema); } throw new Error(`Unsupported type: ${schema}`); } @@ -68,12 +70,12 @@ class BorshDeserializer { if (size <= 32 || schema == 'f64') { return this.buffer.consume_value(schema); } - return this.decode_bigint(size); + return this.decode_bigint(size, schema.startsWith('i')); } - decode_bigint(size) { + decode_bigint(size, signed = false) { const buffer_len = size / 8; const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); - if (buffer[buffer_len - 1]) { + if (signed && buffer[buffer_len - 1]) { // negative number let carry = 1; for (let i = 0; i < buffer_len; i++) { @@ -103,6 +105,15 @@ class BorshDeserializer { } return null; } + decode_enum(schema) { + const valueIndex = this.buffer.consume_value('u8'); + if (valueIndex > schema.enum.length) { + throw new Error(`Enum option ${valueIndex} is not available`); + } + const struct = schema.enum[valueIndex].struct; + const key = Object.keys(struct)[0]; + return { [key]: this.decode_value(struct[key]) }; + } decode_array(schema) { const result = []; const len = schema.array.len ? schema.array.len : this.decode_integer('u32'); @@ -129,12 +140,12 @@ class BorshDeserializer { } return result; } - decode_struct(schema, classType) { + decode_struct(schema) { const result = {}; for (const key in schema.struct) { result[key] = this.decode_value(schema.struct[key]); } - return classType ? new classType(result) : result; + return result; } } exports.BorshDeserializer = BorshDeserializer; diff --git a/lib/index.d.ts b/lib/index.d.ts index c6656d85..ee889bcd 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,5 +1,5 @@ import { Schema, DecodeTypes } from './types'; export declare function serialize(schema: Schema, value: unknown): Uint8Array; -export declare function deserialize(schema: Schema, buffer: Uint8Array, classType?: ObjectConstructor): DecodeTypes; +export declare function deserialize(schema: Schema, buffer: Uint8Array): DecodeTypes; export declare function baseEncode(value: Uint8Array | string): string; export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/index.js b/lib/index.js index 932083a3..1b6397aa 100644 --- a/lib/index.js +++ b/lib/index.js @@ -12,9 +12,9 @@ function serialize(schema, value) { return serializer.encode(value, schema); } exports.serialize = serialize; -function deserialize(schema, buffer, classType) { +function deserialize(schema, buffer) { const deserializer = new deserialize_1.BorshDeserializer(buffer); - return deserializer.decode(schema, classType); + return deserializer.decode(schema); } exports.deserialize = deserialize; // Keeping this for compatibility reasons with the old borsh-js diff --git a/lib/serialize.d.ts b/lib/serialize.d.ts index 1467ba2c..fab59d33 100644 --- a/lib/serialize.d.ts +++ b/lib/serialize.d.ts @@ -1,4 +1,4 @@ -import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType } from './types'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; import { EncodeBuffer } from './buffer'; import BN from 'bn.js'; export declare class BorshSerializer { @@ -10,6 +10,7 @@ export declare class BorshSerializer { encode_string(value: unknown): void; encode_boolean(value: unknown): void; encode_option(value: unknown, schema: OptionType): void; + encode_enum(value: unknown, schema: EnumType): void; encode_array(value: unknown, schema: ArrayType): void; encode_arraylike(value: ArrayLike, schema: ArrayType): void; encode_buffer(value: ArrayBuffer, schema: ArrayType): void; diff --git a/lib/serialize.js b/lib/serialize.js index b398d512..10763ebc 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -50,6 +50,8 @@ class BorshSerializer { if (typeof schema === 'object') { if ('option' in schema) return this.encode_option(value, schema); + if ('enum' in schema) + return this.encode_enum(value, schema); if ('array' in schema) return this.encode_array(value, schema); if ('set' in schema) @@ -108,6 +110,18 @@ class BorshSerializer { this.encode_value(value, schema.option); } } + encode_enum(value, schema) { + utils.expect_enum(value); + const valueKey = Object.keys(value)[0]; + for (let i = 0; i < schema.enum.length; i++) { + const valueSchema = schema.enum[i]; + if (valueKey === Object.keys(valueSchema.struct)[0]) { + this.encoded.store_value(i, 'u8'); + return this.encode_struct(value, valueSchema); + } + } + throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)}`); + } encode_array(value, schema) { if (utils.isArrayLike(value)) return this.encode_arraylike(value, schema); diff --git a/lib/types.d.ts b/lib/types.d.ts index 7e98641f..2bb62f72 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -12,6 +12,9 @@ export type ArrayType = { len?: number; }; }; +export type EnumType = { + enum: Array; +}; export type SetType = { set: Schema; }; @@ -27,4 +30,4 @@ export type StructType = { }; }; export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; -export type DecodeTypes = number | BN | string | boolean | Array | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/lib/utils.d.ts b/lib/utils.d.ts index 2e3d10ed..6b189185 100644 --- a/lib/utils.d.ts +++ b/lib/utils.d.ts @@ -3,6 +3,7 @@ export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string): void; export declare function expect_BN(value: unknown): void; export declare function expect_same_size(length: number, expected: number): void; +export declare function expect_enum(value: unknown): void; export declare class ErrorSchema extends Error { constructor(schema: Schema, expected: string); } diff --git a/lib/utils.js b/lib/utils.js index a4fa13a1..7ec80a34 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.validate_schema = exports.ErrorSchema = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; +exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; const bn_js_1 = __importDefault(require("bn.js")); const types_1 = require("./types"); function isArrayLike(value) { @@ -36,9 +36,15 @@ function expect_same_size(length, expected) { } } exports.expect_same_size = expect_same_size; +function expect_enum(value) { + if (typeof (value) !== 'object' || value === null) { + throw new Error(`Expected object not ${typeof (value)}(${value})`); + } +} +exports.expect_enum = expect_enum; // Validate Schema const VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']); -const VALID_OBJECT_KEYS = ['option', 'array', 'set', 'map', 'struct']; +const VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct']; class ErrorSchema extends Error { constructor(schema, expected) { const message = `Invalid schema: ${JSON.stringify(schema)} expected ${expected}`; @@ -56,6 +62,8 @@ function validate_schema(schema) { const key = keys[0]; if (key === 'option') return validate_schema(schema[key]); + if (key === 'enum') + return validate_enum_schema(schema[key]); if (key === 'array') return validate_array_schema(schema[key]); if (key === 'set') @@ -69,6 +77,19 @@ function validate_schema(schema) { throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(', ') + ' or ' + VALID_STRING_TYPES.join(', ')); } exports.validate_schema = validate_schema; +function validate_enum_schema(schema) { + if (!Array.isArray(schema)) + throw new ErrorSchema(schema, 'Array'); + for (const sch of schema) { + if (typeof sch !== 'object' || !('struct' in sch)) { + throw new Error('Missing "struct" key in enum schema'); + } + if (typeof sch.struct !== 'object' || Object.keys(sch.struct).length !== 1) { + throw new Error('The "struct" key in enum schema must be an object with a single key'); + } + validate_schema({ struct: sch.struct }); + } +} function validate_array_schema(schema) { if (typeof schema !== 'object') throw new ErrorSchema(schema, '{ type, len? }'); From b50bc1f989f442eb06a6dbee9b8be53a90ee5b61 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 11:19:23 +0200 Subject: [PATCH 20/35] better organized testing --- borsh-ts/test/(de)serialize.test.js | 110 +++++++++++++++ borsh-ts/test/.eslintrc.yml | 5 + borsh-ts/test/deserialize.test.js | 207 ---------------------------- borsh-ts/test/serialize.test.js | 201 --------------------------- borsh-ts/test/structures.js | 138 +++++++++++++++++++ borsh-ts/test/utils.test.js | 9 +- borsh-ts/utils.ts | 2 +- lib/utils.js | 2 +- 8 files changed, 263 insertions(+), 411 deletions(-) create mode 100644 borsh-ts/test/(de)serialize.test.js delete mode 100644 borsh-ts/test/deserialize.test.js delete mode 100644 borsh-ts/test/serialize.test.js create mode 100644 borsh-ts/test/structures.js diff --git a/borsh-ts/test/(de)serialize.test.js b/borsh-ts/test/(de)serialize.test.js new file mode 100644 index 00000000..8b33287b --- /dev/null +++ b/borsh-ts/test/(de)serialize.test.js @@ -0,0 +1,110 @@ +const BN = require('bn.js'); +const borsh = require('../../lib/index'); +const testStructures = require('./structures'); + +function check_encode(value, schema, expected) { + const encoded = borsh.serialize(schema, value); + expect(encoded).toEqual(Uint8Array.from(expected)); +} + +function check_decode(expected, schema, encoded) { + const decoded = borsh.deserialize(schema, encoded); + + // console.log(decoded, expected); // visual inspection + + if (expected && typeof (expected) === 'object' && 'eq' in expected) { + return expect(expected.eq(decoded)).toBe(true); + } + + if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); + + if(expected instanceof Map || expected instanceof Set || expected instanceof Array) { + return expect(decoded).toEqual(expected); + } + + expect(JSON.stringify(decoded)).toEqual(JSON.stringify(expected)); +} + +function check_roundtrip(value, schema, encoded) { + check_encode(value, schema, encoded); + check_decode(value, schema, encoded); +} + +test('serialize integers', async () => { + check_roundtrip(100, 'u8', [100]); + check_roundtrip(258, 'u16', [2, 1]); + check_roundtrip(102, 'u32', [102, 0, 0, 0]); + check_roundtrip(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); + check_roundtrip(new BN(104), 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + check_roundtrip(-100, 'i8', [156]); + check_roundtrip(-258, 'i16', [254, 254]); + check_roundtrip(-102, 'i32', [154, 255, 255, 255]); + check_roundtrip(new BN(-103), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); + check_roundtrip(new BN(-104), 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); +}); + +test('serialize booleans', async () => { + check_roundtrip(true, 'bool', [1]); + check_roundtrip(false, 'bool', [0]); +}); + +test('serialize strings', async () => { + check_roundtrip('h"i', 'string', [3, 0, 0, 0, 104, 34, 105]); +}); + +test('serialize floats', async () => { + check_roundtrip(7.23, 'f64', [236, 81, 184, 30, 133, 235, 28, 64]); + check_roundtrip(7.23, 'f32', [41, 92, 231, 64]); + check_roundtrip(10e2, 'f32', [0, 0, 122, 68]); + check_roundtrip(10e2, 'f64', [0, 0, 0, 0, 0, 64, 143, 64]); +}); + +test('serialize arrays', async () => { + check_roundtrip([true, false], { array: { type: 'bool' } }, [2, 0, 0, 0, 1, 0]); + check_roundtrip([true, false], { array: { type: 'bool', len: 2 } }, [1, 0]); + check_encode(new ArrayBuffer(2), { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); + + const buffer = new ArrayBuffer(2); + new Uint8Array(buffer).set([1, 2]); + check_encode(buffer, { array: { type: 'u8', len: 2 } }, [1, 2]); +}); + +test('serialize options', async () => { + check_roundtrip(null, { option: 'u8' }, [0]); + check_roundtrip(1, { option: 'u32' }, [1, 1, 0, 0, 0]); +}); + +test('serialize maps', async () => { + check_roundtrip(new Map(), { map: { key: 'u8', value: 'u8' } }, [0, 0, 0, 0]); + + const map = new Map(); + map.set('testing', 1); + check_roundtrip(map, { map: { key: 'string', value: 'u32' } }, [1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0]); + + check_encode({ 'a': 1, 'b': 2 }, { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); +}); + +test('serialize sets', async () => { + check_roundtrip(new Set(), { set: 'u8' }, [0, 0, 0, 0]); + check_roundtrip(new Set([1, 2]), { set: 'u8' }, [2, 0, 0, 0, 1, 2]); +}); + +test('serialize struct', async () => { + check_roundtrip(testStructures.Numbers, testStructures.schemaNumbers, testStructures.encodedNumbers); + check_roundtrip(testStructures.Options, testStructures.schemaOptions, testStructures.encodedOptions); + check_roundtrip(testStructures.Nested, testStructures.schemaNested, testStructures.encodedNested); + check_roundtrip(testStructures.Mixture, testStructures.schemaMixture, testStructures.encodedMixture); + check_roundtrip(testStructures.BigStruct, testStructures.schemaBigStruct, testStructures.encodedBigStruct); +}); + +test('serialize enums', async () => { + const MyEnumNumbers = { numbers: testStructures.Numbers }; + const MyEnumMixture = { mixture: testStructures.Mixture }; + + const enumSchema = { + enum: [{ struct: { numbers: testStructures.schemaNumbers } }, { struct: { mixture: testStructures.schemaMixture } }] + }; + + check_roundtrip(MyEnumNumbers, enumSchema, [0].concat(testStructures.encodedNumbers)); + check_roundtrip(MyEnumMixture, enumSchema, [1].concat(testStructures.encodedMixture)); +}); \ No newline at end of file diff --git a/borsh-ts/test/.eslintrc.yml b/borsh-ts/test/.eslintrc.yml index ab46369c..0c383946 100644 --- a/borsh-ts/test/.eslintrc.yml +++ b/borsh-ts/test/.eslintrc.yml @@ -1,3 +1,8 @@ extends: '../../.eslintrc.yml' env: jest: true +rules: + no-inner-declarations: 1 + '@typescript-eslint/no-explicit-any': 1 + '@typescript-eslint/explicit-function-return-type': 0 + '@typescript-eslint/no-var-requires': 0 diff --git a/borsh-ts/test/deserialize.test.js b/borsh-ts/test/deserialize.test.js deleted file mode 100644 index 89742292..00000000 --- a/borsh-ts/test/deserialize.test.js +++ /dev/null @@ -1,207 +0,0 @@ -const borsh = require('../../lib/index'); -const BN = require('bn.js'); - -function check_decode(expected, schema, buffer) { - const decoded = borsh.deserialize(schema, buffer); - - if (expected && typeof (expected) === 'object' && 'eq' in expected) { - return expect(expected.eq(decoded)).toBe(true); - } - - if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); - - expect(JSON.stringify(decoded)).toEqual(JSON.stringify(expected)); -} - -test('deserialize integers', async () => { - check_decode(100, 'u8', [100]); - check_decode(258, 'u16', [2, 1]); - check_decode(102, 'u32', [102, 0, 0, 0]); - check_decode(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); - check_decode(new BN(104), 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - check_decode(-100, 'i8', [156]); - check_decode(-258, 'i16', [254, 254]); - check_decode(-102, 'i32', [154, 255, 255, 255]); - check_decode(new BN(-103), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); - check_decode(new BN(-104), 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); -}); - -test('deserialize booleans', async () => { - check_decode(true, 'bool', [1]); - check_decode(false, 'bool', [0]); -}); - -test('deserialize strings', async () => { - check_decode('h"i', 'string', [3, 0, 0, 0, 104, 34, 105]); -}); - -test('deserialize floats', async () => { - check_decode(7.23, 'f64', [236, 81, 184, 30, 133, 235, 28, 64]); - check_decode(7.23, 'f32', [41, 92, 231, 64]); - check_decode(10e2, 'f32', [0, 0, 122, 68]); - check_decode(10e2, 'f64', [0, 0, 0, 0, 0, 64, 143, 64]); -}); - -test('deserialize arrays', async () => { - check_decode([true, false], { array: { type: 'bool' } }, [2, 0, 0, 0, 1, 0]); - check_decode([true, false], { array: { type: 'bool', len: 2 } }, [1, 0]); - check_decode([0, 0], { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); - check_decode([1, 2, 3], { array: { type: 'u8', len: 3 } }, [1, 2, 3]); -}); - -test('deserialize options', async () => { - check_decode(null, { option: 'u8' }, [0]); - check_decode(1, { option: 'u32' }, [1, 1, 0, 0, 0]); -}); - -test('deserialize maps', async () => { - check_decode(new Map(), { map: { key: 'u8', value: 'u8' } }, [0, 0, 0, 0]); - - const map = new Map(); - map.set('testing', 1); - check_decode(map, { map: { key: 'string', value: 'u32' } }, [1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0]); -}); - -test('deserialize sets', async () => { - check_decode(new Set(), { set: 'u8' }, [0, 0, 0, 0]); - check_decode(new Set([1, 2]), { set: 'u8' }, [2, 0, 0, 0, 1, 2]); -}); - -test('serialize struct', async () => { - check_decode(Numbers, numberSchema, expectedNumbers); - - const schemaOpt = { - struct: { - u32: { option: 'u32' }, option: { option: 'string' }, u8: { option: 'u8' } - } - }; - check_decode(Options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1]); - - check_decode(Mixture, mixtureSchema, expectedMixture); -}); - -test('serialize enums', async () => { - const enumSchema = { enum: [{ struct: { numbers: numberSchema }}, { struct: { mixture: mixtureSchema } }] }; - check_decode(MyEnumNumbers, enumSchema, [0].concat(expectedNumbers)); - check_decode(MyEnumMixture, enumSchema, [1].concat(expectedMixture)); -}); - -test('serializes big structs', async () => { - const schema = { - struct: { - u64: 'u64', - u128: 'u128', - arr: { array: { type: 'u8', len: 254 } } - } - }; - - const buffer = (Array(24).fill(255)).concat([...Array(254).keys()]); - - check_decode(BigStruct, schema, buffer); -}); - -test('serializes nested structs', async () => { - const Nested = { - a: { sa: { n: 1 } }, - b: 2, - c: 3, - }; - - const schema = { - struct: { a: { struct: { sa: { struct: { n: 'u8' } } } }, b: 'u8', c: 'u8' } - }; - - const buffer = [1, 2, 3]; - check_decode(Nested, schema, buffer); -}); - -// Complex number structure -const Numbers = { - u8: 1, - u16: 2, - u32: 3, - u64: new BN(4), - u128: new BN(5), - i8: -1, - i16: -2, - i32: -3, - i64: new BN(-4), - f32: 6.0, - f64: 7.1, -}; - -const numberSchema = { - struct: { - u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' - } -}; - -const expectedNumbers = [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]; - -// Options -const Options = { - u32: 2, - option: null, - u8: 1, -}; - -// Complex mixture of types -const Mixture = { - foo: 321, - bar: 123, - u64Val: new BN('4294967297'), - i64Val: new BN(-64), - flag: true, - baz: 'testing', - uint8array: [240, 241], - arr: [['testing'], ['testing']], - u32Arr: [21, 11], - i32Arr: [], - u128Val: new BN(128), - uint8arrays: [[240, 241], [240, 241]], - u64Arr: [new BN('10000000000'), new BN('100000000000')], -}; - -const mixtureSchema = { - struct: { - foo: 'u32', - bar: 'i32', - u64Val: 'u64', - i64Val: 'i64', - flag: 'bool', - baz: 'string', - uint8array: { array: { type: 'u8', len: 2 } }, - arr: { array: { type: { array: { type: 'string' } } } }, - u32Arr: { array: { type: 'u32' } }, - i32Arr: { array: { type: 'i32' } }, - u128Val: 'u128', - uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, - u64Arr: { array: { type: 'u64' } }, - } -}; - -// computed by hand i32, u32, u64val, i64val, B, -const expectedMixture = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, - // string, u8array, - 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, - // Array> - 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, - // u32Arr, i32Arr, u128, - 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // Array, // u64Arr - 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; - -// A structure of big nums -const BigStruct = { - u64: new BN('ffffffffffffffff', 'hex'), - u128: new BN('ffffffffffffffff'.repeat(2), 'hex'), - arr: [...Array(254).keys()], -}; - -const MyEnumNumbers = { - numbers: Numbers -}; - -const MyEnumMixture = { - mixture: Mixture -}; \ No newline at end of file diff --git a/borsh-ts/test/serialize.test.js b/borsh-ts/test/serialize.test.js deleted file mode 100644 index be3ba12c..00000000 --- a/borsh-ts/test/serialize.test.js +++ /dev/null @@ -1,201 +0,0 @@ -const borsh = require('../../lib/index'); -const BN = require('bn.js'); - -function check_encode(value, schema, expected) { - const encoded = borsh.serialize(schema, value); - expect(encoded).toEqual(Uint8Array.from(expected)); -} - -test('serialize integers', async () => { - check_encode(100, 'u8', [100]); - check_encode(258, 'u16', [2, 1]); - check_encode(102, 'u32', [102, 0, 0, 0]); - check_encode(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); - check_encode(new BN(104), 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - check_encode(-100, 'i8', [156]); - check_encode(-258, 'i16', [254, 254]); - check_encode(-102, 'i32', [154, 255, 255, 255]); - check_encode(new BN(-103), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); - check_encode(new BN(-104), 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); -}); - -test('serialize booleans', async () => { - check_encode(true, 'bool', [1]); - check_encode(false, 'bool', [0]); -}); - -test('serialize strings', async () => { - check_encode('h"i', 'string', [3, 0, 0, 0, 104, 34, 105]); -}); - -test('serialize floats', async () => { - check_encode(7.23, 'f64', [236, 81, 184, 30, 133, 235, 28, 64]); - check_encode(7.23, 'f32', [41, 92, 231, 64]); - check_encode(10e2, 'f32', [0, 0, 122, 68]); - check_encode(10e2, 'f64', [0, 0, 0, 0, 0, 64, 143, 64]); -}); - -test('serialize arrays', async () => { - check_encode([true, false], { array: { type: 'bool' } }, [2, 0, 0, 0, 1, 0]); - check_encode([true, false], { array: { type: 'bool', len: 2 } }, [1, 0]); - check_encode(new ArrayBuffer(2), { array: { type: 'u8' } }, [2, 0, 0, 0, 0, 0]); - - const buffer = new ArrayBuffer(2); - new Uint8Array(buffer).set([1, 2]); - check_encode(buffer, { array: { type: 'u8', len: 2 } }, [1, 2]); -}); - -test('serialize options', async () => { - check_encode(null, { option: 'u8' }, [0]); - check_encode(1, { option: 'u32' }, [1, 1, 0, 0, 0]); -}); - -test('serialize maps', async () => { - check_encode(new Map(), { map: { key: 'u8', value: 'u8' } }, [0, 0, 0, 0]); - check_encode({ 'a': 1, 'b': 2 }, { map: { key: 'string', value: 'u8' } }, [2, 0, 0, 0, 1, 0, 0, 0, 97, 1, 1, 0, 0, 0, 98, 2]); - - const map = new Map(); - map.set('testing', 1); - check_encode(map, { map: { key: 'string', value: 'u32' } }, [1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0]); -}); - -test('serialize sets', async () => { - check_encode(new Set(), { set: 'u8' }, [0, 0, 0, 0]); - check_encode(new Set([1, 2]), { set: 'u8' }, [2, 0, 0, 0, 1, 2]); -}); - -test('serialize struct', async () => { - const numbers = new Numbers(); - check_encode(numbers, numberSchema, expectedNumbers); - - const options = new Options(); - const schemaOpt = { - struct: { - u32: { option: 'u32' }, option: { option: 'string' }, u8: { option: 'u8' } - } - }; - check_encode(options, schemaOpt, [1, 2, 0, 0, 0, 0, 1, 1]); - - const mixture = new Mixture(); - check_encode(mixture, mixtureSchema, expectedMixture); -}); - -test('serialize enums', async () => { - const enumSchema = { enum: [{ struct: { Numbers: numberSchema }, construct: Numbers }, { struct: { Mixture: mixtureSchema }, construct: Mixture }] }; - check_encode(new MyEnum({ numbers: new Numbers() }), enumSchema, [0].concat(expectedNumbers)); - check_encode(new MyEnum({ mixture: new Mixture() }), enumSchema, [1].concat(expectedMixture)); -}); - -test('serializes big structs', async () => { - const bigStruct = new BigStruct(); - const schema = { - struct: { - u64: 'u64', - u128: 'u128', - arr: { array: { type: 'u8', len: 254 } } - } - }; - - const expected = Array(24).fill(255).concat([...Array(254).keys()]); - - check_encode(bigStruct, schema, expected); -}); - -// Complex number structure -class Numbers { - u8 = 1; - u16 = 2; - u32 = 3; - u64 = new BN(4); - u128 = new BN(5); - i8 = -1; - i16 = -2; - i32 = -3; - i64 = new BN(-4); - f32 = 6.0; - f64 = 7.1; -} - -const numberSchema = { - struct: { - u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' - } -}; - -const expectedNumbers = [1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64]; - -// Options -class Options { - u32 = 2; - option = null; - u8 = 1; -} - -// Complex mixture of types -class Mixture { - foo = 321; - bar = 123; - u64Val = new BN('4294967297'); - i64Val = new BN(-64); - flag = true; - baz = 'testing'; - uint8array = new Uint8Array([240, 241]); - arr = [['testing'], ['testing']]; - u32Arr = [21, 11]; - i32Arr = []; - u128Val = new BN(128); - uint8arrays = [this.uint8array, this.uint8array]; - u64Arr = [new BN('10000000000'), new BN('100000000000')]; -} - -const mixtureSchema = { - struct: { - foo: 'u32', - bar: 'i32', - u64Val: 'u64', - i64Val: 'i64', - flag: 'bool', - baz: 'string', - uint8array: { array: { type: 'u8', len: 2 } }, - arr: { array: { type: { array: { type: 'string' } } } }, - u32Arr: { array: { type: 'u32' } }, - i32Arr: { array: { type: 'i32' } }, - u128Val: 'u128', - uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, - u64Arr: { array: { type: 'u64' } }, - } -}; - -// computed by hand i32, u32, u64val, i64val, B, -const expectedMixture = [65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 255, 1, - // string, u8array, - 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, - // Array> - 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, - // u32Arr, i32Arr, u128, - 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // Array, // u64Arr - 2, 0, 0, 0, 240, 241, 240, 241, 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0]; - - -// A structure of big nums -const u64MaxHex = 'ffffffffffffffff'; - -class BigStruct { - u64 = new BN(u64MaxHex, 16); - u128 = new BN(u64MaxHex.repeat(2), 16); - arr = []; - - constructor() { - for (let i = 0; i < 254; i++) { - this.arr.push(i); - } - } -} - -class MyEnum { - constructor({ numbers, mixture }) { - if (numbers) this.Numbers = numbers; - if (mixture) this.Mixture = mixture; - } -} \ No newline at end of file diff --git a/borsh-ts/test/structures.js b/borsh-ts/test/structures.js new file mode 100644 index 00000000..1474f8ad --- /dev/null +++ b/borsh-ts/test/structures.js @@ -0,0 +1,138 @@ +const BN = require('bn.js'); + +// Complex number structure +const Numbers = { + u8: 1, + u16: 2, + u32: 3, + u64: new BN(4), + u128: new BN(5), + i8: -1, + i16: -2, + i32: -3, + i64: new BN(-4), + f32: 6.0, + f64: 7.1, +}; + +const schemaNumbers = { + struct: { + u8: 'u8', u16: 'u16', u32: 'u32', u64: 'u64', u128: 'u128', i8: 'i8', + i16: 'i16', i32: 'i32', i64: 'i64', f32: 'f32', f64: 'f64' + } +}; + +const encodedNumbers = [ + 1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 254, 255, 253, 255, 255, 255, 252, 255, 255, 255, + 255, 255, 255, 255, 0, 0, 192, 64, 102, 102, 102, 102, 102, 102, 28, 64 +]; + +// Options +const Options = { + u32: 2, + option: null, + u8: 1, +}; + +const schemaOptions = { + struct: { + u32: { option: 'u32' }, option: { option: 'string' }, u8: { option: 'u8' } + } +}; + +const encodedOptions = [1, 2, 0, 0, 0, 0, 1, 1]; + +// Nested structure +const Nested = { + a: { sa: { n: 1 } }, + b: 2, + c: 3, +}; + +const schemaNested = { + struct: { a: { struct: { sa: { struct: { n: 'u8' } } } }, b: 'u16', c: 'u32' } +}; + +const encodedNested = [1, 2, 0, 3, 0, 0, 0]; + + +// Complex mixture of types +const Mixture = { + foo: 321, + bar: 123, + u64Val: new BN('4294967297'), + i64Val: new BN(-64), + flag: true, + baz: 'testing', + uint8array: [240, 241], + arr: [['testing'], ['testing']], + u32Arr: [21, 11], + i32Arr: [], + u128Val: new BN(128), + uint8arrays: [[240, 241], [240, 241]], + u64Arr: [new BN('10000000000'), new BN('100000000000')], +}; + +const schemaMixture = { + struct: { + foo: 'u32', + bar: 'i32', + u64Val: 'u64', + i64Val: 'i64', + flag: 'bool', + baz: 'string', + uint8array: { array: { type: 'u8', len: 2 } }, + arr: { array: { type: { array: { type: 'string' } } } }, + u32Arr: { array: { type: 'u32' } }, + i32Arr: { array: { type: 'i32' } }, + u128Val: 'u128', + uint8arrays: { array: { type: { array: { type: 'u8', len: 2 } } } }, + u64Arr: { array: { type: 'u64' } }, + } +}; + +const encodedMixture = [ + // i32, u32, u64val, + 65, 1, 0, 0, 123, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + // i64val, B, + 192, 255, 255, 255, 255, 255, 255, 255, 1, + // string, u8array, + 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, 240, 241, + // Array> + 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, + 105, 110, 103, 1, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 105, 110, 103, + // u32Arr, i32Arr, + 2, 0, 0, 0, 21, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, + // u128, + 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // Array, + 2, 0, 0, 0, 240, 241, 240, 241, + // u64Arr + 2, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0, 0, 232, 118, 72, 23, 0, 0, 0 +]; + +// A structure of big nums +const BigStruct = { + u64: new BN('ffffffffffffffff', 'hex'), + u128: new BN('ffffffffffffffff'.repeat(2), 'hex'), + arr: [...Array(254).keys()], +}; + +const schemaBigStruct = { + struct: { + u64: 'u64', + u128: 'u128', + arr: { array: { type: 'u8', len: 254 } } + } +}; + +const encodedBigStruct = Array(24).fill(255).concat([...Array(254).keys()]); + +// export module +module.exports = { + Numbers, schemaNumbers, encodedNumbers, Options, schemaOptions, + encodedOptions, Nested, schemaNested, encodedNested, Mixture, + schemaMixture, encodedMixture, BigStruct, schemaBigStruct, + encodedBigStruct +}; \ No newline at end of file diff --git a/borsh-ts/test/utils.test.js b/borsh-ts/test/utils.test.js index 64892c0b..298431a6 100644 --- a/borsh-ts/test/utils.test.js +++ b/borsh-ts/test/utils.test.js @@ -7,8 +7,13 @@ test('accept valid schemes', async () => { const map = { map: { key: 'u8', value: 'u8' } }; const option = { option: 'u8' }; const struct = { struct: { u8: 'u8' } }; + const enumeration = { enum: [{ struct: { irrational: 'f32' } }, { struct: { rational: { struct: { num: 'u8', den: 'u8' } } } }] }; - const valid = [array, arrayFixed, set, map, option, struct, 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'bool', 'string', 'f32', 'f64']; + const valid = [ + array, arrayFixed, set, map, option, enumeration, struct, + 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', + 'i64', 'i128', 'bool', 'string', 'f32', 'f64' + ]; for (const schema of valid) { expect(() => utils.validate_schema(schema)).not.toThrow(); @@ -22,6 +27,7 @@ test('rejects invalid schemes', async () => { const map = { map: { value: 'u8' } }; const option = { option: null }; const struct = { struct: arrayFixed }; + const enumeration = { enum: [ { struct: { num: 'u8', den: 'u8' } } ] }; const noStruct = { u8: 'u8' }; expect(() => utils.validate_schema(array)).toThrow('Invalid schema: "u8" expected { type, len? }'); @@ -29,6 +35,7 @@ test('rejects invalid schemes', async () => { expect(() => utils.validate_schema(set)).toThrow('Invalid schema: {"type":"u8"} expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); expect(() => utils.validate_schema(map)).toThrow('Invalid schema: {"value":"u8"} expected { key, value }'); expect(() => utils.validate_schema(option)).toThrow('Invalid schema: null expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); + expect(() => utils.validate_schema(enumeration)).toThrow('The "struct" in each enum must have a single key'); expect(() => utils.validate_schema(struct)).toThrow('Invalid schema: {"len":2} expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); expect(() => utils.validate_schema(noStruct)).toThrow('Invalid schema: {"u8":"u8"} expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); expect(() => utils.validate_schema('u7')).toThrow('Invalid schema: "u7" expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index b00af77c..2173d408 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -83,7 +83,7 @@ function validate_enum_schema(schema: Array): void { } if (typeof sch.struct !== 'object' || Object.keys(sch.struct).length !== 1) { - throw new Error('The "struct" key in enum schema must be an object with a single key'); + throw new Error('The "struct" in each enum must have a single key'); } validate_schema({struct: sch.struct}); diff --git a/lib/utils.js b/lib/utils.js index 7ec80a34..01842b6d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -85,7 +85,7 @@ function validate_enum_schema(schema) { throw new Error('Missing "struct" key in enum schema'); } if (typeof sch.struct !== 'object' || Object.keys(sch.struct).length !== 1) { - throw new Error('The "struct" key in enum schema must be an object with a single key'); + throw new Error('The "struct" in each enum must have a single key'); } validate_schema({ struct: sch.struct }); } From 49b96a215cf89bdee59e04604af8f58820ec1398 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 12:10:52 +0200 Subject: [PATCH 21/35] exported schema --- borsh-ts/index.ts | 2 ++ lib/index.d.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 02db4f27..68b40aae 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -3,6 +3,8 @@ import { BorshSerializer } from './serialize'; import { BorshDeserializer } from './deserialize'; import bs58 from 'bs58'; +export { Schema } from './types'; + export function serialize(schema: Schema, value: unknown): Uint8Array { const serializer = new BorshSerializer(); return serializer.encode(value, schema); diff --git a/lib/index.d.ts b/lib/index.d.ts index ee889bcd..34f07f7a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,4 +1,5 @@ import { Schema, DecodeTypes } from './types'; +export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array): DecodeTypes; export declare function baseEncode(value: Uint8Array | string): string; From a47e7ff13f51989c2e100bc61d46407c0ab9b935 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 12:13:53 +0200 Subject: [PATCH 22/35] Added forgotten schemas to schema type --- borsh-ts/types.ts | 2 +- lib/types.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts index e4d663ad..00ca69ea 100644 --- a/borsh-ts/types.ts +++ b/borsh-ts/types.ts @@ -12,7 +12,7 @@ export type EnumType = { enum: Array }; export type SetType = { set: Schema }; export type MapType = { map: { key: Schema, value: Schema } }; export type StructType = { struct: { [key: string]: Schema } }; -export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; +export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; // returned export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/lib/types.d.ts b/lib/types.d.ts index 2bb62f72..4ee12ea0 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -29,5 +29,5 @@ export type StructType = { [key: string]: Schema; }; }; -export type Schema = IntegerType | StringType | ArrayType | SetType | MapType | StructType; +export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; From b34a106d22e83c2ae9ba6d5a4bea4cd68611f05a Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 14:48:44 +0200 Subject: [PATCH 23/35] allowing numbers in BN --- borsh-ts/utils.ts | 4 ++-- lib/utils.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 2173d408..d73d05a0 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -24,8 +24,8 @@ export function expect_type(value: unknown, type: string): void { } export function expect_BN(value: unknown): void { - if (!(value instanceof BN)) { - throw new Error(`Expected BN not ${typeof (value)}(${value})`); + if (!(value instanceof BN) && typeof (value) !== 'number') { + throw new Error(`Expected BN or number not ${typeof (value)}(${value})`); } } diff --git a/lib/utils.js b/lib/utils.js index 01842b6d..4b31e2e4 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -25,8 +25,8 @@ function expect_type(value, type) { } exports.expect_type = expect_type; function expect_BN(value) { - if (!(value instanceof bn_js_1.default)) { - throw new Error(`Expected BN not ${typeof (value)}(${value})`); + if (!(value instanceof bn_js_1.default) && typeof (value) !== 'number') { + throw new Error(`Expected BN or number not ${typeof (value)}(${value})`); } } exports.expect_BN = expect_BN; From 7204e49b228c33dd5787197d53c13fef88bd0cff Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 16:29:55 +0200 Subject: [PATCH 24/35] schema now leads serialization order --- borsh-ts/serialize.ts | 3 ++- lib/serialize.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index 6a7af256..2dfee5b6 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -37,6 +37,7 @@ export class BorshSerializer { this.encoded.store_value(value as number, schema); } else { utils.expect_BN(value); + value = typeof value === 'number'? new BN(value) : value; this.encode_bigint(value as BN, size); } } @@ -167,7 +168,7 @@ export class BorshSerializer { encode_struct(value: unknown, schema: StructType): void { utils.expect_type(value, 'object'); - for (const key of Object.keys(value)) { + for (const key of Object.keys(schema.struct)) { this.encode_value(value[key], schema.struct[key]); } } diff --git a/lib/serialize.js b/lib/serialize.js index 10763ebc..b84739e7 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -70,6 +70,7 @@ class BorshSerializer { } else { utils.expect_BN(value); + value = typeof value === 'number' ? new bn_js_1.default(value) : value; this.encode_bigint(value, size); } } @@ -178,7 +179,7 @@ class BorshSerializer { } encode_struct(value, schema) { utils.expect_type(value, 'object'); - for (const key of Object.keys(value)) { + for (const key of Object.keys(schema.struct)) { this.encode_value(value[key], schema.struct[key]); } } From dd1cbb4f9fada2897e378b786b7f3131fbc745ef Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 17:07:19 +0200 Subject: [PATCH 25/35] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dea38087..3a3718c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "borsh", - "version": "1.0.0", + "version": "1.0.1", "description": "Binary Object Representation Serializer for Hashing", "main": "lib/index.js", "types": "lib/index.d.ts", From d580c8ca0fff6df9e81d370cf41ec3c8cc5fae1f Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 26 Jul 2023 18:57:42 +0200 Subject: [PATCH 26/35] feat: allow strings in BN --- borsh-ts/serialize.ts | 2 +- borsh-ts/utils.ts | 4 ++-- lib/serialize.js | 2 +- lib/utils.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index 2dfee5b6..0d152e5a 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -37,7 +37,7 @@ export class BorshSerializer { this.encoded.store_value(value as number, schema); } else { utils.expect_BN(value); - value = typeof value === 'number'? new BN(value) : value; + value = value instanceof BN? value : new BN(value as number | string); this.encode_bigint(value as BN, size); } } diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index d73d05a0..b2e14123 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -24,8 +24,8 @@ export function expect_type(value: unknown, type: string): void { } export function expect_BN(value: unknown): void { - if (!(value instanceof BN) && typeof (value) !== 'number') { - throw new Error(`Expected BN or number not ${typeof (value)}(${value})`); + if (!(value instanceof BN) && typeof (value) !== 'number' && typeof (value) !== 'string') { + throw new Error(`Expected BN, number or string not ${typeof (value)}(${value})`); } } diff --git a/lib/serialize.js b/lib/serialize.js index b84739e7..55e4cfb0 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -70,7 +70,7 @@ class BorshSerializer { } else { utils.expect_BN(value); - value = typeof value === 'number' ? new bn_js_1.default(value) : value; + value = value instanceof bn_js_1.default ? value : new bn_js_1.default(value); this.encode_bigint(value, size); } } diff --git a/lib/utils.js b/lib/utils.js index 4b31e2e4..63bc7835 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -25,8 +25,8 @@ function expect_type(value, type) { } exports.expect_type = expect_type; function expect_BN(value) { - if (!(value instanceof bn_js_1.default) && typeof (value) !== 'number') { - throw new Error(`Expected BN or number not ${typeof (value)}(${value})`); + if (!(value instanceof bn_js_1.default) && typeof (value) !== 'number' && typeof (value) !== 'string') { + throw new Error(`Expected BN, number or string not ${typeof (value)}(${value})`); } } exports.expect_BN = expect_BN; From d452862238f50caed544bc0a69100a46ac7491ac Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 28 Jul 2023 13:17:06 +0200 Subject: [PATCH 27/35] feat: more tests & checkSchema flag --- borsh-ts/deserialize.ts | 2 -- borsh-ts/index.ts | 7 +++++-- borsh-ts/serialize.ts | 28 +++++++++++++------------ borsh-ts/test/(de)serialize.test.js | 27 +++++++++++++++++++++--- borsh-ts/test/utils.test.js | 2 +- borsh-ts/utils.ts | 16 +++++++-------- lib/deserialize.js | 25 ---------------------- lib/index.d.ts | 4 ++-- lib/index.js | 32 +++++++++++++++++++++++++++-- lib/serialize.d.ts | 1 + lib/serialize.js | 28 +++++++++++++------------ lib/utils.d.ts | 8 ++++---- lib/utils.js | 16 +++++++-------- 13 files changed, 113 insertions(+), 83 deletions(-) diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index f183a1d8..c6c5d22e 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -1,5 +1,4 @@ import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types'; -import * as utils from './utils'; import { DecodeBuffer } from './buffer'; import BN from 'bn.js'; @@ -11,7 +10,6 @@ export class BorshDeserializer { } decode(schema: Schema): DecodeTypes { - utils.validate_schema(schema); return this.decode_value(schema); } diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 68b40aae..3346d4f3 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -1,16 +1,19 @@ import { Schema, DecodeTypes } from './types'; import { BorshSerializer } from './serialize'; import { BorshDeserializer } from './deserialize'; +import * as utils from './utils'; import bs58 from 'bs58'; export { Schema } from './types'; -export function serialize(schema: Schema, value: unknown): Uint8Array { +export function serialize(schema: Schema, value: unknown, checkSchema = true): Uint8Array { + if (checkSchema) utils.validate_schema(schema); const serializer = new BorshSerializer(); return serializer.encode(value, schema); } -export function deserialize(schema: Schema, buffer: Uint8Array): DecodeTypes { +export function deserialize(schema: Schema, buffer: Uint8Array, checkSchema = true): DecodeTypes { + if (checkSchema) utils.validate_schema(schema); const deserializer = new BorshDeserializer(buffer); return deserializer.decode(schema); } diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index 0d152e5a..9831a011 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -5,9 +5,9 @@ import * as utils from './utils'; export class BorshSerializer { encoded: EncodeBuffer = new EncodeBuffer(); + fieldPath: string[] = ['value']; encode(value: unknown, schema: Schema): Uint8Array { - utils.validate_schema(schema); this.encode_value(value, schema); return this.encoded.get_used_buffer(); } @@ -33,10 +33,10 @@ export class BorshSerializer { const size: number = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { - utils.expect_type(value, 'number'); + utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value as number, schema); } else { - utils.expect_BN(value); + utils.expect_BN(value, this.fieldPath); value = value instanceof BN? value : new BN(value as number | string); this.encode_bigint(value as BN, size); } @@ -60,7 +60,7 @@ export class BorshSerializer { } encode_string(value: unknown): void { - utils.expect_type(value, 'string'); + utils.expect_type(value, 'string', this.fieldPath); const _value = value as string; // 4 bytes for length @@ -73,7 +73,7 @@ export class BorshSerializer { } encode_boolean(value: unknown): void { - utils.expect_type(value, 'boolean'); + utils.expect_type(value, 'boolean', this.fieldPath); this.encoded.store_value(value as boolean ? 1 : 0, 'u8'); } @@ -87,7 +87,7 @@ export class BorshSerializer { } encode_enum(value: unknown, schema: EnumType): void { - utils.expect_enum(value); + utils.expect_enum(value, this.fieldPath); const valueKey = Object.keys(value)[0]; @@ -99,18 +99,18 @@ export class BorshSerializer { return this.encode_struct(value, valueSchema as StructType); } } - throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)}`); + throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)} at ${this.fieldPath.join('.')}`); } encode_array(value: unknown, schema: ArrayType): void { if (utils.isArrayLike(value)) return this.encode_arraylike(value as ArrayLike, schema); if (value instanceof ArrayBuffer) return this.encode_buffer(value, schema); - throw new Error(`Expected Array-like not ${typeof (value)}(${value})`); + throw new Error(`Expected Array-like not ${typeof (value)}(${value}) at ${this.fieldPath.join('.')}`); } encode_arraylike(value: ArrayLike, schema: ArrayType): void { if (schema.array.len) { - utils.expect_same_size(value.length, schema.array.len); + utils.expect_same_size(value.length, schema.array.len, this.fieldPath); } else { // 4 bytes for length this.encoded.store_value(value.length, 'u32'); @@ -124,7 +124,7 @@ export class BorshSerializer { encode_buffer(value: ArrayBuffer, schema: ArrayType): void { if (schema.array.len) { - utils.expect_same_size(value.byteLength, schema.array.len); + utils.expect_same_size(value.byteLength, schema.array.len, this.fieldPath); } else { // 4 bytes for length this.encoded.store_value(value.byteLength, 'u32'); @@ -135,7 +135,7 @@ export class BorshSerializer { } encode_set(value: unknown, schema: SetType): void { - utils.expect_type(value, 'object'); + utils.expect_type(value, 'object', this.fieldPath); const isSet = value instanceof Set; const values = isSet ? Array.from(value.values()) : Object.values(value); @@ -150,7 +150,7 @@ export class BorshSerializer { } encode_map(value: unknown, schema: MapType): void { - utils.expect_type(value, 'object'); + utils.expect_type(value, 'object', this.fieldPath); const isMap = value instanceof Map; const keys = isMap ? Array.from(value.keys()) : Object.keys(value); @@ -166,10 +166,12 @@ export class BorshSerializer { } encode_struct(value: unknown, schema: StructType): void { - utils.expect_type(value, 'object'); + utils.expect_type(value, 'object', this.fieldPath); for (const key of Object.keys(schema.struct)) { + this.fieldPath.push(key); this.encode_value(value[key], schema.struct[key]); + this.fieldPath.pop(); } } } \ No newline at end of file diff --git a/borsh-ts/test/(de)serialize.test.js b/borsh-ts/test/(de)serialize.test.js index 8b33287b..200a615f 100644 --- a/borsh-ts/test/(de)serialize.test.js +++ b/borsh-ts/test/(de)serialize.test.js @@ -9,7 +9,7 @@ function check_encode(value, schema, expected) { function check_decode(expected, schema, encoded) { const decoded = borsh.deserialize(schema, encoded); - + // console.log(decoded, expected); // visual inspection if (expected && typeof (expected) === 'object' && 'eq' in expected) { @@ -18,7 +18,7 @@ function check_decode(expected, schema, encoded) { if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); - if(expected instanceof Map || expected instanceof Set || expected instanceof Array) { + if (expected instanceof Map || expected instanceof Set || expected instanceof Array) { return expect(decoded).toEqual(expected); } @@ -102,9 +102,30 @@ test('serialize enums', async () => { const MyEnumMixture = { mixture: testStructures.Mixture }; const enumSchema = { - enum: [{ struct: { numbers: testStructures.schemaNumbers } }, { struct: { mixture: testStructures.schemaMixture } }] + enum: [{ struct: { numbers: testStructures.schemaNumbers } }, { struct: { mixture: testStructures.schemaMixture } }] }; check_roundtrip(MyEnumNumbers, enumSchema, [0].concat(testStructures.encodedNumbers)); check_roundtrip(MyEnumMixture, enumSchema, [1].concat(testStructures.encodedMixture)); +}); + +test('(de)serialize follows the schema order', async () => { + const schema = { + struct: { a: 'u8', b: 'u8' } + }; + + const object = { b: 2, a: 1 }; + const encoded = [1, 2]; + + check_encode(object, schema, encoded); + check_decode({ a: 1, b: 2 }, schema, encoded); +}); + +test('errors on invalid values', async () => { + const schema_array = { array: { type: 'u16' } }; + + expect(() => check_encode(['a'], schema_array, [])).toThrow('Expected number not string(a) at value'); + expect(() => check_encode(3, 'string', [])).toThrow('Expected string not number(3) at value'); + expect(() => check_encode({ 'a': 1, 'b': '2' }, { struct: { a: 'u8', b: 'u8' } }, [])).toThrow('Expected number not string(2) at value.b'); + expect(() => check_encode({ 'a': { 'b': { 'c': 3 } } }, { struct: { a: { struct: { b: { struct: { c: 'string' } } } } } }, [])).toThrow('Expected string not number(3) at value.a.b.c'); }); \ No newline at end of file diff --git a/borsh-ts/test/utils.test.js b/borsh-ts/test/utils.test.js index 298431a6..abdf33c5 100644 --- a/borsh-ts/test/utils.test.js +++ b/borsh-ts/test/utils.test.js @@ -41,4 +41,4 @@ test('rejects invalid schemes', async () => { expect(() => utils.validate_schema('u7')).toThrow('Invalid schema: "u7" expected option, enum, array, set, map, struct or u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, bool, string'); expect(() => utils.validate_schema(Array)).toThrow(); expect(() => utils.validate_schema(Map)).toThrow(); -}); \ No newline at end of file +}); diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index b2e14123..744a353e 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -17,27 +17,27 @@ export function isArrayLike(value: unknown): boolean { ); } -export function expect_type(value: unknown, type: string): void { +export function expect_type(value: unknown, type: string, fieldPath: string[]): void { if (typeof (value) !== type) { - throw new Error(`Expected ${type} not ${typeof (value)}(${value})`); + throw new Error(`Expected ${type} not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } -export function expect_BN(value: unknown): void { +export function expect_BN(value: unknown, fieldPath: string[]): void { if (!(value instanceof BN) && typeof (value) !== 'number' && typeof (value) !== 'string') { - throw new Error(`Expected BN, number or string not ${typeof (value)}(${value})`); + throw new Error(`Expected BN, number or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } -export function expect_same_size(length: number, expected: number): void { +export function expect_same_size(length: number, expected: number, fieldPath: string[]): void { if (length !== expected) { - throw new Error(`Array length ${length} does not match schema length ${expected}`); + throw new Error(`Array length ${length} does not match schema length ${expected} at ${fieldPath.join('.')}`); } } -export function expect_enum(value: unknown): void { +export function expect_enum(value: unknown, fieldPath: string[]): void { if(typeof (value) !== 'object' || value === null ) { - throw new Error(`Expected object not ${typeof (value)}(${value})`); + throw new Error(`Expected object not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } diff --git a/lib/deserialize.js b/lib/deserialize.js index 91c0f623..72c7cfe4 100644 --- a/lib/deserialize.js +++ b/lib/deserialize.js @@ -1,34 +1,10 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BorshDeserializer = void 0; const types_1 = require("./types"); -const utils = __importStar(require("./utils")); const buffer_1 = require("./buffer"); const bn_js_1 = __importDefault(require("bn.js")); class BorshDeserializer { @@ -37,7 +13,6 @@ class BorshDeserializer { this.buffer = new buffer_1.DecodeBuffer(bufferArray); } decode(schema) { - utils.validate_schema(schema); return this.decode_value(schema); } decode_value(schema) { diff --git a/lib/index.d.ts b/lib/index.d.ts index 34f07f7a..f7f12e25 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,6 +1,6 @@ import { Schema, DecodeTypes } from './types'; export { Schema } from './types'; -export declare function serialize(schema: Schema, value: unknown): Uint8Array; -export declare function deserialize(schema: Schema, buffer: Uint8Array): DecodeTypes; +export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; export declare function baseEncode(value: Uint8Array | string): string; export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/index.js b/lib/index.js index 1b6397aa..abfc3ebe 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,27 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -6,13 +29,18 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.baseDecode = exports.baseEncode = exports.deserialize = exports.serialize = void 0; const serialize_1 = require("./serialize"); const deserialize_1 = require("./deserialize"); +const utils = __importStar(require("./utils")); const bs58_1 = __importDefault(require("bs58")); -function serialize(schema, value) { +function serialize(schema, value, checkSchema = true) { + if (checkSchema) + utils.validate_schema(schema); const serializer = new serialize_1.BorshSerializer(); return serializer.encode(value, schema); } exports.serialize = serialize; -function deserialize(schema, buffer) { +function deserialize(schema, buffer, checkSchema = true) { + if (checkSchema) + utils.validate_schema(schema); const deserializer = new deserialize_1.BorshDeserializer(buffer); return deserializer.decode(schema); } diff --git a/lib/serialize.d.ts b/lib/serialize.d.ts index fab59d33..d7d52c16 100644 --- a/lib/serialize.d.ts +++ b/lib/serialize.d.ts @@ -3,6 +3,7 @@ import { EncodeBuffer } from './buffer'; import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; + fieldPath: string[]; encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; diff --git a/lib/serialize.js b/lib/serialize.js index 55e4cfb0..446c32ac 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -33,8 +33,8 @@ const bn_js_1 = __importDefault(require("bn.js")); const utils = __importStar(require("./utils")); class BorshSerializer { encoded = new buffer_1.EncodeBuffer(); + fieldPath = ['value']; encode(value, schema) { - utils.validate_schema(schema); this.encode_value(value, schema); return this.encoded.get_used_buffer(); } @@ -65,11 +65,11 @@ class BorshSerializer { encode_integer(value, schema) { const size = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { - utils.expect_type(value, 'number'); + utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value, schema); } else { - utils.expect_BN(value); + utils.expect_BN(value, this.fieldPath); value = value instanceof bn_js_1.default ? value : new bn_js_1.default(value); this.encode_bigint(value, size); } @@ -89,7 +89,7 @@ class BorshSerializer { this.encoded.store_bytes(new Uint8Array(buffer)); } encode_string(value) { - utils.expect_type(value, 'string'); + utils.expect_type(value, 'string', this.fieldPath); const _value = value; // 4 bytes for length this.encoded.store_value(_value.length, 'u32'); @@ -99,7 +99,7 @@ class BorshSerializer { } } encode_boolean(value) { - utils.expect_type(value, 'boolean'); + utils.expect_type(value, 'boolean', this.fieldPath); this.encoded.store_value(value ? 1 : 0, 'u8'); } encode_option(value, schema) { @@ -112,7 +112,7 @@ class BorshSerializer { } } encode_enum(value, schema) { - utils.expect_enum(value); + utils.expect_enum(value, this.fieldPath); const valueKey = Object.keys(value)[0]; for (let i = 0; i < schema.enum.length; i++) { const valueSchema = schema.enum[i]; @@ -121,18 +121,18 @@ class BorshSerializer { return this.encode_struct(value, valueSchema); } } - throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)}`); + throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)} at ${this.fieldPath.join('.')}`); } encode_array(value, schema) { if (utils.isArrayLike(value)) return this.encode_arraylike(value, schema); if (value instanceof ArrayBuffer) return this.encode_buffer(value, schema); - throw new Error(`Expected Array-like not ${typeof (value)}(${value})`); + throw new Error(`Expected Array-like not ${typeof (value)}(${value}) at ${this.fieldPath.join('.')}`); } encode_arraylike(value, schema) { if (schema.array.len) { - utils.expect_same_size(value.length, schema.array.len); + utils.expect_same_size(value.length, schema.array.len, this.fieldPath); } else { // 4 bytes for length @@ -145,7 +145,7 @@ class BorshSerializer { } encode_buffer(value, schema) { if (schema.array.len) { - utils.expect_same_size(value.byteLength, schema.array.len); + utils.expect_same_size(value.byteLength, schema.array.len, this.fieldPath); } else { // 4 bytes for length @@ -155,7 +155,7 @@ class BorshSerializer { this.encoded.store_bytes(new Uint8Array(value)); } encode_set(value, schema) { - utils.expect_type(value, 'object'); + utils.expect_type(value, 'object', this.fieldPath); const isSet = value instanceof Set; const values = isSet ? Array.from(value.values()) : Object.values(value); // 4 bytes for length @@ -166,7 +166,7 @@ class BorshSerializer { } } encode_map(value, schema) { - utils.expect_type(value, 'object'); + utils.expect_type(value, 'object', this.fieldPath); const isMap = value instanceof Map; const keys = isMap ? Array.from(value.keys()) : Object.keys(value); // 4 bytes for length @@ -178,9 +178,11 @@ class BorshSerializer { } } encode_struct(value, schema) { - utils.expect_type(value, 'object'); + utils.expect_type(value, 'object', this.fieldPath); for (const key of Object.keys(schema.struct)) { + this.fieldPath.push(key); this.encode_value(value[key], schema.struct[key]); + this.fieldPath.pop(); } } } diff --git a/lib/utils.d.ts b/lib/utils.d.ts index 6b189185..56f6a787 100644 --- a/lib/utils.d.ts +++ b/lib/utils.d.ts @@ -1,9 +1,9 @@ import { Schema } from './types'; export declare function isArrayLike(value: unknown): boolean; -export declare function expect_type(value: unknown, type: string): void; -export declare function expect_BN(value: unknown): void; -export declare function expect_same_size(length: number, expected: number): void; -export declare function expect_enum(value: unknown): void; +export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; +export declare function expect_BN(value: unknown, fieldPath: string[]): void; +export declare function expect_same_size(length: number, expected: number, fieldPath: string[]): void; +export declare function expect_enum(value: unknown, fieldPath: string[]): void; export declare class ErrorSchema extends Error { constructor(schema: Schema, expected: string); } diff --git a/lib/utils.js b/lib/utils.js index 63bc7835..7420670c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -18,27 +18,27 @@ function isArrayLike(value) { (value.length - 1) in value)))); } exports.isArrayLike = isArrayLike; -function expect_type(value, type) { +function expect_type(value, type, fieldPath) { if (typeof (value) !== type) { - throw new Error(`Expected ${type} not ${typeof (value)}(${value})`); + throw new Error(`Expected ${type} not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } exports.expect_type = expect_type; -function expect_BN(value) { +function expect_BN(value, fieldPath) { if (!(value instanceof bn_js_1.default) && typeof (value) !== 'number' && typeof (value) !== 'string') { - throw new Error(`Expected BN, number or string not ${typeof (value)}(${value})`); + throw new Error(`Expected BN, number or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } exports.expect_BN = expect_BN; -function expect_same_size(length, expected) { +function expect_same_size(length, expected, fieldPath) { if (length !== expected) { - throw new Error(`Array length ${length} does not match schema length ${expected}`); + throw new Error(`Array length ${length} does not match schema length ${expected} at ${fieldPath.join('.')}`); } } exports.expect_same_size = expect_same_size; -function expect_enum(value) { +function expect_enum(value, fieldPath) { if (typeof (value) !== 'object' || value === null) { - throw new Error(`Expected object not ${typeof (value)}(${value})`); + throw new Error(`Expected object not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } exports.expect_enum = expect_enum; From 988fb57802ad4d92b02fd91988424c3bd9d702c7 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 28 Jul 2023 14:41:02 +0200 Subject: [PATCH 28/35] fix: made compatible to ES5 --- borsh-ts/buffer.ts | 21 +++++--- borsh-ts/index.ts | 7 ++- borsh-ts/serialize.ts | 9 +++- lib/buffer.d.ts | 1 + lib/buffer.js | 74 ++++++++++++------------- lib/deserialize.js | 122 +++++++++++++++++++++--------------------- lib/index.js | 25 +++++---- lib/serialize.d.ts | 1 + lib/serialize.js | 122 ++++++++++++++++++++++-------------------- lib/utils.js | 54 ++++++++++++------- tsconfig.json | 2 +- 11 files changed, 245 insertions(+), 193 deletions(-) diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index 3d1b635d..ddb20a5e 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -1,10 +1,17 @@ import { IntegerType } from './types'; export class EncodeBuffer { - offset = 0; - buffer_size = 256; - buffer: ArrayBuffer = new ArrayBuffer(this.buffer_size); - view: DataView = new DataView(this.buffer); + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + + constructor() { + this.offset = 0; + this.buffer_size = 256; + this.buffer = new ArrayBuffer(this.buffer_size); + this.view = new DataView(this.buffer); + } resize_if_necessary(needed_space: number): void { if (this.buffer_size - this.offset < needed_space) { @@ -40,12 +47,14 @@ export class EncodeBuffer { } export class DecodeBuffer { - offset = 0; - buffer_size = 256; + offset: number; + buffer_size: number; buffer: ArrayBuffer; view: DataView; constructor(buf: Uint8Array) { + this.offset = 0; + this.buffer_size = buf.length; this.buffer = new ArrayBuffer(buf.length); new Uint8Array(this.buffer).set(buf); this.view = new DataView(this.buffer); diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 3346d4f3..55f78962 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -21,8 +21,11 @@ export function deserialize(schema: Schema, buffer: Uint8Array, checkSchema = tr // Keeping this for compatibility reasons with the old borsh-js export function baseEncode(value: Uint8Array | string): string { if (typeof value === 'string') { - // create a uint8array from string decoding utf-8 without using Buffer - value = new Uint8Array([...value].map((c) => c.charCodeAt(0))); + const bytes = []; + for(let c = 0; c < value.length; c++){ + bytes.push(value.charCodeAt(c)); + } + value = new Uint8Array(bytes); } return bs58.encode(value); } diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index 9831a011..a0d92f71 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -4,8 +4,13 @@ import BN from 'bn.js'; import * as utils from './utils'; export class BorshSerializer { - encoded: EncodeBuffer = new EncodeBuffer(); - fieldPath: string[] = ['value']; + encoded: EncodeBuffer; + fieldPath: string[]; + + constructor() { + this.encoded = new EncodeBuffer(); + this.fieldPath = ['value']; + } encode(value: unknown, schema: Schema): Uint8Array { this.encode_value(value, schema); diff --git a/lib/buffer.d.ts b/lib/buffer.d.ts index 5673a7f2..159e6233 100644 --- a/lib/buffer.d.ts +++ b/lib/buffer.d.ts @@ -4,6 +4,7 @@ export declare class EncodeBuffer { buffer_size: number; buffer: ArrayBuffer; view: DataView; + constructor(); resize_if_necessary(needed_space: number): void; get_used_buffer(): Uint8Array; store_value(value: number, type: IntegerType): void; diff --git a/lib/buffer.js b/lib/buffer.js index f80c17af..f737262a 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -1,67 +1,69 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DecodeBuffer = exports.EncodeBuffer = void 0; -class EncodeBuffer { - offset = 0; - buffer_size = 256; - buffer = new ArrayBuffer(this.buffer_size); - view = new DataView(this.buffer); - resize_if_necessary(needed_space) { +var EncodeBuffer = /** @class */ (function () { + function EncodeBuffer() { + this.offset = 0; + this.buffer_size = 256; + this.buffer = new ArrayBuffer(this.buffer_size); + this.view = new DataView(this.buffer); + } + EncodeBuffer.prototype.resize_if_necessary = function (needed_space) { if (this.buffer_size - this.offset < needed_space) { this.buffer_size = Math.max(this.buffer_size * 2, this.buffer_size + needed_space); - const new_buffer = new ArrayBuffer(this.buffer_size); + var new_buffer = new ArrayBuffer(this.buffer_size); new Uint8Array(new_buffer).set(new Uint8Array(this.buffer)); this.buffer = new_buffer; this.view = new DataView(new_buffer); } - } - get_used_buffer() { + }; + EncodeBuffer.prototype.get_used_buffer = function () { return new Uint8Array(this.buffer).slice(0, this.offset); - } - store_value(value, type) { - const bSize = type.substring(1); - const size = parseInt(bSize) / 8; + }; + EncodeBuffer.prototype.store_value = function (value, type) { + var bSize = type.substring(1); + var size = parseInt(bSize) / 8; this.resize_if_necessary(size); - const toCall = type[0] === 'f' ? `setFloat${bSize}` : type[0] === 'i' ? `setInt${bSize}` : `setUint${bSize}`; + var toCall = type[0] === 'f' ? "setFloat".concat(bSize) : type[0] === 'i' ? "setInt".concat(bSize) : "setUint".concat(bSize); this.view[toCall](this.offset, value, true); this.offset += size; - } - store_bytes(from) { + }; + EncodeBuffer.prototype.store_bytes = function (from) { this.resize_if_necessary(from.length); new Uint8Array(this.buffer).set(new Uint8Array(from), this.offset); this.offset += from.length; - } -} + }; + return EncodeBuffer; +}()); exports.EncodeBuffer = EncodeBuffer; -class DecodeBuffer { - offset = 0; - buffer_size = 256; - buffer; - view; - constructor(buf) { +var DecodeBuffer = /** @class */ (function () { + function DecodeBuffer(buf) { + this.offset = 0; + this.buffer_size = buf.length; this.buffer = new ArrayBuffer(buf.length); new Uint8Array(this.buffer).set(buf); this.view = new DataView(this.buffer); } - assert_enough_buffer(size) { + DecodeBuffer.prototype.assert_enough_buffer = function (size) { if (this.offset + size > this.buffer.byteLength) { throw new Error('Error in schema, the buffer is smaller than expected'); } - } - consume_value(type) { - const bSize = type.substring(1); - const size = parseInt(bSize) / 8; + }; + DecodeBuffer.prototype.consume_value = function (type) { + var bSize = type.substring(1); + var size = parseInt(bSize) / 8; this.assert_enough_buffer(size); - const toCall = type[0] === 'f' ? `getFloat${bSize}` : type[0] === 'i' ? `getInt${bSize}` : `getUint${bSize}`; - const ret = this.view[toCall](this.offset, true); + var toCall = type[0] === 'f' ? "getFloat".concat(bSize) : type[0] === 'i' ? "getInt".concat(bSize) : "getUint".concat(bSize); + var ret = this.view[toCall](this.offset, true); this.offset += size; return ret; - } - consume_bytes(size) { + }; + DecodeBuffer.prototype.consume_bytes = function (size) { this.assert_enough_buffer(size); - const ret = this.buffer.slice(this.offset, this.offset + size); + var ret = this.buffer.slice(this.offset, this.offset + size); this.offset += size; return ret; - } -} + }; + return DecodeBuffer; +}()); exports.DecodeBuffer = DecodeBuffer; diff --git a/lib/deserialize.js b/lib/deserialize.js index 72c7cfe4..87977315 100644 --- a/lib/deserialize.js +++ b/lib/deserialize.js @@ -4,18 +4,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BorshDeserializer = void 0; -const types_1 = require("./types"); -const buffer_1 = require("./buffer"); -const bn_js_1 = __importDefault(require("bn.js")); -class BorshDeserializer { - buffer; - constructor(bufferArray) { +var types_1 = require("./types"); +var buffer_1 = require("./buffer"); +var bn_js_1 = __importDefault(require("bn.js")); +var BorshDeserializer = /** @class */ (function () { + function BorshDeserializer(bufferArray) { this.buffer = new buffer_1.DecodeBuffer(bufferArray); } - decode(schema) { + BorshDeserializer.prototype.decode = function (schema) { return this.decode_value(schema); - } - decode_value(schema) { + }; + BorshDeserializer.prototype.decode_value = function (schema) { if (typeof schema === 'string') { if (types_1.integers.includes(schema)) return this.decode_integer(schema); @@ -38,89 +37,92 @@ class BorshDeserializer { if ('struct' in schema) return this.decode_struct(schema); } - throw new Error(`Unsupported type: ${schema}`); - } - decode_integer(schema) { - const size = parseInt(schema.substring(1)); + throw new Error("Unsupported type: ".concat(schema)); + }; + BorshDeserializer.prototype.decode_integer = function (schema) { + var size = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { return this.buffer.consume_value(schema); } return this.decode_bigint(size, schema.startsWith('i')); - } - decode_bigint(size, signed = false) { - const buffer_len = size / 8; - const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + }; + BorshDeserializer.prototype.decode_bigint = function (size, signed) { + if (signed === void 0) { signed = false; } + var buffer_len = size / 8; + var buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); if (signed && buffer[buffer_len - 1]) { // negative number - let carry = 1; - for (let i = 0; i < buffer_len; i++) { - const v = (buffer[i] ^ 0xff) + carry; + var carry = 1; + for (var i = 0; i < buffer_len; i++) { + var v = (buffer[i] ^ 0xff) + carry; buffer[i] = v & 0xff; carry = v >> 8; } return new bn_js_1.default(buffer, 'le').mul(new bn_js_1.default(-1)); } return new bn_js_1.default(buffer, 'le'); - } - decode_string() { - const len = this.decode_integer('u32'); - const buffer = new Uint8Array(this.buffer.consume_bytes(len)); + }; + BorshDeserializer.prototype.decode_string = function () { + var len = this.decode_integer('u32'); + var buffer = new Uint8Array(this.buffer.consume_bytes(len)); return String.fromCharCode.apply(null, buffer); - } - decode_boolean() { + }; + BorshDeserializer.prototype.decode_boolean = function () { return this.buffer.consume_value('u8') > 0; - } - decode_option(schema) { - const option = this.buffer.consume_value('u8'); + }; + BorshDeserializer.prototype.decode_option = function (schema) { + var option = this.buffer.consume_value('u8'); if (option === 1) { return this.decode_value(schema.option); } if (option !== 0) { - throw new Error(`Invalid option ${option}`); + throw new Error("Invalid option ".concat(option)); } return null; - } - decode_enum(schema) { - const valueIndex = this.buffer.consume_value('u8'); + }; + BorshDeserializer.prototype.decode_enum = function (schema) { + var _a; + var valueIndex = this.buffer.consume_value('u8'); if (valueIndex > schema.enum.length) { - throw new Error(`Enum option ${valueIndex} is not available`); + throw new Error("Enum option ".concat(valueIndex, " is not available")); } - const struct = schema.enum[valueIndex].struct; - const key = Object.keys(struct)[0]; - return { [key]: this.decode_value(struct[key]) }; - } - decode_array(schema) { - const result = []; - const len = schema.array.len ? schema.array.len : this.decode_integer('u32'); - for (let i = 0; i < len; ++i) { + var struct = schema.enum[valueIndex].struct; + var key = Object.keys(struct)[0]; + return _a = {}, _a[key] = this.decode_value(struct[key]), _a; + }; + BorshDeserializer.prototype.decode_array = function (schema) { + var result = []; + var len = schema.array.len ? schema.array.len : this.decode_integer('u32'); + for (var i = 0; i < len; ++i) { result.push(this.decode_value(schema.array.type)); } return result; - } - decode_set(schema) { - const len = this.decode_integer('u32'); - const result = new Set(); - for (let i = 0; i < len; ++i) { + }; + BorshDeserializer.prototype.decode_set = function (schema) { + var len = this.decode_integer('u32'); + var result = new Set(); + for (var i = 0; i < len; ++i) { result.add(this.decode_value(schema.set)); } return result; - } - decode_map(schema) { - const len = this.decode_integer('u32'); - const result = new Map(); - for (let i = 0; i < len; ++i) { - const key = this.decode_value(schema.map.key); - const value = this.decode_value(schema.map.value); + }; + BorshDeserializer.prototype.decode_map = function (schema) { + var len = this.decode_integer('u32'); + var result = new Map(); + for (var i = 0; i < len; ++i) { + var key = this.decode_value(schema.map.key); + var value = this.decode_value(schema.map.value); result.set(key, value); } return result; - } - decode_struct(schema) { - const result = {}; - for (const key in schema.struct) { + }; + BorshDeserializer.prototype.decode_struct = function (schema) { + var result = {}; + for (var key in schema.struct) { result[key] = this.decode_value(schema.struct[key]); } return result; - } -} + }; + return BorshDeserializer; +}()); exports.BorshDeserializer = BorshDeserializer; diff --git a/lib/index.js b/lib/index.js index abfc3ebe..9f1a0cbb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -27,29 +27,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.baseDecode = exports.baseEncode = exports.deserialize = exports.serialize = void 0; -const serialize_1 = require("./serialize"); -const deserialize_1 = require("./deserialize"); -const utils = __importStar(require("./utils")); -const bs58_1 = __importDefault(require("bs58")); -function serialize(schema, value, checkSchema = true) { +var serialize_1 = require("./serialize"); +var deserialize_1 = require("./deserialize"); +var utils = __importStar(require("./utils")); +var bs58_1 = __importDefault(require("bs58")); +function serialize(schema, value, checkSchema) { + if (checkSchema === void 0) { checkSchema = true; } if (checkSchema) utils.validate_schema(schema); - const serializer = new serialize_1.BorshSerializer(); + var serializer = new serialize_1.BorshSerializer(); return serializer.encode(value, schema); } exports.serialize = serialize; -function deserialize(schema, buffer, checkSchema = true) { +function deserialize(schema, buffer, checkSchema) { + if (checkSchema === void 0) { checkSchema = true; } if (checkSchema) utils.validate_schema(schema); - const deserializer = new deserialize_1.BorshDeserializer(buffer); + var deserializer = new deserialize_1.BorshDeserializer(buffer); return deserializer.decode(schema); } exports.deserialize = deserialize; // Keeping this for compatibility reasons with the old borsh-js function baseEncode(value) { if (typeof value === 'string') { - // create a uint8array from string decoding utf-8 without using Buffer - value = new Uint8Array([...value].map((c) => c.charCodeAt(0))); + var bytes = []; + for (var c = 0; c < value.length; c++) { + bytes.push(value.charCodeAt(c)); + } + value = new Uint8Array(bytes); } return bs58_1.default.encode(value); } diff --git a/lib/serialize.d.ts b/lib/serialize.d.ts index d7d52c16..374154a9 100644 --- a/lib/serialize.d.ts +++ b/lib/serialize.d.ts @@ -4,6 +4,7 @@ import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; + constructor(); encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; diff --git a/lib/serialize.js b/lib/serialize.js index 446c32ac..ddad9878 100644 --- a/lib/serialize.js +++ b/lib/serialize.js @@ -27,18 +27,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BorshSerializer = void 0; -const types_1 = require("./types"); -const buffer_1 = require("./buffer"); -const bn_js_1 = __importDefault(require("bn.js")); -const utils = __importStar(require("./utils")); -class BorshSerializer { - encoded = new buffer_1.EncodeBuffer(); - fieldPath = ['value']; - encode(value, schema) { +var types_1 = require("./types"); +var buffer_1 = require("./buffer"); +var bn_js_1 = __importDefault(require("bn.js")); +var utils = __importStar(require("./utils")); +var BorshSerializer = /** @class */ (function () { + function BorshSerializer() { + this.encoded = new buffer_1.EncodeBuffer(); + this.fieldPath = ['value']; + } + BorshSerializer.prototype.encode = function (value, schema) { this.encode_value(value, schema); return this.encoded.get_used_buffer(); - } - encode_value(value, schema) { + }; + BorshSerializer.prototype.encode_value = function (value, schema) { if (typeof schema === 'string') { if (types_1.integers.includes(schema)) return this.encode_integer(value, schema); @@ -61,9 +63,9 @@ class BorshSerializer { if ('struct' in schema) return this.encode_struct(value, schema); } - } - encode_integer(value, schema) { - const size = parseInt(schema.substring(1)); + }; + BorshSerializer.prototype.encode_integer = function (value, schema) { + var size = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value, schema); @@ -73,36 +75,36 @@ class BorshSerializer { value = value instanceof bn_js_1.default ? value : new bn_js_1.default(value); this.encode_bigint(value, size); } - } - encode_bigint(value, size) { - const buffer_len = size / 8; - const buffer = value.toArray('le', buffer_len); + }; + BorshSerializer.prototype.encode_bigint = function (value, size) { + var buffer_len = size / 8; + var buffer = value.toArray('le', buffer_len); if (value.lt(new bn_js_1.default(0))) { // compute two's complement - let carry = 1; - for (let i = 0; i < buffer_len; i++) { - const v = (buffer[i] ^ 0xff) + carry; + var carry = 1; + for (var i = 0; i < buffer_len; i++) { + var v = (buffer[i] ^ 0xff) + carry; carry = v >> 8; buffer[i] = v & 0xff; } } this.encoded.store_bytes(new Uint8Array(buffer)); - } - encode_string(value) { + }; + BorshSerializer.prototype.encode_string = function (value) { utils.expect_type(value, 'string', this.fieldPath); - const _value = value; + var _value = value; // 4 bytes for length this.encoded.store_value(_value.length, 'u32'); // string bytes - for (let i = 0; i < _value.length; i++) { + for (var i = 0; i < _value.length; i++) { this.encoded.store_value(_value.charCodeAt(i), 'u8'); } - } - encode_boolean(value) { + }; + BorshSerializer.prototype.encode_boolean = function (value) { utils.expect_type(value, 'boolean', this.fieldPath); this.encoded.store_value(value ? 1 : 0, 'u8'); - } - encode_option(value, schema) { + }; + BorshSerializer.prototype.encode_option = function (value, schema) { if (value === null || value === undefined) { this.encoded.store_value(0, 'u8'); } @@ -110,27 +112,27 @@ class BorshSerializer { this.encoded.store_value(1, 'u8'); this.encode_value(value, schema.option); } - } - encode_enum(value, schema) { + }; + BorshSerializer.prototype.encode_enum = function (value, schema) { utils.expect_enum(value, this.fieldPath); - const valueKey = Object.keys(value)[0]; - for (let i = 0; i < schema.enum.length; i++) { - const valueSchema = schema.enum[i]; + var valueKey = Object.keys(value)[0]; + for (var i = 0; i < schema.enum.length; i++) { + var valueSchema = schema.enum[i]; if (valueKey === Object.keys(valueSchema.struct)[0]) { this.encoded.store_value(i, 'u8'); return this.encode_struct(value, valueSchema); } } - throw new Error(`Enum key (${valueKey}) not found in enum schema: ${JSON.stringify(schema)} at ${this.fieldPath.join('.')}`); - } - encode_array(value, schema) { + throw new Error("Enum key (".concat(valueKey, ") not found in enum schema: ").concat(JSON.stringify(schema), " at ").concat(this.fieldPath.join('.'))); + }; + BorshSerializer.prototype.encode_array = function (value, schema) { if (utils.isArrayLike(value)) return this.encode_arraylike(value, schema); if (value instanceof ArrayBuffer) return this.encode_buffer(value, schema); - throw new Error(`Expected Array-like not ${typeof (value)}(${value}) at ${this.fieldPath.join('.')}`); - } - encode_arraylike(value, schema) { + throw new Error("Expected Array-like not ".concat(typeof (value), "(").concat(value, ") at ").concat(this.fieldPath.join('.'))); + }; + BorshSerializer.prototype.encode_arraylike = function (value, schema) { if (schema.array.len) { utils.expect_same_size(value.length, schema.array.len, this.fieldPath); } @@ -139,11 +141,11 @@ class BorshSerializer { this.encoded.store_value(value.length, 'u32'); } // array values - for (let i = 0; i < value.length; i++) { + for (var i = 0; i < value.length; i++) { this.encode_value(value[i], schema.array.type); } - } - encode_buffer(value, schema) { + }; + BorshSerializer.prototype.encode_buffer = function (value, schema) { if (schema.array.len) { utils.expect_same_size(value.byteLength, schema.array.len, this.fieldPath); } @@ -153,37 +155,41 @@ class BorshSerializer { } // array values this.encoded.store_bytes(new Uint8Array(value)); - } - encode_set(value, schema) { + }; + BorshSerializer.prototype.encode_set = function (value, schema) { utils.expect_type(value, 'object', this.fieldPath); - const isSet = value instanceof Set; - const values = isSet ? Array.from(value.values()) : Object.values(value); + var isSet = value instanceof Set; + var values = isSet ? Array.from(value.values()) : Object.values(value); // 4 bytes for length this.encoded.store_value(values.length, 'u32'); // set values - for (const value of values) { - this.encode_value(value, schema.set); + for (var _i = 0, values_1 = values; _i < values_1.length; _i++) { + var value_1 = values_1[_i]; + this.encode_value(value_1, schema.set); } - } - encode_map(value, schema) { + }; + BorshSerializer.prototype.encode_map = function (value, schema) { utils.expect_type(value, 'object', this.fieldPath); - const isMap = value instanceof Map; - const keys = isMap ? Array.from(value.keys()) : Object.keys(value); + var isMap = value instanceof Map; + var keys = isMap ? Array.from(value.keys()) : Object.keys(value); // 4 bytes for length this.encoded.store_value(keys.length, 'u32'); // store key/values - for (const key of keys) { + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; this.encode_value(key, schema.map.key); this.encode_value(isMap ? value.get(key) : value[key], schema.map.value); } - } - encode_struct(value, schema) { + }; + BorshSerializer.prototype.encode_struct = function (value, schema) { utils.expect_type(value, 'object', this.fieldPath); - for (const key of Object.keys(schema.struct)) { + for (var _i = 0, _a = Object.keys(schema.struct); _i < _a.length; _i++) { + var key = _a[_i]; this.fieldPath.push(key); this.encode_value(value[key], schema.struct[key]); this.fieldPath.pop(); } - } -} + }; + return BorshSerializer; +}()); exports.BorshSerializer = BorshSerializer; diff --git a/lib/utils.js b/lib/utils.js index 7420670c..3a71898b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,11 +1,26 @@ "use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; -const bn_js_1 = __importDefault(require("bn.js")); -const types_1 = require("./types"); +var bn_js_1 = __importDefault(require("bn.js")); +var types_1 = require("./types"); function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like return (Array.isArray(value) || @@ -20,46 +35,48 @@ function isArrayLike(value) { exports.isArrayLike = isArrayLike; function expect_type(value, type, fieldPath) { if (typeof (value) !== type) { - throw new Error(`Expected ${type} not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); + throw new Error("Expected ".concat(type, " not ").concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } exports.expect_type = expect_type; function expect_BN(value, fieldPath) { if (!(value instanceof bn_js_1.default) && typeof (value) !== 'number' && typeof (value) !== 'string') { - throw new Error(`Expected BN, number or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); + throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } exports.expect_BN = expect_BN; function expect_same_size(length, expected, fieldPath) { if (length !== expected) { - throw new Error(`Array length ${length} does not match schema length ${expected} at ${fieldPath.join('.')}`); + throw new Error("Array length ".concat(length, " does not match schema length ").concat(expected, " at ").concat(fieldPath.join('.'))); } } exports.expect_same_size = expect_same_size; function expect_enum(value, fieldPath) { if (typeof (value) !== 'object' || value === null) { - throw new Error(`Expected object not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); + throw new Error("Expected object not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } exports.expect_enum = expect_enum; // Validate Schema -const VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']); -const VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct']; -class ErrorSchema extends Error { - constructor(schema, expected) { - const message = `Invalid schema: ${JSON.stringify(schema)} expected ${expected}`; - super(message); +var VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']); +var VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct']; +var ErrorSchema = /** @class */ (function (_super) { + __extends(ErrorSchema, _super); + function ErrorSchema(schema, expected) { + var message = "Invalid schema: ".concat(JSON.stringify(schema), " expected ").concat(expected); + return _super.call(this, message) || this; } -} + return ErrorSchema; +}(Error)); exports.ErrorSchema = ErrorSchema; function validate_schema(schema) { if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) { return; } if (schema && typeof (schema) === 'object') { - const keys = Object.keys(schema); + var keys = Object.keys(schema); if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { - const key = keys[0]; + var key = keys[0]; if (key === 'option') return validate_schema(schema[key]); if (key === 'enum') @@ -80,7 +97,8 @@ exports.validate_schema = validate_schema; function validate_enum_schema(schema) { if (!Array.isArray(schema)) throw new ErrorSchema(schema, 'Array'); - for (const sch of schema) { + for (var _i = 0, schema_1 = schema; _i < schema_1.length; _i++) { + var sch = schema_1[_i]; if (typeof sch !== 'object' || !('struct' in sch)) { throw new Error('Missing "struct" key in enum schema'); } @@ -94,7 +112,7 @@ function validate_array_schema(schema) { if (typeof schema !== 'object') throw new ErrorSchema(schema, '{ type, len? }'); if (schema.len && typeof schema.len !== 'number') { - throw new Error(`Invalid schema: ${schema}`); + throw new Error("Invalid schema: ".concat(schema)); } if ('type' in schema) return validate_schema(schema.type); @@ -112,7 +130,7 @@ function validate_map_schema(schema) { function validate_struct_schema(schema) { if (typeof schema !== 'object') throw new ErrorSchema(schema, 'object'); - for (const key in schema) { + for (var key in schema) { validate_schema(schema[key]); } } diff --git a/tsconfig.json b/tsconfig.json index fe2ec2d1..c1f9157f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "dom" ], "module": "commonjs", - "target": "esnext", + "target": "ES5", "moduleResolution": "node", "outDir": "./lib", "declaration": true, From 263e90206ec8c5d83a8245e830b686e971bed44a Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 28 Jul 2023 19:10:37 +0200 Subject: [PATCH 29/35] updated readme --- README.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e228845f..c3e326af 100644 --- a/README.md +++ b/README.md @@ -31,26 +31,13 @@ const decodedStr = borsh.deserialize('string', encodedStr); import * as borsh from 'borsh'; import BN from 'bn.js'; -const value = new Test({x: 255, y: new BN(20), z: '123', arr: [1, 2, 3]}); +const value = {x: 255, y: new BN(20), z: '123', arr: [1, 2, 3]}; const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; const encoded = borsh.serialize(schema, value); const decoded = borsh.deserialize(schema, encoded); ``` -### (De)serializing a Class Instance -```javascript -import * as borsh from 'borsh'; -import BN from 'bn.js'; - -class Test{ constructor({x, y, z, arr}){ Object.assign(this, {x, y, z, arr}) }} -const value = new Test({x: 255, y: new BN(20), z: '123', arr: [1, 2, 3]}); -const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; - -const encoded = borsh.serialize(schema, value); -const decoded = borsh.deserialize(schema, encoded, Test); // decoded is an instance of Test -``` - ## API The package exposes the following functions: - `serialize(schema: Schema, obj: any): Uint8Array` - serializes an object `obj` according to the schema `schema`. @@ -70,12 +57,13 @@ Basic types are described by a string. The following types are supported: - `bool` - boolean value. - `string` - UTF-8 string. -### Arrays, Options, Maps, Sets, and Structs +### Arrays, Options, Maps, Sets, Enums, and Structs More complex objects are described by a JSON object. The following types are supported: - `{ array: { type: Schema, len?: number } }` - an array of objects of the same type. The type of the array elements is described by the `type` field. If the field `len` is present, the array is fixed-size and the length of the array is `len`. Otherwise, the array is dynamic-sized and the length of the array is serialized before the elements. - `{ option: Schema }` - an optional object. The type of the object is described by the `type` field. - `{ map: { key: Schema, value: Schema }}` - a map. The type of the keys and values are described by the `key` and `value` fields respectively. - `{ set: Schema }` - a set. The type of the elements is described by the `type` field. +- `{ enum: [{ className1: { struct: {...} } }, { className2: { struct: {...} } }, ... ] }` - an enum. The variants of the enum are described by the `className1`, `className2`, etc. fields. The variants are structs. - `{ struct: { field1: Schema1, field2: Schema2, ... } }` - a struct. The fields of the struct are described by the `field1`, `field2`, etc. fields. ### Type Mappings @@ -90,7 +78,7 @@ More complex objects are described by a JSON object. The following types are sup | `string` | UTF-8 string | | `type[]` | fixed-size byte array | | `type[]` | dynamic sized array | -| N/A | enum | +| `object` | enum | | `Map` | HashMap | | `Set` | HashSet | | `null` or `type` | Option | From b97b6e72a3d6f5555e8f9863862474267600dd6e Mon Sep 17 00:00:00 2001 From: gagdiez Date: Mon, 31 Jul 2023 14:57:20 +0200 Subject: [PATCH 30/35] feat: building cjs & esm --- .build_scripts/prepare-package-json.js | 29 +++++ borsh-ts/test/(de)serialize.test.js | 2 +- borsh-ts/test/base_decode.test.js | 2 +- borsh-ts/test/utils.test.js | 2 +- lib/{ => cjs}/buffer.d.ts | 0 lib/{ => cjs}/buffer.js | 2 +- lib/{ => cjs}/deserialize.d.ts | 0 lib/{ => cjs}/deserialize.js | 10 +- lib/{ => cjs}/index.d.ts | 0 lib/{ => cjs}/index.js | 6 +- lib/{ => cjs}/serialize.d.ts | 0 lib/{ => cjs}/serialize.js | 10 +- lib/{ => cjs}/types.d.ts | 0 lib/{ => cjs}/types.js | 2 +- lib/{ => cjs}/utils.d.ts | 0 lib/{ => cjs}/utils.js | 4 +- lib/esm/buffer.d.ts | 22 ++++ lib/esm/buffer.js | 66 ++++++++++ lib/esm/deserialize.d.ts | 19 +++ lib/esm/deserialize.js | 122 ++++++++++++++++++ lib/esm/index.d.ts | 6 + lib/esm/index.js | 32 +++++ lib/esm/package.json | 1 + lib/esm/serialize.d.ts | 22 ++++ lib/esm/serialize.js | 166 +++++++++++++++++++++++++ lib/esm/types.d.ts | 33 +++++ lib/esm/types.js | 1 + lib/esm/utils.d.ts | 10 ++ lib/esm/utils.js | 124 ++++++++++++++++++ lib/types/buffer.d.ts | 22 ++++ lib/types/deserialize.d.ts | 19 +++ lib/types/index.d.ts | 6 + lib/types/serialize.d.ts | 22 ++++ lib/types/types.d.ts | 33 +++++ lib/types/utils.d.ts | 10 ++ package.json | 19 ++- tsconfig.cjs.json | 7 ++ tsconfig.esm.json | 7 ++ tsconfig.json | 8 -- tsconfig.types.json | 8 ++ 40 files changed, 822 insertions(+), 32 deletions(-) create mode 100644 .build_scripts/prepare-package-json.js rename lib/{ => cjs}/buffer.d.ts (100%) rename lib/{ => cjs}/buffer.js (97%) rename lib/{ => cjs}/deserialize.d.ts (100%) rename lib/{ => cjs}/deserialize.js (94%) rename lib/{ => cjs}/index.d.ts (100%) rename lib/{ => cjs}/index.js (93%) rename lib/{ => cjs}/serialize.d.ts (100%) rename lib/{ => cjs}/serialize.js (96%) rename lib/{ => cjs}/types.d.ts (100%) rename lib/{ => cjs}/types.js (69%) rename lib/{ => cjs}/utils.d.ts (100%) rename lib/{ => cjs}/utils.js (97%) create mode 100644 lib/esm/buffer.d.ts create mode 100644 lib/esm/buffer.js create mode 100644 lib/esm/deserialize.d.ts create mode 100644 lib/esm/deserialize.js create mode 100644 lib/esm/index.d.ts create mode 100644 lib/esm/index.js create mode 100644 lib/esm/package.json create mode 100644 lib/esm/serialize.d.ts create mode 100644 lib/esm/serialize.js create mode 100644 lib/esm/types.d.ts create mode 100644 lib/esm/types.js create mode 100644 lib/esm/utils.d.ts create mode 100644 lib/esm/utils.js create mode 100644 lib/types/buffer.d.ts create mode 100644 lib/types/deserialize.d.ts create mode 100644 lib/types/index.d.ts create mode 100644 lib/types/serialize.d.ts create mode 100644 lib/types/types.d.ts create mode 100644 lib/types/utils.d.ts create mode 100644 tsconfig.cjs.json create mode 100644 tsconfig.esm.json create mode 100644 tsconfig.types.json diff --git a/.build_scripts/prepare-package-json.js b/.build_scripts/prepare-package-json.js new file mode 100644 index 00000000..398867fa --- /dev/null +++ b/.build_scripts/prepare-package-json.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); + +const buildDir = './lib'; +function createEsmModulePackageJson() { + fs.readdir(buildDir, function (err, dirs) { + if (err) { + throw err; + } + dirs.forEach(function (dir) { + if (dir === 'esm') { + var packageJsonFile = path.join(buildDir, dir, '/package.json'); + if (!fs.existsSync(packageJsonFile)) { + fs.writeFile( + packageJsonFile, + new Uint8Array(Buffer.from('{"type": "module"}')), + function (err) { + if (err) { + throw err; + } + } + ); + } + } + }); + }); +} + +createEsmModulePackageJson(); diff --git a/borsh-ts/test/(de)serialize.test.js b/borsh-ts/test/(de)serialize.test.js index 200a615f..f5d30357 100644 --- a/borsh-ts/test/(de)serialize.test.js +++ b/borsh-ts/test/(de)serialize.test.js @@ -1,5 +1,5 @@ const BN = require('bn.js'); -const borsh = require('../../lib/index'); +const borsh = require('../../lib/cjs/index'); const testStructures = require('./structures'); function check_encode(value, schema, expected) { diff --git a/borsh-ts/test/base_decode.test.js b/borsh-ts/test/base_decode.test.js index 85ef6f11..11d5e2b4 100644 --- a/borsh-ts/test/base_decode.test.js +++ b/borsh-ts/test/base_decode.test.js @@ -1,4 +1,4 @@ -const borsh = require('../../lib/index'); +const borsh = require('../../lib/cjs/index'); test('baseEncode string test', async () => { const encodedValue = borsh.baseEncode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); diff --git a/borsh-ts/test/utils.test.js b/borsh-ts/test/utils.test.js index abdf33c5..309ca116 100644 --- a/borsh-ts/test/utils.test.js +++ b/borsh-ts/test/utils.test.js @@ -1,4 +1,4 @@ -const utils = require('../../lib/utils'); +const utils = require('../../lib/cjs/utils'); test('accept valid schemes', async () => { const array = { array: { type: 'u8' } }; diff --git a/lib/buffer.d.ts b/lib/cjs/buffer.d.ts similarity index 100% rename from lib/buffer.d.ts rename to lib/cjs/buffer.d.ts diff --git a/lib/buffer.js b/lib/cjs/buffer.js similarity index 97% rename from lib/buffer.js rename to lib/cjs/buffer.js index f737262a..e65ce82d 100644 --- a/lib/buffer.js +++ b/lib/cjs/buffer.js @@ -1,5 +1,5 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.__esModule = true; exports.DecodeBuffer = exports.EncodeBuffer = void 0; var EncodeBuffer = /** @class */ (function () { function EncodeBuffer() { diff --git a/lib/deserialize.d.ts b/lib/cjs/deserialize.d.ts similarity index 100% rename from lib/deserialize.d.ts rename to lib/cjs/deserialize.d.ts diff --git a/lib/deserialize.js b/lib/cjs/deserialize.js similarity index 94% rename from lib/deserialize.js rename to lib/cjs/deserialize.js index 87977315..bce9d078 100644 --- a/lib/deserialize.js +++ b/lib/cjs/deserialize.js @@ -2,7 +2,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.__esModule = true; exports.BorshDeserializer = void 0; var types_1 = require("./types"); var buffer_1 = require("./buffer"); @@ -58,9 +58,9 @@ var BorshDeserializer = /** @class */ (function () { buffer[i] = v & 0xff; carry = v >> 8; } - return new bn_js_1.default(buffer, 'le').mul(new bn_js_1.default(-1)); + return new bn_js_1["default"](buffer, 'le').mul(new bn_js_1["default"](-1)); } - return new bn_js_1.default(buffer, 'le'); + return new bn_js_1["default"](buffer, 'le'); }; BorshDeserializer.prototype.decode_string = function () { var len = this.decode_integer('u32'); @@ -83,10 +83,10 @@ var BorshDeserializer = /** @class */ (function () { BorshDeserializer.prototype.decode_enum = function (schema) { var _a; var valueIndex = this.buffer.consume_value('u8'); - if (valueIndex > schema.enum.length) { + if (valueIndex > schema["enum"].length) { throw new Error("Enum option ".concat(valueIndex, " is not available")); } - var struct = schema.enum[valueIndex].struct; + var struct = schema["enum"][valueIndex].struct; var key = Object.keys(struct)[0]; return _a = {}, _a[key] = this.decode_value(struct[key]), _a; }; diff --git a/lib/index.d.ts b/lib/cjs/index.d.ts similarity index 100% rename from lib/index.d.ts rename to lib/cjs/index.d.ts diff --git a/lib/index.js b/lib/cjs/index.js similarity index 93% rename from lib/index.js rename to lib/cjs/index.js index 9f1a0cbb..b80d9b02 100644 --- a/lib/index.js +++ b/lib/cjs/index.js @@ -25,7 +25,7 @@ var __importStar = (this && this.__importStar) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.__esModule = true; exports.baseDecode = exports.baseEncode = exports.deserialize = exports.serialize = void 0; var serialize_1 = require("./serialize"); var deserialize_1 = require("./deserialize"); @@ -56,10 +56,10 @@ function baseEncode(value) { } value = new Uint8Array(bytes); } - return bs58_1.default.encode(value); + return bs58_1["default"].encode(value); } exports.baseEncode = baseEncode; function baseDecode(value) { - return new Uint8Array(bs58_1.default.decode(value)); + return new Uint8Array(bs58_1["default"].decode(value)); } exports.baseDecode = baseDecode; diff --git a/lib/serialize.d.ts b/lib/cjs/serialize.d.ts similarity index 100% rename from lib/serialize.d.ts rename to lib/cjs/serialize.d.ts diff --git a/lib/serialize.js b/lib/cjs/serialize.js similarity index 96% rename from lib/serialize.js rename to lib/cjs/serialize.js index ddad9878..c48f48bc 100644 --- a/lib/serialize.js +++ b/lib/cjs/serialize.js @@ -25,7 +25,7 @@ var __importStar = (this && this.__importStar) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.__esModule = true; exports.BorshSerializer = void 0; var types_1 = require("./types"); var buffer_1 = require("./buffer"); @@ -72,14 +72,14 @@ var BorshSerializer = /** @class */ (function () { } else { utils.expect_BN(value, this.fieldPath); - value = value instanceof bn_js_1.default ? value : new bn_js_1.default(value); + value = value instanceof bn_js_1["default"] ? value : new bn_js_1["default"](value); this.encode_bigint(value, size); } }; BorshSerializer.prototype.encode_bigint = function (value, size) { var buffer_len = size / 8; var buffer = value.toArray('le', buffer_len); - if (value.lt(new bn_js_1.default(0))) { + if (value.lt(new bn_js_1["default"](0))) { // compute two's complement var carry = 1; for (var i = 0; i < buffer_len; i++) { @@ -116,8 +116,8 @@ var BorshSerializer = /** @class */ (function () { BorshSerializer.prototype.encode_enum = function (value, schema) { utils.expect_enum(value, this.fieldPath); var valueKey = Object.keys(value)[0]; - for (var i = 0; i < schema.enum.length; i++) { - var valueSchema = schema.enum[i]; + for (var i = 0; i < schema["enum"].length; i++) { + var valueSchema = schema["enum"][i]; if (valueKey === Object.keys(valueSchema.struct)[0]) { this.encoded.store_value(i, 'u8'); return this.encode_struct(value, valueSchema); diff --git a/lib/types.d.ts b/lib/cjs/types.d.ts similarity index 100% rename from lib/types.d.ts rename to lib/cjs/types.d.ts diff --git a/lib/types.js b/lib/cjs/types.js similarity index 69% rename from lib/types.js rename to lib/cjs/types.js index 77e609f1..d1bc892a 100644 --- a/lib/types.js +++ b/lib/cjs/types.js @@ -1,4 +1,4 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.__esModule = true; exports.integers = void 0; exports.integers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; diff --git a/lib/utils.d.ts b/lib/cjs/utils.d.ts similarity index 100% rename from lib/utils.d.ts rename to lib/cjs/utils.d.ts diff --git a/lib/utils.js b/lib/cjs/utils.js similarity index 97% rename from lib/utils.js rename to lib/cjs/utils.js index 3a71898b..0e193fb7 100644 --- a/lib/utils.js +++ b/lib/cjs/utils.js @@ -17,7 +17,7 @@ var __extends = (this && this.__extends) || (function () { var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.__esModule = true; exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; var bn_js_1 = __importDefault(require("bn.js")); var types_1 = require("./types"); @@ -40,7 +40,7 @@ function expect_type(value, type, fieldPath) { } exports.expect_type = expect_type; function expect_BN(value, fieldPath) { - if (!(value instanceof bn_js_1.default) && typeof (value) !== 'number' && typeof (value) !== 'string') { + if (!(value instanceof bn_js_1["default"]) && typeof (value) !== 'number' && typeof (value) !== 'string') { throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } diff --git a/lib/esm/buffer.d.ts b/lib/esm/buffer.d.ts new file mode 100644 index 00000000..159e6233 --- /dev/null +++ b/lib/esm/buffer.d.ts @@ -0,0 +1,22 @@ +import { IntegerType } from './types'; +export declare class EncodeBuffer { + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + constructor(); + resize_if_necessary(needed_space: number): void; + get_used_buffer(): Uint8Array; + store_value(value: number, type: IntegerType): void; + store_bytes(from: Uint8Array): void; +} +export declare class DecodeBuffer { + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + constructor(buf: Uint8Array); + assert_enough_buffer(size: number): void; + consume_value(type: IntegerType): number; + consume_bytes(size: number): ArrayBuffer; +} diff --git a/lib/esm/buffer.js b/lib/esm/buffer.js new file mode 100644 index 00000000..d244a1fb --- /dev/null +++ b/lib/esm/buffer.js @@ -0,0 +1,66 @@ +var EncodeBuffer = /** @class */ (function () { + function EncodeBuffer() { + this.offset = 0; + this.buffer_size = 256; + this.buffer = new ArrayBuffer(this.buffer_size); + this.view = new DataView(this.buffer); + } + EncodeBuffer.prototype.resize_if_necessary = function (needed_space) { + if (this.buffer_size - this.offset < needed_space) { + this.buffer_size = Math.max(this.buffer_size * 2, this.buffer_size + needed_space); + var new_buffer = new ArrayBuffer(this.buffer_size); + new Uint8Array(new_buffer).set(new Uint8Array(this.buffer)); + this.buffer = new_buffer; + this.view = new DataView(new_buffer); + } + }; + EncodeBuffer.prototype.get_used_buffer = function () { + return new Uint8Array(this.buffer).slice(0, this.offset); + }; + EncodeBuffer.prototype.store_value = function (value, type) { + var bSize = type.substring(1); + var size = parseInt(bSize) / 8; + this.resize_if_necessary(size); + var toCall = type[0] === 'f' ? "setFloat".concat(bSize) : type[0] === 'i' ? "setInt".concat(bSize) : "setUint".concat(bSize); + this.view[toCall](this.offset, value, true); + this.offset += size; + }; + EncodeBuffer.prototype.store_bytes = function (from) { + this.resize_if_necessary(from.length); + new Uint8Array(this.buffer).set(new Uint8Array(from), this.offset); + this.offset += from.length; + }; + return EncodeBuffer; +}()); +export { EncodeBuffer }; +var DecodeBuffer = /** @class */ (function () { + function DecodeBuffer(buf) { + this.offset = 0; + this.buffer_size = buf.length; + this.buffer = new ArrayBuffer(buf.length); + new Uint8Array(this.buffer).set(buf); + this.view = new DataView(this.buffer); + } + DecodeBuffer.prototype.assert_enough_buffer = function (size) { + if (this.offset + size > this.buffer.byteLength) { + throw new Error('Error in schema, the buffer is smaller than expected'); + } + }; + DecodeBuffer.prototype.consume_value = function (type) { + var bSize = type.substring(1); + var size = parseInt(bSize) / 8; + this.assert_enough_buffer(size); + var toCall = type[0] === 'f' ? "getFloat".concat(bSize) : type[0] === 'i' ? "getInt".concat(bSize) : "getUint".concat(bSize); + var ret = this.view[toCall](this.offset, true); + this.offset += size; + return ret; + }; + DecodeBuffer.prototype.consume_bytes = function (size) { + this.assert_enough_buffer(size); + var ret = this.buffer.slice(this.offset, this.offset + size); + this.offset += size; + return ret; + }; + return DecodeBuffer; +}()); +export { DecodeBuffer }; diff --git a/lib/esm/deserialize.d.ts b/lib/esm/deserialize.d.ts new file mode 100644 index 00000000..bcc1e07f --- /dev/null +++ b/lib/esm/deserialize.d.ts @@ -0,0 +1,19 @@ +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; +import { DecodeBuffer } from './buffer'; +import BN from 'bn.js'; +export declare class BorshDeserializer { + buffer: DecodeBuffer; + constructor(bufferArray: Uint8Array); + decode(schema: Schema): DecodeTypes; + decode_value(schema: Schema): DecodeTypes; + decode_integer(schema: IntegerType): number | BN; + decode_bigint(size: number, signed?: boolean): BN; + decode_string(): string; + decode_boolean(): boolean; + decode_option(schema: OptionType): DecodeTypes; + decode_enum(schema: EnumType): DecodeTypes; + decode_array(schema: ArrayType): Array; + decode_set(schema: SetType): Set; + decode_map(schema: MapType): Map; + decode_struct(schema: StructType): object; +} diff --git a/lib/esm/deserialize.js b/lib/esm/deserialize.js new file mode 100644 index 00000000..121b92d0 --- /dev/null +++ b/lib/esm/deserialize.js @@ -0,0 +1,122 @@ +import { integers } from './types'; +import { DecodeBuffer } from './buffer'; +import BN from 'bn.js'; +var BorshDeserializer = /** @class */ (function () { + function BorshDeserializer(bufferArray) { + this.buffer = new DecodeBuffer(bufferArray); + } + BorshDeserializer.prototype.decode = function (schema) { + return this.decode_value(schema); + }; + BorshDeserializer.prototype.decode_value = function (schema) { + if (typeof schema === 'string') { + if (integers.includes(schema)) + return this.decode_integer(schema); + if (schema === 'string') + return this.decode_string(); + if (schema === 'bool') + return this.decode_boolean(); + } + if (typeof schema === 'object') { + if ('option' in schema) + return this.decode_option(schema); + if ('enum' in schema) + return this.decode_enum(schema); + if ('array' in schema) + return this.decode_array(schema); + if ('set' in schema) + return this.decode_set(schema); + if ('map' in schema) + return this.decode_map(schema); + if ('struct' in schema) + return this.decode_struct(schema); + } + throw new Error("Unsupported type: ".concat(schema)); + }; + BorshDeserializer.prototype.decode_integer = function (schema) { + var size = parseInt(schema.substring(1)); + if (size <= 32 || schema == 'f64') { + return this.buffer.consume_value(schema); + } + return this.decode_bigint(size, schema.startsWith('i')); + }; + BorshDeserializer.prototype.decode_bigint = function (size, signed) { + if (signed === void 0) { signed = false; } + var buffer_len = size / 8; + var buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + if (signed && buffer[buffer_len - 1]) { + // negative number + var carry = 1; + for (var i = 0; i < buffer_len; i++) { + var v = (buffer[i] ^ 0xff) + carry; + buffer[i] = v & 0xff; + carry = v >> 8; + } + return new BN(buffer, 'le').mul(new BN(-1)); + } + return new BN(buffer, 'le'); + }; + BorshDeserializer.prototype.decode_string = function () { + var len = this.decode_integer('u32'); + var buffer = new Uint8Array(this.buffer.consume_bytes(len)); + return String.fromCharCode.apply(null, buffer); + }; + BorshDeserializer.prototype.decode_boolean = function () { + return this.buffer.consume_value('u8') > 0; + }; + BorshDeserializer.prototype.decode_option = function (schema) { + var option = this.buffer.consume_value('u8'); + if (option === 1) { + return this.decode_value(schema.option); + } + if (option !== 0) { + throw new Error("Invalid option ".concat(option)); + } + return null; + }; + BorshDeserializer.prototype.decode_enum = function (schema) { + var _a; + var valueIndex = this.buffer.consume_value('u8'); + if (valueIndex > schema["enum"].length) { + throw new Error("Enum option ".concat(valueIndex, " is not available")); + } + var struct = schema["enum"][valueIndex].struct; + var key = Object.keys(struct)[0]; + return _a = {}, _a[key] = this.decode_value(struct[key]), _a; + }; + BorshDeserializer.prototype.decode_array = function (schema) { + var result = []; + var len = schema.array.len ? schema.array.len : this.decode_integer('u32'); + for (var i = 0; i < len; ++i) { + result.push(this.decode_value(schema.array.type)); + } + return result; + }; + BorshDeserializer.prototype.decode_set = function (schema) { + var len = this.decode_integer('u32'); + var result = new Set(); + for (var i = 0; i < len; ++i) { + result.add(this.decode_value(schema.set)); + } + return result; + }; + BorshDeserializer.prototype.decode_map = function (schema) { + var len = this.decode_integer('u32'); + var result = new Map(); + for (var i = 0; i < len; ++i) { + var key = this.decode_value(schema.map.key); + var value = this.decode_value(schema.map.value); + result.set(key, value); + } + return result; + }; + BorshDeserializer.prototype.decode_struct = function (schema) { + var result = {}; + for (var key in schema.struct) { + result[key] = this.decode_value(schema.struct[key]); + } + return result; + }; + return BorshDeserializer; +}()); +export { BorshDeserializer }; diff --git a/lib/esm/index.d.ts b/lib/esm/index.d.ts new file mode 100644 index 00000000..f7f12e25 --- /dev/null +++ b/lib/esm/index.d.ts @@ -0,0 +1,6 @@ +import { Schema, DecodeTypes } from './types'; +export { Schema } from './types'; +export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; +export declare function baseEncode(value: Uint8Array | string): string; +export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/esm/index.js b/lib/esm/index.js new file mode 100644 index 00000000..88b90653 --- /dev/null +++ b/lib/esm/index.js @@ -0,0 +1,32 @@ +import { BorshSerializer } from './serialize'; +import { BorshDeserializer } from './deserialize'; +import * as utils from './utils'; +import bs58 from 'bs58'; +export function serialize(schema, value, checkSchema) { + if (checkSchema === void 0) { checkSchema = true; } + if (checkSchema) + utils.validate_schema(schema); + var serializer = new BorshSerializer(); + return serializer.encode(value, schema); +} +export function deserialize(schema, buffer, checkSchema) { + if (checkSchema === void 0) { checkSchema = true; } + if (checkSchema) + utils.validate_schema(schema); + var deserializer = new BorshDeserializer(buffer); + return deserializer.decode(schema); +} +// Keeping this for compatibility reasons with the old borsh-js +export function baseEncode(value) { + if (typeof value === 'string') { + var bytes = []; + for (var c = 0; c < value.length; c++) { + bytes.push(value.charCodeAt(c)); + } + value = new Uint8Array(bytes); + } + return bs58.encode(value); +} +export function baseDecode(value) { + return new Uint8Array(bs58.decode(value)); +} diff --git a/lib/esm/package.json b/lib/esm/package.json new file mode 100644 index 00000000..1632c2c4 --- /dev/null +++ b/lib/esm/package.json @@ -0,0 +1 @@ +{"type": "module"} \ No newline at end of file diff --git a/lib/esm/serialize.d.ts b/lib/esm/serialize.d.ts new file mode 100644 index 00000000..374154a9 --- /dev/null +++ b/lib/esm/serialize.d.ts @@ -0,0 +1,22 @@ +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; +import { EncodeBuffer } from './buffer'; +import BN from 'bn.js'; +export declare class BorshSerializer { + encoded: EncodeBuffer; + fieldPath: string[]; + constructor(); + encode(value: unknown, schema: Schema): Uint8Array; + encode_value(value: unknown, schema: Schema): void; + encode_integer(value: unknown, schema: IntegerType): void; + encode_bigint(value: BN, size: number): void; + encode_string(value: unknown): void; + encode_boolean(value: unknown): void; + encode_option(value: unknown, schema: OptionType): void; + encode_enum(value: unknown, schema: EnumType): void; + encode_array(value: unknown, schema: ArrayType): void; + encode_arraylike(value: ArrayLike, schema: ArrayType): void; + encode_buffer(value: ArrayBuffer, schema: ArrayType): void; + encode_set(value: unknown, schema: SetType): void; + encode_map(value: unknown, schema: MapType): void; + encode_struct(value: unknown, schema: StructType): void; +} diff --git a/lib/esm/serialize.js b/lib/esm/serialize.js new file mode 100644 index 00000000..e98f2d76 --- /dev/null +++ b/lib/esm/serialize.js @@ -0,0 +1,166 @@ +import { integers } from './types'; +import { EncodeBuffer } from './buffer'; +import BN from 'bn.js'; +import * as utils from './utils'; +var BorshSerializer = /** @class */ (function () { + function BorshSerializer() { + this.encoded = new EncodeBuffer(); + this.fieldPath = ['value']; + } + BorshSerializer.prototype.encode = function (value, schema) { + this.encode_value(value, schema); + return this.encoded.get_used_buffer(); + }; + BorshSerializer.prototype.encode_value = function (value, schema) { + if (typeof schema === 'string') { + if (integers.includes(schema)) + return this.encode_integer(value, schema); + if (schema === 'string') + return this.encode_string(value); + if (schema === 'bool') + return this.encode_boolean(value); + } + if (typeof schema === 'object') { + if ('option' in schema) + return this.encode_option(value, schema); + if ('enum' in schema) + return this.encode_enum(value, schema); + if ('array' in schema) + return this.encode_array(value, schema); + if ('set' in schema) + return this.encode_set(value, schema); + if ('map' in schema) + return this.encode_map(value, schema); + if ('struct' in schema) + return this.encode_struct(value, schema); + } + }; + BorshSerializer.prototype.encode_integer = function (value, schema) { + var size = parseInt(schema.substring(1)); + if (size <= 32 || schema == 'f64') { + utils.expect_type(value, 'number', this.fieldPath); + this.encoded.store_value(value, schema); + } + else { + utils.expect_BN(value, this.fieldPath); + value = value instanceof BN ? value : new BN(value); + this.encode_bigint(value, size); + } + }; + BorshSerializer.prototype.encode_bigint = function (value, size) { + var buffer_len = size / 8; + var buffer = value.toArray('le', buffer_len); + if (value.lt(new BN(0))) { + // compute two's complement + var carry = 1; + for (var i = 0; i < buffer_len; i++) { + var v = (buffer[i] ^ 0xff) + carry; + carry = v >> 8; + buffer[i] = v & 0xff; + } + } + this.encoded.store_bytes(new Uint8Array(buffer)); + }; + BorshSerializer.prototype.encode_string = function (value) { + utils.expect_type(value, 'string', this.fieldPath); + var _value = value; + // 4 bytes for length + this.encoded.store_value(_value.length, 'u32'); + // string bytes + for (var i = 0; i < _value.length; i++) { + this.encoded.store_value(_value.charCodeAt(i), 'u8'); + } + }; + BorshSerializer.prototype.encode_boolean = function (value) { + utils.expect_type(value, 'boolean', this.fieldPath); + this.encoded.store_value(value ? 1 : 0, 'u8'); + }; + BorshSerializer.prototype.encode_option = function (value, schema) { + if (value === null || value === undefined) { + this.encoded.store_value(0, 'u8'); + } + else { + this.encoded.store_value(1, 'u8'); + this.encode_value(value, schema.option); + } + }; + BorshSerializer.prototype.encode_enum = function (value, schema) { + utils.expect_enum(value, this.fieldPath); + var valueKey = Object.keys(value)[0]; + for (var i = 0; i < schema["enum"].length; i++) { + var valueSchema = schema["enum"][i]; + if (valueKey === Object.keys(valueSchema.struct)[0]) { + this.encoded.store_value(i, 'u8'); + return this.encode_struct(value, valueSchema); + } + } + throw new Error("Enum key (".concat(valueKey, ") not found in enum schema: ").concat(JSON.stringify(schema), " at ").concat(this.fieldPath.join('.'))); + }; + BorshSerializer.prototype.encode_array = function (value, schema) { + if (utils.isArrayLike(value)) + return this.encode_arraylike(value, schema); + if (value instanceof ArrayBuffer) + return this.encode_buffer(value, schema); + throw new Error("Expected Array-like not ".concat(typeof (value), "(").concat(value, ") at ").concat(this.fieldPath.join('.'))); + }; + BorshSerializer.prototype.encode_arraylike = function (value, schema) { + if (schema.array.len) { + utils.expect_same_size(value.length, schema.array.len, this.fieldPath); + } + else { + // 4 bytes for length + this.encoded.store_value(value.length, 'u32'); + } + // array values + for (var i = 0; i < value.length; i++) { + this.encode_value(value[i], schema.array.type); + } + }; + BorshSerializer.prototype.encode_buffer = function (value, schema) { + if (schema.array.len) { + utils.expect_same_size(value.byteLength, schema.array.len, this.fieldPath); + } + else { + // 4 bytes for length + this.encoded.store_value(value.byteLength, 'u32'); + } + // array values + this.encoded.store_bytes(new Uint8Array(value)); + }; + BorshSerializer.prototype.encode_set = function (value, schema) { + utils.expect_type(value, 'object', this.fieldPath); + var isSet = value instanceof Set; + var values = isSet ? Array.from(value.values()) : Object.values(value); + // 4 bytes for length + this.encoded.store_value(values.length, 'u32'); + // set values + for (var _i = 0, values_1 = values; _i < values_1.length; _i++) { + var value_1 = values_1[_i]; + this.encode_value(value_1, schema.set); + } + }; + BorshSerializer.prototype.encode_map = function (value, schema) { + utils.expect_type(value, 'object', this.fieldPath); + var isMap = value instanceof Map; + var keys = isMap ? Array.from(value.keys()) : Object.keys(value); + // 4 bytes for length + this.encoded.store_value(keys.length, 'u32'); + // store key/values + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; + this.encode_value(key, schema.map.key); + this.encode_value(isMap ? value.get(key) : value[key], schema.map.value); + } + }; + BorshSerializer.prototype.encode_struct = function (value, schema) { + utils.expect_type(value, 'object', this.fieldPath); + for (var _i = 0, _a = Object.keys(schema.struct); _i < _a.length; _i++) { + var key = _a[_i]; + this.fieldPath.push(key); + this.encode_value(value[key], schema.struct[key]); + this.fieldPath.pop(); + } + }; + return BorshSerializer; +}()); +export { BorshSerializer }; diff --git a/lib/esm/types.d.ts b/lib/esm/types.d.ts new file mode 100644 index 00000000..4ee12ea0 --- /dev/null +++ b/lib/esm/types.d.ts @@ -0,0 +1,33 @@ +import BN from 'bn.js'; +export declare const integers: string[]; +export type IntegerType = typeof integers[number]; +export type BoolType = 'bool'; +export type StringType = 'string'; +export type OptionType = { + option: Schema; +}; +export type ArrayType = { + array: { + type: Schema; + len?: number; + }; +}; +export type EnumType = { + enum: Array; +}; +export type SetType = { + set: Schema; +}; +export type MapType = { + map: { + key: Schema; + value: Schema; + }; +}; +export type StructType = { + struct: { + [key: string]: Schema; + }; +}; +export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; +export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/lib/esm/types.js b/lib/esm/types.js new file mode 100644 index 00000000..4d4e9331 --- /dev/null +++ b/lib/esm/types.js @@ -0,0 +1 @@ +export var integers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; diff --git a/lib/esm/utils.d.ts b/lib/esm/utils.d.ts new file mode 100644 index 00000000..56f6a787 --- /dev/null +++ b/lib/esm/utils.d.ts @@ -0,0 +1,10 @@ +import { Schema } from './types'; +export declare function isArrayLike(value: unknown): boolean; +export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; +export declare function expect_BN(value: unknown, fieldPath: string[]): void; +export declare function expect_same_size(length: number, expected: number, fieldPath: string[]): void; +export declare function expect_enum(value: unknown, fieldPath: string[]): void; +export declare class ErrorSchema extends Error { + constructor(schema: Schema, expected: string); +} +export declare function validate_schema(schema: Schema): void; diff --git a/lib/esm/utils.js b/lib/esm/utils.js new file mode 100644 index 00000000..59591dbf --- /dev/null +++ b/lib/esm/utils.js @@ -0,0 +1,124 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +import BN from 'bn.js'; +import { integers } from './types'; +export function isArrayLike(value) { + // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like + return (Array.isArray(value) || + (!!value && + typeof value === 'object' && + 'length' in value && + typeof (value.length) === 'number' && + (value.length === 0 || + (value.length > 0 && + (value.length - 1) in value)))); +} +export function expect_type(value, type, fieldPath) { + if (typeof (value) !== type) { + throw new Error("Expected ".concat(type, " not ").concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); + } +} +export function expect_BN(value, fieldPath) { + if (!(value instanceof BN) && typeof (value) !== 'number' && typeof (value) !== 'string') { + throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); + } +} +export function expect_same_size(length, expected, fieldPath) { + if (length !== expected) { + throw new Error("Array length ".concat(length, " does not match schema length ").concat(expected, " at ").concat(fieldPath.join('.'))); + } +} +export function expect_enum(value, fieldPath) { + if (typeof (value) !== 'object' || value === null) { + throw new Error("Expected object not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); + } +} +// Validate Schema +var VALID_STRING_TYPES = integers.concat(['bool', 'string']); +var VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct']; +var ErrorSchema = /** @class */ (function (_super) { + __extends(ErrorSchema, _super); + function ErrorSchema(schema, expected) { + var message = "Invalid schema: ".concat(JSON.stringify(schema), " expected ").concat(expected); + return _super.call(this, message) || this; + } + return ErrorSchema; +}(Error)); +export { ErrorSchema }; +export function validate_schema(schema) { + if (typeof (schema) === 'string' && VALID_STRING_TYPES.includes(schema)) { + return; + } + if (schema && typeof (schema) === 'object') { + var keys = Object.keys(schema); + if (keys.length === 1 && VALID_OBJECT_KEYS.includes(keys[0])) { + var key = keys[0]; + if (key === 'option') + return validate_schema(schema[key]); + if (key === 'enum') + return validate_enum_schema(schema[key]); + if (key === 'array') + return validate_array_schema(schema[key]); + if (key === 'set') + return validate_schema(schema[key]); + if (key === 'map') + return validate_map_schema(schema[key]); + if (key === 'struct') + return validate_struct_schema(schema[key]); + } + } + throw new ErrorSchema(schema, VALID_OBJECT_KEYS.join(', ') + ' or ' + VALID_STRING_TYPES.join(', ')); +} +function validate_enum_schema(schema) { + if (!Array.isArray(schema)) + throw new ErrorSchema(schema, 'Array'); + for (var _i = 0, schema_1 = schema; _i < schema_1.length; _i++) { + var sch = schema_1[_i]; + if (typeof sch !== 'object' || !('struct' in sch)) { + throw new Error('Missing "struct" key in enum schema'); + } + if (typeof sch.struct !== 'object' || Object.keys(sch.struct).length !== 1) { + throw new Error('The "struct" in each enum must have a single key'); + } + validate_schema({ struct: sch.struct }); + } +} +function validate_array_schema(schema) { + if (typeof schema !== 'object') + throw new ErrorSchema(schema, '{ type, len? }'); + if (schema.len && typeof schema.len !== 'number') { + throw new Error("Invalid schema: ".concat(schema)); + } + if ('type' in schema) + return validate_schema(schema.type); + throw new ErrorSchema(schema, '{ type, len? }'); +} +function validate_map_schema(schema) { + if (typeof schema === 'object' && 'key' in schema && 'value' in schema) { + validate_schema(schema.key); + validate_schema(schema.value); + } + else { + throw new ErrorSchema(schema, '{ key, value }'); + } +} +function validate_struct_schema(schema) { + if (typeof schema !== 'object') + throw new ErrorSchema(schema, 'object'); + for (var key in schema) { + validate_schema(schema[key]); + } +} diff --git a/lib/types/buffer.d.ts b/lib/types/buffer.d.ts new file mode 100644 index 00000000..159e6233 --- /dev/null +++ b/lib/types/buffer.d.ts @@ -0,0 +1,22 @@ +import { IntegerType } from './types'; +export declare class EncodeBuffer { + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + constructor(); + resize_if_necessary(needed_space: number): void; + get_used_buffer(): Uint8Array; + store_value(value: number, type: IntegerType): void; + store_bytes(from: Uint8Array): void; +} +export declare class DecodeBuffer { + offset: number; + buffer_size: number; + buffer: ArrayBuffer; + view: DataView; + constructor(buf: Uint8Array); + assert_enough_buffer(size: number): void; + consume_value(type: IntegerType): number; + consume_bytes(size: number): ArrayBuffer; +} diff --git a/lib/types/deserialize.d.ts b/lib/types/deserialize.d.ts new file mode 100644 index 00000000..bcc1e07f --- /dev/null +++ b/lib/types/deserialize.d.ts @@ -0,0 +1,19 @@ +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; +import { DecodeBuffer } from './buffer'; +import BN from 'bn.js'; +export declare class BorshDeserializer { + buffer: DecodeBuffer; + constructor(bufferArray: Uint8Array); + decode(schema: Schema): DecodeTypes; + decode_value(schema: Schema): DecodeTypes; + decode_integer(schema: IntegerType): number | BN; + decode_bigint(size: number, signed?: boolean): BN; + decode_string(): string; + decode_boolean(): boolean; + decode_option(schema: OptionType): DecodeTypes; + decode_enum(schema: EnumType): DecodeTypes; + decode_array(schema: ArrayType): Array; + decode_set(schema: SetType): Set; + decode_map(schema: MapType): Map; + decode_struct(schema: StructType): object; +} diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts new file mode 100644 index 00000000..f7f12e25 --- /dev/null +++ b/lib/types/index.d.ts @@ -0,0 +1,6 @@ +import { Schema, DecodeTypes } from './types'; +export { Schema } from './types'; +export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; +export declare function baseEncode(value: Uint8Array | string): string; +export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/types/serialize.d.ts b/lib/types/serialize.d.ts new file mode 100644 index 00000000..374154a9 --- /dev/null +++ b/lib/types/serialize.d.ts @@ -0,0 +1,22 @@ +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; +import { EncodeBuffer } from './buffer'; +import BN from 'bn.js'; +export declare class BorshSerializer { + encoded: EncodeBuffer; + fieldPath: string[]; + constructor(); + encode(value: unknown, schema: Schema): Uint8Array; + encode_value(value: unknown, schema: Schema): void; + encode_integer(value: unknown, schema: IntegerType): void; + encode_bigint(value: BN, size: number): void; + encode_string(value: unknown): void; + encode_boolean(value: unknown): void; + encode_option(value: unknown, schema: OptionType): void; + encode_enum(value: unknown, schema: EnumType): void; + encode_array(value: unknown, schema: ArrayType): void; + encode_arraylike(value: ArrayLike, schema: ArrayType): void; + encode_buffer(value: ArrayBuffer, schema: ArrayType): void; + encode_set(value: unknown, schema: SetType): void; + encode_map(value: unknown, schema: MapType): void; + encode_struct(value: unknown, schema: StructType): void; +} diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts new file mode 100644 index 00000000..4ee12ea0 --- /dev/null +++ b/lib/types/types.d.ts @@ -0,0 +1,33 @@ +import BN from 'bn.js'; +export declare const integers: string[]; +export type IntegerType = typeof integers[number]; +export type BoolType = 'bool'; +export type StringType = 'string'; +export type OptionType = { + option: Schema; +}; +export type ArrayType = { + array: { + type: Schema; + len?: number; + }; +}; +export type EnumType = { + enum: Array; +}; +export type SetType = { + set: Schema; +}; +export type MapType = { + map: { + key: Schema; + value: Schema; + }; +}; +export type StructType = { + struct: { + [key: string]: Schema; + }; +}; +export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; +export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/lib/types/utils.d.ts b/lib/types/utils.d.ts new file mode 100644 index 00000000..56f6a787 --- /dev/null +++ b/lib/types/utils.d.ts @@ -0,0 +1,10 @@ +import { Schema } from './types'; +export declare function isArrayLike(value: unknown): boolean; +export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; +export declare function expect_BN(value: unknown, fieldPath: string[]): void; +export declare function expect_same_size(length: number, expected: number, fieldPath: string[]): void; +export declare function expect_enum(value: unknown, fieldPath: string[]): void; +export declare class ErrorSchema extends Error { + constructor(schema: Schema, expected: string); +} +export declare function validate_schema(schema: Schema): void; diff --git a/package.json b/package.json index 3a3718c5..da6b7302 100644 --- a/package.json +++ b/package.json @@ -2,19 +2,30 @@ "name": "borsh", "version": "1.0.1", "description": "Binary Object Representation Serializer for Hashing", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "./lib/cjs/index.js", + "module": "./lib/esm/index.js", + "types": "./lib/types/index.d.ts", "files": [ "lib", "LICENSE-APACHE", "LICENSE-MIT.txt" ], "scripts": { - "build": "tsc -p ./tsconfig.json", "test": "jest test --runInBand", "pretest": "yarn build", "lint": "eslint borsh-ts/**/*.ts", - "fix": "eslint borsh-ts/**/*.ts --fix" + "fix": "eslint borsh-ts/**/*.ts --fix", + "compile": "tsc -b ./tsconfig.cjs.json ./tsconfig.esm.json ./tsconfig.types.json", + "build:clean": "rm -rf ./lib", + "build": "npm run build:clean && npm run compile && node .build_scripts/prepare-package-json.js" + }, + "exports": { + ".": { + "types": "./lib/types/lib.d.ts", + "require": "./lib/cjs/lib.js", + "import": "./lib/esm/lib.js", + "default": "./lib/esm/lib.js" + } }, "repository": { "type": "git", diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 00000000..ec2cc5f5 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib/cjs", + "module": "commonjs" + } +} \ No newline at end of file diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 00000000..d3cbe916 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib/esm", + "module": "esnext" + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c1f9157f..7550b042 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,7 @@ { "compilerOptions": { "esModuleInterop": true, - "lib": [ - "es2015", - "esnext", - "dom" - ], - "module": "commonjs", - "target": "ES5", "moduleResolution": "node", - "outDir": "./lib", "declaration": true, "preserveSymlinks": true, "preserveWatchOutput": true, diff --git a/tsconfig.types.json b/tsconfig.types.json new file mode 100644 index 00000000..4cc6da97 --- /dev/null +++ b/tsconfig.types.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib/types", + "declaration": true, + "emitDeclarationOnly": true + } +} From dbebcd726d86c7657e362644b00790f5cfd02f20 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Tue, 1 Aug 2023 14:28:50 +0200 Subject: [PATCH 31/35] feat: cjs & esm working versions --- borsh-ts/buffer.ts | 2 +- borsh-ts/deserialize.ts | 4 ++-- borsh-ts/index.ts | 8 ++++---- borsh-ts/serialize.ts | 6 +++--- borsh-ts/utils.ts | 2 +- examples/cjs/index.js | 9 +++++++++ examples/cjs/package.json | 10 ++++++++++ examples/cjs/tsconfig.json | 11 +++++++++++ examples/esm/index.js | 9 +++++++++ examples/esm/package.json | 11 +++++++++++ examples/esm/tsconfig.json | 11 +++++++++++ lib/cjs/buffer.d.ts | 2 +- lib/cjs/deserialize.d.ts | 4 ++-- lib/cjs/deserialize.js | 8 ++++---- lib/cjs/index.d.ts | 2 +- lib/cjs/index.js | 10 +++++----- lib/cjs/serialize.d.ts | 4 ++-- lib/cjs/serialize.js | 10 +++++----- lib/cjs/utils.d.ts | 2 +- lib/cjs/utils.js | 4 ++-- lib/esm/buffer.d.ts | 2 +- lib/esm/deserialize.d.ts | 4 ++-- lib/esm/deserialize.js | 4 ++-- lib/esm/index.d.ts | 2 +- lib/esm/index.js | 6 +++--- lib/esm/serialize.d.ts | 4 ++-- lib/esm/serialize.js | 6 +++--- lib/esm/utils.d.ts | 2 +- lib/esm/utils.js | 2 +- lib/types/buffer.d.ts | 2 +- lib/types/deserialize.d.ts | 4 ++-- lib/types/index.d.ts | 2 +- lib/types/serialize.d.ts | 4 ++-- lib/types/utils.d.ts | 2 +- package.json | 8 ++++---- 35 files changed, 122 insertions(+), 61 deletions(-) create mode 100644 examples/cjs/index.js create mode 100644 examples/cjs/package.json create mode 100644 examples/cjs/tsconfig.json create mode 100644 examples/esm/index.js create mode 100644 examples/esm/package.json create mode 100644 examples/esm/tsconfig.json diff --git a/borsh-ts/buffer.ts b/borsh-ts/buffer.ts index ddb20a5e..79b0b984 100644 --- a/borsh-ts/buffer.ts +++ b/borsh-ts/buffer.ts @@ -1,4 +1,4 @@ -import { IntegerType } from './types'; +import { IntegerType } from './types.js'; export class EncodeBuffer { offset: number; diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index c6c5d22e..807cb67a 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -1,5 +1,5 @@ -import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types'; -import { DecodeBuffer } from './buffer'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types.js'; +import { DecodeBuffer } from './buffer.js'; import BN from 'bn.js'; export class BorshDeserializer { diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 55f78962..7f5971d7 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -1,7 +1,7 @@ -import { Schema, DecodeTypes } from './types'; -import { BorshSerializer } from './serialize'; -import { BorshDeserializer } from './deserialize'; -import * as utils from './utils'; +import { Schema, DecodeTypes } from './types.js'; +import { BorshSerializer } from './serialize.js'; +import { BorshDeserializer } from './deserialize.js'; +import * as utils from './utils.js'; import bs58 from 'bs58'; export { Schema } from './types'; diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index a0d92f71..648d142a 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -1,7 +1,7 @@ -import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types'; -import { EncodeBuffer } from './buffer'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types.js'; +import { EncodeBuffer } from './buffer.js'; import BN from 'bn.js'; -import * as utils from './utils'; +import * as utils from './utils.js'; export class BorshSerializer { encoded: EncodeBuffer; diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 744a353e..656dc2d3 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -1,5 +1,5 @@ import BN from 'bn.js'; -import { Schema, StructType, integers } from './types'; +import { Schema, StructType, integers } from './types.js'; export function isArrayLike(value: unknown): boolean { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like diff --git a/examples/cjs/index.js b/examples/cjs/index.js new file mode 100644 index 00000000..03f08df4 --- /dev/null +++ b/examples/cjs/index.js @@ -0,0 +1,9 @@ +const borsh = require('borsh-js'); + +const encodedU16 = borsh.serialize('u16', 2); +const decodedU16 = borsh.deserialize('u16', encodedU16); +console.log(decodedU16); + +const encodedStr = borsh.serialize('string', 'testing'); +const decodedStr = borsh.deserialize('string', encodedStr); +console.log(decodedStr); diff --git a/examples/cjs/package.json b/examples/cjs/package.json new file mode 100644 index 00000000..5acfda80 --- /dev/null +++ b/examples/cjs/package.json @@ -0,0 +1,10 @@ +{ + "name": "cjs-example", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "borsh-js": "file:../../" + } +} \ No newline at end of file diff --git a/examples/cjs/tsconfig.json b/examples/cjs/tsconfig.json new file mode 100644 index 00000000..a0e17040 --- /dev/null +++ b/examples/cjs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2015" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "module": "commonjs" /* Specify what module code is generated. */, + "outDir": "./build" /* Specify an output folder for all emitted files. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "strict": true /* Enable all strict type-checking options. */, + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} \ No newline at end of file diff --git a/examples/esm/index.js b/examples/esm/index.js new file mode 100644 index 00000000..4301e1d9 --- /dev/null +++ b/examples/esm/index.js @@ -0,0 +1,9 @@ +import * as borsh from 'borsh-js'; + +const encodedU16 = borsh.serialize('u16', 2); +const decodedU16 = borsh.deserialize('u16', encodedU16); +console.log(decodedU16); + +const encodedStr = borsh.serialize('string', 'testing'); +const decodedStr = borsh.deserialize('string', encodedStr); +console.log(decodedStr); diff --git a/examples/esm/package.json b/examples/esm/package.json new file mode 100644 index 00000000..4a1e776b --- /dev/null +++ b/examples/esm/package.json @@ -0,0 +1,11 @@ +{ + "name": "esm-example", + "private": true, + "version": "1.0.0", + "description": "", + "type": "module", + "main": "index.js", + "dependencies": { + "borsh-js": "file:../../" + } +} \ No newline at end of file diff --git a/examples/esm/tsconfig.json b/examples/esm/tsconfig.json new file mode 100644 index 00000000..176365da --- /dev/null +++ b/examples/esm/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2015" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "module": "esnext" /* Specify what module code is generated. */, + "outDir": "./build" /* Specify an output folder for all emitted files. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "strict": true /* Enable all strict type-checking options. */, + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} \ No newline at end of file diff --git a/lib/cjs/buffer.d.ts b/lib/cjs/buffer.d.ts index 159e6233..c850d11e 100644 --- a/lib/cjs/buffer.d.ts +++ b/lib/cjs/buffer.d.ts @@ -1,4 +1,4 @@ -import { IntegerType } from './types'; +import { IntegerType } from './types.js'; export declare class EncodeBuffer { offset: number; buffer_size: number; diff --git a/lib/cjs/deserialize.d.ts b/lib/cjs/deserialize.d.ts index bcc1e07f..3acdfd97 100644 --- a/lib/cjs/deserialize.d.ts +++ b/lib/cjs/deserialize.d.ts @@ -1,5 +1,5 @@ -import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; -import { DecodeBuffer } from './buffer'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; +import { DecodeBuffer } from './buffer.js'; import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; diff --git a/lib/cjs/deserialize.js b/lib/cjs/deserialize.js index bce9d078..2ad3eae8 100644 --- a/lib/cjs/deserialize.js +++ b/lib/cjs/deserialize.js @@ -4,19 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; exports.__esModule = true; exports.BorshDeserializer = void 0; -var types_1 = require("./types"); -var buffer_1 = require("./buffer"); +var types_js_1 = require("./types.js"); +var buffer_js_1 = require("./buffer.js"); var bn_js_1 = __importDefault(require("bn.js")); var BorshDeserializer = /** @class */ (function () { function BorshDeserializer(bufferArray) { - this.buffer = new buffer_1.DecodeBuffer(bufferArray); + this.buffer = new buffer_js_1.DecodeBuffer(bufferArray); } BorshDeserializer.prototype.decode = function (schema) { return this.decode_value(schema); }; BorshDeserializer.prototype.decode_value = function (schema) { if (typeof schema === 'string') { - if (types_1.integers.includes(schema)) + if (types_js_1.integers.includes(schema)) return this.decode_integer(schema); if (schema === 'string') return this.decode_string(); diff --git a/lib/cjs/index.d.ts b/lib/cjs/index.d.ts index f7f12e25..2c5203f1 100644 --- a/lib/cjs/index.d.ts +++ b/lib/cjs/index.d.ts @@ -1,4 +1,4 @@ -import { Schema, DecodeTypes } from './types'; +import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; diff --git a/lib/cjs/index.js b/lib/cjs/index.js index b80d9b02..fe190922 100644 --- a/lib/cjs/index.js +++ b/lib/cjs/index.js @@ -27,15 +27,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; exports.__esModule = true; exports.baseDecode = exports.baseEncode = exports.deserialize = exports.serialize = void 0; -var serialize_1 = require("./serialize"); -var deserialize_1 = require("./deserialize"); -var utils = __importStar(require("./utils")); +var serialize_js_1 = require("./serialize.js"); +var deserialize_js_1 = require("./deserialize.js"); +var utils = __importStar(require("./utils.js")); var bs58_1 = __importDefault(require("bs58")); function serialize(schema, value, checkSchema) { if (checkSchema === void 0) { checkSchema = true; } if (checkSchema) utils.validate_schema(schema); - var serializer = new serialize_1.BorshSerializer(); + var serializer = new serialize_js_1.BorshSerializer(); return serializer.encode(value, schema); } exports.serialize = serialize; @@ -43,7 +43,7 @@ function deserialize(schema, buffer, checkSchema) { if (checkSchema === void 0) { checkSchema = true; } if (checkSchema) utils.validate_schema(schema); - var deserializer = new deserialize_1.BorshDeserializer(buffer); + var deserializer = new deserialize_js_1.BorshDeserializer(buffer); return deserializer.decode(schema); } exports.deserialize = deserialize; diff --git a/lib/cjs/serialize.d.ts b/lib/cjs/serialize.d.ts index 374154a9..7ac22859 100644 --- a/lib/cjs/serialize.d.ts +++ b/lib/cjs/serialize.d.ts @@ -1,5 +1,5 @@ -import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; -import { EncodeBuffer } from './buffer'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; +import { EncodeBuffer } from './buffer.js'; import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; diff --git a/lib/cjs/serialize.js b/lib/cjs/serialize.js index c48f48bc..2163e2e4 100644 --- a/lib/cjs/serialize.js +++ b/lib/cjs/serialize.js @@ -27,13 +27,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; exports.__esModule = true; exports.BorshSerializer = void 0; -var types_1 = require("./types"); -var buffer_1 = require("./buffer"); +var types_js_1 = require("./types.js"); +var buffer_js_1 = require("./buffer.js"); var bn_js_1 = __importDefault(require("bn.js")); -var utils = __importStar(require("./utils")); +var utils = __importStar(require("./utils.js")); var BorshSerializer = /** @class */ (function () { function BorshSerializer() { - this.encoded = new buffer_1.EncodeBuffer(); + this.encoded = new buffer_js_1.EncodeBuffer(); this.fieldPath = ['value']; } BorshSerializer.prototype.encode = function (value, schema) { @@ -42,7 +42,7 @@ var BorshSerializer = /** @class */ (function () { }; BorshSerializer.prototype.encode_value = function (value, schema) { if (typeof schema === 'string') { - if (types_1.integers.includes(schema)) + if (types_js_1.integers.includes(schema)) return this.encode_integer(value, schema); if (schema === 'string') return this.encode_string(value); diff --git a/lib/cjs/utils.d.ts b/lib/cjs/utils.d.ts index 56f6a787..e9ee504a 100644 --- a/lib/cjs/utils.d.ts +++ b/lib/cjs/utils.d.ts @@ -1,4 +1,4 @@ -import { Schema } from './types'; +import { Schema } from './types.js'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; export declare function expect_BN(value: unknown, fieldPath: string[]): void; diff --git a/lib/cjs/utils.js b/lib/cjs/utils.js index 0e193fb7..cb5ccb22 100644 --- a/lib/cjs/utils.js +++ b/lib/cjs/utils.js @@ -20,7 +20,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { exports.__esModule = true; exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; var bn_js_1 = __importDefault(require("bn.js")); -var types_1 = require("./types"); +var types_js_1 = require("./types.js"); function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like return (Array.isArray(value) || @@ -58,7 +58,7 @@ function expect_enum(value, fieldPath) { } exports.expect_enum = expect_enum; // Validate Schema -var VALID_STRING_TYPES = types_1.integers.concat(['bool', 'string']); +var VALID_STRING_TYPES = types_js_1.integers.concat(['bool', 'string']); var VALID_OBJECT_KEYS = ['option', 'enum', 'array', 'set', 'map', 'struct']; var ErrorSchema = /** @class */ (function (_super) { __extends(ErrorSchema, _super); diff --git a/lib/esm/buffer.d.ts b/lib/esm/buffer.d.ts index 159e6233..c850d11e 100644 --- a/lib/esm/buffer.d.ts +++ b/lib/esm/buffer.d.ts @@ -1,4 +1,4 @@ -import { IntegerType } from './types'; +import { IntegerType } from './types.js'; export declare class EncodeBuffer { offset: number; buffer_size: number; diff --git a/lib/esm/deserialize.d.ts b/lib/esm/deserialize.d.ts index bcc1e07f..3acdfd97 100644 --- a/lib/esm/deserialize.d.ts +++ b/lib/esm/deserialize.d.ts @@ -1,5 +1,5 @@ -import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; -import { DecodeBuffer } from './buffer'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; +import { DecodeBuffer } from './buffer.js'; import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; diff --git a/lib/esm/deserialize.js b/lib/esm/deserialize.js index 121b92d0..46efa0b2 100644 --- a/lib/esm/deserialize.js +++ b/lib/esm/deserialize.js @@ -1,5 +1,5 @@ -import { integers } from './types'; -import { DecodeBuffer } from './buffer'; +import { integers } from './types.js'; +import { DecodeBuffer } from './buffer.js'; import BN from 'bn.js'; var BorshDeserializer = /** @class */ (function () { function BorshDeserializer(bufferArray) { diff --git a/lib/esm/index.d.ts b/lib/esm/index.d.ts index f7f12e25..2c5203f1 100644 --- a/lib/esm/index.d.ts +++ b/lib/esm/index.d.ts @@ -1,4 +1,4 @@ -import { Schema, DecodeTypes } from './types'; +import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; diff --git a/lib/esm/index.js b/lib/esm/index.js index 88b90653..abc18e77 100644 --- a/lib/esm/index.js +++ b/lib/esm/index.js @@ -1,6 +1,6 @@ -import { BorshSerializer } from './serialize'; -import { BorshDeserializer } from './deserialize'; -import * as utils from './utils'; +import { BorshSerializer } from './serialize.js'; +import { BorshDeserializer } from './deserialize.js'; +import * as utils from './utils.js'; import bs58 from 'bs58'; export function serialize(schema, value, checkSchema) { if (checkSchema === void 0) { checkSchema = true; } diff --git a/lib/esm/serialize.d.ts b/lib/esm/serialize.d.ts index 374154a9..7ac22859 100644 --- a/lib/esm/serialize.d.ts +++ b/lib/esm/serialize.d.ts @@ -1,5 +1,5 @@ -import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; -import { EncodeBuffer } from './buffer'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; +import { EncodeBuffer } from './buffer.js'; import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; diff --git a/lib/esm/serialize.js b/lib/esm/serialize.js index e98f2d76..50f4c6cb 100644 --- a/lib/esm/serialize.js +++ b/lib/esm/serialize.js @@ -1,7 +1,7 @@ -import { integers } from './types'; -import { EncodeBuffer } from './buffer'; +import { integers } from './types.js'; +import { EncodeBuffer } from './buffer.js'; import BN from 'bn.js'; -import * as utils from './utils'; +import * as utils from './utils.js'; var BorshSerializer = /** @class */ (function () { function BorshSerializer() { this.encoded = new EncodeBuffer(); diff --git a/lib/esm/utils.d.ts b/lib/esm/utils.d.ts index 56f6a787..e9ee504a 100644 --- a/lib/esm/utils.d.ts +++ b/lib/esm/utils.d.ts @@ -1,4 +1,4 @@ -import { Schema } from './types'; +import { Schema } from './types.js'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; export declare function expect_BN(value: unknown, fieldPath: string[]): void; diff --git a/lib/esm/utils.js b/lib/esm/utils.js index 59591dbf..79fb1e6c 100644 --- a/lib/esm/utils.js +++ b/lib/esm/utils.js @@ -14,7 +14,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); import BN from 'bn.js'; -import { integers } from './types'; +import { integers } from './types.js'; export function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like return (Array.isArray(value) || diff --git a/lib/types/buffer.d.ts b/lib/types/buffer.d.ts index 159e6233..c850d11e 100644 --- a/lib/types/buffer.d.ts +++ b/lib/types/buffer.d.ts @@ -1,4 +1,4 @@ -import { IntegerType } from './types'; +import { IntegerType } from './types.js'; export declare class EncodeBuffer { offset: number; buffer_size: number; diff --git a/lib/types/deserialize.d.ts b/lib/types/deserialize.d.ts index bcc1e07f..3acdfd97 100644 --- a/lib/types/deserialize.d.ts +++ b/lib/types/deserialize.d.ts @@ -1,5 +1,5 @@ -import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; -import { DecodeBuffer } from './buffer'; +import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; +import { DecodeBuffer } from './buffer.js'; import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index f7f12e25..2c5203f1 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -1,4 +1,4 @@ -import { Schema, DecodeTypes } from './types'; +import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; diff --git a/lib/types/serialize.d.ts b/lib/types/serialize.d.ts index 374154a9..7ac22859 100644 --- a/lib/types/serialize.d.ts +++ b/lib/types/serialize.d.ts @@ -1,5 +1,5 @@ -import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types'; -import { EncodeBuffer } from './buffer'; +import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; +import { EncodeBuffer } from './buffer.js'; import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; diff --git a/lib/types/utils.d.ts b/lib/types/utils.d.ts index 56f6a787..e9ee504a 100644 --- a/lib/types/utils.d.ts +++ b/lib/types/utils.d.ts @@ -1,4 +1,4 @@ -import { Schema } from './types'; +import { Schema } from './types.js'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; export declare function expect_BN(value: unknown, fieldPath: string[]): void; diff --git a/package.json b/package.json index da6b7302..bda1e211 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,10 @@ }, "exports": { ".": { - "types": "./lib/types/lib.d.ts", - "require": "./lib/cjs/lib.js", - "import": "./lib/esm/lib.js", - "default": "./lib/esm/lib.js" + "types": "./lib/types/index.d.ts", + "require": "./lib/cjs/index.js", + "import": "./lib/esm/index.js", + "default": "./lib/esm/index.js" } }, "repository": { From 22a824c1b1478f13a504893932b3c23a18c0c8aa Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 2 Aug 2023 14:10:57 +0200 Subject: [PATCH 32/35] removed BN.js & bs58 --- borsh-ts/deserialize.ts | 18 +++++------------- borsh-ts/index.ts | 19 +------------------ borsh-ts/serialize.ts | 24 +++++++++--------------- borsh-ts/test/(de)serialize.test.js | 16 +++++----------- borsh-ts/test/.eslintrc.yml | 2 ++ borsh-ts/test/base_decode.test.js | 23 ----------------------- borsh-ts/test/structures.js | 20 +++++++++----------- borsh-ts/types.ts | 4 +--- borsh-ts/utils.ts | 3 +-- examples/esm/index.js | 6 +++--- lib/cjs/deserialize.d.ts | 5 ++--- lib/cjs/deserialize.js | 16 +++------------- lib/cjs/index.d.ts | 2 -- lib/cjs/index.js | 22 +--------------------- lib/cjs/serialize.d.ts | 3 +-- lib/cjs/serialize.js | 19 +++++-------------- lib/cjs/types.d.ts | 3 +-- lib/cjs/utils.js | 6 +----- lib/esm/deserialize.d.ts | 5 ++--- lib/esm/deserialize.js | 13 +++---------- lib/esm/index.d.ts | 2 -- lib/esm/index.js | 15 --------------- lib/esm/serialize.d.ts | 3 +-- lib/esm/serialize.js | 16 +++++----------- lib/esm/types.d.ts | 3 +-- lib/esm/utils.js | 3 +-- lib/types/deserialize.d.ts | 5 ++--- lib/types/index.d.ts | 2 -- lib/types/serialize.d.ts | 3 +-- lib/types/types.d.ts | 3 +-- package.json | 7 +------ 31 files changed, 68 insertions(+), 223 deletions(-) delete mode 100644 borsh-ts/test/base_decode.test.js diff --git a/borsh-ts/deserialize.ts b/borsh-ts/deserialize.ts index 807cb67a..6a247c68 100644 --- a/borsh-ts/deserialize.ts +++ b/borsh-ts/deserialize.ts @@ -1,6 +1,5 @@ import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types.js'; import { DecodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export class BorshDeserializer { buffer: DecodeBuffer; @@ -32,7 +31,7 @@ export class BorshDeserializer { throw new Error(`Unsupported type: ${schema}`); } - decode_integer(schema: IntegerType): number | BN { + decode_integer(schema: IntegerType): number | bigint { const size: number = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { @@ -41,22 +40,15 @@ export class BorshDeserializer { return this.decode_bigint(size, schema.startsWith('i')); } - decode_bigint(size: number, signed = false): BN { + decode_bigint(size: number, signed = false): bigint { const buffer_len = size / 8; const buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + const bits = buffer.reduceRight((r, x) => r + x.toString(16).padStart(2, '0'), ''); if (signed && buffer[buffer_len - 1]) { - // negative number - let carry = 1; - for (let i = 0; i < buffer_len; i++) { - const v = (buffer[i] ^ 0xff) + carry; - buffer[i] = v & 0xff; - carry = v >> 8; - } - return new BN(buffer, 'le').mul(new BN(-1)); + return BigInt.asIntN(size, BigInt(`0x${bits}`)); } - - return new BN(buffer, 'le'); + return BigInt(`0x${bits}`); } decode_string(): string { diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 7f5971d7..0a95d873 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -2,7 +2,6 @@ import { Schema, DecodeTypes } from './types.js'; import { BorshSerializer } from './serialize.js'; import { BorshDeserializer } from './deserialize.js'; import * as utils from './utils.js'; -import bs58 from 'bs58'; export { Schema } from './types'; @@ -16,20 +15,4 @@ export function deserialize(schema: Schema, buffer: Uint8Array, checkSchema = tr if (checkSchema) utils.validate_schema(schema); const deserializer = new BorshDeserializer(buffer); return deserializer.decode(schema); -} - -// Keeping this for compatibility reasons with the old borsh-js -export function baseEncode(value: Uint8Array | string): string { - if (typeof value === 'string') { - const bytes = []; - for(let c = 0; c < value.length; c++){ - bytes.push(value.charCodeAt(c)); - } - value = new Uint8Array(bytes); - } - return bs58.encode(value); -} - -export function baseDecode(value: string): Uint8Array { - return new Uint8Array(bs58.decode(value)); -} +} \ No newline at end of file diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index 648d142a..c8e42aa8 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -1,13 +1,12 @@ import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, integers, EnumType } from './types.js'; import { EncodeBuffer } from './buffer.js'; -import BN from 'bn.js'; import * as utils from './utils.js'; export class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; - constructor() { + constructor() { this.encoded = new EncodeBuffer(); this.fieldPath = ['value']; } @@ -42,23 +41,18 @@ export class BorshSerializer { this.encoded.store_value(value as number, schema); } else { utils.expect_BN(value, this.fieldPath); - value = value instanceof BN? value : new BN(value as number | string); - this.encode_bigint(value as BN, size); + value = value instanceof BigInt ? value : BigInt(value as number | string); + this.encode_bigint(value as bigint, size); } } - encode_bigint(value: BN, size: number): void { + encode_bigint(value: bigint, size: number): void { const buffer_len = size / 8; - const buffer = value.toArray('le', buffer_len); - - if (value.lt(new BN(0))) { - // compute two's complement - let carry = 1; - for (let i = 0; i < buffer_len; i++) { - const v = (buffer[i] ^ 0xff) + carry; - carry = v >> 8; - buffer[i] = v & 0xff; - } + const buffer = new Uint8Array(buffer_len); + + for (let i = 0; i < buffer_len; i++) { + buffer[i] = Number(value & BigInt(0xff)); + value = value >> BigInt(8); } this.encoded.store_bytes(new Uint8Array(buffer)); diff --git a/borsh-ts/test/(de)serialize.test.js b/borsh-ts/test/(de)serialize.test.js index f5d30357..21bc0a08 100644 --- a/borsh-ts/test/(de)serialize.test.js +++ b/borsh-ts/test/(de)serialize.test.js @@ -1,4 +1,3 @@ -const BN = require('bn.js'); const borsh = require('../../lib/cjs/index'); const testStructures = require('./structures'); @@ -11,18 +10,13 @@ function check_decode(expected, schema, encoded) { const decoded = borsh.deserialize(schema, encoded); // console.log(decoded, expected); // visual inspection - - if (expected && typeof (expected) === 'object' && 'eq' in expected) { - return expect(expected.eq(decoded)).toBe(true); - } - if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); if (expected instanceof Map || expected instanceof Set || expected instanceof Array) { return expect(decoded).toEqual(expected); } - expect(JSON.stringify(decoded)).toEqual(JSON.stringify(expected)); + expect(decoded).toEqual(expected); } function check_roundtrip(value, schema, encoded) { @@ -34,13 +28,13 @@ test('serialize integers', async () => { check_roundtrip(100, 'u8', [100]); check_roundtrip(258, 'u16', [2, 1]); check_roundtrip(102, 'u32', [102, 0, 0, 0]); - check_roundtrip(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); - check_roundtrip(new BN(104), 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + check_roundtrip(103n, 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); + check_roundtrip(104n, 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); check_roundtrip(-100, 'i8', [156]); check_roundtrip(-258, 'i16', [254, 254]); check_roundtrip(-102, 'i32', [154, 255, 255, 255]); - check_roundtrip(new BN(-103), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); - check_roundtrip(new BN(-104), 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); + check_roundtrip(-103n, 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); + check_roundtrip(-104n, 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); }); test('serialize booleans', async () => { diff --git a/borsh-ts/test/.eslintrc.yml b/borsh-ts/test/.eslintrc.yml index 0c383946..979617f7 100644 --- a/borsh-ts/test/.eslintrc.yml +++ b/borsh-ts/test/.eslintrc.yml @@ -1,6 +1,8 @@ extends: '../../.eslintrc.yml' env: + es6: true jest: true + es2020: true rules: no-inner-declarations: 1 '@typescript-eslint/no-explicit-any': 1 diff --git a/borsh-ts/test/base_decode.test.js b/borsh-ts/test/base_decode.test.js deleted file mode 100644 index 11d5e2b4..00000000 --- a/borsh-ts/test/base_decode.test.js +++ /dev/null @@ -1,23 +0,0 @@ -const borsh = require('../../lib/cjs/index'); - -test('baseEncode string test', async () => { - const encodedValue = borsh.baseEncode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); - const expectedValue = 'HKk9gqNj4xb4rLdJuzT5zzJbLa4vHBdYCxQT9H99csQh6nz3Hfpqn4jtWA92'; - expect(encodedValue).toEqual(expectedValue); -}); - -test('baseEncode array test', async () => { - expect(borsh.baseEncode([1, 2, 3, 4, 5])).toEqual('7bWpTW'); -}); - -test('baseDecode test', async () => { - const value = 'HKk9gqNj4xb4rLdJu'; - const expectedDecodedArray = [3, 96, 254, 84, 10, 240, 93, 199, 52, 244, 164, 240, 6]; - const expectedBuffer = new Uint8Array(Buffer.from(expectedDecodedArray)); - expect(borsh.baseDecode(value)).toEqual(expectedBuffer); -}); - -test('base encode and decode test', async () => { - const value = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; - expect(borsh.baseEncode(borsh.baseDecode(value))).toEqual(value); -}); \ No newline at end of file diff --git a/borsh-ts/test/structures.js b/borsh-ts/test/structures.js index 1474f8ad..1351bd26 100644 --- a/borsh-ts/test/structures.js +++ b/borsh-ts/test/structures.js @@ -1,16 +1,14 @@ -const BN = require('bn.js'); - // Complex number structure const Numbers = { u8: 1, u16: 2, u32: 3, - u64: new BN(4), - u128: new BN(5), + u64: 4n, + u128: 5n, i8: -1, i16: -2, i32: -3, - i64: new BN(-4), + i64: -4n, f32: 6.0, f64: 7.1, }; @@ -61,17 +59,17 @@ const encodedNested = [1, 2, 0, 3, 0, 0, 0]; const Mixture = { foo: 321, bar: 123, - u64Val: new BN('4294967297'), - i64Val: new BN(-64), + u64Val: BigInt('4294967297'), + i64Val: -64n, flag: true, baz: 'testing', uint8array: [240, 241], arr: [['testing'], ['testing']], u32Arr: [21, 11], i32Arr: [], - u128Val: new BN(128), + u128Val: 128n, uint8arrays: [[240, 241], [240, 241]], - u64Arr: [new BN('10000000000'), new BN('100000000000')], + u64Arr: [BigInt('10000000000'), 100000000000n], }; const schemaMixture = { @@ -114,8 +112,8 @@ const encodedMixture = [ // A structure of big nums const BigStruct = { - u64: new BN('ffffffffffffffff', 'hex'), - u128: new BN('ffffffffffffffff'.repeat(2), 'hex'), + u64: BigInt('18446744073709551615'), + u128: BigInt('340282366920938463463374607431768211455'), arr: [...Array(254).keys()], }; diff --git a/borsh-ts/types.ts b/borsh-ts/types.ts index 00ca69ea..87903ef9 100644 --- a/borsh-ts/types.ts +++ b/borsh-ts/types.ts @@ -1,5 +1,3 @@ -import BN from 'bn.js'; - export const integers = ['u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128', 'f32', 'f64']; export type IntegerType = typeof integers[number]; @@ -15,4 +13,4 @@ export type StructType = { struct: { [key: string]: Schema } }; export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; // returned -export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | bigint | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 656dc2d3..716a338e 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -1,4 +1,3 @@ -import BN from 'bn.js'; import { Schema, StructType, integers } from './types.js'; export function isArrayLike(value: unknown): boolean { @@ -24,7 +23,7 @@ export function expect_type(value: unknown, type: string, fieldPath: string[]): } export function expect_BN(value: unknown, fieldPath: string[]): void { - if (!(value instanceof BN) && typeof (value) !== 'number' && typeof (value) !== 'string') { + if (!['number', 'string', 'bigint'].includes(typeof(value))) { throw new Error(`Expected BN, number or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } diff --git a/examples/esm/index.js b/examples/esm/index.js index 4301e1d9..ab745dd7 100644 --- a/examples/esm/index.js +++ b/examples/esm/index.js @@ -4,6 +4,6 @@ const encodedU16 = borsh.serialize('u16', 2); const decodedU16 = borsh.deserialize('u16', encodedU16); console.log(decodedU16); -const encodedStr = borsh.serialize('string', 'testing'); -const decodedStr = borsh.deserialize('string', encodedStr); -console.log(decodedStr); +const encodedStr = borsh.serialize('u64', '100000000000000000'); +const decodedStr = borsh.deserialize('u64', encodedStr); +console.log(decodedStr); \ No newline at end of file diff --git a/lib/cjs/deserialize.d.ts b/lib/cjs/deserialize.d.ts index 3acdfd97..d8368552 100644 --- a/lib/cjs/deserialize.d.ts +++ b/lib/cjs/deserialize.d.ts @@ -1,13 +1,12 @@ import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; import { DecodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; constructor(bufferArray: Uint8Array); decode(schema: Schema): DecodeTypes; decode_value(schema: Schema): DecodeTypes; - decode_integer(schema: IntegerType): number | BN; - decode_bigint(size: number, signed?: boolean): BN; + decode_integer(schema: IntegerType): number | bigint; + decode_bigint(size: number, signed?: boolean): bigint; decode_string(): string; decode_boolean(): boolean; decode_option(schema: OptionType): DecodeTypes; diff --git a/lib/cjs/deserialize.js b/lib/cjs/deserialize.js index 2ad3eae8..cf6c248a 100644 --- a/lib/cjs/deserialize.js +++ b/lib/cjs/deserialize.js @@ -1,12 +1,8 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; exports.__esModule = true; exports.BorshDeserializer = void 0; var types_js_1 = require("./types.js"); var buffer_js_1 = require("./buffer.js"); -var bn_js_1 = __importDefault(require("bn.js")); var BorshDeserializer = /** @class */ (function () { function BorshDeserializer(bufferArray) { this.buffer = new buffer_js_1.DecodeBuffer(bufferArray); @@ -50,17 +46,11 @@ var BorshDeserializer = /** @class */ (function () { if (signed === void 0) { signed = false; } var buffer_len = size / 8; var buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + var bits = buffer.reduceRight(function (r, x) { return r + x.toString(16).padStart(2, '0'); }, ''); if (signed && buffer[buffer_len - 1]) { - // negative number - var carry = 1; - for (var i = 0; i < buffer_len; i++) { - var v = (buffer[i] ^ 0xff) + carry; - buffer[i] = v & 0xff; - carry = v >> 8; - } - return new bn_js_1["default"](buffer, 'le').mul(new bn_js_1["default"](-1)); + return BigInt.asIntN(size, BigInt("0x".concat(bits))); } - return new bn_js_1["default"](buffer, 'le'); + return BigInt("0x".concat(bits)); }; BorshDeserializer.prototype.decode_string = function () { var len = this.decode_integer('u32'); diff --git a/lib/cjs/index.d.ts b/lib/cjs/index.d.ts index 2c5203f1..a6aeaa97 100644 --- a/lib/cjs/index.d.ts +++ b/lib/cjs/index.d.ts @@ -2,5 +2,3 @@ import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; -export declare function baseEncode(value: Uint8Array | string): string; -export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/cjs/index.js b/lib/cjs/index.js index fe190922..f6d4bf4b 100644 --- a/lib/cjs/index.js +++ b/lib/cjs/index.js @@ -22,15 +22,11 @@ var __importStar = (this && this.__importStar) || function (mod) { __setModuleDefault(result, mod); return result; }; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; exports.__esModule = true; -exports.baseDecode = exports.baseEncode = exports.deserialize = exports.serialize = void 0; +exports.deserialize = exports.serialize = void 0; var serialize_js_1 = require("./serialize.js"); var deserialize_js_1 = require("./deserialize.js"); var utils = __importStar(require("./utils.js")); -var bs58_1 = __importDefault(require("bs58")); function serialize(schema, value, checkSchema) { if (checkSchema === void 0) { checkSchema = true; } if (checkSchema) @@ -47,19 +43,3 @@ function deserialize(schema, buffer, checkSchema) { return deserializer.decode(schema); } exports.deserialize = deserialize; -// Keeping this for compatibility reasons with the old borsh-js -function baseEncode(value) { - if (typeof value === 'string') { - var bytes = []; - for (var c = 0; c < value.length; c++) { - bytes.push(value.charCodeAt(c)); - } - value = new Uint8Array(bytes); - } - return bs58_1["default"].encode(value); -} -exports.baseEncode = baseEncode; -function baseDecode(value) { - return new Uint8Array(bs58_1["default"].decode(value)); -} -exports.baseDecode = baseDecode; diff --git a/lib/cjs/serialize.d.ts b/lib/cjs/serialize.d.ts index 7ac22859..dc9edf31 100644 --- a/lib/cjs/serialize.d.ts +++ b/lib/cjs/serialize.d.ts @@ -1,6 +1,5 @@ import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; import { EncodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; @@ -8,7 +7,7 @@ export declare class BorshSerializer { encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; - encode_bigint(value: BN, size: number): void; + encode_bigint(value: bigint, size: number): void; encode_string(value: unknown): void; encode_boolean(value: unknown): void; encode_option(value: unknown, schema: OptionType): void; diff --git a/lib/cjs/serialize.js b/lib/cjs/serialize.js index 2163e2e4..824693f9 100644 --- a/lib/cjs/serialize.js +++ b/lib/cjs/serialize.js @@ -22,14 +22,10 @@ var __importStar = (this && this.__importStar) || function (mod) { __setModuleDefault(result, mod); return result; }; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; exports.__esModule = true; exports.BorshSerializer = void 0; var types_js_1 = require("./types.js"); var buffer_js_1 = require("./buffer.js"); -var bn_js_1 = __importDefault(require("bn.js")); var utils = __importStar(require("./utils.js")); var BorshSerializer = /** @class */ (function () { function BorshSerializer() { @@ -72,21 +68,16 @@ var BorshSerializer = /** @class */ (function () { } else { utils.expect_BN(value, this.fieldPath); - value = value instanceof bn_js_1["default"] ? value : new bn_js_1["default"](value); + value = value instanceof BigInt ? value : BigInt(value); this.encode_bigint(value, size); } }; BorshSerializer.prototype.encode_bigint = function (value, size) { var buffer_len = size / 8; - var buffer = value.toArray('le', buffer_len); - if (value.lt(new bn_js_1["default"](0))) { - // compute two's complement - var carry = 1; - for (var i = 0; i < buffer_len; i++) { - var v = (buffer[i] ^ 0xff) + carry; - carry = v >> 8; - buffer[i] = v & 0xff; - } + var buffer = new Uint8Array(buffer_len); + for (var i = 0; i < buffer_len; i++) { + buffer[i] = Number(value & BigInt(0xff)); + value = value >> BigInt(8); } this.encoded.store_bytes(new Uint8Array(buffer)); }; diff --git a/lib/cjs/types.d.ts b/lib/cjs/types.d.ts index 4ee12ea0..54825981 100644 --- a/lib/cjs/types.d.ts +++ b/lib/cjs/types.d.ts @@ -1,4 +1,3 @@ -import BN from 'bn.js'; export declare const integers: string[]; export type IntegerType = typeof integers[number]; export type BoolType = 'bool'; @@ -30,4 +29,4 @@ export type StructType = { }; }; export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; -export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | bigint | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/lib/cjs/utils.js b/lib/cjs/utils.js index cb5ccb22..209f704e 100644 --- a/lib/cjs/utils.js +++ b/lib/cjs/utils.js @@ -14,12 +14,8 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; exports.__esModule = true; exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; -var bn_js_1 = __importDefault(require("bn.js")); var types_js_1 = require("./types.js"); function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like @@ -40,7 +36,7 @@ function expect_type(value, type, fieldPath) { } exports.expect_type = expect_type; function expect_BN(value, fieldPath) { - if (!(value instanceof bn_js_1["default"]) && typeof (value) !== 'number' && typeof (value) !== 'string') { + if (!['number', 'string', 'bigint'].includes(typeof (value))) { throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } diff --git a/lib/esm/deserialize.d.ts b/lib/esm/deserialize.d.ts index 3acdfd97..d8368552 100644 --- a/lib/esm/deserialize.d.ts +++ b/lib/esm/deserialize.d.ts @@ -1,13 +1,12 @@ import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; import { DecodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; constructor(bufferArray: Uint8Array); decode(schema: Schema): DecodeTypes; decode_value(schema: Schema): DecodeTypes; - decode_integer(schema: IntegerType): number | BN; - decode_bigint(size: number, signed?: boolean): BN; + decode_integer(schema: IntegerType): number | bigint; + decode_bigint(size: number, signed?: boolean): bigint; decode_string(): string; decode_boolean(): boolean; decode_option(schema: OptionType): DecodeTypes; diff --git a/lib/esm/deserialize.js b/lib/esm/deserialize.js index 46efa0b2..d1aff9af 100644 --- a/lib/esm/deserialize.js +++ b/lib/esm/deserialize.js @@ -1,6 +1,5 @@ import { integers } from './types.js'; import { DecodeBuffer } from './buffer.js'; -import BN from 'bn.js'; var BorshDeserializer = /** @class */ (function () { function BorshDeserializer(bufferArray) { this.buffer = new DecodeBuffer(bufferArray); @@ -44,17 +43,11 @@ var BorshDeserializer = /** @class */ (function () { if (signed === void 0) { signed = false; } var buffer_len = size / 8; var buffer = new Uint8Array(this.buffer.consume_bytes(buffer_len)); + var bits = buffer.reduceRight(function (r, x) { return r + x.toString(16).padStart(2, '0'); }, ''); if (signed && buffer[buffer_len - 1]) { - // negative number - var carry = 1; - for (var i = 0; i < buffer_len; i++) { - var v = (buffer[i] ^ 0xff) + carry; - buffer[i] = v & 0xff; - carry = v >> 8; - } - return new BN(buffer, 'le').mul(new BN(-1)); + return BigInt.asIntN(size, BigInt("0x".concat(bits))); } - return new BN(buffer, 'le'); + return BigInt("0x".concat(bits)); }; BorshDeserializer.prototype.decode_string = function () { var len = this.decode_integer('u32'); diff --git a/lib/esm/index.d.ts b/lib/esm/index.d.ts index 2c5203f1..a6aeaa97 100644 --- a/lib/esm/index.d.ts +++ b/lib/esm/index.d.ts @@ -2,5 +2,3 @@ import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; -export declare function baseEncode(value: Uint8Array | string): string; -export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/esm/index.js b/lib/esm/index.js index abc18e77..211567cc 100644 --- a/lib/esm/index.js +++ b/lib/esm/index.js @@ -1,7 +1,6 @@ import { BorshSerializer } from './serialize.js'; import { BorshDeserializer } from './deserialize.js'; import * as utils from './utils.js'; -import bs58 from 'bs58'; export function serialize(schema, value, checkSchema) { if (checkSchema === void 0) { checkSchema = true; } if (checkSchema) @@ -16,17 +15,3 @@ export function deserialize(schema, buffer, checkSchema) { var deserializer = new BorshDeserializer(buffer); return deserializer.decode(schema); } -// Keeping this for compatibility reasons with the old borsh-js -export function baseEncode(value) { - if (typeof value === 'string') { - var bytes = []; - for (var c = 0; c < value.length; c++) { - bytes.push(value.charCodeAt(c)); - } - value = new Uint8Array(bytes); - } - return bs58.encode(value); -} -export function baseDecode(value) { - return new Uint8Array(bs58.decode(value)); -} diff --git a/lib/esm/serialize.d.ts b/lib/esm/serialize.d.ts index 7ac22859..dc9edf31 100644 --- a/lib/esm/serialize.d.ts +++ b/lib/esm/serialize.d.ts @@ -1,6 +1,5 @@ import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; import { EncodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; @@ -8,7 +7,7 @@ export declare class BorshSerializer { encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; - encode_bigint(value: BN, size: number): void; + encode_bigint(value: bigint, size: number): void; encode_string(value: unknown): void; encode_boolean(value: unknown): void; encode_option(value: unknown, schema: OptionType): void; diff --git a/lib/esm/serialize.js b/lib/esm/serialize.js index 50f4c6cb..667f876e 100644 --- a/lib/esm/serialize.js +++ b/lib/esm/serialize.js @@ -1,6 +1,5 @@ import { integers } from './types.js'; import { EncodeBuffer } from './buffer.js'; -import BN from 'bn.js'; import * as utils from './utils.js'; var BorshSerializer = /** @class */ (function () { function BorshSerializer() { @@ -43,21 +42,16 @@ var BorshSerializer = /** @class */ (function () { } else { utils.expect_BN(value, this.fieldPath); - value = value instanceof BN ? value : new BN(value); + value = value instanceof BigInt ? value : BigInt(value); this.encode_bigint(value, size); } }; BorshSerializer.prototype.encode_bigint = function (value, size) { var buffer_len = size / 8; - var buffer = value.toArray('le', buffer_len); - if (value.lt(new BN(0))) { - // compute two's complement - var carry = 1; - for (var i = 0; i < buffer_len; i++) { - var v = (buffer[i] ^ 0xff) + carry; - carry = v >> 8; - buffer[i] = v & 0xff; - } + var buffer = new Uint8Array(buffer_len); + for (var i = 0; i < buffer_len; i++) { + buffer[i] = Number(value & BigInt(0xff)); + value = value >> BigInt(8); } this.encoded.store_bytes(new Uint8Array(buffer)); }; diff --git a/lib/esm/types.d.ts b/lib/esm/types.d.ts index 4ee12ea0..54825981 100644 --- a/lib/esm/types.d.ts +++ b/lib/esm/types.d.ts @@ -1,4 +1,3 @@ -import BN from 'bn.js'; export declare const integers: string[]; export type IntegerType = typeof integers[number]; export type BoolType = 'bool'; @@ -30,4 +29,4 @@ export type StructType = { }; }; export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; -export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | bigint | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/lib/esm/utils.js b/lib/esm/utils.js index 79fb1e6c..93abde42 100644 --- a/lib/esm/utils.js +++ b/lib/esm/utils.js @@ -13,7 +13,6 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); -import BN from 'bn.js'; import { integers } from './types.js'; export function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like @@ -32,7 +31,7 @@ export function expect_type(value, type, fieldPath) { } } export function expect_BN(value, fieldPath) { - if (!(value instanceof BN) && typeof (value) !== 'number' && typeof (value) !== 'string') { + if (!['number', 'string', 'bigint'].includes(typeof (value))) { throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } diff --git a/lib/types/deserialize.d.ts b/lib/types/deserialize.d.ts index 3acdfd97..d8368552 100644 --- a/lib/types/deserialize.d.ts +++ b/lib/types/deserialize.d.ts @@ -1,13 +1,12 @@ import { ArrayType, DecodeTypes, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; import { DecodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export declare class BorshDeserializer { buffer: DecodeBuffer; constructor(bufferArray: Uint8Array); decode(schema: Schema): DecodeTypes; decode_value(schema: Schema): DecodeTypes; - decode_integer(schema: IntegerType): number | BN; - decode_bigint(size: number, signed?: boolean): BN; + decode_integer(schema: IntegerType): number | bigint; + decode_bigint(size: number, signed?: boolean): bigint; decode_string(): string; decode_boolean(): boolean; decode_option(schema: OptionType): DecodeTypes; diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index 2c5203f1..a6aeaa97 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -2,5 +2,3 @@ import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; -export declare function baseEncode(value: Uint8Array | string): string; -export declare function baseDecode(value: string): Uint8Array; diff --git a/lib/types/serialize.d.ts b/lib/types/serialize.d.ts index 7ac22859..dc9edf31 100644 --- a/lib/types/serialize.d.ts +++ b/lib/types/serialize.d.ts @@ -1,6 +1,5 @@ import { ArrayType, MapType, IntegerType, OptionType, Schema, SetType, StructType, EnumType } from './types.js'; import { EncodeBuffer } from './buffer.js'; -import BN from 'bn.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; @@ -8,7 +7,7 @@ export declare class BorshSerializer { encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; - encode_bigint(value: BN, size: number): void; + encode_bigint(value: bigint, size: number): void; encode_string(value: unknown): void; encode_boolean(value: unknown): void; encode_option(value: unknown, schema: OptionType): void; diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts index 4ee12ea0..54825981 100644 --- a/lib/types/types.d.ts +++ b/lib/types/types.d.ts @@ -1,4 +1,3 @@ -import BN from 'bn.js'; export declare const integers: string[]; export type IntegerType = typeof integers[number]; export type BoolType = 'bool'; @@ -30,4 +29,4 @@ export type StructType = { }; }; export type Schema = IntegerType | BoolType | StringType | OptionType | ArrayType | EnumType | SetType | MapType | StructType; -export type DecodeTypes = number | BN | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; +export type DecodeTypes = number | bigint | string | boolean | Array | EnumType | ArrayBuffer | Map | Set | object | null; diff --git a/package.json b/package.json index bda1e211..bbda303a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "borsh", - "version": "1.0.1", + "version": "1.0.0", "description": "Binary Object Representation Serializer for Hashing", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", @@ -48,16 +48,11 @@ "devDependencies": { "@types/babel__core": "^7.1.2", "@types/babel__template": "^7.0.2", - "@types/bn.js": "^5.1.0", "@types/node": "^12.7.3", "@typescript-eslint/eslint-plugin": "^5.28.0", "@typescript-eslint/parser": "^5.28.0", "eslint": "^8.17.0", "jest": "^26.0.1", "typescript": "^4" - }, - "dependencies": { - "bn.js": "^5.2.0", - "bs58": "^4.0.0" } } From fb89acf7800a717f1048f104acded64693bba2ca Mon Sep 17 00:00:00 2001 From: gagdiez Date: Wed, 2 Aug 2023 14:15:40 +0200 Subject: [PATCH 33/35] simplified tests --- borsh-ts/test/(de)serialize.test.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/borsh-ts/test/(de)serialize.test.js b/borsh-ts/test/(de)serialize.test.js index 21bc0a08..55a6fed6 100644 --- a/borsh-ts/test/(de)serialize.test.js +++ b/borsh-ts/test/(de)serialize.test.js @@ -8,14 +8,8 @@ function check_encode(value, schema, expected) { function check_decode(expected, schema, encoded) { const decoded = borsh.deserialize(schema, encoded); - // console.log(decoded, expected); // visual inspection if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); - - if (expected instanceof Map || expected instanceof Set || expected instanceof Array) { - return expect(decoded).toEqual(expected); - } - expect(decoded).toEqual(expected); } From 2384755f5dd0f325a58ae27326d82f9f7bd7b1ee Mon Sep 17 00:00:00 2001 From: gagdiez Date: Thu, 3 Aug 2023 13:57:49 +0200 Subject: [PATCH 34/35] small change in bigint method --- README.md | 31 +++++++++++++++---------------- borsh-ts/serialize.ts | 5 ++--- borsh-ts/utils.ts | 6 +++--- lib/cjs/serialize.js | 5 ++--- lib/cjs/utils.d.ts | 2 +- lib/cjs/utils.js | 10 +++++----- lib/esm/serialize.js | 5 ++--- lib/esm/utils.d.ts | 2 +- lib/esm/utils.js | 6 +++--- lib/types/utils.d.ts | 2 +- 10 files changed, 35 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c3e326af..3bcf9708 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,8 @@ const decodedStr = borsh.deserialize('string', encodedStr); ### (De)serializing an Object ```javascript import * as borsh from 'borsh'; -import BN from 'bn.js'; -const value = {x: 255, y: new BN(20), z: '123', arr: [1, 2, 3]}; +const value = {x: 255, y: BigInt(20), z: '123', arr: [1, 2, 3]}; const schema = { struct: { x: 'u8', y: 'u64', 'z': 'string', 'arr': { array: { type: 'u8' }}}}; const encoded = borsh.serialize(schema, value); @@ -68,20 +67,20 @@ More complex objects are described by a JSON object. The following types are sup ### Type Mappings -| Javascript | Borsh | -|--------------------------------------------|-----------------------------------| -| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | -| [`BN`](https://github.com/indutny/bn.js/) | `u64` `u128` `i64` `i128` | -| `number` | `f32` `f64` | -| `number` | `f32` `f64` | -| `boolean` | `bool` | -| `string` | UTF-8 string | -| `type[]` | fixed-size byte array | -| `type[]` | dynamic sized array | -| `object` | enum | -| `Map` | HashMap | -| `Set` | HashSet | -| `null` or `type` | Option | +| Javascript | Borsh | +|------------------|-----------------------------------| +| `number` | `u8` `u16` `u32` `i8` `i16` `i32` | +| `bigint` | `u64` `u128` `i64` `i128` | +| `number` | `f32` `f64` | +| `number` | `f32` `f64` | +| `boolean` | `bool` | +| `string` | UTF-8 string | +| `type[]` | fixed-size byte array | +| `type[]` | dynamic sized array | +| `object` | enum | +| `Map` | HashMap | +| `Set` | HashSet | +| `null` or `type` | Option | --- diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index c8e42aa8..8307d5a4 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -40,9 +40,8 @@ export class BorshSerializer { utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value as number, schema); } else { - utils.expect_BN(value, this.fieldPath); - value = value instanceof BigInt ? value : BigInt(value as number | string); - this.encode_bigint(value as bigint, size); + utils.expect_bigint(value, this.fieldPath); + this.encode_bigint(BigInt(value as string), size); } } diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 716a338e..72d9c3af 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -22,9 +22,9 @@ export function expect_type(value: unknown, type: string, fieldPath: string[]): } } -export function expect_BN(value: unknown, fieldPath: string[]): void { - if (!['number', 'string', 'bigint'].includes(typeof(value))) { - throw new Error(`Expected BN, number or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); +export function expect_bigint(value: unknown, fieldPath: string[]): void { + if (!['number', 'string', 'bigint', 'boolean'].includes(typeof(value))) { + throw new Error(`Expected bigint, number, boolean or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } diff --git a/lib/cjs/serialize.js b/lib/cjs/serialize.js index 824693f9..051d4417 100644 --- a/lib/cjs/serialize.js +++ b/lib/cjs/serialize.js @@ -67,9 +67,8 @@ var BorshSerializer = /** @class */ (function () { this.encoded.store_value(value, schema); } else { - utils.expect_BN(value, this.fieldPath); - value = value instanceof BigInt ? value : BigInt(value); - this.encode_bigint(value, size); + utils.expect_bigint(value, this.fieldPath); + this.encode_bigint(BigInt(value), size); } }; BorshSerializer.prototype.encode_bigint = function (value, size) { diff --git a/lib/cjs/utils.d.ts b/lib/cjs/utils.d.ts index e9ee504a..3047d77b 100644 --- a/lib/cjs/utils.d.ts +++ b/lib/cjs/utils.d.ts @@ -1,7 +1,7 @@ import { Schema } from './types.js'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; -export declare function expect_BN(value: unknown, fieldPath: string[]): void; +export declare function expect_bigint(value: unknown, fieldPath: string[]): void; export declare function expect_same_size(length: number, expected: number, fieldPath: string[]): void; export declare function expect_enum(value: unknown, fieldPath: string[]): void; export declare class ErrorSchema extends Error { diff --git a/lib/cjs/utils.js b/lib/cjs/utils.js index 209f704e..ec590a31 100644 --- a/lib/cjs/utils.js +++ b/lib/cjs/utils.js @@ -15,7 +15,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); exports.__esModule = true; -exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_BN = exports.expect_type = exports.isArrayLike = void 0; +exports.validate_schema = exports.ErrorSchema = exports.expect_enum = exports.expect_same_size = exports.expect_bigint = exports.expect_type = exports.isArrayLike = void 0; var types_js_1 = require("./types.js"); function isArrayLike(value) { // source: https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like @@ -35,12 +35,12 @@ function expect_type(value, type, fieldPath) { } } exports.expect_type = expect_type; -function expect_BN(value, fieldPath) { - if (!['number', 'string', 'bigint'].includes(typeof (value))) { - throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); +function expect_bigint(value, fieldPath) { + if (!['number', 'string', 'bigint', 'boolean'].includes(typeof (value))) { + throw new Error("Expected bigint, number, boolean or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } -exports.expect_BN = expect_BN; +exports.expect_bigint = expect_bigint; function expect_same_size(length, expected, fieldPath) { if (length !== expected) { throw new Error("Array length ".concat(length, " does not match schema length ").concat(expected, " at ").concat(fieldPath.join('.'))); diff --git a/lib/esm/serialize.js b/lib/esm/serialize.js index 667f876e..09bc67d5 100644 --- a/lib/esm/serialize.js +++ b/lib/esm/serialize.js @@ -41,9 +41,8 @@ var BorshSerializer = /** @class */ (function () { this.encoded.store_value(value, schema); } else { - utils.expect_BN(value, this.fieldPath); - value = value instanceof BigInt ? value : BigInt(value); - this.encode_bigint(value, size); + utils.expect_bigint(value, this.fieldPath); + this.encode_bigint(BigInt(value), size); } }; BorshSerializer.prototype.encode_bigint = function (value, size) { diff --git a/lib/esm/utils.d.ts b/lib/esm/utils.d.ts index e9ee504a..3047d77b 100644 --- a/lib/esm/utils.d.ts +++ b/lib/esm/utils.d.ts @@ -1,7 +1,7 @@ import { Schema } from './types.js'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; -export declare function expect_BN(value: unknown, fieldPath: string[]): void; +export declare function expect_bigint(value: unknown, fieldPath: string[]): void; export declare function expect_same_size(length: number, expected: number, fieldPath: string[]): void; export declare function expect_enum(value: unknown, fieldPath: string[]): void; export declare class ErrorSchema extends Error { diff --git a/lib/esm/utils.js b/lib/esm/utils.js index 93abde42..3e50986a 100644 --- a/lib/esm/utils.js +++ b/lib/esm/utils.js @@ -30,9 +30,9 @@ export function expect_type(value, type, fieldPath) { throw new Error("Expected ".concat(type, " not ").concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } -export function expect_BN(value, fieldPath) { - if (!['number', 'string', 'bigint'].includes(typeof (value))) { - throw new Error("Expected BN, number or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); +export function expect_bigint(value, fieldPath) { + if (!['number', 'string', 'bigint', 'boolean'].includes(typeof (value))) { + throw new Error("Expected bigint, number, boolean or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } export function expect_same_size(length, expected, fieldPath) { diff --git a/lib/types/utils.d.ts b/lib/types/utils.d.ts index e9ee504a..3047d77b 100644 --- a/lib/types/utils.d.ts +++ b/lib/types/utils.d.ts @@ -1,7 +1,7 @@ import { Schema } from './types.js'; export declare function isArrayLike(value: unknown): boolean; export declare function expect_type(value: unknown, type: string, fieldPath: string[]): void; -export declare function expect_BN(value: unknown, fieldPath: string[]): void; +export declare function expect_bigint(value: unknown, fieldPath: string[]): void; export declare function expect_same_size(length: number, expected: number, fieldPath: string[]): void; export declare function expect_enum(value: unknown, fieldPath: string[]): void; export declare class ErrorSchema extends Error { From e1881a0b3fbcca4fc030808da8c86c8a8441ee0d Mon Sep 17 00:00:00 2001 From: gagdiez Date: Thu, 3 Aug 2023 14:47:13 +0200 Subject: [PATCH 35/35] added compatibility with BN --- borsh-ts/index.ts | 10 +++++----- borsh-ts/serialize.ts | 20 +++++++++++--------- borsh-ts/test/(de)serialize.test.js | 6 ++++-- borsh-ts/utils.ts | 4 +++- lib/cjs/index.d.ts | 4 ++-- lib/cjs/index.js | 14 +++++++------- lib/cjs/serialize.d.ts | 3 ++- lib/cjs/serialize.js | 19 ++++++++++--------- lib/cjs/utils.js | 4 +++- lib/esm/index.d.ts | 4 ++-- lib/esm/index.js | 14 +++++++------- lib/esm/serialize.d.ts | 3 ++- lib/esm/serialize.js | 19 ++++++++++--------- lib/esm/utils.js | 4 +++- lib/types/index.d.ts | 4 ++-- lib/types/serialize.d.ts | 3 ++- package.json | 3 ++- 17 files changed, 77 insertions(+), 61 deletions(-) diff --git a/borsh-ts/index.ts b/borsh-ts/index.ts index 0a95d873..09fe93d4 100644 --- a/borsh-ts/index.ts +++ b/borsh-ts/index.ts @@ -5,14 +5,14 @@ import * as utils from './utils.js'; export { Schema } from './types'; -export function serialize(schema: Schema, value: unknown, checkSchema = true): Uint8Array { - if (checkSchema) utils.validate_schema(schema); - const serializer = new BorshSerializer(); +export function serialize(schema: Schema, value: unknown, validate = true): Uint8Array { + if (validate) utils.validate_schema(schema); + const serializer = new BorshSerializer(validate); return serializer.encode(value, schema); } -export function deserialize(schema: Schema, buffer: Uint8Array, checkSchema = true): DecodeTypes { - if (checkSchema) utils.validate_schema(schema); +export function deserialize(schema: Schema, buffer: Uint8Array, validate = true): DecodeTypes { + if (validate) utils.validate_schema(schema); const deserializer = new BorshDeserializer(buffer); return deserializer.decode(schema); } \ No newline at end of file diff --git a/borsh-ts/serialize.ts b/borsh-ts/serialize.ts index 8307d5a4..fe7845ac 100644 --- a/borsh-ts/serialize.ts +++ b/borsh-ts/serialize.ts @@ -5,10 +5,12 @@ import * as utils from './utils.js'; export class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; + checkTypes: boolean; - constructor() { + constructor(checkTypes) { this.encoded = new EncodeBuffer(); this.fieldPath = ['value']; + this.checkTypes = checkTypes; } encode(value: unknown, schema: Schema): Uint8Array { @@ -37,10 +39,10 @@ export class BorshSerializer { const size: number = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { - utils.expect_type(value, 'number', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value as number, schema); } else { - utils.expect_bigint(value, this.fieldPath); + this.checkTypes && utils.expect_bigint(value, this.fieldPath); this.encode_bigint(BigInt(value as string), size); } } @@ -58,7 +60,7 @@ export class BorshSerializer { } encode_string(value: unknown): void { - utils.expect_type(value, 'string', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'string', this.fieldPath); const _value = value as string; // 4 bytes for length @@ -71,7 +73,7 @@ export class BorshSerializer { } encode_boolean(value: unknown): void { - utils.expect_type(value, 'boolean', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'boolean', this.fieldPath); this.encoded.store_value(value as boolean ? 1 : 0, 'u8'); } @@ -85,7 +87,7 @@ export class BorshSerializer { } encode_enum(value: unknown, schema: EnumType): void { - utils.expect_enum(value, this.fieldPath); + this.checkTypes && utils.expect_enum(value, this.fieldPath); const valueKey = Object.keys(value)[0]; @@ -133,7 +135,7 @@ export class BorshSerializer { } encode_set(value: unknown, schema: SetType): void { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); const isSet = value instanceof Set; const values = isSet ? Array.from(value.values()) : Object.values(value); @@ -148,7 +150,7 @@ export class BorshSerializer { } encode_map(value: unknown, schema: MapType): void { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); const isMap = value instanceof Map; const keys = isMap ? Array.from(value.keys()) : Object.keys(value); @@ -164,7 +166,7 @@ export class BorshSerializer { } encode_struct(value: unknown, schema: StructType): void { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); for (const key of Object.keys(schema.struct)) { this.fieldPath.push(key); diff --git a/borsh-ts/test/(de)serialize.test.js b/borsh-ts/test/(de)serialize.test.js index 55a6fed6..59247260 100644 --- a/borsh-ts/test/(de)serialize.test.js +++ b/borsh-ts/test/(de)serialize.test.js @@ -1,5 +1,6 @@ const borsh = require('../../lib/cjs/index'); const testStructures = require('./structures'); +const BN = require('bn.js'); function check_encode(value, schema, expected) { const encoded = borsh.serialize(schema, value); @@ -9,6 +10,7 @@ function check_encode(value, schema, expected) { function check_decode(expected, schema, encoded) { const decoded = borsh.deserialize(schema, encoded); // console.log(decoded, expected); // visual inspection + if (expected instanceof BN) return expect(BigInt(expected) === decoded).toBe(true); if (schema === 'f32') return expect(decoded).toBeCloseTo(expected); expect(decoded).toEqual(expected); } @@ -22,12 +24,12 @@ test('serialize integers', async () => { check_roundtrip(100, 'u8', [100]); check_roundtrip(258, 'u16', [2, 1]); check_roundtrip(102, 'u32', [102, 0, 0, 0]); - check_roundtrip(103n, 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); + check_roundtrip(new BN(103), 'u64', [103, 0, 0, 0, 0, 0, 0, 0]); check_roundtrip(104n, 'u128', [104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); check_roundtrip(-100, 'i8', [156]); check_roundtrip(-258, 'i16', [254, 254]); check_roundtrip(-102, 'i32', [154, 255, 255, 255]); - check_roundtrip(-103n, 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); + check_roundtrip(new BN(-103n), 'i64', [153, 255, 255, 255, 255, 255, 255, 255]); check_roundtrip(-104n, 'i128', [152, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); }); diff --git a/borsh-ts/utils.ts b/borsh-ts/utils.ts index 72d9c3af..778667c4 100644 --- a/borsh-ts/utils.ts +++ b/borsh-ts/utils.ts @@ -23,7 +23,9 @@ export function expect_type(value: unknown, type: string, fieldPath: string[]): } export function expect_bigint(value: unknown, fieldPath: string[]): void { - if (!['number', 'string', 'bigint', 'boolean'].includes(typeof(value))) { + const basicType = ['number', 'string', 'bigint', 'boolean'].includes(typeof(value)); + const strObject = typeof (value) === 'object' && value !== null && 'toString' in value; + if (!basicType && !strObject) { throw new Error(`Expected bigint, number, boolean or string not ${typeof (value)}(${value}) at ${fieldPath.join('.')}`); } } diff --git a/lib/cjs/index.d.ts b/lib/cjs/index.d.ts index a6aeaa97..29ea1887 100644 --- a/lib/cjs/index.d.ts +++ b/lib/cjs/index.d.ts @@ -1,4 +1,4 @@ import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; -export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; -export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; +export declare function serialize(schema: Schema, value: unknown, validate?: boolean): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, validate?: boolean): DecodeTypes; diff --git a/lib/cjs/index.js b/lib/cjs/index.js index f6d4bf4b..a02e4927 100644 --- a/lib/cjs/index.js +++ b/lib/cjs/index.js @@ -27,17 +27,17 @@ exports.deserialize = exports.serialize = void 0; var serialize_js_1 = require("./serialize.js"); var deserialize_js_1 = require("./deserialize.js"); var utils = __importStar(require("./utils.js")); -function serialize(schema, value, checkSchema) { - if (checkSchema === void 0) { checkSchema = true; } - if (checkSchema) +function serialize(schema, value, validate) { + if (validate === void 0) { validate = true; } + if (validate) utils.validate_schema(schema); - var serializer = new serialize_js_1.BorshSerializer(); + var serializer = new serialize_js_1.BorshSerializer(validate); return serializer.encode(value, schema); } exports.serialize = serialize; -function deserialize(schema, buffer, checkSchema) { - if (checkSchema === void 0) { checkSchema = true; } - if (checkSchema) +function deserialize(schema, buffer, validate) { + if (validate === void 0) { validate = true; } + if (validate) utils.validate_schema(schema); var deserializer = new deserialize_js_1.BorshDeserializer(buffer); return deserializer.decode(schema); diff --git a/lib/cjs/serialize.d.ts b/lib/cjs/serialize.d.ts index dc9edf31..adaaae9a 100644 --- a/lib/cjs/serialize.d.ts +++ b/lib/cjs/serialize.d.ts @@ -3,7 +3,8 @@ import { EncodeBuffer } from './buffer.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; - constructor(); + checkTypes: boolean; + constructor(checkTypes: any); encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; diff --git a/lib/cjs/serialize.js b/lib/cjs/serialize.js index 051d4417..2f47e1b8 100644 --- a/lib/cjs/serialize.js +++ b/lib/cjs/serialize.js @@ -28,9 +28,10 @@ var types_js_1 = require("./types.js"); var buffer_js_1 = require("./buffer.js"); var utils = __importStar(require("./utils.js")); var BorshSerializer = /** @class */ (function () { - function BorshSerializer() { + function BorshSerializer(checkTypes) { this.encoded = new buffer_js_1.EncodeBuffer(); this.fieldPath = ['value']; + this.checkTypes = checkTypes; } BorshSerializer.prototype.encode = function (value, schema) { this.encode_value(value, schema); @@ -63,11 +64,11 @@ var BorshSerializer = /** @class */ (function () { BorshSerializer.prototype.encode_integer = function (value, schema) { var size = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { - utils.expect_type(value, 'number', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value, schema); } else { - utils.expect_bigint(value, this.fieldPath); + this.checkTypes && utils.expect_bigint(value, this.fieldPath); this.encode_bigint(BigInt(value), size); } }; @@ -81,7 +82,7 @@ var BorshSerializer = /** @class */ (function () { this.encoded.store_bytes(new Uint8Array(buffer)); }; BorshSerializer.prototype.encode_string = function (value) { - utils.expect_type(value, 'string', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'string', this.fieldPath); var _value = value; // 4 bytes for length this.encoded.store_value(_value.length, 'u32'); @@ -91,7 +92,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_boolean = function (value) { - utils.expect_type(value, 'boolean', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'boolean', this.fieldPath); this.encoded.store_value(value ? 1 : 0, 'u8'); }; BorshSerializer.prototype.encode_option = function (value, schema) { @@ -104,7 +105,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_enum = function (value, schema) { - utils.expect_enum(value, this.fieldPath); + this.checkTypes && utils.expect_enum(value, this.fieldPath); var valueKey = Object.keys(value)[0]; for (var i = 0; i < schema["enum"].length; i++) { var valueSchema = schema["enum"][i]; @@ -147,7 +148,7 @@ var BorshSerializer = /** @class */ (function () { this.encoded.store_bytes(new Uint8Array(value)); }; BorshSerializer.prototype.encode_set = function (value, schema) { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); var isSet = value instanceof Set; var values = isSet ? Array.from(value.values()) : Object.values(value); // 4 bytes for length @@ -159,7 +160,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_map = function (value, schema) { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); var isMap = value instanceof Map; var keys = isMap ? Array.from(value.keys()) : Object.keys(value); // 4 bytes for length @@ -172,7 +173,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_struct = function (value, schema) { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); for (var _i = 0, _a = Object.keys(schema.struct); _i < _a.length; _i++) { var key = _a[_i]; this.fieldPath.push(key); diff --git a/lib/cjs/utils.js b/lib/cjs/utils.js index ec590a31..317783f9 100644 --- a/lib/cjs/utils.js +++ b/lib/cjs/utils.js @@ -36,7 +36,9 @@ function expect_type(value, type, fieldPath) { } exports.expect_type = expect_type; function expect_bigint(value, fieldPath) { - if (!['number', 'string', 'bigint', 'boolean'].includes(typeof (value))) { + var basicType = ['number', 'string', 'bigint', 'boolean'].includes(typeof (value)); + var strObject = typeof (value) === 'object' && value !== null && 'toString' in value; + if (!basicType && !strObject) { throw new Error("Expected bigint, number, boolean or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } diff --git a/lib/esm/index.d.ts b/lib/esm/index.d.ts index a6aeaa97..29ea1887 100644 --- a/lib/esm/index.d.ts +++ b/lib/esm/index.d.ts @@ -1,4 +1,4 @@ import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; -export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; -export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; +export declare function serialize(schema: Schema, value: unknown, validate?: boolean): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, validate?: boolean): DecodeTypes; diff --git a/lib/esm/index.js b/lib/esm/index.js index 211567cc..6137b438 100644 --- a/lib/esm/index.js +++ b/lib/esm/index.js @@ -1,16 +1,16 @@ import { BorshSerializer } from './serialize.js'; import { BorshDeserializer } from './deserialize.js'; import * as utils from './utils.js'; -export function serialize(schema, value, checkSchema) { - if (checkSchema === void 0) { checkSchema = true; } - if (checkSchema) +export function serialize(schema, value, validate) { + if (validate === void 0) { validate = true; } + if (validate) utils.validate_schema(schema); - var serializer = new BorshSerializer(); + var serializer = new BorshSerializer(validate); return serializer.encode(value, schema); } -export function deserialize(schema, buffer, checkSchema) { - if (checkSchema === void 0) { checkSchema = true; } - if (checkSchema) +export function deserialize(schema, buffer, validate) { + if (validate === void 0) { validate = true; } + if (validate) utils.validate_schema(schema); var deserializer = new BorshDeserializer(buffer); return deserializer.decode(schema); diff --git a/lib/esm/serialize.d.ts b/lib/esm/serialize.d.ts index dc9edf31..adaaae9a 100644 --- a/lib/esm/serialize.d.ts +++ b/lib/esm/serialize.d.ts @@ -3,7 +3,8 @@ import { EncodeBuffer } from './buffer.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; - constructor(); + checkTypes: boolean; + constructor(checkTypes: any); encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; diff --git a/lib/esm/serialize.js b/lib/esm/serialize.js index 09bc67d5..acfd8aee 100644 --- a/lib/esm/serialize.js +++ b/lib/esm/serialize.js @@ -2,9 +2,10 @@ import { integers } from './types.js'; import { EncodeBuffer } from './buffer.js'; import * as utils from './utils.js'; var BorshSerializer = /** @class */ (function () { - function BorshSerializer() { + function BorshSerializer(checkTypes) { this.encoded = new EncodeBuffer(); this.fieldPath = ['value']; + this.checkTypes = checkTypes; } BorshSerializer.prototype.encode = function (value, schema) { this.encode_value(value, schema); @@ -37,11 +38,11 @@ var BorshSerializer = /** @class */ (function () { BorshSerializer.prototype.encode_integer = function (value, schema) { var size = parseInt(schema.substring(1)); if (size <= 32 || schema == 'f64') { - utils.expect_type(value, 'number', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'number', this.fieldPath); this.encoded.store_value(value, schema); } else { - utils.expect_bigint(value, this.fieldPath); + this.checkTypes && utils.expect_bigint(value, this.fieldPath); this.encode_bigint(BigInt(value), size); } }; @@ -55,7 +56,7 @@ var BorshSerializer = /** @class */ (function () { this.encoded.store_bytes(new Uint8Array(buffer)); }; BorshSerializer.prototype.encode_string = function (value) { - utils.expect_type(value, 'string', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'string', this.fieldPath); var _value = value; // 4 bytes for length this.encoded.store_value(_value.length, 'u32'); @@ -65,7 +66,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_boolean = function (value) { - utils.expect_type(value, 'boolean', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'boolean', this.fieldPath); this.encoded.store_value(value ? 1 : 0, 'u8'); }; BorshSerializer.prototype.encode_option = function (value, schema) { @@ -78,7 +79,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_enum = function (value, schema) { - utils.expect_enum(value, this.fieldPath); + this.checkTypes && utils.expect_enum(value, this.fieldPath); var valueKey = Object.keys(value)[0]; for (var i = 0; i < schema["enum"].length; i++) { var valueSchema = schema["enum"][i]; @@ -121,7 +122,7 @@ var BorshSerializer = /** @class */ (function () { this.encoded.store_bytes(new Uint8Array(value)); }; BorshSerializer.prototype.encode_set = function (value, schema) { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); var isSet = value instanceof Set; var values = isSet ? Array.from(value.values()) : Object.values(value); // 4 bytes for length @@ -133,7 +134,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_map = function (value, schema) { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); var isMap = value instanceof Map; var keys = isMap ? Array.from(value.keys()) : Object.keys(value); // 4 bytes for length @@ -146,7 +147,7 @@ var BorshSerializer = /** @class */ (function () { } }; BorshSerializer.prototype.encode_struct = function (value, schema) { - utils.expect_type(value, 'object', this.fieldPath); + this.checkTypes && utils.expect_type(value, 'object', this.fieldPath); for (var _i = 0, _a = Object.keys(schema.struct); _i < _a.length; _i++) { var key = _a[_i]; this.fieldPath.push(key); diff --git a/lib/esm/utils.js b/lib/esm/utils.js index 3e50986a..6e722cbf 100644 --- a/lib/esm/utils.js +++ b/lib/esm/utils.js @@ -31,7 +31,9 @@ export function expect_type(value, type, fieldPath) { } } export function expect_bigint(value, fieldPath) { - if (!['number', 'string', 'bigint', 'boolean'].includes(typeof (value))) { + var basicType = ['number', 'string', 'bigint', 'boolean'].includes(typeof (value)); + var strObject = typeof (value) === 'object' && value !== null && 'toString' in value; + if (!basicType && !strObject) { throw new Error("Expected bigint, number, boolean or string not ".concat(typeof (value), "(").concat(value, ") at ").concat(fieldPath.join('.'))); } } diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index a6aeaa97..29ea1887 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -1,4 +1,4 @@ import { Schema, DecodeTypes } from './types.js'; export { Schema } from './types'; -export declare function serialize(schema: Schema, value: unknown, checkSchema?: boolean): Uint8Array; -export declare function deserialize(schema: Schema, buffer: Uint8Array, checkSchema?: boolean): DecodeTypes; +export declare function serialize(schema: Schema, value: unknown, validate?: boolean): Uint8Array; +export declare function deserialize(schema: Schema, buffer: Uint8Array, validate?: boolean): DecodeTypes; diff --git a/lib/types/serialize.d.ts b/lib/types/serialize.d.ts index dc9edf31..adaaae9a 100644 --- a/lib/types/serialize.d.ts +++ b/lib/types/serialize.d.ts @@ -3,7 +3,8 @@ import { EncodeBuffer } from './buffer.js'; export declare class BorshSerializer { encoded: EncodeBuffer; fieldPath: string[]; - constructor(); + checkTypes: boolean; + constructor(checkTypes: any); encode(value: unknown, schema: Schema): Uint8Array; encode_value(value: unknown, schema: Schema): void; encode_integer(value: unknown, schema: IntegerType): void; diff --git a/package.json b/package.json index bbda303a..3440a114 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@typescript-eslint/parser": "^5.28.0", "eslint": "^8.17.0", "jest": "^26.0.1", - "typescript": "^4" + "typescript": "^4", + "bn.js": "^5.2.0" } }