Last active
February 9, 2022 12:30
-
-
Save jozanza/0ccbdcc2d676350a5b71191d5c479061 to your computer and use it in GitHub Desktop.
Borsh parsing is really pretty simple
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // https://gist.github.com/diafygi/90a3e80ca1c2793220e5/ | |
| const B58_CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' | |
| // prettier-ignore | |
| const toBase58: (buf: Uint8Array, map?: string) => string = (B: any, A: any = B58_CHARS) => {let d: any = [], s = '', i: any, j: any, c, n; for(i in B){j=0,c=B[i];s+=c||s.length^i?"":1;while(j in d||c){n=d[j];n=n?n*256+c:c;c=n/58|0;d[j]=n%58;j++}}while(j--)s+=A[d[j]];return s;} | |
| export const toJSON = (data: any) => { | |
| return JSON.stringify( | |
| data, | |
| (_key, value) => (typeof value === 'bigint' ? value.toString() : value), | |
| 2, | |
| ) | |
| } | |
| export type Nullable<T> = T | null | |
| export interface BorshBool { | |
| kind: 'bool' | |
| } | |
| export type IntKind = | |
| | 'u8' | |
| | 'u16' | |
| | 'u32' | |
| | 'u64' | |
| | 'u128' | |
| | 'u256' | |
| | 'u512' | |
| | 'i8' | |
| | 'i16' | |
| | 'i32' | |
| | 'i64' | |
| | 'i128' | |
| | 'i256' | |
| | 'i512' | |
| export interface BorshInt<T extends IntKind = IntKind> { | |
| kind: T | |
| } | |
| export type StringEncoding = 'utf8' | 'base58' | |
| export interface BorshString< | |
| Length extends Nullable<number> = Nullable<number>, | |
| Encoding extends StringEncoding = StringEncoding, | |
| > { | |
| kind: 'string' | |
| length: Length | |
| encoding: Encoding | |
| } | |
| export type EnumVariant = [ | |
| number, | |
| { ident: string; inner: Nullable<BorshType> }, | |
| ] | |
| export interface BorshEnum<T extends EnumVariant[] = EnumVariant[]> { | |
| kind: 'enum' | |
| variants: T | |
| } | |
| export interface BorshArray< | |
| T extends BorshType = BorshType, | |
| Length extends number = number, | |
| > { | |
| kind: 'array' | |
| length: Length | |
| type: T | |
| } | |
| export interface BorshOption<T extends BorshType = BorshType> { | |
| kind: 'option' | |
| type: T | |
| } | |
| export interface BorshVec<T extends BorshType = BorshType> { | |
| kind: 'vec' | |
| type: T | |
| } | |
| export interface BorshTuple<T extends BorshType[] = BorshType[]> { | |
| kind: 'tuple' | |
| fields: T | |
| } | |
| export interface BorshStruct< | |
| T extends Array<[string, BorshType]> = Array<[string, BorshType]>, | |
| > { | |
| kind: 'struct' | |
| fields: T | |
| } | |
| export interface BorshAlias<T extends string = string> { | |
| kind: 'alias' | |
| name: T | |
| } | |
| export type BorshType = | |
| | BorshBool | |
| | BorshInt | |
| | BorshString | |
| | BorshEnum | |
| | BorshArray | |
| | BorshOption | |
| | BorshVec | |
| | BorshTuple | |
| | BorshStruct | |
| | BorshAlias | |
| // TODO | |
| // | { kind: 'hashSet'; type: BorshType } | |
| // | { kind: 'hashMap'; type: BorshType } | |
| export interface BorshSchema { | |
| rootDefinition: string | |
| definitions: Array<[string, BorshType]> | |
| } | |
| export function decode(schema: BorshSchema, buf: Uint8Array) { | |
| const ctx = createDecoderContext(schema, buf) | |
| const type = ctx.defs.get(schema.rootDefinition) | |
| // prettier-ignore | |
| if (!type) throw new Error(`Couldn't find schema definition for "${schema.rootDefinition}"`) | |
| return decodeAny(ctx, type) | |
| } | |
| export interface DecoderContext { | |
| defs: Map<string, BorshType> | |
| buf: Uint8Array | |
| view: DataView | |
| i: number | |
| } | |
| export const createDecoderContext = (schema: BorshSchema, buf: Uint8Array) => { | |
| const defs = new Map(schema.definitions) | |
| const def = defs.get(schema.rootDefinition) | |
| // prettier-ignore | |
| if (!def) throw new Error(`Couldn't find schema definition for "${schema.rootDefinition}"`) | |
| const ctx: DecoderContext = { | |
| defs, | |
| buf, | |
| view: new DataView(buf.buffer), | |
| i: 0, | |
| } | |
| return ctx | |
| } | |
| export type Decoder<V extends BorshType = BorshType, T = any> = ( | |
| ctx: DecoderContext, | |
| type: V, | |
| ) => T | |
| export const decodeAny: Decoder = (ctx, t) => { | |
| // prettier-ignore | |
| switch (t.kind) { | |
| // Boolean | |
| case 'bool': return decodeBool(ctx, t) | |
| // Integer | |
| case 'u8': return decodeU8(ctx, t as BorshInt<'u8'>) | |
| case 'u16': return decodeU16(ctx, t as BorshInt<'u16'>) | |
| case 'u32': return decodeU32(ctx, t as BorshInt<'u32'>) | |
| case 'u64': return decodeU64(ctx, t as BorshInt<'u64'>) | |
| case 'u128': return decodeU128(ctx, t as BorshInt<'u128'>) | |
| case 'u256': return decodeU256(ctx, t as BorshInt<'u256'>) | |
| case 'u512': return decodeU512(ctx, t as BorshInt<'u512'>) | |
| case 'i8': return decodeI8(ctx, t as BorshInt<'i8'>) | |
| case 'i16': return decodeI16(ctx, t as BorshInt<'i16'>) | |
| case 'i32': return decodeI32(ctx, t as BorshInt<'i32'>) | |
| case 'i64': return decodeI64(ctx, t as BorshInt<'i64'>) | |
| case 'i128': return decodeI128(ctx, t as BorshInt<'i128'>) | |
| case 'i256': return decodeI256(ctx, t as BorshInt<'i256'>) | |
| case 'i512': return decodeI512(ctx, t as BorshInt<'i512'>) | |
| // String | |
| case 'string': return decodeString(ctx, t) | |
| // Enum | |
| case 'enum': return decodeEnum(ctx, t) | |
| // Option | |
| case 'option': return decodeOption(ctx, t) | |
| // Array | |
| case 'array': return decodeArray(ctx, t) | |
| // Vector | |
| case 'vec': return decodeVec(ctx, t) | |
| // Tuple | |
| case 'tuple': return decodeTuple(ctx, t) | |
| // Struct | |
| case 'struct': return decodeStruct(ctx, t) | |
| // Alias | |
| case 'alias': return decodeAlias(ctx, t) | |
| } | |
| } | |
| const decodeBool: Decoder<BorshBool, boolean> = (ctx, _) => { | |
| const n = ctx.view.getUint8(ctx.i) !== 0 | |
| ctx.i++ | |
| return n | |
| } | |
| const decodeU8: Decoder<BorshInt<'u8'>, number> = (ctx, _) => { | |
| const n = ctx.view.getUint8(ctx.i) | |
| ctx.i++ | |
| return n | |
| } | |
| const decodeU16: Decoder<BorshInt<'u16'>, number> = (ctx, _) => { | |
| const n = ctx.view.getUint16(ctx.i, true) | |
| ctx.i += 2 | |
| return n | |
| } | |
| const decodeU32: Decoder<BorshInt<'u32'>, number> = (ctx, _) => { | |
| const n = ctx.view.getUint32(ctx.i, true) | |
| ctx.i += 4 | |
| return n | |
| } | |
| const decodeU64: Decoder<BorshInt<'u64'>, bigint> = (ctx, _) => { | |
| const n = ctx.view.getBigUint64(ctx.i, true) | |
| ctx.i += 8 | |
| return n | |
| } | |
| const decodeU128: Decoder<BorshInt<'u128'>, bigint> = (ctx, _) => { | |
| const t = { kind: 'u64' } as const | |
| const a = decodeU64(ctx, t) | |
| const b = decodeU64(ctx, t) | |
| return (a << 64n) | b | |
| } | |
| const decodeU256: Decoder<BorshInt<'u256'>, bigint> = (ctx, _) => { | |
| const t = { kind: 'u128' } as const | |
| const a = decodeU128(ctx, t) | |
| const b = decodeU128(ctx, t) | |
| return (a << 128n) | b | |
| } | |
| const decodeU512: Decoder<BorshInt<'u512'>, bigint> = (ctx, _) => { | |
| const t = { kind: 'u256' } as const | |
| const a = decodeU256(ctx, t) | |
| const b = decodeU256(ctx, t) | |
| return (a << 256n) | b | |
| } | |
| const decodeI8: Decoder<BorshInt<'i8'>, number> = (ctx, _) => { | |
| const n = ctx.view.getInt8(ctx.i) | |
| ctx.i++ | |
| return n | |
| } | |
| const decodeI16: Decoder<BorshInt<'i16'>, number> = (ctx, _) => { | |
| const n = ctx.view.getInt16(ctx.i, true) | |
| ctx.i += 2 | |
| return n | |
| } | |
| const decodeI32: Decoder<BorshInt<'i32'>, number> = (ctx, _) => { | |
| const n = ctx.view.getInt32(ctx.i, true) | |
| ctx.i += 4 | |
| return n | |
| } | |
| const decodeI64: Decoder<BorshInt<'i64'>, bigint> = (ctx, _) => { | |
| const n = ctx.view.getBigInt64(ctx.i, true) | |
| ctx.i += 8 | |
| return n | |
| } | |
| const decodeI128: Decoder<BorshInt<'i128'>, bigint> = (ctx, _) => { | |
| const t = { kind: 'u128' } as const | |
| return BigInt.asIntN(128, decodeU128(ctx, t)) | |
| } | |
| const decodeI256: Decoder<BorshInt<'i256'>, bigint> = (ctx, _) => { | |
| const t = { kind: 'u256' } as const | |
| return BigInt.asIntN(256, decodeU256(ctx, t)) | |
| } | |
| const decodeI512: Decoder<BorshInt<'i512'>, bigint> = (ctx, _) => { | |
| const t = { kind: 'u512' } as const | |
| return BigInt.asIntN(512, decodeU512(ctx, t)) | |
| } | |
| const decodeString: Decoder<BorshString, string> = (ctx, t) => { | |
| const len = t.length ?? decodeU32(ctx, { kind: 'u32' }) | |
| const buf = ctx.buf.slice(ctx.i, ctx.i + len) | |
| ctx.i += len | |
| if (t.encoding === 'base58') { | |
| return toBase58(buf).replace(/\u0000/g, '') | |
| } | |
| return new TextDecoder().decode(buf).replace(/\u0000/g, '') | |
| } | |
| const decodeEnum: Decoder<BorshEnum, string | { type: string; data: any }> = ( | |
| ctx, | |
| t, | |
| ) => { | |
| const tag = decodeU8(ctx, { kind: 'u8' }) | |
| const variant = t.variants.find((x) => x[0] === tag) | |
| if (!variant) throw new Error(`Invalid enum tag: ${tag}`) | |
| const [_, { ident, inner }] = variant | |
| if (!inner) return ident | |
| return { | |
| type: ident, | |
| data: decodeAny(ctx, inner), | |
| } | |
| } | |
| const decodeOption: Decoder<BorshOption, Nullable<any>> = (ctx, t) => { | |
| return decodeBool(ctx, { kind: 'bool' }) ? decodeAny(ctx, t.type) : null | |
| } | |
| const decodeArray: Decoder<BorshArray, Array<any>> = (ctx, t) => { | |
| const xs = [] // TODO: use TypedArrays and BigIntArrays | |
| const { type, length } = t | |
| for (let i = 0; i < length; i++) { | |
| xs.push(decodeAny(ctx, type)) | |
| } | |
| return xs | |
| } | |
| const decodeVec: Decoder<BorshVec, Array<any>> = (ctx, t) => { | |
| const xs = [] // TODO: use TypedArrays and BigIntArrays | |
| const length = decodeU32(ctx, { kind: 'u32' }) | |
| const { type } = t | |
| for (let i = 0; i < length; i++) { | |
| xs.push(decodeAny(ctx, type)) | |
| } | |
| return xs | |
| } | |
| const decodeTuple: Decoder<BorshTuple, Array<any>> = (ctx, t) => { | |
| const xs = [] | |
| for (const type of t.fields) { | |
| xs.push(decodeAny(ctx, type)) | |
| } | |
| return xs | |
| } | |
| const decodeStruct: Decoder<BorshStruct, Record<string, any>> = (ctx, t) => { | |
| const obj: Record<string, any> = {} | |
| for (const [key, type] of t.fields) { | |
| obj[key] = decodeAny(ctx, type) | |
| } | |
| return obj | |
| } | |
| const decodeAlias: Decoder<BorshAlias, Array<any>> = (ctx, t) => { | |
| const type = ctx.defs.get(t.name) | |
| if (!type) throw new Error(`Couldn't get aliased definition: "${t.name}" `) | |
| return decodeAny(ctx, type) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment