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 { 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 { blob: Layout; 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 { return new BNLayout(8, false, property); } export function i64(property?: string): Layout { return new BNLayout(8, true, property); } export function u128(property?: string): Layout { return new BNLayout(16, false, property); } export function i128(property?: string): Layout { return new BNLayout(16, true, property); } export function u256(property?: string): Layout { return new BNLayout(32, false, property); } export function i256(property?: string): Layout { return new BNLayout(32, true, property); } class WrappedLayout extends LayoutCls { layout: Layout; decoder: (data: T) => U; encoder: (src: U) => T; constructor( layout: Layout, 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 { return new WrappedLayout( blob(32), (b: Buffer) => new PublicKey(b), (key: PublicKey) => key.toBuffer(), property ); } class OptionLayout extends LayoutCls { layout: Layout; discriminator: Layout; constructor(layout: Layout, 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( layout: Layout, property?: string ): Layout { return new OptionLayout(layout, property); } export function bool(property?: string): Layout { 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( elementLayout: Layout, property?: string ): Layout { 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( tag: BN, layout: Layout, property?: string ): Layout { 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 { 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 { return new WrappedLayout( vecU8(), (data) => data.toString("utf-8"), (s) => Buffer.from(s, "utf-8"), property ); } export interface EnumLayout extends Layout { registry: Record>; } export function rustEnum( variants: Layout[], property?: string, discriminant?: Layout ): EnumLayout { const unionLayout = union(discriminant ?? u8(), property); variants.forEach((variant, index) => unionLayout.addVariant(index, variant, variant.property) ); return unionLayout; } export function array( elementLayout: Layout, length: number, property?: string ): Layout { const layout: Layout<{ values: T[] }> = struct([ seq(elementLayout, length, "values"), ]); return new WrappedLayout( layout, ({ values }) => values, (values) => ({ values }), property ); } class MapEntryLayout extends LayoutCls<[K, V]> { keyLayout: Layout; valueLayout: Layout; constructor(keyLayout: Layout, valueLayout: Layout, 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( keyLayout: Layout, valueLayout: Layout, property?: string ): Layout> { 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 ); }