Place u8a decoding top-of-tree (#4230)
* Place u8a decoding top-of-tree * Adjust
This commit is contained in:
@@ -6,7 +6,7 @@ import type { CodecHash, Hash } from '../interfaces/runtime';
|
||||
import type { AnyNumber, INumber, Registry } from '../types';
|
||||
import type { UIntBitLength } from './types';
|
||||
|
||||
import { assert, BN, BN_BILLION, BN_HUNDRED, BN_MILLION, BN_QUINTILL, BN_ZERO, bnToBn, bnToHex, bnToU8a, formatBalance, formatNumber, hexToBn, isBn, isHex, isU8a, stringify, u8aToBn } from '@polkadot/util';
|
||||
import { assert, BN, BN_BILLION, BN_HUNDRED, BN_MILLION, BN_QUINTILL, BN_ZERO, bnToBn, bnToHex, bnToU8a, formatBalance, formatNumber, hexToBn, isBn, isHex, isU8a, u8aToBn } from '@polkadot/util';
|
||||
|
||||
export const DEFAULT_UINT_BITS = 64;
|
||||
|
||||
@@ -28,20 +28,8 @@ function toPercentage (value: BN, divisor: BN): string {
|
||||
|
||||
/** @internal */
|
||||
function decodeAbstractInt (value: AnyNumber, bitLength: UIntBitLength, isNegative: boolean): string {
|
||||
// This function returns a string, which will be passed in the BN
|
||||
// constructor. It would be ideal to actually return a BN, but there's a
|
||||
// bug: https://github.com/indutny/bn.js/issues/206.
|
||||
if (isU8a(value)) {
|
||||
if (!value.length) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
try {
|
||||
// NOTE When passing u8a in (typically from decoded data), it is always Little Endian
|
||||
return u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative }).toString();
|
||||
} catch (error) {
|
||||
throw new Error(`AbstractInt: failed on ${stringify(value)}:: ${(error as Error).message}`);
|
||||
}
|
||||
return u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative }).toString();
|
||||
} else if (isBn(value)) {
|
||||
return value.toString();
|
||||
} else if (isHex(value, -1, true)) {
|
||||
@@ -68,7 +56,15 @@ export abstract class AbstractInt extends BN implements INumber {
|
||||
readonly #isSigned: boolean;
|
||||
|
||||
constructor (registry: Registry, value: AnyNumber = 0, bitLength: UIntBitLength = DEFAULT_UINT_BITS, isSigned = false) {
|
||||
super(decodeAbstractInt(value, bitLength, isSigned));
|
||||
// Construct via a string, which will be passed in the BN constructor.
|
||||
// It would be ideal to actually return a BN, but there is an issue:
|
||||
// https://github.com/indutny/bn.js/issues/206
|
||||
super(
|
||||
// shortcut isU8a as used in SCALE decoding
|
||||
isU8a(value)
|
||||
? u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative: isSigned }).toString()
|
||||
: decodeAbstractInt(value, bitLength, isSigned)
|
||||
);
|
||||
|
||||
this.registry = registry;
|
||||
this.#bitLength = bitLength;
|
||||
|
||||
@@ -164,15 +164,19 @@ export class Enum implements IEnum {
|
||||
|
||||
readonly #raw: Codec;
|
||||
|
||||
constructor (registry: Registry, def: Record<string, string | Constructor> | Record<string, number> | string[], value?: unknown, index?: number) {
|
||||
const defInfo = extractDef(registry, def);
|
||||
const decoded = decodeEnum(registry, defInfo.def, value, index);
|
||||
constructor (registry: Registry, Types: Record<string, string | Constructor> | Record<string, number> | string[], value?: unknown, index?: number) {
|
||||
const { def, isBasic, isIndexed } = extractDef(registry, Types);
|
||||
|
||||
// shortcut isU8a as used in SCALE decoding
|
||||
const decoded = isU8a(value) && value.length && !isNumber(index)
|
||||
? createFromValue(registry, def, value[0], value.subarray(1))
|
||||
: decodeEnum(registry, def, value, index);
|
||||
|
||||
this.registry = registry;
|
||||
this.#def = defInfo.def;
|
||||
this.#isBasic = defInfo.isBasic;
|
||||
this.#isIndexed = defInfo.isIndexed;
|
||||
this.#indexes = Object.values(defInfo.def).map(({ index }) => index);
|
||||
this.#def = def;
|
||||
this.#isBasic = isBasic;
|
||||
this.#isIndexed = isIndexed;
|
||||
this.#indexes = Object.values(def).map(({ index }) => index);
|
||||
this.#entryIndex = this.#indexes.indexOf(decoded.index) || 0;
|
||||
this.#raw = decoded.value;
|
||||
|
||||
|
||||
@@ -5,22 +5,20 @@ import type { HexString } from '@polkadot/util/types';
|
||||
import type { CodecHash, Hash } from '../interfaces';
|
||||
import type { AnyJson, Codec, Constructor, IOption, Registry } from '../types';
|
||||
|
||||
import { assert, isNull, isU8a, isUndefined, u8aToHex } from '@polkadot/util';
|
||||
import { assert, isCodec, isNull, isU8a, isUndefined, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { Null } from '../primitive/Null';
|
||||
import { typeToConstructor } from './utils';
|
||||
|
||||
/** @internal */
|
||||
function decodeOption (registry: Registry, typeName: Constructor | string, value?: unknown): Codec {
|
||||
if (isNull(value) || isUndefined(value) || value instanceof Null || value === '0x') {
|
||||
return new Null(registry);
|
||||
function decodeOption (registry: Registry, Type: Constructor, value?: unknown): Codec {
|
||||
// In the case of an option, unwrap the inner
|
||||
if (value instanceof Option) {
|
||||
value = value.value;
|
||||
}
|
||||
|
||||
const Type = typeToConstructor(registry, typeName);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
if (value instanceof Option) {
|
||||
return decodeOption(registry, Type, value.value);
|
||||
if (isNull(value) || isUndefined(value) || value instanceof Null || value === '0x') {
|
||||
return new Null(registry);
|
||||
} else if (value instanceof Type) {
|
||||
// don't re-create, use as it (which also caters for derived types)
|
||||
return value;
|
||||
@@ -55,12 +53,19 @@ export class Option<T extends Codec> implements IOption<T> {
|
||||
readonly #raw: T;
|
||||
|
||||
constructor (registry: Registry, typeName: Constructor<T> | string, value?: unknown) {
|
||||
this.registry = registry;
|
||||
this.#Type = typeToConstructor(registry, typeName);
|
||||
this.#raw = decodeOption(registry, typeName, value) as T;
|
||||
const Type = typeToConstructor(registry, typeName);
|
||||
const decoded = isU8a(value) && value.length && !isCodec(value)
|
||||
? value[0] === 0
|
||||
? new Null(registry)
|
||||
: new Type(registry, value.subarray(1))
|
||||
: decodeOption(registry, Type, value);
|
||||
|
||||
if (this.#raw.initialU8aLength) {
|
||||
this.#initialU8aLength = 1 + this.#raw.initialU8aLength;
|
||||
this.registry = registry;
|
||||
this.#Type = Type;
|
||||
this.#raw = decoded as T;
|
||||
|
||||
if (decoded.initialU8aLength) {
|
||||
this.#initialU8aLength = 1 + decoded.initialU8aLength;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { compareMap, decodeU8a, mapToTypeMap, typesToMap } from './utils';
|
||||
type TypesDef<T = Codec> = Record<string, string | Constructor<T>>;
|
||||
|
||||
/** @internal */
|
||||
function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map<string, string>): Iterable<[string, Codec]> {
|
||||
function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map<string, string>): [Iterable<[string, Codec]>, number] {
|
||||
let jsonObj: Record<string, unknown> | undefined;
|
||||
const inputKeys = Object.keys(Types);
|
||||
const typeofArray = Array.isArray(value);
|
||||
@@ -71,34 +71,7 @@ function decodeStructFromObject (registry: Registry, Types: ConstructorDef, valu
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode input to pass into constructor.
|
||||
*
|
||||
* @param Types - Types definition.
|
||||
* @param value - Value to decode, one of:
|
||||
* - null
|
||||
* - undefined
|
||||
* - hex
|
||||
* - Uint8Array
|
||||
* - object with `{ key1: value1, key2: value2 }`, assuming `key1` and `key2`
|
||||
* are also keys in `Types`
|
||||
* - array with `[value1, value2]` assuming the array has the same length as
|
||||
* `Object.keys(Types)`
|
||||
* @param jsonMap
|
||||
* @internal
|
||||
*/
|
||||
function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map<string, string>): [Iterable<[string, Codec]>, number] {
|
||||
if (isU8a(value) || isHex(value)) {
|
||||
return decodeU8a(registry, u8aToU8a(value), Types, true);
|
||||
} else if (value instanceof Struct) {
|
||||
return [value as Iterable<[string, Codec]>, 0];
|
||||
}
|
||||
|
||||
// We assume from here that value is a JS object (Array, Map, Object)
|
||||
return [decodeStructFromObject(registry, Types, value || {}, jsonMap), 0];
|
||||
return [raw, 0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,10 +101,15 @@ export class Struct<
|
||||
|
||||
readonly #Types: ConstructorDef;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
constructor (registry: Registry, Types: S, value?: V | Map<unknown, unknown> | unknown[] | HexString | null, jsonMap = new Map<string, string>()) {
|
||||
const typeMap = mapToTypeMap(registry, Types);
|
||||
const [decoded, decodedLength] = decodeStruct(registry, typeMap, value, jsonMap);
|
||||
const [decoded, decodedLength] = isU8a(value)
|
||||
? decodeU8a<Codec, [string, Codec]>(registry, value, typeMap, true)
|
||||
: isHex(value)
|
||||
? decodeU8a<Codec, [string, Codec]>(registry, u8aToU8a(value), typeMap, true)
|
||||
: value instanceof Struct
|
||||
? [value as Iterable<[string, Codec]>, 0]
|
||||
: decodeStructFromObject(registry, typeMap, value || {}, jsonMap);
|
||||
|
||||
super(decoded);
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ import { UInt } from '.';
|
||||
describe('UInt', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('decodes an empty Uint8array correctly', (): void => {
|
||||
expect(
|
||||
new UInt(registry, new Uint8Array()).toNumber()
|
||||
).toEqual(0);
|
||||
});
|
||||
|
||||
it('still has the BN interfaces', (): void => {
|
||||
expect([
|
||||
new UInt(registry, 32).mul(BN_TWO).toNumber(),
|
||||
|
||||
@@ -17,6 +17,9 @@ export class Null implements Codec {
|
||||
|
||||
public createdAtHash?: Hash;
|
||||
|
||||
// Added for compatibility reasons, e.g. see Option
|
||||
public readonly initialU8aLength = 0;
|
||||
|
||||
constructor (registry: Registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user