Decoding cleanups (#4228)

* Decoding cleanups

* CHANGELOG

* Additional adjustments

* Adjust

* Ordering
This commit is contained in:
Jaco
2021-11-18 08:24:52 +02:00
committed by GitHub
parent 56db42fedf
commit 6228999e6f
6 changed files with 60 additions and 81 deletions
+2
View File
@@ -12,7 +12,9 @@ Changes:
- Adjust runtime `*::{Call, Event}` matching for metadata v14
- Adjust derive decoration with (optional) known methods
- Expose `*{Call, Event, Error}` TS types for metadata v14
- Align Option `.toJSON` output with Substrate input
- Ajust PascalCase conversions
- Prepare pre-v13 types for optional injection
+2 -2
View File
@@ -7,7 +7,7 @@ import type { AnyJson, Codec, Constructor, ISet, Registry } from '../types';
import { compactFromU8a, compactToU8a, isHex, isU8a, logger, stringify, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util';
import { compareSet, decodeU8a, sortSet, typeToConstructor } from './utils';
import { compareSet, decodeU8aVec, sortSet, typeToConstructor } from './utils';
const l = logger('BTreeSet');
@@ -15,7 +15,7 @@ const l = logger('BTreeSet');
function decodeSetFromU8a<V extends Codec> (registry: Registry, ValClass: Constructor<V>, u8a: Uint8Array): [Set<V>, number] {
const output = new Set<V>();
const [offset, length] = compactFromU8a(u8a);
const [values, decodedLength] = decodeU8a<V>(registry, u8a.subarray(offset), new Array(length.toNumber()).fill(ValClass));
const [values, decodedLength] = decodeU8aVec(registry, u8a, offset, ValClass, length.toNumber());
for (let i = 0; i < values.length; i++) {
output.add(values[i]);
+36 -51
View File
@@ -5,7 +5,7 @@ import type { HexString } from '@polkadot/util/types';
import type { CodecHash, Hash } from '../interfaces';
import type { AnyJson, Codec, Constructor, IEnum, Registry } from '../types';
import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, isUndefined, objectProperties, stringCamelCase, stringify, stringPascalCase, u8aConcat, u8aToHex } from '@polkadot/util';
import { assert, isHex, isNumber, isObject, isString, isU8a, isUndefined, objectProperties, stringCamelCase, stringify, stringPascalCase, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util';
import { Null } from '../primitive/Null';
import { mapToTypeMap, typesToMap } from './utils';
@@ -40,40 +40,37 @@ function isRustEnum (def: Record<string, string | Constructor> | Record<string,
}
function extractDef (registry: Registry, _def: Record<string, string | Constructor> | Record<string, number> | string[]): { def: TypesDef; isBasic: boolean; isIndexed: boolean } {
if (Array.isArray(_def)) {
return {
def: _def.reduce((def: TypesDef, key, index): TypesDef => {
def[key] = { Type: Null, index };
return def;
}, {}),
isBasic: true,
isIndexed: false
};
}
const def: TypesDef = {};
let isBasic: boolean;
let isIndexed: boolean;
let def: TypesDef;
if (isRustEnum(_def)) {
def = Object
.entries(mapToTypeMap(registry, _def))
.reduce((def: TypesDef, [key, Type], index): TypesDef => {
def[key] = { Type, index };
if (Array.isArray(_def)) {
for (let i = 0; i < _def.length; i++) {
def[_def[i]] = { Type: Null, index: i };
}
isBasic = true;
isIndexed = false;
} else if (isRustEnum(_def)) {
const entries = Object.entries(mapToTypeMap(registry, _def));
for (let i = 0; i < entries.length; i++) {
const [key, Type] = entries[i];
def[key] = { Type, index: i };
}
return def;
}, {});
isBasic = !Object.values(def).some(({ Type }) => Type !== Null);
isIndexed = false;
} else {
def = Object
.entries(_def)
.reduce((def: TypesDef, [key, index]): TypesDef => {
def[key] = { Type: Null, index };
const entries = Object.entries(_def);
for (let i = 0; i < entries.length; i++) {
const [key, index] = entries[i];
def[key] = { Type: Null, index };
}
return def;
}, {});
isBasic = true;
isIndexed = true;
}
@@ -114,23 +111,24 @@ function decodeFromJSON (registry: Registry, def: TypesDef, key: string, value?:
}
}
function decodeFromString (registry: Registry, def: TypesDef, value: string): Decoded {
return isHex(value)
// eslint-disable-next-line @typescript-eslint/no-use-before-define
? decodeFromValue(registry, def, hexToU8a(value))
: decodeFromJSON(registry, def, value);
}
function decodeEnum (registry: Registry, def: TypesDef, value?: unknown, index?: number): Decoded {
// NOTE We check the index path first, before looking at values - this allows treating
// the optional indexes before anything else, more-specific > less-specific
if (isNumber(index)) {
return createFromValue(registry, def, index, value);
} else if (isU8a(value) || isHex(value)) {
const u8a = u8aToU8a(value);
function decodeFromValue (registry: Registry, def: TypesDef, value?: unknown): Decoded {
if (isU8a(value)) {
// nested, we don't want to match isObject below
if (value.length) {
return createFromValue(registry, def, value[0], value.subarray(1));
if (u8a.length) {
return createFromValue(registry, def, u8a[0], u8a.subarray(1));
}
} else if (value instanceof Enum) {
return createFromValue(registry, def, value.index, value.value);
} else if (isNumber(value)) {
return createFromValue(registry, def, value);
} else if (isString(value)) {
return decodeFromString(registry, def, value.toString());
return decodeFromJSON(registry, def, value.toString());
} else if (isObject(value)) {
const key = Object.keys(value)[0];
@@ -141,19 +139,6 @@ function decodeFromValue (registry: Registry, def: TypesDef, value?: unknown): D
return createFromValue(registry, def, Object.values(def)[0].index);
}
function decodeEnum (registry: Registry, def: TypesDef, value?: unknown, index?: number): Decoded {
// NOTE We check the index path first, before looking at values - this allows treating
// the optional indexes before anything else, more-specific > less-specific
if (isNumber(index)) {
return createFromValue(registry, def, index, value);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
} else if (value instanceof Enum) {
return createFromValue(registry, def, value.index, value.value);
}
return decodeFromValue(registry, def, value);
}
/**
* @name Enum
* @description
+6 -11
View File
@@ -5,7 +5,7 @@ import type { HexString } from '@polkadot/util/types';
import type { CodecHash, Hash } from '../interfaces/runtime';
import type { AnyJson, BareOpts, Codec, Constructor, ConstructorDef, IStruct, Registry } from '../types';
import { assert, hexToU8a, isBoolean, isFunction, isHex, isObject, isU8a, isUndefined, objectProperties, stringCamelCase, stringify, u8aConcat, u8aToHex } from '@polkadot/util';
import { assert, isBoolean, isFunction, isHex, isObject, isU8a, isUndefined, objectProperties, stringCamelCase, stringify, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util';
import { compareMap, decodeU8a, mapToTypeMap, typesToMap } from './utils';
@@ -74,10 +74,6 @@ function decodeStructFromObject (registry: Registry, Types: ConstructorDef, valu
return raw;
}
function decodeZip (k: string, v: Codec): [string, Codec] {
return [k, v];
}
/**
* Decode input to pass into constructor.
*
@@ -95,12 +91,10 @@ function decodeZip (k: string, v: Codec): [string, Codec] {
* @internal
*/
function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map<string, string>): [Iterable<[string, Codec]>, number] {
if (isU8a(value)) {
return decodeU8a(registry, value, Types, decodeZip);
if (isU8a(value) || isHex(value)) {
return decodeU8a(registry, u8aToU8a(value), Types, true);
} else if (value instanceof Struct) {
return [value as Iterable<[string, Codec]>, 0];
} else if (isHex(value)) {
return decodeStruct(registry, Types, hexToU8a(value), jsonMap);
}
// We assume from here that value is a JS object (Array, Map, Object)
@@ -136,14 +130,15 @@ export class Struct<
// 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 [decoded, decodedLength] = decodeStruct(registry, mapToTypeMap(registry, Types), value, jsonMap);
const typeMap = mapToTypeMap(registry, Types);
const [decoded, decodedLength] = decodeStruct(registry, typeMap, value, jsonMap);
super(decoded);
this.registry = registry;
this.initialU8aLength = decodedLength;
this.#jsonMap = jsonMap;
this.#Types = mapToTypeMap(registry, Types);
this.#Types = typeMap;
}
public static with<S extends TypesDef> (Types: S, jsonMap?: Map<string, string>): Constructor<Struct<S>> {
+8 -11
View File
@@ -4,25 +4,22 @@
import type { AnyU8a, Constructor, Registry } from '../types';
import type { U8aBitLength } from './types';
import { assert, isU8a, u8aToU8a } from '@polkadot/util';
import { assert, u8aToU8a } from '@polkadot/util';
import { Raw } from './Raw';
/** @internal */
function decodeU8aFixed (value: AnyU8a, bitLength: U8aBitLength): [AnyU8a, number] {
if (isU8a(value)) {
const byteLength = bitLength / 8;
const u8a = u8aToU8a(value);
const byteLength = bitLength / 8;
if (!value || !value.length) {
return [new Uint8Array(byteLength), 0];
}
assert(value.length >= byteLength, () => `Expected at least ${byteLength} bytes (${bitLength} bits), found ${value.length} bytes`);
return [value.subarray(0, byteLength), byteLength];
if (!u8a.length) {
return [new Uint8Array(byteLength), 0];
}
return decodeU8aFixed(u8aToU8a(value), bitLength);
assert(u8a.length >= byteLength, () => `Expected at least ${byteLength} bytes (${bitLength} bits), found ${u8a.length} bytes`);
return [u8a.subarray(0, byteLength), byteLength];
}
/**
+6 -6
View File
@@ -5,15 +5,15 @@ import type { Codec, Constructor, Registry } from '../../types';
import { u8aToHex } from '@polkadot/util';
function formatFailure (error: Error, type: string, u8a: Uint8Array, key?: string): string {
function formatFailure (error: Error, type: string | null, u8a: Uint8Array, key?: string): string {
return `decodeU8a: failed at ${u8aToHex(u8a)}…${key ? ` on ${key}` : ''}${type ? `: ${type}` : ''}:: ${error.message}`;
}
function getRawType (registry: Registry, Type: Constructor): string {
function getRawType (registry: Registry, Type: Constructor): string | null {
try {
return new Type(registry).toRawType();
} catch {
return '';
return null;
}
}
@@ -25,7 +25,7 @@ function getRawType (registry: Registry, Type: Constructor): string {
* @param result - The result array (will be returned with values pushed)
* @param types - The array of Constructor to decode the U8a against.
*/
export function decodeU8a <T extends Codec = Codec, E = T> (registry: Registry, u8a: Uint8Array, types: Constructor[] | { [index: string]: Constructor }, zip?: (key: string, value: T) => E): [E[], number] {
export function decodeU8a <T extends Codec = Codec, E = T> (registry: Registry, u8a: Uint8Array, types: Constructor[] | { [index: string]: Constructor }, withZip?: boolean): [E[], number] {
const [Types, keys]: [Constructor[], string[]] = Array.isArray(types)
? [types, []]
: [Object.values(types), Object.keys(types)];
@@ -38,8 +38,8 @@ export function decodeU8a <T extends Codec = Codec, E = T> (registry: Registry,
const value = new Types[i](registry, u8a.subarray(offset));
offset += value.initialU8aLength || value.encodedLength;
result[i] = zip
? zip(keys[i], value as T)
result[i] = withZip
? [keys[i], value] as unknown as E
: value as unknown as E;
} catch (error) {
throw new Error(formatFailure(