import { abiDecode, Resolved } from "../serializer/decoder.js" import { abiEncode } from "../serializer/encoder.js" import { ABIField, ABISerializableConstructor, ABISerializableObject, ABITypeModifiers } from "../serializer/serializable.js" import { isInstanceOf } from "../utils.js" import { ABI } from "./abi.js" export interface StructConstructor extends ABISerializableConstructor { new (...args: any[]): T structFields: ABIField[] } export class Struct implements ABISerializableObject { static abiName = '__struct' static abiFields: ABIField[] static abiBase: ABISerializableConstructor static from(this: T, value: any): InstanceType static from(value: any): unknown static from(value: any) { if (value[Resolved] === true) { // objects already resolved return new this(value) } if (isInstanceOf(value, this)) { return value } return abiDecode({object: value, type: this}) } static get structFields() { const rv: ABIField[] = [] const walk = (t: ABISerializableConstructor) => { if (t.abiBase) { walk(t.abiBase) } for (const field of t.abiFields || []) { rv.push(field) } } walk(this) return rv } /** @internal */ constructor(object: any) { const self = this.constructor as typeof Struct for (const field of self.structFields) { const isOptional = typeof field.type === 'string' ? new ABI.ResolvedType(String(field.type)).isOptional : field.optional const value = object[field.name] if (isOptional && !value) continue this[field.name] = value } } /** * Return true if this struct equals the other. * * Note: This compares the ABI encoded bytes of both structs, subclasses * should implement their own fast equality check when possible. */ equals(other: any): boolean { const self = this.constructor as typeof Struct if ( other.constructor && typeof other.constructor.abiName === 'string' && other.constructor.abiName !== self.abiName ) { return false } return abiEncode({object: this}).equals(abiEncode({object: self.from(other) as any})) } /** @internal */ toJSON() { const self = this.constructor as typeof Struct const rv: any = {} for (const field of self.structFields) { if (field.optional && !this[field.name]) continue rv[field.name] = this[field.name] } return rv } } export namespace Struct { const FieldsOwner = Symbol('FieldsOwner') export function type(name: string) { return function (struct: T) { struct.abiName = name return struct } } export function field( type: ABISerializableConstructor | string, options: ABITypeModifiers = {} ) { return (target: T, name: string) => { const ctor = target.constructor as StructConstructor if (!ctor.abiFields) { ctor.abiFields = [] ctor.abiFields[FieldsOwner] = ctor } else if (ctor.abiFields[FieldsOwner] !== ctor) { // if the target class isn't the owner we set the base and start new fields ctor.abiBase = ctor.abiFields[FieldsOwner] ctor.abiFields = [] ctor.abiFields[FieldsOwner] = ctor } ctor.abiFields.push({...options, name, type}) } } }