@polkadot/types
Version: 
Implementation of the Parity codec
232 lines (231 loc) • 7.82 kB
JavaScript
import { Enum, Raw, Tuple, U64 } from '@polkadot/types-codec';
import { bnToBn, formatNumber, hexToU8a, isHex, isObject, isU8a, u8aToBn, u8aToU8a } from '@polkadot/util';
import { IMMORTAL_ERA } from './constants.js';
function getTrailingZeros(period) {
    const binary = period.toString(2);
    let index = 0;
    while (binary[binary.length - 1 - index] === '0') {
        index++;
    }
    return index;
}
/** @internal */
function decodeMortalEra(registry, value) {
    if (isU8a(value) || isHex(value) || Array.isArray(value)) {
        return decodeMortalU8a(registry, u8aToU8a(value));
    }
    else if (!value) {
        return [new U64(registry), new U64(registry)];
    }
    else if (isObject(value)) {
        return decodeMortalObject(registry, value);
    }
    throw new Error('Invalid data passed to Mortal era');
}
/** @internal */
function decodeMortalObject(registry, value) {
    const { current, period } = value;
    let calPeriod = Math.pow(2, Math.ceil(Math.log2(period)));
    calPeriod = Math.min(Math.max(calPeriod, 4), 1 << 16);
    const phase = current % calPeriod;
    const quantizeFactor = Math.max(calPeriod >> 12, 1);
    const quantizedPhase = phase / quantizeFactor * quantizeFactor;
    return [new U64(registry, calPeriod), new U64(registry, quantizedPhase)];
}
/** @internal */
function decodeMortalU8a(registry, value) {
    if (value.length === 0) {
        return [new U64(registry), new U64(registry)];
    }
    const first = u8aToBn(value.subarray(0, 1)).toNumber();
    const second = u8aToBn(value.subarray(1, 2)).toNumber();
    const encoded = first + (second << 8);
    const period = 2 << (encoded % (1 << 4));
    const quantizeFactor = Math.max(period >> 12, 1);
    const phase = (encoded >> 4) * quantizeFactor;
    if (period < 4 || phase >= period) {
        throw new Error('Invalid data passed to Mortal era');
    }
    return [new U64(registry, period), new U64(registry, phase)];
}
/** @internal */
function decodeExtrinsicEra(value = new Uint8Array()) {
    if (isU8a(value)) {
        return (!value.length || value[0] === 0)
            ? new Uint8Array([0])
            : new Uint8Array([1, value[0], value[1]]);
    }
    else if (!value) {
        return new Uint8Array([0]);
    }
    else if (value instanceof GenericExtrinsicEra) {
        return decodeExtrinsicEra(value.toU8a());
    }
    else if (isHex(value)) {
        return decodeExtrinsicEra(hexToU8a(value));
    }
    else if (isObject(value)) {
        const entries = Object.entries(value).map(([k, v]) => [k.toLowerCase(), v]);
        const mortal = entries.find(([k]) => k.toLowerCase() === 'mortalera');
        const immortal = entries.find(([k]) => k.toLowerCase() === 'immortalera');
        // this is to de-serialize from JSON
        return mortal
            ? { MortalEra: mortal[1] }
            : immortal
                ? { ImmortalEra: immortal[1] }
                : { MortalEra: value };
    }
    throw new Error('Invalid data passed to Era');
}
/**
 * @name ImmortalEra
 * @description
 * The ImmortalEra for an extrinsic
 */
export class ImmortalEra extends Raw {
    constructor(registry, _value) {
        // For immortals, we always provide the known value (i.e. treated as a
        // constant no matter how it is constructed - it is a fixed structure)
        super(registry, IMMORTAL_ERA);
    }
}
/**
 * @name MortalEra
 * @description
 * The MortalEra for an extrinsic, indicating period and phase
 */
export class MortalEra extends Tuple {
    constructor(registry, value) {
        super(registry, {
            period: U64,
            phase: U64
        }, decodeMortalEra(registry, value));
    }
    /**
     * @description Encoded length for mortals occupy 2 bytes, different from the actual Tuple since it is encoded. This is a shortcut fro `toU8a().length`
     */
    get encodedLength() {
        return 2 | 0;
    }
    /**
     * @description The period of this Mortal wraps as a [[U64]]
     */
    get period() {
        return this[0];
    }
    /**
     * @description The phase of this Mortal wraps as a [[U64]]
     */
    get phase() {
        return this[1];
    }
    /**
     * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
     */
    toHuman() {
        return {
            period: formatNumber(this.period),
            phase: formatNumber(this.phase)
        };
    }
    /**
     * @description Returns a JSON representation of the actual value
     */
    toJSON() {
        return this.toHex();
    }
    /**
     * @description Encodes the value as a Uint8Array as per the parity-codec specifications
     * @param isBare true when the value has none of the type-specific prefixes (internal)
     * Period and phase are encoded:
     *   - The period of validity from the block hash found in the signing material.
     *   - The phase in the period that this transaction's lifetime begins (and, importantly,
     *     implies which block hash is included in the signature material). If the `period` is
     *     greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
     *     `period` is.
     */
    toU8a(_isBare) {
        const period = this.period.toNumber();
        const encoded = Math.min(15, Math.max(1, getTrailingZeros(period) - 1)) + ((this.phase.toNumber() / Math.max(period >> 12, 1)) << 4);
        return new Uint8Array([
            encoded & 0xff,
            encoded >> 8
        ]);
    }
    /**
     * @description Get the block number of the start of the era whose properties this object describes that `current` belongs to.
     */
    birth(current) {
        const phase = this.phase.toNumber();
        const period = this.period.toNumber();
        // FIXME No toNumber() here
        return (~~((Math.max(bnToBn(current).toNumber(), phase) - phase) / period) * period) + phase;
    }
    /**
     * @description Get the block number of the first block at which the era has ended.
     */
    death(current) {
        // FIXME No toNumber() here
        return this.birth(current) + this.period.toNumber();
    }
}
/**
 * @name GenericExtrinsicEra
 * @description
 * The era for an extrinsic, indicating either a mortal or immortal extrinsic
 */
export class GenericExtrinsicEra extends Enum {
    constructor(registry, value) {
        super(registry, {
            ImmortalEra,
            MortalEra
        }, decodeExtrinsicEra(value));
    }
    /**
     * @description Override the encoded length method
     */
    get encodedLength() {
        return this.isImmortalEra
            ? this.asImmortalEra.encodedLength
            : this.asMortalEra.encodedLength;
    }
    /**
     * @description Returns the item as a [[ImmortalEra]]
     */
    get asImmortalEra() {
        if (!this.isImmortalEra) {
            throw new Error(`Cannot convert '${this.type}' via asImmortalEra`);
        }
        return this.inner;
    }
    /**
     * @description Returns the item as a [[MortalEra]]
     */
    get asMortalEra() {
        if (!this.isMortalEra) {
            throw new Error(`Cannot convert '${this.type}' via asMortalEra`);
        }
        return this.inner;
    }
    /**
     * @description `true` if Immortal
     */
    get isImmortalEra() {
        return this.index === 0;
    }
    /**
     * @description `true` if Mortal
     */
    get isMortalEra() {
        return this.index > 0;
    }
    /**
     * @description Encodes the value as a Uint8Array as per the parity-codec specifications
     * @param isBare true when the value has none of the type-specific prefixes (internal)
     */
    toU8a(isBare) {
        return this.isMortalEra
            ? this.asMortalEra.toU8a(isBare)
            : this.asImmortalEra.toU8a(isBare);
    }
}