Skip to content

Instantly share code, notes, and snippets.

@jozanza
Last active February 9, 2022 12:30
Show Gist options
  • Save jozanza/0ccbdcc2d676350a5b71191d5c479061 to your computer and use it in GitHub Desktop.
Save jozanza/0ccbdcc2d676350a5b71191d5c479061 to your computer and use it in GitHub Desktop.
Borsh parsing is really pretty simple
// 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