Place u8a decoding top-of-tree (#4230)

* Place u8a decoding top-of-tree

* Adjust
This commit is contained in:
Jaco
2021-11-18 12:41:27 +02:00
committed by GitHub
parent 62a476cb4c
commit 49a034a1a0
6 changed files with 59 additions and 67 deletions
+11 -15
View File
@@ -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;
+11 -7
View File
@@ -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;
+19 -14
View File
@@ -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;
}
}
+9 -31
View File
@@ -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);
+6
View File
@@ -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(),
+3
View File
@@ -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;
}