All files / src/chain struct.ts

98.21% Statements 55/56
89.28% Branches 25/28
100% Functions 12/12
98.07% Lines 51/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                  17373x 12408x 12408x   4965x     1x 1x             6715x   3767x   2948x 14x   2934x       3933x 3933x 4514x 581x   4514x 18064x     3933x 3933x         3769x 3769x 17371x 17371x 17371x 17209x 17205x                     5x 5x             5x         164x 164x 164x 693x 685x 683x   164x       1x 1x   93x 93x 93x             388x 388x 388x 88x 88x 300x   4x 4x 4x   388x        
import {
    ABIField,
    ABISerializableConstructor,
    ABISerializableObject,
    ABITypeModifiers,
} from '../serializer/serializable'
import {abiDecode, Resolved} from '../serializer/decoder'
import {abiEncode} from '../serializer/encoder'
import {isInstanceOf} from '../utils'
import {ABI} from './abi'
 
export interface StructConstructor extends ABISerializableConstructor {
    new <T extends Struct>(...args: any[]): T
    structFields: ABIField[]
}
 
function fieldModifiers(field: ABIField) {
    if (typeof field.type === 'string') {
        const resolved = new ABI.ResolvedType(field.type)
        return {isOptional: resolved.isOptional, isExtension: resolved.isExtension}
    }
    return {isOptional: !!field.optional, isExtension: !!field.extension}
}
 
export class Struct implements ABISerializableObject {
    static abiName = '__struct'
    static abiFields: ABIField[]
    static abiBase: ABISerializableConstructor
 
    static from<T extends StructConstructor>(this: T, value: any): InstanceType<T>
    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, isExtension} = fieldModifiers(field)
            const value = object[field.name]
            if (isOptional && !value) continue
            if (isExtension && value === undefined) 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
        Iif (
            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
            if (this[field.name] === undefined && fieldModifiers(field).isExtension) continue
            rv[field.name] = this[field.name]
        }
        return rv
    }
}
 
export namespace Struct {
    const FieldsOwner = Symbol('FieldsOwner')
    export function type(name: string) {
        return function <T extends StructConstructor>(struct: T) {
            struct.abiName = name
            return struct
        }
    }
    export function field(
        type: ABISerializableConstructor | string,
        options: ABITypeModifiers = {}
    ) {
        return <T extends Struct>(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})
        }
    }
}