New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@dao-xyz/borsh

Package Overview
Dependencies
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dao-xyz/borsh - npm Package Compare versions

Comparing version 2.0.7 to 2.1.0

347

lib/cjs/__tests__/index.test.js

@@ -32,3 +32,3 @@ "use strict";

], TestStruct.prototype, "b", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const expectedResult = new index_1.StructKind({

@@ -68,3 +68,3 @@ fields: [

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
expect((0, index_1.getSchema)(TestStruct)).toEqual(new index_1.StructKind({

@@ -86,3 +86,4 @@ fields: [{ key: "a", type: InnerStruct }],

], TestStruct.prototype, "c", void 0);
let schema = (0, index_1.validate)([TestStruct]).get(TestStruct);
(0, index_1.validate)(TestStruct);
let schema = (0, index_1.getSchema)(TestStruct);
expect(schema.fields.length).toEqual(2);

@@ -109,3 +110,3 @@ expect(schema.fields[0].key).toEqual("a");

], TestStruct.prototype, "b", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const expectedResult = new index_1.StructKind({

@@ -146,3 +147,3 @@ fields: [

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const buf = (0, index_1.serialize)(new TestStruct({ a: [1, 2, 3] }));

@@ -164,3 +165,3 @@ expect(buf).toEqual(Buffer.from([3, 0, 0, 0, 1, 2, 3]));

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const buf = (0, index_1.serialize)(new TestStruct({ a: [1, 2, 3] }));

@@ -182,3 +183,3 @@ expect(buf).toEqual(Buffer.from([1, 2, 3]));

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
expect(() => (0, index_1.serialize)(new TestStruct({ a: [1, 2] }))).toThrowError();

@@ -197,3 +198,3 @@ });

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
expect(() => (0, index_1.deserialize)(Buffer.from([1, 2]), TestStruct)).toThrowError();

@@ -222,3 +223,3 @@ });

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const arr = [

@@ -249,3 +250,3 @@ new Element({ a: 1 }),

const instance = new TestEnum(3);
(0, index_1.validate)([TestEnum]);
(0, index_1.validate)(TestEnum);
const buf = (0, index_1.serialize)(instance);

@@ -263,3 +264,3 @@ expect(buf).toEqual(Buffer.from([1, 3]));

const instance = new TestEnum();
(0, index_1.validate)([TestEnum]);
(0, index_1.validate)(TestEnum);
const buf = (0, index_1.serialize)(instance);

@@ -285,3 +286,3 @@ expect(buf).toEqual(Buffer.from([1]));

], TestStruct.prototype, "variant", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
expect((0, index_1.getSchema)(TestStruct)).toBeDefined();

@@ -326,3 +327,3 @@ expect((0, index_1.getSchema)(ImplementationByVariant)).toBeDefined();

const instance = new TestStruct(new Enum1(4));
(0, index_1.validate)([Enum0, Enum1, TestStruct]);
(0, index_1.validate)(Super);
expect((0, index_1.getSchema)(Enum0)).toBeDefined();

@@ -337,3 +338,3 @@ expect((0, index_1.getSchema)(Enum1)).toBeDefined();

});
test("extended enum", () => {
test("extended enum top variants", () => {
class SuperSuper {

@@ -380,2 +381,93 @@ }

});
test("extended enum inheritance variants", () => {
let SuperSuper = class SuperSuper {
};
SuperSuper = __decorate([
(0, index_1.variant)(1)
], SuperSuper);
let Super = class Super extends SuperSuper {
constructor() {
super();
}
};
Super = __decorate([
(0, index_1.variant)(2)
], Super);
let Enum0 = class Enum0 extends Super {
constructor(a) {
super();
this.a = a;
}
};
__decorate([
(0, index_1.field)({ type: "u8" })
], Enum0.prototype, "a", void 0);
Enum0 = __decorate([
(0, index_1.variant)([3, 100])
], Enum0);
let Enum1 = class Enum1 extends Super {
constructor(b) {
super();
this.b = b;
}
};
__decorate([
(0, index_1.field)({ type: "u8" })
], Enum1.prototype, "b", void 0);
Enum1 = __decorate([
(0, index_1.variant)([3, 4])
], Enum1);
const instance = new Enum1(5);
// validate([Enum0, Enum1, Super, SuperSuper]);
expect((0, index_1.getSchema)(Enum0)).toBeDefined();
expect((0, index_1.getSchema)(Enum1)).toBeDefined();
const serialized = (0, index_1.serialize)(instance);
expect(serialized).toEqual(Buffer.from([1, 2, 3, 4, 5]));
const deserialied = (0, index_1.deserialize)(Buffer.from(serialized), SuperSuper, false, binary_1.BinaryReader);
expect(deserialied).toBeInstanceOf(Enum1);
expect(deserialied.b).toEqual(5);
});
test("inheritance without variant", () => {
class Super {
}
class A extends Super {
}
__decorate([
(0, index_1.field)({ type: "u8" })
], A.prototype, "a", void 0);
class B extends A {
constructor(opts) {
super();
if (opts) {
Object.assign(this, opts);
}
}
}
__decorate([
(0, index_1.field)({ type: "u8" })
], B.prototype, "b", void 0);
let C1 = class C1 extends B {
constructor(opts) {
super();
if (opts) {
Object.assign(this, opts);
}
}
};
C1 = __decorate([
(0, index_1.variant)(0)
], C1);
let C2 = class C2 extends B {
};
C2 = __decorate([
(0, index_1.variant)(1)
], C2);
(0, index_1.validate)(Super);
const serialized = (0, index_1.serialize)(new C1({ a: 1, b: 2 }));
expect(serialized).toEqual(Buffer.from([1, 2, 0]));
const deserialied = (0, index_1.deserialize)(Buffer.from(serialized), Super, false, binary_1.BinaryReader);
expect(deserialied).toBeInstanceOf(C1);
expect(deserialied.a).toEqual(1);
expect(deserialied.b).toEqual(2);
});
test("wrapped enum", () => {

@@ -405,3 +497,3 @@ class Super {

const instance = new TestStruct(new Enum2(3));
(0, index_1.validate)([Enum2, TestStruct]);
(0, index_1.validate)(Super);
expect((0, index_1.getSchema)(Enum2)).toBeDefined();

@@ -451,3 +543,3 @@ expect((0, index_1.getSchema)(TestStruct)).toBeDefined();

const instance = new TestStruct(new Enum1(5));
(0, index_1.validate)([Enum0, Enum1, TestStruct]);
(0, index_1.validate)(Super);
expect((0, index_1.getSchema)(Enum1)).toBeDefined();

@@ -461,2 +553,39 @@ expect((0, index_1.getSchema)(TestStruct)).toBeDefined();

});
test("enum string variant", () => {
class Ape {
constructor(name) {
this.name = name;
}
}
__decorate([
(0, index_1.field)({ type: "String" })
], Ape.prototype, "name", void 0);
let Gorilla = class Gorilla extends Ape {
};
Gorilla = __decorate([
(0, index_1.variant)("🦍")
], Gorilla);
let Orangutan = class Orangutan extends Ape {
};
Orangutan = __decorate([
(0, index_1.variant)("🦧")
], Orangutan);
class HighCouncil {
constructor(members) {
if (members) {
this.members = members;
}
}
}
__decorate([
(0, index_1.field)({ type: (0, index_1.vec)(Ape) })
], HighCouncil.prototype, "members", void 0);
let bytes = (0, index_1.serialize)(new HighCouncil([new Gorilla("Go"), new Orangutan("Ora")]));
let deserialized = (0, index_1.deserialize)(Buffer.from(bytes), HighCouncil);
expect(deserialized).toBeInstanceOf(HighCouncil);
expect(deserialized.members[0]).toBeInstanceOf(Gorilla);
expect(deserialized.members[0].name).toEqual("Go");
expect(deserialized.members[1]).toBeInstanceOf(Orangutan);
expect(deserialized.members[1].name).toEqual("Ora");
});
});

@@ -473,3 +602,3 @@ describe("option", () => {

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const expectedResult = new index_1.StructKind({

@@ -510,3 +639,3 @@ fields: [

], TestStruct.prototype, "a", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const expectedResult = new index_1.StructKind({

@@ -552,3 +681,3 @@ fields: [

], TestStruct.prototype, "obj", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const serialized = (0, index_1.serialize)(new TestStruct({ a: 2, b: 3 }));

@@ -575,3 +704,3 @@ const deserialied = (0, index_1.deserialize)(Buffer.from(serialized), TestStruct, false, binary_1.BinaryReader);

], TestStruct.prototype, "b", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
const expectedResult = new index_1.StructKind({

@@ -603,3 +732,3 @@ fields: [

const thrower = () => {
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
};

@@ -620,3 +749,3 @@ // Error is thrown since 1 field with index 1 is undefined behaviour

const thrower = () => {
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
};

@@ -636,3 +765,4 @@ // Error is thrown since missing field with index 1

], TestStruct.prototype, "b", void 0);
const schema = (0, index_1.validate)([TestStruct]).get(TestStruct);
(0, index_1.validate)(TestStruct);
const schema = (0, index_1.getSchema)(TestStruct);
const expectedResult = new index_1.StructKind({

@@ -664,3 +794,3 @@ fields: [

const bytes = Uint8Array.from([1, 0]); // has an extra 0
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
expect(() => (0, index_1.deserialize)(Buffer.from(bytes), TestStruct, false)).toThrowError(error_1.BorshError);

@@ -693,2 +823,108 @@ expect((0, index_1.deserialize)(Buffer.from(bytes), TestStruct, true).a).toEqual(1);

});
test("variant type conflict", () => {
class Super {
constructor() { }
}
let A = class A extends Super {
constructor() {
super();
}
};
A = __decorate([
(0, index_1.variant)([0, 0]) // Same as B
], A);
let B = class B extends Super {
constructor() {
super();
}
};
B = __decorate([
(0, index_1.variant)(0) // Same as A
], B);
expect(() => (0, index_1.validate)(Super)).toThrowError(error_1.BorshError);
});
test("variant type conflict inheritance", () => {
class SuperSuper {
}
class Super extends SuperSuper {
}
let A = class A extends Super {
constructor() {
super();
}
};
A = __decorate([
(0, index_1.variant)([0, 0]) // Same as B
], A);
let B = class B extends SuperSuper {
constructor() {
super();
}
};
B = __decorate([
(0, index_1.variant)(0) // Same as A
], B);
expect(() => (0, index_1.validate)(SuperSuper)).toThrowError(error_1.BorshError);
});
test("variant type conflict array length", () => {
class Super {
}
let A = class A extends Super {
constructor() {
super();
}
};
A = __decorate([
(0, index_1.variant)([0, 0]) // Same as B
], A);
let B = class B extends Super {
constructor() {
super();
}
};
B = __decorate([
(0, index_1.variant)([0]) // Same as A
], B);
expect(() => (0, index_1.validate)(Super)).toThrowError(error_1.BorshError);
});
test("error for non optimized code", () => {
class TestStruct {
constructor() { }
}
class A extends TestStruct {
}
__decorate([
(0, index_1.field)({ type: "String" })
], A.prototype, "string", void 0);
class B extends TestStruct {
}
__decorate([
(0, index_1.field)({ type: "String" })
], B.prototype, "string", void 0);
expect(() => (0, index_1.validate)(TestStruct)).toThrowError(error_1.BorshError);
});
test("error for non optimized code on deserialization", () => {
class TestStruct {
constructor() { }
}
class A extends TestStruct {
constructor() {
super(...arguments);
this.string = "A";
}
}
__decorate([
(0, index_1.field)({ type: "String" })
], A.prototype, "string", void 0);
class B extends TestStruct {
constructor() {
super(...arguments);
this.string = "B";
}
}
__decorate([
(0, index_1.field)({ type: "String" })
], B.prototype, "string", void 0);
expect(() => (0, index_1.deserialize)(Buffer.from((0, index_1.serialize)(new A())), TestStruct)).toThrowError(error_1.BorshError);
});
test("variant conflict, indices", () => {

@@ -732,62 +968,5 @@ const classDef = () => {

], TestStruct.prototype, "missing", void 0);
expect(() => (0, index_1.validate)([TestStruct])).toThrowError(error_1.BorshError);
(0, index_1.validate)([TestStruct], true); // Should be ok since we allow undefined
expect(() => (0, index_1.validate)(TestStruct)).toThrowError(error_1.BorshError);
(0, index_1.validate)(TestStruct, true); // Should be ok since we allow undefined
});
test("missing variant", () => {
class Super {
}
let Enum0 = class Enum0 extends Super {
constructor() {
super();
}
};
Enum0 = __decorate([
(0, index_1.variant)(0)
], Enum0);
class TestStruct {
constructor(missing) {
this.missing = missing;
}
}
__decorate([
(0, index_1.field)({ type: Super })
], TestStruct.prototype, "missing", void 0);
(0, index_1.validate)([TestStruct]);
expect((0, index_1.getSchema)(Enum0)).toBeDefined();
expect((0, index_1.getSchema)(TestStruct)).toBeDefined();
expect((0, index_1.getSchema)(Super)).toBeUndefined();
});
test("missing variant one off", () => {
class Super {
}
let Enum0 = class Enum0 extends Super {
constructor() {
super();
}
};
Enum0 = __decorate([
(0, index_1.variant)(0)
], Enum0);
let Enum1 = class Enum1 extends Super {
constructor() {
super();
}
};
Enum1 = __decorate([
(0, index_1.variant)(1)
], Enum1);
class TestStruct {
constructor(missing) {
this.missing = missing;
}
}
__decorate([
(0, index_1.field)({ type: Super })
], TestStruct.prototype, "missing", void 0);
(0, index_1.validate)([TestStruct]);
expect((0, index_1.getSchema)(Enum0)).toBeDefined();
expect((0, index_1.getSchema)(Enum1)).toBeDefined();
expect((0, index_1.getSchema)(TestStruct)).toBeDefined();
expect((0, index_1.getSchema)(Super)).toBeUndefined();
});
test("valid dependency", () => {

@@ -810,5 +989,5 @@ class Implementation {

], TestStruct.prototype, "missing", void 0);
(0, index_1.validate)([TestStruct]);
(0, index_1.validate)(TestStruct);
});
});
//# sourceMappingURL=index.test.js.map
/// <reference types="node" />
import { StructKind, SimpleField, CustomField } from "./types";
import { StructKind, SimpleField, CustomField, Constructor } from "./types";
import { BinaryWriter, BinaryReader } from "./binary";

@@ -27,2 +27,6 @@ export * from "./binary";

export declare const getSchema: (ctor: Function) => StructKind;
export declare const getSchemasBottomUp: (ctor: Function) => {
clazz: Function;
schema: StructKind;
}[];
/**

@@ -33,4 +37,4 @@ *

*/
export declare const variant: (index: number | number[]) => (ctor: Function) => void;
export declare const getVariantIndex: (clazz: any) => number | number[] | undefined;
export declare const variant: (index: number | number[] | string) => (ctor: Function) => void;
export declare const getVariantIndex: (clazz: any) => number | number[] | string | undefined;
/**

@@ -46,2 +50,2 @@ * @param properties, the properties of the field mapping to schema

*/
export declare const validate: (clazzes: any[], allowUndefined?: boolean) => Map<any, StructKind>;
export declare const validate: (clazzes: Constructor<any> | Constructor<any>[], allowUndefined?: boolean) => void;

@@ -20,3 +20,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.validate = exports.field = exports.getVariantIndex = exports.variant = exports.getSchema = exports.deserializeUnchecked = exports.deserialize = exports.serialize = exports.serializeStruct = exports.serializeField = exports.baseDecode = exports.baseEncode = void 0;
exports.validate = exports.field = exports.getVariantIndex = exports.variant = exports.getSchemasBottomUp = exports.getSchema = exports.deserializeUnchecked = exports.deserialize = exports.serialize = exports.serializeStruct = exports.serializeField = exports.baseDecode = exports.baseEncode = void 0;
const bs58_1 = __importDefault(require("bs58"));

@@ -93,14 +93,32 @@ const types_1 = require("./types");

}
const structSchema = (0, exports.getSchema)(obj.constructor);
if (!structSchema) {
// Serialize content as struct, we do not invoke serializeStruct since it will cause circular calls to this method
const structSchemas = (0, exports.getSchemasBottomUp)(obj.constructor);
// If Schema has fields, "structSchema" will be non empty and "fields" will exist
if (structSchemas.length == 0) {
throw new error_1.BorshError(`Class ${obj.constructor.name} is missing in schema`);
}
if (structSchema instanceof types_1.StructKind) {
structSchema.fields.map((field) => {
serializeField(field.key, obj[field.key], field.type, writer);
});
}
else {
throw new error_1.BorshError(`Unexpected schema for ${obj.constructor.name}`);
}
structSchemas.forEach((v) => {
if (v.schema instanceof types_1.StructKind) {
const index = v.schema.variant;
if (index != undefined) {
if (typeof index === "number") {
writer.writeU8(index);
}
else if (Array.isArray(index)) {
index.forEach((i) => {
writer.writeU8(i);
});
}
else { // is string
writer.writeString(index);
}
}
v.schema.fields.map((field) => {
serializeField(field.key, obj[field.key], field.type, writer);
});
}
else {
throw new error_1.BorshError(`Unexpected schema for ${obj.constructor.name}`);
}
});
}

@@ -154,59 +172,96 @@ exports.serializeStruct = serializeStruct;

}
let structSchema = (0, exports.getSchema)(clazz); //schema.get(clazz);
let idx = undefined;
if (!structSchema) {
// We find the deserialization schema from one of the subclasses
// it must be an enum
idx = [reader.readU8()];
// Try polymorphic deserialziation (i.e. get all subclasses and find best
// class this can be deserialized to)
const result = {};
// assume clazz is super class
if ((0, exports.getVariantIndex)(clazz) !== undefined) {
// It is an (stupid) enum, but we deserialize into its variant directly
// This means we should omit the variant index
let index = (0, exports.getVariantIndex)(clazz);
if (typeof index === "number") {
reader.readU8();
}
else if (Array.isArray(index)) {
for (const _ of index) {
reader.readU8();
}
}
else { // string
reader.readString();
}
}
// Polymorphic serialization, i.e. reversed prototype iteration using descriminators
let once = false;
let currClazz = clazz;
while (((0, exports.getSchema)(currClazz) || getDependencies(currClazz).size > 0)) {
let structSchema = (0, exports.getSchema)(currClazz);
once = true;
let variantsIndex = undefined;
let variantString = undefined;
let nextClazz = undefined;
let dependencies = getNonTrivialDependencies(currClazz);
if (structSchema) {
for (const field of structSchema.fields) {
result[field.key] = deserializeField(field.key, field.type, reader);
}
}
// We know that we should serialize into the variant that accounts to the first byte of the read
for (const [_key, actualClazz] of getDependenciesRecursively(clazz)) {
for (const [_key, actualClazz] of dependencies) {
const variantIndex = (0, exports.getVariantIndex)(actualClazz);
if (variantIndex !== undefined) {
if (typeof variantIndex === "number") {
if (variantIndex == idx[0]) {
clazz = actualClazz;
structSchema = (0, exports.getSchema)(clazz);
if (!variantsIndex) {
variantsIndex = [reader.readU8()];
}
if (variantIndex == variantsIndex[0]) {
nextClazz = actualClazz;
break;
}
} // variant is array, check all values
else {
while (idx.length < variantIndex.length) {
idx.push(reader.readU8());
}
else if (Array.isArray(variantIndex)) { // variant is array, check all values
if (!variantsIndex) {
variantsIndex = [];
while (variantsIndex.length < variantIndex.length) {
variantsIndex.push(reader.readU8());
}
}
// Compare variants
if (idx.length === variantIndex.length &&
idx.every((value, index) => value === variantIndex[index])) {
clazz = actualClazz;
structSchema = (0, exports.getSchema)(clazz);
if (variantsIndex.length === variantIndex.length &&
variantsIndex.every((value, index) => value === variantIndex[index])) {
nextClazz = actualClazz;
break;
}
}
else { // is string
if (variantString == undefined) {
variantString = reader.readString();
}
// Compare variants is just string compare
if (variantString === variantIndex) {
nextClazz = actualClazz;
break;
}
}
}
}
if (!structSchema)
throw new error_1.BorshError(`Class ${clazz.name} is missing in schema`);
}
else if ((0, exports.getVariantIndex)(clazz) !== undefined) {
// It is an enum, but we deserialize into its variant directly
// This means we should omit the variant index
let index = (0, exports.getVariantIndex)(clazz);
if (typeof index === "number") {
reader.readU8();
}
else {
for (const _ of index) {
reader.readU8();
if (nextClazz == undefined) {
// do a recursive call and copy result,
// this is not computationally performant since we are going to traverse multiple path
// and possible do deserialziation on bad paths
if (dependencies.size == 1) // still deterministic
nextClazz = dependencies.values().next().value;
else if (dependencies.size > 1) {
const classes = [...dependencies.values()].map((f) => f.name).join(', ');
throw new error_1.BorshError(`Multiple ambigious deserialization paths from ${currClazz.name} found: ${classes}. This is not allowed, and would not be performant if allowed`);
}
}
}
if (structSchema instanceof types_1.StructKind) {
const result = {};
for (const field of (0, exports.getSchema)(clazz).fields) {
result[field.key] = deserializeField(field.key, field.type, reader);
if (nextClazz == undefined) {
break;
}
return Object.assign(new clazz(), result);
currClazz = nextClazz;
/* if (!structSchema)
throw new BorshError(`Class ${clazz.name} is missing in schema`); */
}
throw new error_1.BorshError(`Unexpected schema ${clazz.constructor.name}`);
if (!once) {
throw new error_1.BorshError(`Unexpected schema ${clazz.constructor.name}`);
}
return Object.assign(new currClazz(), result);
}

@@ -249,2 +304,8 @@ /**

};
const setDependencyToProtoType = (ctor) => {
var _a;
let proto = Object.getPrototypeOf(ctor);
if (((_a = proto.prototype) === null || _a === void 0 ? void 0 : _a.constructor) != undefined)
setDependency(proto, ctor);
};
const setDependency = (ctor, dependency) => {

@@ -254,2 +315,6 @@ let dependencies = getDependencies(ctor);

if (key != undefined && dependencies.has(key)) {
if (dependencies.get(key) == dependency) {
// already added;
return;
}
throw new error_1.BorshError(`Conflicting variants: Dependency ${dependencies.get(key).name} and ${dependency.name} share same variant index(es)`);

@@ -271,3 +336,3 @@ }

dependencies.set(key, dependency);
ctor.prototype._borsh_dependency = dependencies;
setDependencies(ctor, dependencies);
};

@@ -285,5 +350,30 @@ const hasDependencies = (ctor, schema) => {

};
const getDependencyKey = (ctor) => "_borsh_dependency_" + ctor.name;
const getDependencies = (ctor) => {
return ctor.prototype._borsh_dependency ? ctor.prototype._borsh_dependency : new Map();
let existing = ctor.prototype.constructor[getDependencyKey(ctor)];
if (existing)
return existing;
return new Map();
};
const getNonTrivialDependencies = (ctor) => {
let ret = new Map();
let existing = ctor.prototype.constructor[getDependencyKey(ctor)];
if (existing)
existing.forEach((v, k) => {
let schema = (0, exports.getSchema)(v);
if (schema.fields.length > 0 || schema.variant != undefined) { // non trivial
ret.set(k, v);
}
else { // check recursively
let req = getNonTrivialDependencies(v);
req.forEach((rv, rk) => {
ret.set(rk, rv);
});
}
});
return ret;
};
const setDependencies = (ctor, dependencies) => {
return ctor.prototype.constructor[getDependencyKey(ctor)] = dependencies;
};
/**

@@ -307,8 +397,25 @@ * Flat map class inheritance tree into hashmap where key represents variant key

const setSchema = (ctor, schema) => {
ctor.prototype._borsh_schema = schema;
ctor.prototype.constructor["_borsh_schema_" + ctor.name] = schema;
};
const getSchema = (ctor) => {
return ctor.prototype._borsh_schema;
if (ctor.prototype == undefined) {
const t = 123;
}
return ctor.prototype.constructor["_borsh_schema_" + ctor.name];
};
exports.getSchema = getSchema;
const getSchemasBottomUp = (ctor) => {
let schemas = [];
while (ctor.prototype != undefined) {
let schema = (0, exports.getSchema)(ctor);
if (schema)
schemas.push({
clazz: ctor,
schema
});
ctor = Object.getPrototypeOf(ctor);
}
return schemas.reverse();
};
exports.getSchemasBottomUp = getSchemasBottomUp;
/**

@@ -321,24 +428,5 @@ *

return (ctor) => {
getOrCreateStructMeta(ctor);
let schema = getOrCreateStructMeta(ctor);
// Create a custom serialization, for enum by prepend instruction index
ctor.prototype.borshSerialize = function (writer) {
if (typeof index === "number") {
writer.writeU8(index);
}
else {
index.forEach((i) => {
writer.writeU8(i);
});
}
// Serialize content as struct, we do not invoke serializeStruct since it will cause circular calls to this method
const structSchema = (0, exports.getSchema)(ctor);
// If Schema has fields, "structSchema" will be non empty and "fields" will exist
if (structSchema === null || structSchema === void 0 ? void 0 : structSchema.fields)
for (const field of structSchema.fields) {
serializeField(field.key, this[field.key], field.type, writer);
}
};
ctor.prototype._borsh_variant_index = function () {
return index; // creates a function that returns the variant index on the class
};
schema.variant = index;
// Define Schema for this class, even though it might miss fields since this is a variant

@@ -355,5 +443,3 @@ const clazzes = (0, types_1.extendingClasses)(ctor);

const getVariantIndex = (clazz) => {
if (clazz.prototype._borsh_variant_index)
return clazz.prototype._borsh_variant_index();
return undefined;
return getOrCreateStructMeta(clazz).variant;
};

@@ -367,2 +453,3 @@ exports.getVariantIndex = getVariantIndex;

return (target, name) => {
setDependencyToProtoType(target.constructor);
const schema = getOrCreateStructMeta(target.constructor);

@@ -411,57 +498,52 @@ const key = name.toString();

const validateIterator = (clazzes, allowUndefined, visited) => {
clazzes = Array.isArray(clazzes) ? clazzes : [clazzes];
let schemas = new Map();
let dependencies = new Set();
clazzes.forEach((clazz) => {
visited.add(clazz.name);
const schema = (0, exports.getSchema)(clazz);
if (schema) {
schemas.set(clazz, schema);
// By field
schema.getDependencies().forEach((depenency) => {
dependencies.add(depenency);
});
clazzes.forEach((clazz, ix) => {
while (Object.getPrototypeOf(clazz).prototype != undefined) {
clazz = Object.getPrototypeOf(clazz);
}
// Class dependencies (inheritance)
getDependenciesRecursively(clazz).forEach((dependency) => {
if (clazzes.find(c => c == dependency) == undefined) {
dependencies.add(dependency);
let dependencies = getDependenciesRecursively(clazz);
dependencies.set('_', clazz);
dependencies.forEach((v, k) => {
const schema = (0, exports.getSchema)(v);
if (!schema) {
return;
}
schemas.set(v, schema);
visited.add(v.name);
});
});
let filteredDependencies = [];
dependencies.forEach((dependency) => {
if (visited.has(dependency.name)) {
return;
}
filteredDependencies.push(dependency);
visited.add(dependency.name);
});
// Generate schemas for nested types
filteredDependencies.forEach((dependency) => {
if (!schemas.has(dependency)) {
const dependencySchema = validateIterator([dependency], allowUndefined, visited);
dependencySchema.forEach((value, key) => {
schemas.set(key, value);
});
}
});
schemas.forEach((structSchema, clazz) => {
if (!structSchema.fields && !hasDependencies(clazz, schemas)) {
throw new error_1.BorshError("Missing schema for class " + clazz.name);
}
structSchema.fields.forEach((field) => {
if (!field) {
throw new error_1.BorshError("Field is missing definition, most likely due to field indexing with missing indices");
let lastVariant = undefined;
let lastKey = undefined;
getNonTrivialDependencies(clazz).forEach((dependency, key) => {
if (!lastVariant)
lastVariant = (0, exports.getVariantIndex)(dependency);
else if (!validateVariantAreCompatible(lastVariant, (0, exports.getVariantIndex)(dependency))) {
throw new error_1.BorshError(`Class ${dependency.name} is extended by classes with variants of different types. Expecting only one of number, number[] or string`);
}
if (allowUndefined) {
return;
if (lastKey != undefined && lastVariant == undefined) {
throw new error_1.BorshError(`Classes inherit ${clazz} and are introducing new field without introducing variants. This leads to unoptimized deserialization`);
}
if (field.type instanceof Function) {
if (!schemas.has(field.type) && !hasDependencies(field.type, schemas)) {
throw new error_1.BorshError("Unknown field type: " + field.type.name);
lastKey = key;
});
schemas.forEach((structSchema, clazz) => {
if (!structSchema.fields && !hasDependencies(clazz, schemas)) {
throw new error_1.BorshError("Missing schema for class " + clazz.name);
}
structSchema.fields.forEach((field) => {
if (!field) {
throw new error_1.BorshError("Field is missing definition, most likely due to field indexing with missing indices");
}
}
if (allowUndefined) {
return;
}
if (field.type instanceof Function) {
if (!(0, exports.getSchema)(field.type) && !hasDependencies(field.type, schemas)) {
throw new error_1.BorshError("Unknown field type: " + field.type.name);
}
// Validate field
validateIterator(field.type, allowUndefined, visited);
}
});
});
});
return schemas;
};

@@ -473,2 +555,13 @@ const resize = (arr, newSize, defaultValue) => {

};
const validateVariantAreCompatible = (a, b) => {
if (typeof a != typeof b) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length != b.length) {
return false;
}
}
return true;
};
//# sourceMappingURL=index.js.map

@@ -40,4 +40,6 @@ import { BinaryReader, BinaryWriter } from "./binary";

export declare class StructKind {
variant?: number | number[] | string;
fields: Field[];
constructor(properties?: {
variant?: number | number[] | string;
fields: Field[];

@@ -44,0 +46,0 @@ });

@@ -65,2 +65,3 @@ "use strict";

this.fields = properties.fields;
this.variant = properties.variant;
}

@@ -67,0 +68,0 @@ else {

/// <reference types="node" />
import { StructKind, SimpleField, CustomField } from "./types";
import { StructKind, SimpleField, CustomField, Constructor } from "./types";
import { BinaryWriter, BinaryReader } from "./binary";

@@ -27,2 +27,6 @@ export * from "./binary";

export declare const getSchema: (ctor: Function) => StructKind;
export declare const getSchemasBottomUp: (ctor: Function) => {
clazz: Function;
schema: StructKind;
}[];
/**

@@ -33,4 +37,4 @@ *

*/
export declare const variant: (index: number | number[]) => (ctor: Function) => void;
export declare const getVariantIndex: (clazz: any) => number | number[] | undefined;
export declare const variant: (index: number | number[] | string) => (ctor: Function) => void;
export declare const getVariantIndex: (clazz: any) => number | number[] | string | undefined;
/**

@@ -46,2 +50,2 @@ * @param properties, the properties of the field mapping to schema

*/
export declare const validate: (clazzes: any[], allowUndefined?: boolean) => Map<any, StructKind>;
export declare const validate: (clazzes: Constructor<any> | Constructor<any>[], allowUndefined?: boolean) => void;

@@ -40,4 +40,6 @@ import { BinaryReader, BinaryWriter } from "./binary";

export declare class StructKind {
variant?: number | number[] | string;
fields: Field[];
constructor(properties?: {
variant?: number | number[] | string;
fields: Field[];

@@ -44,0 +46,0 @@ });

{
"name": "@dao-xyz/borsh",
"version": "2.0.7",
"version": "2.1.0",
"readme": "README.md",
"homepage": "https://github.com/dao-xyz/borsh-ts#README",
"description": "Binary Object Representation Serializer for Hashing",
"description": "Binary Object Representation Serializer for Hashing simplified with decorators",
"author": "dao.xyz",

@@ -8,0 +8,0 @@ "license": "Apache-2.0",

@@ -7,3 +7,3 @@ # Borsh TS

**Borsh TS** is *unofficial* implementation of the [Borsh] binary serialization format for TypeScript projects. The motivation behind this library is to provide more convinient methods using **field and class decorators.**
**Borsh TS** is a Typescript implementation of the [Borsh] binary serialization format for TypeScript projects. The motivation behind this library is to provide more convinient methods using **field and class decorators.**

@@ -127,4 +127,16 @@ Borsh stands for _Binary Object Representation Serializer for Hashing_. It is meant to be used in security-critical projects as it prioritizes consistency,

```
Variants can be 'number', 'number[]' (represents nested Rust Enums) or 'string' (not part of the Borsh specification). i.e.
```typescript
@variant(0)
class ClazzA
...
@variant([0,1])
class ClazzB
...
@variant("clazz c")
class ClazzC
```
**Nested Schema generation for structs**

@@ -235,5 +247,25 @@ ```typescript

## Inheritance
Schema generation with class inheritance is not supported (yet)
Schema generation is supported if deserialization is deterministic
e.g.
```typescript
class A {
@field({type: 'number'})
a: number
}
@variant(0)
class B1 extends A{
@field({type: 'number'})
b1: number
}
@variant(1)
class B2 extends A{
@field({type: 'number'})
b2: number
}
```
## Type Mappings

@@ -240,0 +272,0 @@

@@ -33,3 +33,3 @@ import BN from "bn.js";

}
validate([TestStruct]);
validate(TestStruct);
const expectedResult: StructKind = new StructKind({

@@ -70,3 +70,3 @@ fields: [

validate([TestStruct]);
validate(TestStruct);
expect(getSchema(TestStruct)).toEqual(

@@ -96,3 +96,4 @@ new StructKind({

let schema = validate([TestStruct]).get(TestStruct);
validate(TestStruct);
let schema = getSchema(TestStruct);
expect(schema.fields.length).toEqual(2);

@@ -120,3 +121,3 @@ expect(schema.fields[0].key).toEqual("a");

}
validate([TestStruct]);
validate(TestStruct);
const expectedResult: StructKind = new StructKind({

@@ -160,3 +161,3 @@ fields: [

validate([TestStruct]);
validate(TestStruct);
const buf = serialize(new TestStruct({ a: [1, 2, 3] }));

@@ -180,3 +181,3 @@ expect(buf).toEqual(Buffer.from([3, 0, 0, 0, 1, 2, 3]));

validate([TestStruct]);
validate(TestStruct);
const buf = serialize(new TestStruct({ a: [1, 2, 3] }));

@@ -200,3 +201,3 @@ expect(buf).toEqual(Buffer.from([1, 2, 3]));

validate([TestStruct]);
validate(TestStruct);
expect(() => serialize(new TestStruct({ a: [1, 2] }))).toThrowError();

@@ -216,3 +217,3 @@ });

}
validate([TestStruct]);
validate(TestStruct);
expect(() => deserialize(Buffer.from([1, 2]), TestStruct)).toThrowError();

@@ -244,3 +245,3 @@ });

validate([TestStruct]);
validate(TestStruct);
const arr = [

@@ -270,3 +271,3 @@ new Element({ a: 1 }),

const instance = new TestEnum(3);
validate([TestEnum]);
validate(TestEnum);
const buf = serialize(instance);

@@ -282,3 +283,3 @@ expect(buf).toEqual(Buffer.from([1, 3]));

const instance = new TestEnum();
validate([TestEnum]);
validate(TestEnum);
const buf = serialize(instance);

@@ -305,3 +306,3 @@ expect(buf).toEqual(Buffer.from([1]));

}
validate([TestStruct]);
validate(TestStruct);
expect(getSchema(TestStruct)).toBeDefined();

@@ -345,3 +346,3 @@ expect(getSchema(ImplementationByVariant)).toBeDefined();

const instance = new TestStruct(new Enum1(4));
validate([Enum0, Enum1, TestStruct]);
validate(Super);
expect(getSchema(Enum0)).toBeDefined();

@@ -363,3 +364,3 @@ expect(getSchema(Enum1)).toBeDefined();

test("extended enum", () => {
test("extended enum top variants", () => {
class SuperSuper {}

@@ -412,2 +413,97 @@

test("extended enum inheritance variants", () => {
@variant(1)
class SuperSuper {}
@variant(2)
class Super extends SuperSuper {
constructor() {
super();
}
}
@variant([3, 100])
class Enum0 extends Super {
@field({ type: "u8" })
public a: number;
constructor(a: number) {
super();
this.a = a;
}
}
@variant([3, 4])
class Enum1 extends Super {
@field({ type: "u8" })
public b: number;
constructor(b: number) {
super();
this.b = b;
}
}
const instance = new Enum1(5);
// validate([Enum0, Enum1, Super, SuperSuper]);
expect(getSchema(Enum0)).toBeDefined();
expect(getSchema(Enum1)).toBeDefined();
const serialized = serialize(instance);
expect(serialized).toEqual(Buffer.from([1, 2, 3, 4, 5]));
const deserialied = deserialize(
Buffer.from(serialized),
SuperSuper,
false,
BinaryReader
);
expect(deserialied).toBeInstanceOf(Enum1);
expect((deserialied as Enum1).b).toEqual(5);
});
test("inheritance without variant", () => {
class Super {}
class A extends Super {
@field({ type: "u8" })
public a: number;
}
class B extends A {
@field({ type: "u8" })
public b: number;
constructor(opts?: { a: number; b: number }) {
super();
if (opts) {
Object.assign(this, opts);
}
}
}
@variant(0)
class C1 extends B {
constructor(opts?: { a: number; b: number }) {
super();
if (opts) {
Object.assign(this, opts);
}
}
}
@variant(1)
class C2 extends B {}
validate(Super);
const serialized = serialize(new C1({ a: 1, b: 2 }));
expect(serialized).toEqual(Buffer.from([1, 2, 0]));
const deserialied = deserialize(
Buffer.from(serialized),
Super,
false,
BinaryReader
);
expect(deserialied).toBeInstanceOf(C1);
expect((deserialied as C1).a).toEqual(1);
expect((deserialied as C1).b).toEqual(2);
});
test("wrapped enum", () => {

@@ -436,3 +532,3 @@ class Super {}

const instance = new TestStruct(new Enum2(3));
validate([Enum2, TestStruct]);
validate(Super);
expect(getSchema(Enum2)).toBeDefined();

@@ -486,3 +582,3 @@ expect(getSchema(TestStruct)).toBeDefined();

const instance = new TestStruct(new Enum1(5));
validate([Enum0, Enum1, TestStruct]);
validate(Super);
expect(getSchema(Enum1)).toBeDefined();

@@ -501,2 +597,39 @@ expect(getSchema(TestStruct)).toBeDefined();

});
test("enum string variant", () => {
class Ape {
@field({ type: "String" })
name: string;
constructor(name?: string) {
this.name = name;
}
}
@variant("🦍")
class Gorilla extends Ape {}
@variant("🦧")
class Orangutan extends Ape {}
class HighCouncil {
@field({ type: vec(Ape) })
members: Ape[];
constructor(members?: Ape[]) {
if (members) {
this.members = members;
}
}
}
let bytes = serialize(
new HighCouncil([new Gorilla("Go"), new Orangutan("Ora")])
);
let deserialized = deserialize(Buffer.from(bytes), HighCouncil);
expect(deserialized).toBeInstanceOf(HighCouncil);
expect(deserialized.members[0]).toBeInstanceOf(Gorilla);
expect(deserialized.members[0].name).toEqual("Go");
expect(deserialized.members[1]).toBeInstanceOf(Orangutan);
expect(deserialized.members[1].name).toEqual("Ora");
});
});

@@ -513,3 +646,3 @@

}
validate([TestStruct]);
validate(TestStruct);
const expectedResult: StructKind = new StructKind({

@@ -550,3 +683,3 @@ fields: [

}
validate([TestStruct]);
validate(TestStruct);
const expectedResult: StructKind = new StructKind({

@@ -601,3 +734,3 @@ fields: [

validate([TestStruct]);
validate(TestStruct);
const serialized = serialize(new TestStruct({ a: 2, b: 3 }));

@@ -630,3 +763,3 @@ const deserialied = deserialize(

}
validate([TestStruct]);
validate(TestStruct);
const expectedResult: StructKind = new StructKind({

@@ -664,3 +797,3 @@ fields: [

const thrower = (): void => {
validate([TestStruct]);
validate(TestStruct);
};

@@ -681,3 +814,3 @@

const thrower = (): void => {
validate([TestStruct]);
validate(TestStruct);
};

@@ -698,3 +831,5 @@

}
const schema: StructKind = validate([TestStruct]).get(TestStruct);
validate(TestStruct);
const schema = getSchema(TestStruct);
const expectedResult: StructKind = new StructKind({

@@ -728,3 +863,3 @@ fields: [

const bytes = Uint8Array.from([1, 0]); // has an extra 0
validate([TestStruct]);
validate(TestStruct);
expect(() =>

@@ -758,2 +893,98 @@ deserialize(Buffer.from(bytes), TestStruct, false)

test("variant type conflict", () => {
class Super {
constructor() {}
}
@variant([0, 0]) // Same as B
class A extends Super {
constructor() {
super();
}
}
@variant(0) // Same as A
class B extends Super {
constructor() {
super();
}
}
expect(() => validate(Super)).toThrowError(BorshError);
});
test("variant type conflict inheritance", () => {
class SuperSuper {}
class Super extends SuperSuper {}
@variant([0, 0]) // Same as B
class A extends Super {
constructor() {
super();
}
}
@variant(0) // Same as A
class B extends SuperSuper {
constructor() {
super();
}
}
expect(() => validate(SuperSuper)).toThrowError(BorshError);
});
test("variant type conflict array length", () => {
class Super {}
@variant([0, 0]) // Same as B
class A extends Super {
constructor() {
super();
}
}
@variant([0]) // Same as A
class B extends Super {
constructor() {
super();
}
}
expect(() => validate(Super)).toThrowError(BorshError);
});
test("error for non optimized code", () => {
class TestStruct {
constructor() {}
}
class A extends TestStruct {
@field({ type: "String" })
string: string;
}
class B extends TestStruct {
@field({ type: "String" })
string: string;
}
expect(() => validate(TestStruct)).toThrowError(BorshError);
});
test("error for non optimized code on deserialization", () => {
class TestStruct {
constructor() {}
}
class A extends TestStruct {
@field({ type: "String" })
string: string = "A";
}
class B extends TestStruct {
@field({ type: "String" })
string: string = "B";
}
expect(() =>
deserialize(Buffer.from(serialize(new A())), TestStruct)
).toThrowError(BorshError);
});
test("variant conflict, indices", () => {

@@ -797,60 +1028,6 @@ const classDef = () => {

}
expect(() => validate([TestStruct])).toThrowError(BorshError);
validate([TestStruct], true); // Should be ok since we allow undefined
expect(() => validate(TestStruct)).toThrowError(BorshError);
validate(TestStruct, true); // Should be ok since we allow undefined
});
test("missing variant", () => {
class Super {}
@variant(0)
class Enum0 extends Super {
constructor() {
super();
}
}
class TestStruct {
@field({ type: Super })
public missing: Super;
constructor(missing?: Super) {
this.missing = missing;
}
}
validate([TestStruct]);
expect(getSchema(Enum0)).toBeDefined();
expect(getSchema(TestStruct)).toBeDefined();
expect(getSchema(Super)).toBeUndefined();
});
test("missing variant one off", () => {
class Super {}
@variant(0)
class Enum0 extends Super {
constructor() {
super();
}
}
@variant(1)
class Enum1 extends Super {
constructor() {
super();
}
}
class TestStruct {
@field({ type: Super })
public missing: Super;
constructor(missing?: Super) {
this.missing = missing;
}
}
validate([TestStruct]);
expect(getSchema(Enum0)).toBeDefined();
expect(getSchema(Enum1)).toBeDefined();
expect(getSchema(TestStruct)).toBeDefined();
expect(getSchema(Super)).toBeUndefined();
});
test("valid dependency", () => {

@@ -873,4 +1050,4 @@ class Implementation {

}
validate([TestStruct]);
validate(TestStruct);
});
});

@@ -11,2 +11,3 @@ import bs58 from "bs58";

extendingClasses,
Constructor,
} from "./types";

@@ -91,14 +92,35 @@ import { BorshError } from "./error";

const structSchema = getSchema(obj.constructor)
if (!structSchema) {
// Serialize content as struct, we do not invoke serializeStruct since it will cause circular calls to this method
const structSchemas = getSchemasBottomUp(obj.constructor);
// If Schema has fields, "structSchema" will be non empty and "fields" will exist
if (structSchemas.length == 0) {
throw new BorshError(`Class ${obj.constructor.name} is missing in schema`);
}
if (structSchema instanceof StructKind) {
structSchema.fields.map((field) => {
serializeField(field.key, obj[field.key], field.type, writer);
});
} else {
throw new BorshError(`Unexpected schema for ${obj.constructor.name}`);
}
structSchemas.forEach((v) => {
if (v.schema instanceof StructKind) {
const index = v.schema.variant;
if (index != undefined) {
if (typeof index === "number") {
writer.writeU8(index);
} else if (Array.isArray(index)) {
index.forEach((i) => {
writer.writeU8(i);
});
}
else { // is string
writer.writeString(index);
}
}
v.schema.fields.map((field) => {
serializeField(field.key, obj[field.key], field.type, writer);
});
} else {
throw new BorshError(`Unexpected schema for ${obj.constructor.name}`);
}
})
}

@@ -169,36 +191,73 @@

let structSchema = getSchema(clazz);//schema.get(clazz);
let idx = undefined;
const result: { [key: string]: any } = {};
if (!structSchema) {
// We find the deserialization schema from one of the subclasses
// assume clazz is super class
if (getVariantIndex(clazz) !== undefined) {
// It is an (stupid) enum, but we deserialize into its variant directly
// This means we should omit the variant index
let index = getVariantIndex(clazz);
if (typeof index === "number") {
reader.readU8();
} else if (Array.isArray(index)) {
for (const _ of index) {
reader.readU8();
}
}
else { // string
reader.readString();
}
}
// it must be an enum
idx = [reader.readU8()];
// Try polymorphic deserialziation (i.e. get all subclasses and find best
// class this can be deserialized to)
// Polymorphic serialization, i.e. reversed prototype iteration using descriminators
let once = false;
let currClazz = clazz;
while ((getSchema(currClazz) || getDependencies(currClazz).size > 0)) {
let structSchema = getSchema(currClazz);
once = true;
let variantsIndex: number[] = undefined;
let variantString: string = undefined;
let nextClazz = undefined;
let dependencies = getNonTrivialDependencies(currClazz);
if (structSchema) {
for (const field of structSchema.fields) {
result[field.key] = deserializeField(
field.key,
field.type,
reader
);
}
}
// We know that we should serialize into the variant that accounts to the first byte of the read
for (const [_key, actualClazz] of getDependenciesRecursively(clazz)) {
for (const [_key, actualClazz] of dependencies) {
const variantIndex = getVariantIndex(actualClazz);
if (variantIndex !== undefined) {
if (typeof variantIndex === "number") {
if (variantIndex == idx[0]) {
clazz = actualClazz;
structSchema = getSchema(clazz);
if (!variantsIndex) {
variantsIndex = [reader.readU8()];
}
if (variantIndex == variantsIndex[0]) {
nextClazz = actualClazz;
break;
}
} // variant is array, check all values
else {
while (idx.length < variantIndex.length) {
idx.push(reader.readU8());
}
else if (Array.isArray(variantIndex)) { // variant is array, check all values
if (!variantsIndex) {
variantsIndex = [];
while (variantsIndex.length < variantIndex.length) {
variantsIndex.push(reader.readU8());
}
}
// Compare variants
if (
idx.length === variantIndex.length &&
idx.every((value, index) => value === variantIndex[index])
variantsIndex.length === variantIndex.length &&
(variantsIndex as number[]).every((value, index) => value === variantIndex[index])
) {
clazz = actualClazz;
structSchema = getSchema(clazz);
nextClazz = actualClazz;
break;

@@ -208,31 +267,40 @@ }

else { // is string
if (variantString == undefined) {
variantString = reader.readString();
}
// Compare variants is just string compare
if (
variantString === variantIndex
) {
nextClazz = actualClazz;
break;
}
}
}
}
if (!structSchema)
throw new BorshError(`Class ${clazz.name} is missing in schema`);
} else if (getVariantIndex(clazz) !== undefined) {
// It is an enum, but we deserialize into its variant directly
// This means we should omit the variant index
let index = getVariantIndex(clazz);
if (typeof index === "number") {
reader.readU8();
} else {
for (const _ of index) {
reader.readU8();
if (nextClazz == undefined) {
// do a recursive call and copy result,
// this is not computationally performant since we are going to traverse multiple path
// and possible do deserialziation on bad paths
if (dependencies.size == 1) // still deterministic
nextClazz = dependencies.values().next().value;
else if (dependencies.size > 1) {
const classes = [...dependencies.values()].map((f) => f.name).join(', ')
throw new BorshError(`Multiple ambigious deserialization paths from ${currClazz.name} found: ${classes}. This is not allowed, and would not be performant if allowed`)
}
}
}
if (structSchema instanceof StructKind) {
const result: { [key: string]: any } = {};
for (const field of getSchema(clazz).fields) {
result[field.key] = deserializeField(
field.key,
field.type,
reader
);
if (nextClazz == undefined) {
break;
}
return Object.assign(new clazz(), result);
currClazz = nextClazz;
/* if (!structSchema)
throw new BorshError(`Class ${clazz.name} is missing in schema`); */
}
throw new BorshError(`Unexpected schema ${clazz.constructor.name}`);
if (!once) {
throw new BorshError(`Unexpected schema ${clazz.constructor.name}`);
}
return Object.assign(new currClazz(), result);
}

@@ -289,2 +357,7 @@

}
const setDependencyToProtoType = (ctor: Function) => {
let proto = Object.getPrototypeOf(ctor);
if (proto.prototype?.constructor != undefined)
setDependency(proto, ctor);
}

@@ -295,2 +368,6 @@ const setDependency = (ctor: Function, dependency: Function) => {

if (key != undefined && dependencies.has(key)) {
if (dependencies.get(key) == dependency) {
// already added;
return;
}
throw new BorshError(`Conflicting variants: Dependency ${dependencies.get(key).name} and ${dependency.name} share same variant index(es)`)

@@ -312,3 +389,3 @@ }

dependencies.set(key, dependency);
ctor.prototype._borsh_dependency = dependencies;
setDependencies(ctor, dependencies);
}

@@ -329,6 +406,36 @@

const getDependencyKey = (ctor: Function) => "_borsh_dependency_" + ctor.name
const getDependencies = (ctor: Function): Map<string, Function> => {
return ctor.prototype._borsh_dependency ? ctor.prototype._borsh_dependency : new Map();
let existing = ctor.prototype.constructor[getDependencyKey(ctor)]
if (existing)
return existing;
return new Map();
}
const getNonTrivialDependencies = (ctor: Function): Map<string, Function> => {
let ret = new Map<string, Function>();
let existing = ctor.prototype.constructor[getDependencyKey(ctor)] as Map<string, Function>;
if (existing)
existing.forEach((v, k) => {
let schema = getSchema(v);
if (schema.fields.length > 0 || schema.variant != undefined) { // non trivial
ret.set(k, v);
}
else { // check recursively
let req = getNonTrivialDependencies(v);
req.forEach((rv, rk) => {
ret.set(rk, rv);
})
}
});
return ret;
}
const setDependencies = (ctor: Function, dependencies: Map<string, Function>): Map<string, Function> => {
return ctor.prototype.constructor[getDependencyKey(ctor)] = dependencies
}
/**

@@ -355,9 +462,30 @@ * Flat map class inheritance tree into hashmap where key represents variant key

const setSchema = (ctor: Function, schema: StructKind) => {
ctor.prototype._borsh_schema = schema;
ctor.prototype.constructor["_borsh_schema_" + ctor.name] = schema
}
export const getSchema = (ctor: Function): StructKind => {
return ctor.prototype._borsh_schema
if (ctor.prototype == undefined) {
const t = 123;
}
return ctor.prototype.constructor["_borsh_schema_" + ctor.name];
}
export const getSchemasBottomUp = (ctor: Function): { clazz: Function, schema: StructKind }[] => {
let schemas: { clazz: Function, schema: StructKind }[] = [];
while (ctor.prototype != undefined) {
let schema = getSchema(ctor);
if (schema)
schemas.push({
clazz: ctor,
schema
});
ctor = Object.getPrototypeOf(ctor);
}
return schemas.reverse();
}
/**

@@ -368,35 +496,9 @@ *

*/
export const variant = (index: number | number[]) => {
export const variant = (index: number | number[] | string) => {
return (ctor: Function) => {
getOrCreateStructMeta(ctor);
let schema = getOrCreateStructMeta(ctor);
// Create a custom serialization, for enum by prepend instruction index
ctor.prototype.borshSerialize = function (
writer: BinaryWriter
) {
if (typeof index === "number") {
writer.writeU8(index);
} else {
index.forEach((i) => {
writer.writeU8(i);
});
}
schema.variant = index;
// Serialize content as struct, we do not invoke serializeStruct since it will cause circular calls to this method
const structSchema: StructKind = getSchema(ctor);
// If Schema has fields, "structSchema" will be non empty and "fields" will exist
if (structSchema?.fields)
for (const field of structSchema.fields) {
serializeField(
field.key,
this[field.key],
field.type,
writer
);
}
};
ctor.prototype._borsh_variant_index = function () {
return index; // creates a function that returns the variant index on the class
};
// Define Schema for this class, even though it might miss fields since this is a variant

@@ -414,6 +516,4 @@ const clazzes = extendingClasses(ctor);

export const getVariantIndex = (clazz: any): number | number[] | undefined => {
if (clazz.prototype._borsh_variant_index)
return clazz.prototype._borsh_variant_index();
return undefined;
export const getVariantIndex = (clazz: any): number | number[] | string | undefined => {
return getOrCreateStructMeta(clazz).variant;
};

@@ -427,3 +527,3 @@

return (target: {} | any, name?: PropertyKey): any => {
setDependencyToProtoType(target.constructor);
const schema = getOrCreateStructMeta(target.constructor);

@@ -469,71 +569,67 @@ const key = name.toString();

*/
export const validate = (clazzes: any[], allowUndefined = false) => {
export const validate = (clazzes: Constructor<any> | Constructor<any>[], allowUndefined = false) => {
return validateIterator(clazzes, allowUndefined, new Set());
};
const validateIterator = (clazzes: any[], allowUndefined: boolean, visited: Set<string>) => {
const validateIterator = (clazzes: Constructor<any> | Constructor<any>[], allowUndefined: boolean, visited: Set<string>) => {
clazzes = Array.isArray(clazzes) ? clazzes : [clazzes];
let schemas = new Map<any, StructKind>();
let dependencies = new Set<Function>();
clazzes.forEach((clazz) => {
visited.add(clazz.name);
const schema = getSchema(clazz);
if (schema) {
schemas.set(clazz, schema);
// By field
schema.getDependencies().forEach((depenency) => {
dependencies.add(depenency);
});
clazzes.forEach((clazz, ix) => {
while (Object.getPrototypeOf(clazz).prototype != undefined) {
clazz = Object.getPrototypeOf(clazz);
}
// Class dependencies (inheritance)
getDependenciesRecursively(clazz).forEach((dependency) => {
if (clazzes.find(c => c == dependency) == undefined) {
dependencies.add(dependency);
let dependencies = getDependenciesRecursively(clazz);
dependencies.set('_', clazz);
dependencies.forEach((v, k) => {
const schema = getSchema(v);
if (!schema) {
return;
}
})
schemas.set(v, schema);
visited.add(v.name);
});
let filteredDependencies: Function[] = [];
dependencies.forEach((dependency) => {
if (visited.has(dependency.name)) {
return;
}
filteredDependencies.push(dependency);
visited.add(dependency.name);
})
});
let lastVariant: number | number[] | string = undefined;
let lastKey: string = undefined;
getNonTrivialDependencies(clazz).forEach((dependency, key) => {
if (!lastVariant)
lastVariant = getVariantIndex(dependency);
else if (!validateVariantAreCompatible(lastVariant, getVariantIndex(dependency))) {
throw new BorshError(`Class ${dependency.name} is extended by classes with variants of different types. Expecting only one of number, number[] or string`)
}
// Generate schemas for nested types
filteredDependencies.forEach((dependency) => {
if (!schemas.has(dependency)) {
const dependencySchema = validateIterator([dependency], allowUndefined, visited);
dependencySchema.forEach((value, key) => {
schemas.set(key, value);
});
}
});
schemas.forEach((structSchema, clazz) => {
if (!structSchema.fields && !hasDependencies(clazz, schemas)) {
throw new BorshError("Missing schema for class " + clazz.name);
}
structSchema.fields.forEach((field) => {
if (!field) {
throw new BorshError(
"Field is missing definition, most likely due to field indexing with missing indices"
);
if (lastKey != undefined && lastVariant == undefined) {
throw new BorshError(`Classes inherit ${clazz} and are introducing new field without introducing variants. This leads to unoptimized deserialization`)
}
if (allowUndefined) {
return;
lastKey = key;
})
schemas.forEach((structSchema, clazz) => {
if (!structSchema.fields && !hasDependencies(clazz, schemas)) {
throw new BorshError("Missing schema for class " + clazz.name);
}
if (field.type instanceof Function) {
if (!schemas.has(field.type) && !hasDependencies(field.type, schemas)) {
throw new BorshError("Unknown field type: " + field.type.name);
structSchema.fields.forEach((field) => {
if (!field) {
throw new BorshError(
"Field is missing definition, most likely due to field indexing with missing indices"
);
}
}
});
})
return schemas;
if (allowUndefined) {
return;
}
if (field.type instanceof Function) {
if (!getSchema(field.type) && !hasDependencies(field.type, schemas)) {
throw new BorshError("Unknown field type: " + field.type.name);
}
// Validate field
validateIterator(field.type, allowUndefined, visited);
}
});
})
});
}

@@ -546,1 +642,13 @@

};
const validateVariantAreCompatible = (a: number | number[] | string, b: number | number[] | string) => {
if (typeof a != typeof b) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length != b.length) {
return false;
}
}
return true;
}

@@ -92,6 +92,8 @@ import { BinaryReader, BinaryWriter } from "./binary";

export class StructKind {
variant?: number | number[] | string
fields: Field[];
constructor(properties?: { fields: Field[] }) {
constructor(properties?: { variant?: number | number[] | string, fields: Field[] }) {
if (properties) {
this.fields = properties.fields;
this.variant = properties.variant;
} else {

@@ -98,0 +100,0 @@ this.fields = [];

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc