Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major Update of Borsh #65

Merged
merged 35 commits into from
Aug 4, 2023
Merged
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8f0b620
indexed by class name instead of the class itself
gagdiez Jul 14, 2023
6e514ab
serializer 1.0
gagdiez Jul 19, 2023
7b3fe19
Implemented deserializer
gagdiez Jul 20, 2023
8fba23d
Fixed indentation
gagdiez Jul 20, 2023
9cd1a01
Added schema validation
gagdiez Jul 21, 2023
6137793
minor improvements
gagdiez Jul 21, 2023
a8a0e83
added more tests
gagdiez Jul 21, 2023
7d46f8b
added more tests
gagdiez Jul 21, 2023
5e89593
added more tests
gagdiez Jul 21, 2023
c64fd81
updated readme
gagdiez Jul 21, 2023
fb64d67
minor fix to examples
gagdiez Jul 21, 2023
da7ef6b
bump in version
gagdiez Jul 21, 2023
67c75f4
minor update to README.md
gagdiez Jul 21, 2023
0367248
minor update to README.md
gagdiez Jul 21, 2023
04b13b3
trigger actions
gagdiez Jul 21, 2023
ad6eeb0
Removed unnecesary packages + fixed lint
gagdiez Jul 21, 2023
f0ee708
simplified buffer
gagdiez Jul 21, 2023
e2a8310
added base encode/decode
gagdiez Jul 21, 2023
019d58e
implemented enums and removed deserializing of classes
gagdiez Jul 25, 2023
b50bc1f
better organized testing
gagdiez Jul 26, 2023
49b96a2
exported schema
gagdiez Jul 26, 2023
a47e7ff
Added forgotten schemas to schema type
gagdiez Jul 26, 2023
b34a106
allowing numbers in BN
gagdiez Jul 26, 2023
7204e49
schema now leads serialization order
gagdiez Jul 26, 2023
dd1cbb4
bump version
gagdiez Jul 26, 2023
d580c8c
feat: allow strings in BN
gagdiez Jul 26, 2023
d452862
feat: more tests & checkSchema flag
gagdiez Jul 28, 2023
988fb57
fix: made compatible to ES5
gagdiez Jul 28, 2023
263e902
updated readme
gagdiez Jul 28, 2023
b97b6e7
feat: building cjs & esm
gagdiez Jul 31, 2023
dbebcd7
feat: cjs & esm working versions
gagdiez Aug 1, 2023
22a824c
removed BN.js & bs58
gagdiez Aug 2, 2023
fb89acf
simplified tests
gagdiez Aug 2, 2023
2384755
small change in bigint method
gagdiez Aug 3, 2023
e1881a0
added compatibility with BN
gagdiez Aug 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added schema validation
gagdiez committed Jul 21, 2023
commit 9cd1a01732894ca1a8376d5460733a4d018373c1
6 changes: 3 additions & 3 deletions borsh-ts/buffer.ts
Original file line number Diff line number Diff line change
@@ -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;

23 changes: 14 additions & 9 deletions borsh-ts/deserialize.ts
Original file line number Diff line number Diff line change
@@ -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;
}
40 changes: 22 additions & 18 deletions borsh-ts/serialize.ts
Original file line number Diff line number Diff line change
@@ -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,16 +142,16 @@ 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);
}
}

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]);
this.encode_value(value[key], schema.struct[key]);
}
}
}
6 changes: 3 additions & 3 deletions borsh-ts/types.ts
Original file line number Diff line number Diff line change
@@ -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<any> | ArrayBuffer | Map<any, any> | Set<any> | object | null;
47 changes: 47 additions & 0 deletions borsh-ts/utils.ts
Original file line number Diff line number Diff line change
@@ -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]);
}
}
6 changes: 3 additions & 3 deletions lib/buffer.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { NumberType } from './types';
import { IntegerType } 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_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;
}
5 changes: 3 additions & 2 deletions lib/deserialize.d.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading