import { PublicKey } from '@solana/web3.js' import BN, { isBN } from 'bn.js' import { Blob, Layout, UInt, Structure as _Structure, Union as _Union, offset as _offset, seq as _seq, u32 as _u32, u8 as _u8, union as _union, bits, blob, } from './buffer-layout' export * from './buffer-layout' export { blob } export class BNLayout

extends Layout { blob: Layout signed: boolean constructor(span: number, signed: boolean, property?: P) { //@ts-expect-error type wrong for super()'s type different from extends, but it desn't matter super(span, property) this.blob = blob(span) this.signed = signed } /** @override */ 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 } /** @override */ encode(src: BN, b: Buffer, offset = 0) { if (typeof src === 'number') src = new BN(src) // src will pass a number accidently in union if (this.signed) { src = src.toTwos(this.span * 8) } return this.blob.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset) } } export class WideBits

extends Layout, P> { _lower: any _upper: any // TODO: unknown constructor(property?: P) { //@ts-expect-error type wrong for super()'s type different from extends , but it desn't matter super(8, property) this._lower = bits(_u32(), false) this._upper = bits(_u32(), false) } addBoolean(property: string) { if (this._lower.fields.length < 32) { this._lower.addBoolean(property) } else { this._upper.addBoolean(property) } } decode(b: Buffer, offset = 0): Record { const lowerDecoded = this._lower.decode(b, offset) const upperDecoded = this._upper.decode(b, offset + this._lower.span) return { ...lowerDecoded, ...upperDecoded } } encode(src: any /* TEMP */, b: Buffer, offset = 0) { return this._lower.encode(src, b, offset) + this._upper.encode(src, b, offset + this._lower.span) } } export function u8

(property?: P): UInt { return new UInt(1, property) } export function u32

(property?: P): UInt { return new UInt(4, property) } export function u64

(property?: P): BNLayout

{ return new BNLayout(8, false, property) } export function u128

(property?: P): BNLayout

{ return new BNLayout(16, false, property) } export function i8

(property?: P): BNLayout

{ return new BNLayout(1, true, property) } export function i64

(property?: P): BNLayout

{ return new BNLayout(8, true, property) } export function i128

(property?: P): BNLayout

{ return new BNLayout(16, true, property) } export class WrappedLayout extends Layout { layout: Layout decoder: (data: T) => U encoder: (src: U) => T constructor(layout: Layout, decoder: (data: T) => U, encoder: (src: U) => T, property?: P) { //@ts-expect-error type wrong for super()'s type different from extends , but it desn't matter 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?: P): Layout { return new WrappedLayout( blob(32), (b: Buffer) => new PublicKey(b), (key: PublicKey) => key.toBuffer(), property, ) } export class OptionLayout extends Layout { layout: Layout discriminator: Layout constructor(layout: Layout, property?: P) { //@ts-expect-error type wrong for super()'s type different from extends , but it desn't matter 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?: P): Layout { return new OptionLayout(layout, property) } export function bool

(property?: P): Layout { return new WrappedLayout(_u8(), decodeBool, encodeBool, property) } export function decodeBool(value: number): boolean { if (value === 0) { return false } else if (value === 1) { return true } throw new Error('Invalid bool: ' + value) } export function encodeBool(value: boolean): number { return value ? 1 : 0 } export function vec(elementLayout: Layout, property?: P): Layout { const length = _u32('length') const layout: Layout<{ values: T[] }> = struct([ length, seq(elementLayout, _offset(length, -length.span), 'values'), ]) as any // Something I don't know return new WrappedLayout( layout, ({ values }) => values, (values) => ({ values }), property, ) } export function tagged(tag: BN, layout: Layout, property?: P): Layout { const wrappedLayout: Layout<{ tag: BN; data: T }> = struct([u64('tag'), layout.replicate('data')]) as any // Something I don't know 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?: P): Layout { const length = _u32('length') const layout: Layout<{ data: Buffer }> = struct([length, blob(_offset(length, -length.span), 'data')]) as any // Something I don't know return new WrappedLayout( layout, ({ data }) => data, (data) => ({ data }), property, ) } export function str

(property?: P): 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?: P): EnumLayout { const unionLayout = _union(_u8(), property) variants.forEach((variant, index) => unionLayout.addVariant(index, variant, variant.property)) return unionLayout as any // ?why use UnionLayout? This must be a fault } export function array( elementLayout: Layout, length: number, property?: P, ): Layout { const layout = struct([seq(elementLayout, length, 'values')]) as any as Layout<{ values: T[] }> // Something I don't know return new WrappedLayout( layout, ({ values }) => values, (values) => ({ values }), property, ) } export class Structure extends _Structure { /** @override */ decode(b: Buffer, offset?: number) { return super.decode(b, offset) } } export function struct( fields: T, property?: P, decodePrefixes?: boolean, ): T extends Layout[] ? Structure< Value, P, { [K in Exclude, ''>]: Extract> extends Layout ? V : any } > : any { //@ts-expect-error this type is not quite satisfied the define, but, never no need to worry about. return new Structure(fields, property, decodePrefixes) } export type GetLayoutSchemaFromStructure> = T extends Structure ? S : any export type GetStructureFromLayoutSchema = Structure export class Union extends _Union { encodeInstruction(instruction: any): Buffer { const instructionMaxSpan = Math.max(...Object.values(this.registry).map((r) => r.span)) const b = Buffer.alloc(instructionMaxSpan) return b.slice(0, this.encode(instruction, b)) } decodeInstruction(instruction: any) { return this.decode(instruction) } } export function union( discr: any, defaultLayout?: any, property?: string, ): Union { return new Union(discr, defaultLayout, property) } class Zeros extends Blob { decode(b: Buffer, offset: number) { const slice = super.decode(b, offset) if (!slice.every((v) => v === 0)) { throw new Error('nonzero padding bytes') } return slice } } export function zeros(length: number) { return new Zeros(length) } export function seq( elementLayout: Layout, count: number | BN | Layout, property?: AnotherP, ): Layout { let parsedCount: number const superCount = typeof count === 'number' ? count : isBN(count) ? count.toNumber() : new Proxy(count as unknown as Layout /* pretend to be Layout */, { get(target, property) { if (!parsedCount) { // get count in targetLayout. note that count may be BN const countProperty = Reflect.get(target, 'count') // let targetLayout's property:count be a number parsedCount = isBN(countProperty) ? countProperty.toNumber() : countProperty // record the count Reflect.set(target, 'count', parsedCount) } return Reflect.get(target, property) }, set(target, property, value) { if (property === 'count') { parsedCount = value } return Reflect.set(target, property, value) }, }) // @ts-expect-error force type return _seq(elementLayout, superCount, property) }