add borsh + u256 changes + update to v0.2.6 (#2290)

Co-authored-by: henrye <henry@notanemail>
This commit is contained in:
Henry-E 2022-11-28 17:20:13 +00:00 committed by GitHub
parent 704fdce0cd
commit 35f4e26ef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 405 additions and 0 deletions

View File

@ -0,0 +1,33 @@
{
"name": "@coral-xyz/borsh",
"version": "0.2.6",
"description": "Anchor Borsh",
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",
"exports": {
".": "./dist/lib/index.js"
},
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=10"
},
"scripts": {
"build": "tsc",
"test": "",
"clean": "rm -rf dist",
"prepare": "run-s clean build"
},
"dependencies": {
"bn.js": "^5.1.2",
"buffer-layout": "^1.2.0"
},
"peerDependencies": {
"@solana/web3.js": "^1.2.0"
},
"files": [
"dist"
]
}

View File

@ -0,0 +1,350 @@
import {
blob,
Layout as LayoutCls,
offset,
seq,
struct,
u32,
u8,
union,
} from "buffer-layout";
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
export {
u8,
s8 as i8,
u16,
s16 as i16,
u32,
s32 as i32,
f32,
f64,
struct,
} from "buffer-layout";
export interface Layout<T> {
span: number;
property?: string;
decode(b: Buffer, offset?: number): T;
encode(src: T, b: Buffer, offset?: number): number;
getSpan(b: Buffer, offset?: number): number;
replicate(name: string): this;
}
class BNLayout extends LayoutCls<BN> {
blob: Layout<Buffer>;
signed: boolean;
constructor(span: number, signed: boolean, property?: string) {
super(span, property);
this.blob = blob(span);
this.signed = signed;
}
decode(b: Buffer, offset = 0) {
const num = new BN(this.blob.decode(b, offset), 10, "le");
if (this.signed) {
return num.fromTwos(this.span * 8).clone();
}
return num;
}
encode(src: BN, b: Buffer, offset = 0) {
if (this.signed) {
src = src.toTwos(this.span * 8);
}
return this.blob.encode(
src.toArrayLike(Buffer, "le", this.span),
b,
offset
);
}
}
export function u64(property?: string): Layout<BN> {
return new BNLayout(8, false, property);
}
export function i64(property?: string): Layout<BN> {
return new BNLayout(8, true, property);
}
export function u128(property?: string): Layout<BN> {
return new BNLayout(16, false, property);
}
export function i128(property?: string): Layout<BN> {
return new BNLayout(16, true, property);
}
export function u256(property?: string): Layout<BN> {
return new BNLayout(32, false, property);
}
export function i256(property?: string): Layout<BN> {
return new BNLayout(32, true, property);
}
class WrappedLayout<T, U> extends LayoutCls<U> {
layout: Layout<T>;
decoder: (data: T) => U;
encoder: (src: U) => T;
constructor(
layout: Layout<T>,
decoder: (data: T) => U,
encoder: (src: U) => T,
property?: string
) {
super(layout.span, property);
this.layout = layout;
this.decoder = decoder;
this.encoder = encoder;
}
decode(b: Buffer, offset?: number): U {
return this.decoder(this.layout.decode(b, offset));
}
encode(src: U, b: Buffer, offset?: number): number {
return this.layout.encode(this.encoder(src), b, offset);
}
getSpan(b: Buffer, offset?: number): number {
return this.layout.getSpan(b, offset);
}
}
export function publicKey(property?: string): Layout<PublicKey> {
return new WrappedLayout(
blob(32),
(b: Buffer) => new PublicKey(b),
(key: PublicKey) => key.toBuffer(),
property
);
}
class OptionLayout<T> extends LayoutCls<T | null> {
layout: Layout<T>;
discriminator: Layout<number>;
constructor(layout: Layout<T>, property?: string) {
super(-1, property);
this.layout = layout;
this.discriminator = u8();
}
encode(src: T | null, b: Buffer, offset = 0): number {
if (src === null || src === undefined) {
return this.discriminator.encode(0, b, offset);
}
this.discriminator.encode(1, b, offset);
return this.layout.encode(src, b, offset + 1) + 1;
}
decode(b: Buffer, offset = 0): T | null {
const discriminator = this.discriminator.decode(b, offset);
if (discriminator === 0) {
return null;
} else if (discriminator === 1) {
return this.layout.decode(b, offset + 1);
}
throw new Error("Invalid option " + this.property);
}
getSpan(b: Buffer, offset = 0): number {
const discriminator = this.discriminator.decode(b, offset);
if (discriminator === 0) {
return 1;
} else if (discriminator === 1) {
return this.layout.getSpan(b, offset + 1) + 1;
}
throw new Error("Invalid option " + this.property);
}
}
export function option<T>(
layout: Layout<T>,
property?: string
): Layout<T | null> {
return new OptionLayout<T>(layout, property);
}
export function bool(property?: string): Layout<boolean> {
return new WrappedLayout(u8(), decodeBool, encodeBool, property);
}
function decodeBool(value: number): boolean {
if (value === 0) {
return false;
} else if (value === 1) {
return true;
}
throw new Error("Invalid bool: " + value);
}
function encodeBool(value: boolean): number {
return value ? 1 : 0;
}
export function vec<T>(
elementLayout: Layout<T>,
property?: string
): Layout<T[]> {
const length = u32("length");
const layout: Layout<{ values: T[] }> = struct([
length,
seq(elementLayout, offset(length, -length.span), "values"),
]);
return new WrappedLayout(
layout,
({ values }) => values,
(values) => ({ values }),
property
);
}
export function tagged<T>(
tag: BN,
layout: Layout<T>,
property?: string
): Layout<T> {
const wrappedLayout: Layout<{ tag: BN; data: T }> = struct([
u64("tag"),
layout.replicate("data"),
]);
function decodeTag({ tag: receivedTag, data }: { tag: BN; data: T }) {
if (!receivedTag.eq(tag)) {
throw new Error(
"Invalid tag, expected: " +
tag.toString("hex") +
", got: " +
receivedTag.toString("hex")
);
}
return data;
}
return new WrappedLayout(
wrappedLayout,
decodeTag,
(data) => ({ tag, data }),
property
);
}
export function vecU8(property?: string): Layout<Buffer> {
const length = u32("length");
const layout: Layout<{ data: Buffer }> = struct([
length,
blob(offset(length, -length.span), "data"),
]);
return new WrappedLayout(
layout,
({ data }) => data,
(data) => ({ data }),
property
);
}
export function str(property?: string): Layout<string> {
return new WrappedLayout(
vecU8(),
(data) => data.toString("utf-8"),
(s) => Buffer.from(s, "utf-8"),
property
);
}
export interface EnumLayout<T> extends Layout<T> {
registry: Record<string, Layout<any>>;
}
export function rustEnum<T>(
variants: Layout<any>[],
property?: string,
discriminant?: Layout<any>
): EnumLayout<T> {
const unionLayout = union(discriminant ?? u8(), property);
variants.forEach((variant, index) =>
unionLayout.addVariant(index, variant, variant.property)
);
return unionLayout;
}
export function array<T>(
elementLayout: Layout<T>,
length: number,
property?: string
): Layout<T[]> {
const layout: Layout<{ values: T[] }> = struct([
seq(elementLayout, length, "values"),
]);
return new WrappedLayout(
layout,
({ values }) => values,
(values) => ({ values }),
property
);
}
class MapEntryLayout<K, V> extends LayoutCls<[K, V]> {
keyLayout: Layout<K>;
valueLayout: Layout<V>;
constructor(keyLayout: Layout<K>, valueLayout: Layout<V>, property?: string) {
super(keyLayout.span + valueLayout.span, property);
this.keyLayout = keyLayout;
this.valueLayout = valueLayout;
}
decode(b: Buffer, offset?: number): [K, V] {
offset = offset || 0;
const key = this.keyLayout.decode(b, offset);
const value = this.valueLayout.decode(
b,
offset + this.keyLayout.getSpan(b, offset)
);
return [key, value];
}
encode(src: [K, V], b: Buffer, offset?: number): number {
offset = offset || 0;
const keyBytes = this.keyLayout.encode(src[0], b, offset);
const valueBytes = this.valueLayout.encode(src[1], b, offset + keyBytes);
return keyBytes + valueBytes;
}
getSpan(b: Buffer, offset?: number): number {
return (
this.keyLayout.getSpan(b, offset) + this.valueLayout.getSpan(b, offset)
);
}
}
export function map<K, V>(
keyLayout: Layout<K>,
valueLayout: Layout<V>,
property?: string
): Layout<Map<K, V>> {
const length = u32("length");
const layout: Layout<{ values: [K, V][] }> = struct([
length,
seq(
new MapEntryLayout(keyLayout, valueLayout),
offset(length, -length.span),
"values"
),
]);
return new WrappedLayout(
layout,
({ values }) => new Map(values),
(values) => ({ values: Array.from(values.entries()) }),
property
);
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"outDir": "./dist/lib",
"rootDir": "./src",
"composite": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": ["../../types/", "../../node_modules/@types"]
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts", "**/node_modules"]
}