From 85ae6f949b45af9cea5eb819c2a1776bb4aea9e1 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 26 Feb 2020 21:30:38 +0100 Subject: [PATCH] Allow construction of HashMap (#1955) * Allow construction of HashMap * WIP... add Map to codec, extend BTreeMap & HashMap from it * Rework various map tests * registry.createClass * createClass in tests --- CHANGELOG.md | 7 + packages/api-derive/src/accounts/indexToId.ts | 4 +- packages/api/src/submittable/createClass.ts | 3 +- .../metadata/src/Decorated/Decorated.spec.ts | 4 +- .../consts/fromMetadata/fromMetadata.spec.ts | 4 +- packages/typegen/src/generate/tsDef.ts | 2 + packages/typegen/src/util/derived.ts | 4 +- packages/typegen/src/util/formatting.ts | 27 ++- packages/types/src/codec/BTreeMap.spec.ts | 68 +----- packages/types/src/codec/BTreeMap.ts | 206 +---------------- packages/types/src/codec/BTreeSet.ts | 21 +- packages/types/src/codec/Compact.spec.ts | 6 +- packages/types/src/codec/Compact.ts | 2 +- packages/types/src/codec/HashMap.spec.ts | 21 ++ packages/types/src/codec/HashMap.ts | 17 ++ packages/types/src/codec/Map.spec.ts | 73 ++++++ packages/types/src/codec/Map.ts | 210 ++++++++++++++++++ packages/types/src/codec/Struct.spec.ts | 14 +- packages/types/src/codec/Tuple.spec.ts | 12 +- packages/types/src/codec/UInt.ts | 5 +- packages/types/src/codec/index.ts | 1 + packages/types/src/codec/utils/encodeTypes.ts | 2 + packages/types/src/codec/utils/index.ts | 4 +- .../types/src/codec/utils/mapToTypeMap.ts | 16 +- .../src/codec/utils/typeToConstructor.ts | 15 ++ packages/types/src/create/createClass.ts | 20 +- packages/types/src/create/createType.spec.ts | 10 +- packages/types/src/create/getTypeDef.ts | 1 + packages/types/src/create/registry.ts | 7 + packages/types/src/create/sanitize.ts | 2 +- packages/types/src/create/types.ts | 1 + packages/types/src/extrinsic/Extrinsic.ts | 3 +- packages/types/src/extrinsic/v2/Extrinsic.ts | 3 +- packages/types/src/extrinsic/v3/Extrinsic.ts | 3 +- packages/types/src/extrinsic/v4/Extrinsic.ts | 3 +- .../types/src/interfaces/rpc/definitions.ts | 8 +- packages/types/src/interfaces/rpc/types.ts | 8 +- packages/types/src/types/registry.ts | 1 + 38 files changed, 467 insertions(+), 351 deletions(-) create mode 100644 packages/types/src/codec/HashMap.spec.ts create mode 100644 packages/types/src/codec/HashMap.ts create mode 100644 packages/types/src/codec/Map.spec.ts create mode 100644 packages/types/src/codec/Map.ts create mode 100644 packages/types/src/codec/utils/typeToConstructor.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d15544b3de..91a10c7267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.5.0-beta.x + +- Allow `BTreeMap` to be initialized with a `Record` object (in addition to `Map`) +- Allow for `HashMap` definitions +- Refresh the `NetworkState` types as returned by the `rpc.system.networkState()` call to current +- Expose `registry.createClass()`, to allow for creating type classes. If you are using `createClass` or `ClassOf` in your code, it is recommended to swap to this instance. + # 1.4.1 Feb 26, 2020 - **Breaking change** (TypeScript only) The `*.d.ts` files now contain TypeScript 3.8 features (specifically `#private`), which is not usable in TS versions < 3.8 diff --git a/packages/api-derive/src/accounts/indexToId.ts b/packages/api-derive/src/accounts/indexToId.ts index 37ab9444a2..8d159c2be5 100644 --- a/packages/api-derive/src/accounts/indexToId.ts +++ b/packages/api-derive/src/accounts/indexToId.ts @@ -9,12 +9,12 @@ import { Observable, of } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { ENUMSET_SIZE } from '@polkadot/types/generic/AccountIndex'; -import { ClassOf, Option, Vec, createType } from '@polkadot/types'; +import { Option, Vec, createType } from '@polkadot/types'; import { memo } from '../util'; function queryEnumSet (api: ApiInterfaceRx, _accountIndex: AccountIndex | string): Observable { - const accountIndex = _accountIndex instanceof ClassOf(api.registry, 'AccountIndex') + const accountIndex = _accountIndex instanceof api.registry.createClass('AccountIndex') ? _accountIndex : createType(api.registry, 'AccountIndex', _accountIndex); diff --git a/packages/api/src/submittable/createClass.ts b/packages/api/src/submittable/createClass.ts index 8022923480..92f4e11c7b 100644 --- a/packages/api/src/submittable/createClass.ts +++ b/packages/api/src/submittable/createClass.ts @@ -10,7 +10,6 @@ import { AddressOrPair, SignerOptions, SubmittableExtrinsic, SubmittablePaymentR import { Observable, combineLatest, of } from 'rxjs'; import { first, map, mapTo, mergeMap, switchMap, tap } from 'rxjs/operators'; -import { ClassOf } from '@polkadot/types'; import { assert, isBn, isFunction, isNumber, isUndefined } from '@polkadot/util'; import { filterEvents, isKeyringPair } from '../util'; @@ -32,7 +31,7 @@ const DEFAULT_MORTAL_LENGTH = 5 * ONE_MINUTE; export default function createClass ({ api, apiType, decorateMethod }: SubmittableOptions): Constructor> { // an instance of the base extrinsic for us to extend - const Extrinsic = ClassOf(api.registry, 'Extrinsic'); + const Extrinsic = api.registry.createClass('Extrinsic'); return class Submittable extends Extrinsic implements SubmittableExtrinsic { readonly #ignoreStatusCb: boolean; diff --git a/packages/metadata/src/Decorated/Decorated.spec.ts b/packages/metadata/src/Decorated/Decorated.spec.ts index 9ff055740f..e7c61208c1 100644 --- a/packages/metadata/src/Decorated/Decorated.spec.ts +++ b/packages/metadata/src/Decorated/Decorated.spec.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { ClassOf, TypeRegistry } from '@polkadot/types/create'; +import { TypeRegistry } from '@polkadot/types/create'; import { u8aToHex } from '@polkadot/util'; import Decorated from './Decorated'; @@ -40,7 +40,7 @@ describe('Decorated', () => { }); it('should return constants with the correct type and value', (): void => { - expect(decorated.consts.democracy.cooloffPeriod).toBeInstanceOf(ClassOf(registry, 'BlockNumber')); + expect(decorated.consts.democracy.cooloffPeriod).toBeInstanceOf(registry.createClass('BlockNumber')); expect(decorated.consts.democracy.cooloffPeriod.toHex()).toEqual('0x000c4e00'); }); }); diff --git a/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts b/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts index e163d73fb0..1b916627e9 100644 --- a/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts +++ b/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { ClassOf, Metadata, TypeRegistry } from '@polkadot/types'; +import { Metadata, TypeRegistry } from '@polkadot/types'; import rpcMetadata from '../../../Metadata/static'; import fromMetadata from '../fromMetadata'; @@ -14,7 +14,7 @@ const consts = fromMetadata(registry, metadata); describe('fromMetadata', (): void => { it('should return constants with the correct type and value', (): void => { - expect(consts.democracy.cooloffPeriod).toBeInstanceOf(ClassOf(registry, 'BlockNumber')); + expect(consts.democracy.cooloffPeriod).toBeInstanceOf(registry.createClass('BlockNumber')); // 3 second blocks, 28 days expect(consts.democracy.cooloffPeriod.toNumber()).toEqual(28 * 24 * 60 * (60 / 3)); }); diff --git a/packages/typegen/src/generate/tsDef.ts b/packages/typegen/src/generate/tsDef.ts index b3e551af57..3a71544a57 100644 --- a/packages/typegen/src/generate/tsDef.ts +++ b/packages/typegen/src/generate/tsDef.ts @@ -38,6 +38,7 @@ function tsExport (definitions: object, def: TypeDef, imports: TypeImports): str const tsBTreeMap = tsExport; const tsBTreeSet = tsExport; const tsCompact = tsExport; +const tsHashMap = tsExport; const tsOption = tsExport; const tsPlain = tsExport; const tsTuple = tsExport; @@ -156,6 +157,7 @@ function generateInterfaces (definitions: object, { types }: { types: Record` string + */ +/** @internal */ +function formatHashMap (key: string, val: string): string { + return `HashMap<${key}, ${val}>`; +} + /** * Given the inner `O` & `E`, return a `Result` string */ @@ -150,13 +158,16 @@ export function formatType (definitions: object, type: string | TypeDef, imports setImports(definitions, imports, [typeDef.type]); + // FIXME Swap to Record to check all types switch (typeDef.info) { case TypeDefInfo.Compact: { setImports(definitions, imports, ['Compact']); + return formatCompact(formatType(definitions, (typeDef.sub as TypeDef).type, imports)); } case TypeDefInfo.Option: { setImports(definitions, imports, ['Option']); + return formatOption(formatType(definitions, (typeDef.sub as TypeDef).type, imports)); } case TypeDefInfo.Plain: { @@ -164,6 +175,7 @@ export function formatType (definitions: object, type: string | TypeDef, imports } case TypeDefInfo.Vec: { setImports(definitions, imports, ['Vec']); + return formatVec(formatType(definitions, (typeDef.sub as TypeDef).type, imports)); } case TypeDefInfo.Tuple: { @@ -190,21 +202,34 @@ export function formatType (definitions: object, type: string | TypeDef, imports } case TypeDefInfo.BTreeMap: { setImports(definitions, imports, ['BTreeMap']); + const [keyDef, valDef] = (typeDef.sub as TypeDef[]); + return formatBTreeMap(formatType(definitions, keyDef.type, imports), formatType(definitions, valDef.type, imports)); } case TypeDefInfo.BTreeSet: { setImports(definitions, imports, ['BTreeSet']); + const valDef = typeDef.sub as TypeDef; + return formatBTreeSet(formatType(definitions, valDef.type, imports)); } + case TypeDefInfo.HashMap: { + setImports(definitions, imports, ['HashMap']); + + const [keyDef, valDef] = (typeDef.sub as TypeDef[]); + + return formatHashMap(formatType(definitions, keyDef.type, imports), formatType(definitions, valDef.type, imports)); + } case TypeDefInfo.Result: { setImports(definitions, imports, ['Result']); + const [okDef, errorDef] = (typeDef.sub as TypeDef[]); + return formatResult(formatType(definitions, okDef.type, imports), formatType(definitions, errorDef.type, imports)); } default: { - throw new Error(`Cannot format ${type}.`); + throw new Error(`Cannot format ${JSON.stringify(type)}`); } } } diff --git a/packages/types/src/codec/BTreeMap.spec.ts b/packages/types/src/codec/BTreeMap.spec.ts index efc588a102..103edb8a49 100644 --- a/packages/types/src/codec/BTreeMap.spec.ts +++ b/packages/types/src/codec/BTreeMap.spec.ts @@ -6,72 +6,16 @@ import { TypeRegistry } from '../create'; import Text from '../primitive/Text'; import U32 from '../primitive/U32'; import Struct from './Struct'; -import { CodecTo } from '../types'; import BTreeMap from './BTreeMap'; const registry = new TypeRegistry(); -const mockU32TextMap = new Map(); -mockU32TextMap.set(new Text(registry, 'bazzing'), new U32(registry, 69)); -const mockU32TextMapString = '{"bazzing":69}'; -const mockU32TextMapObject = { bazzing: 69 }; -const mockU32TextMapHexString = '0x041c62617a7a696e6745000000'; -const mockU32TextMapUint8Array = Uint8Array.from([4, 28, 98, 97, 122, 122, 105, 110, 103, 69, 0, 0, 0]); -const mockU32U32Map = new Map(); -mockU32U32Map.set(new U32(registry, 1), new U32(registry, 2)); -mockU32U32Map.set(new U32(registry, 23), new U32(registry, 24)); -mockU32U32Map.set(new U32(registry, 28), new U32(registry, 30)); -mockU32U32Map.set(new U32(registry, 45), new U32(registry, 80)); -const mockU32U32MapString = '{"1":2,"23":24,"28":30,"45":80}'; -const mockU32U32MapObject = { 1: 2, 23: 24, 28: 30, 45: 80 }; -const mockU32U32MapHexString = '0x10043102000000083233180000000832381e00000008343550000000'; -const mockU32U32MapUint8Array = Uint8Array.from([16, 4, 49, 2, 0, 0, 0, 8, 50, 51, 24, 0, 0, 0, 8, 50, 56, 30, 0, 0, 0, 8, 52, 53, 80, 0, 0, 0]); +const mockU32TextMap = new Map(); + +mockU32TextMap.set(new Text(registry, 'bazzing'), new U32(registry, 69)); describe('BTreeMap', (): void => { - describe('decoding', (): void => { - const testDecode = (type: string, input: any, output: string): void => - it(`can decode from ${type}`, (): void => { - const s = new BTreeMap(registry, Text, U32, input); - - expect(s.toString()).toBe(output); - }); - - testDecode('map', mockU32TextMap, mockU32TextMapString); - testDecode('hex', mockU32TextMapHexString, mockU32TextMapString); - testDecode('Uint8Array', mockU32TextMapUint8Array, mockU32TextMapString); - - testDecode('map', mockU32U32Map, mockU32U32MapString); - testDecode('hex', mockU32U32MapHexString, mockU32U32MapString); - testDecode('Uint8Array', mockU32U32MapUint8Array, mockU32U32MapString); - }); - - describe('encoding', (): void => { - const testEncode = (to: CodecTo, expected: any): void => - it(`can encode ${to}`, (): void => { - const s = new BTreeMap(registry, Text, U32, mockU32TextMap); - expect(s[to]()).toEqual(expected); - }); - - testEncode('toHex', mockU32TextMapHexString); - testEncode('toJSON', mockU32TextMapObject); - testEncode('toU8a', mockU32TextMapUint8Array); - testEncode('toString', mockU32TextMapString); - }); - - describe('encoding muple values', (): void => { - const testEncode = (to: CodecTo, expected: any): void => - it(`can encode ${to}`, (): void => { - const s = new BTreeMap(registry, Text, U32, mockU32U32Map); - expect(s[to]()).toEqual(expected); - }); - - testEncode('toHex', mockU32U32MapHexString); - testEncode('toJSON', mockU32U32MapObject); - testEncode('toU8a', mockU32U32MapUint8Array); - testEncode('toString', mockU32U32MapString); - }); - it('decodes null', (): void => { expect( new ( @@ -83,9 +27,9 @@ describe('BTreeMap', (): void => { it('decodes within more complicated types', (): void => { const s = new Struct(registry, { placeholder: U32, - value: BTreeMap.with(Text, U32) + value: 'BTreeMap' as any }); - s.set('value', new BTreeMap(registry, Text, U32, mockU32TextMap)); + s.set('value', new (BTreeMap.with(Text, U32))(registry, mockU32TextMap)); expect(s.toString()).toBe('{"placeholder":0,"value":{"bazzing":69}}'); }); @@ -94,7 +38,7 @@ describe('BTreeMap', (): void => { (): BTreeMap => new ( BTreeMap.with(Text, U32) )(registry, 'ABC') - ).toThrowError(/BTreeMap: cannot decode type/); + ).toThrowError(/Map: cannot decode type/); }); it('correctly encodes length', (): void => { diff --git a/packages/types/src/codec/BTreeMap.ts b/packages/types/src/codec/BTreeMap.ts index e85c09cc1d..b9491c9f53 100644 --- a/packages/types/src/codec/BTreeMap.ts +++ b/packages/types/src/codec/BTreeMap.ts @@ -2,212 +2,16 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { H256 } from '../interfaces/runtime'; -import { AnyJson, Constructor, Codec, InterfaceTypes, Registry } from '../types'; +import { Constructor, Codec, InterfaceTypes, Registry } from '../types'; -import { isHex, hexToU8a, isObject, isU8a, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util'; -import { blake2AsU8a } from '@polkadot/util-crypto'; +import CodecMap from './Map'; -import Compact from './Compact'; -import Raw from './Raw'; -import { compareMap, decodeU8a, typeToConstructor } from './utils'; - -/** @internal */ -function decodeBTreeMapFromU8a (registry: Registry, KeyClass: Constructor, ValClass: Constructor, u8a: Uint8Array): Map { - const output = new Map(); - const [offset, length] = Compact.decodeU8a(u8a); - const types = []; - - for (let i = 0; i < length.toNumber(); i++) { - types.push(KeyClass, ValClass); - } - - const values = decodeU8a(registry, u8a.subarray(offset), types); - - for (let i = 0; i < values.length; i += 2) { - output.set(values[i] as K, values[i + 1] as V); - } - - return output; -} - -/** @internal */ -function decodeBTreeMapFromMap (registry: Registry, KeyClass: Constructor, ValClass: Constructor, value: Map): Map { - const output = new Map(); - - value.forEach((val: any, key: any) => { - try { - output.set( - key instanceof KeyClass - ? key - : new KeyClass(registry, key), - val instanceof ValClass - ? val - : new ValClass(registry, val) - ); - } catch (error) { - console.error('Failed to decode BTreeMap key or value:', error.message); - - throw error; - } - }); - - return output; -} - -/** - * Decode input to pass into constructor. - * - * @param KeyClass - Type of the map key - * @param ValClass - Type of the map value - * @param value - Value to decode, one of: - * - null - * - undefined - * - hex - * - Uint8Array - * - Map, where both key and value types are either - * constructors or decodeable values for their types. - * @param jsonMap - * @internal - */ -function decodeBTreeMap (registry: Registry, keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes, value: Uint8Array | string | Map): Map { - const KeyClass = typeToConstructor(registry, keyType); - const ValClass = typeToConstructor(registry, valType); - - if (!value) { - return new Map(); - } else if (isHex(value)) { - return decodeBTreeMap(registry, KeyClass, ValClass, hexToU8a(value)); - } else if (isU8a(value)) { - return decodeBTreeMapFromU8a(registry, KeyClass, ValClass, u8aToU8a(value)); - } else if (value instanceof Map) { - return decodeBTreeMapFromMap(registry, KeyClass, ValClass, value); - } else if (isObject(value)) { - return decodeBTreeMapFromMap(registry, KeyClass, ValClass, new Map(Object.entries(value))); - } - - throw new Error('BTreeMap: cannot decode type'); -} - -export default class BTreeMap extends Map implements Codec { - public readonly registry: Registry; - - readonly #KeyClass: Constructor; - - readonly #ValClass: Constructor; - - constructor (registry: Registry, keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes, rawValue: any) { - super(decodeBTreeMap(registry, keyType, valType, rawValue)); - - this.registry = registry; - this.#KeyClass = typeToConstructor(registry, keyType); - this.#ValClass = typeToConstructor(registry, valType); - } - - public static with (keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes): Constructor> { +export default class BTreeMap extends CodecMap { + public static with (keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes): Constructor> { return class extends BTreeMap { constructor (registry: Registry, value?: any) { - super(registry, keyType, valType, value); + super(registry, 'BTreeMap', keyType, valType, value); } }; } - - /** - * @description The length of the value when encoded as a Uint8Array - */ - public get encodedLength (): number { - let len = Compact.encodeU8a(this.size).length; - - this.forEach((v: V, k: K) => { - len += v.encodedLength + k.encodedLength; - }); - - return len; - } - - /** - * @description Returns a hash of the value - */ - public get hash (): H256 { - return new Raw(this.registry, blake2AsU8a(this.toU8a(), 256)); - } - - /** - * @description Checks if the value is an empty value - */ - public get isEmpty (): boolean { - return this.size === 0; - } - - /** - * @description Compares the value of the input to see if there is a match - */ - public eq (other?: any): boolean { - return compareMap(this, other); - } - - /** - * @description Returns a hex string representation of the value. isLe returns a LE (number-only) representation - */ - public toHex (): string { - return u8aToHex(this.toU8a()); - } - - /** - * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information - */ - public toHuman (isExtended?: boolean): AnyJson { - const json: any = {}; - - this.forEach((v: V, k: K) => { - json[k.toString()] = v.toHuman(isExtended); - }); - - return json; - } - - /** - * @description Converts the Object to JSON, typically used for RPC transfers - */ - public toJSON (): AnyJson { - const json: any = {}; - - this.forEach((v: V, k: K) => { - json[k.toString()] = v.toJSON(); - }); - - return json; - } - - /** - * @description Returns the base runtime type name for this instance - */ - public toRawType (): string { - return `BTreeMap<${new this.#KeyClass(this.registry).toRawType()},${new this.#ValClass(this.registry).toRawType()}>`; - } - - /** - * @description Returns the string representation of the value - */ - public toString (): string { - return JSON.stringify(this.toJSON()); - } - - /** - * @description Encodes the value as a Uint8Array as per the SCALE specifications - * @param isBare true when the value has none of the type-specific prefixes (internal) - */ - public toU8a (isBare?: boolean): Uint8Array { - const encoded = new Array(); - - if (!isBare) { - encoded.push(Compact.encodeU8a(this.size)); - } - - this.forEach((v: V, k: K) => { - encoded.push(k.toU8a(isBare), v.toU8a(isBare)); - }); - - return u8aConcat(...encoded); - } } diff --git a/packages/types/src/codec/BTreeSet.ts b/packages/types/src/codec/BTreeSet.ts index 90b10df6d4..dfdc5fc819 100644 --- a/packages/types/src/codec/BTreeSet.ts +++ b/packages/types/src/codec/BTreeSet.ts @@ -13,7 +13,7 @@ import Raw from './Raw'; import { compareSet, decodeU8a, typeToConstructor } from './utils'; /** @internal */ -function decodeBTreeSetFromU8a (registry: Registry, ValClass: Constructor, u8a: Uint8Array): Set { +function decodeSetFromU8a (registry: Registry, ValClass: Constructor, u8a: Uint8Array): Set { const output = new Set(); const [offset, length] = Compact.decodeU8a(u8a); const types = []; @@ -32,19 +32,16 @@ function decodeBTreeSetFromU8a (registry: Registry, Val } /** @internal */ -function decodeBTreeSetFromSet (registry: Registry, ValClass: Constructor, value: Set): Set { +function decodeSetFromSet (registry: Registry, ValClass: Constructor, value: Set): Set { const output = new Set(); - value.forEach((v: any) => { - let val; + value.forEach((val: any) => { try { - val = (v instanceof ValClass) ? v : new ValClass(registry, v); + output.add((val instanceof ValClass) ? val : new ValClass(registry, val)); } catch (error) { console.error('Failed to decode BTreeSet key or value:', error.message); throw error; } - - output.add(val); }); return output; @@ -64,7 +61,7 @@ function decodeBTreeSetFromSet (registry: Registry, Val * @param jsonSet * @internal */ -function decodeBTreeSet (registry: Registry, valType: Constructor | keyof InterfaceTypes, value: Uint8Array | string | Set): Set { +function decodeSet (registry: Registry, valType: Constructor | keyof InterfaceTypes, value: Uint8Array | string | Set): Set { if (!value) { return new Set(); } @@ -72,11 +69,11 @@ function decodeBTreeSet (registry: Registry, valType: C const ValClass = typeToConstructor(registry, valType); if (isHex(value)) { - return decodeBTreeSet(registry, ValClass, hexToU8a(value)); + return decodeSet(registry, ValClass, hexToU8a(value)); } else if (isU8a(value)) { - return decodeBTreeSetFromU8a(registry, ValClass, u8aToU8a(value)); + return decodeSetFromU8a(registry, ValClass, u8aToU8a(value)); } else if (value instanceof Set) { - return decodeBTreeSetFromSet(registry, ValClass, value); + return decodeSetFromSet(registry, ValClass, value); } throw new Error('BTreeSet: cannot decode type'); @@ -88,7 +85,7 @@ export default class BTreeSet extends Set implements readonly #ValClass: Constructor; constructor (registry: Registry, valType: Constructor | keyof InterfaceTypes, rawValue: any) { - super(decodeBTreeSet(registry, valType, rawValue)); + super(decodeSet(registry, valType, rawValue)); this.registry = registry; this.#ValClass = typeToConstructor(registry, valType); diff --git a/packages/types/src/codec/Compact.spec.ts b/packages/types/src/codec/Compact.spec.ts index 43a1808222..d12afdc129 100644 --- a/packages/types/src/codec/Compact.spec.ts +++ b/packages/types/src/codec/Compact.spec.ts @@ -4,7 +4,7 @@ import BN from 'bn.js'; -import { ClassOf, TypeRegistry } from '../create'; +import { TypeRegistry } from '../create'; import Moment from './Date'; import U32 from '../primitive/U32'; import Compact from './Compact'; @@ -115,7 +115,7 @@ describe('Compact', (): void => { it('has the correct bitLength for constructor values (BlockNumber)', (): void => { expect( - new (Compact.with(ClassOf(registry, 'BlockNumber')))(registry, 0xfffffff9).bitLength() + new (Compact.with(registry.createClass('BlockNumber')))(registry, 0xfffffff9).bitLength() ).toEqual(32); }); @@ -127,7 +127,7 @@ describe('Compact', (): void => { it('has the correct encodedLength for constructor values (class BlockNumber)', (): void => { expect( - new (Compact.with(ClassOf(registry, 'BlockNumber')))(registry, 0xfffffff9).encodedLength + new (Compact.with(registry.createClass('BlockNumber')))(registry, 0xfffffff9).encodedLength ).toEqual(5); }); diff --git a/packages/types/src/codec/Compact.ts b/packages/types/src/codec/Compact.ts index bbf208c877..9a35919c44 100644 --- a/packages/types/src/codec/Compact.ts +++ b/packages/types/src/codec/Compact.ts @@ -8,7 +8,7 @@ import BN from 'bn.js'; import { compactAddLength, compactFromU8a, compactStripLength, compactToU8a, isBigInt, isBn, isNumber, isString } from '@polkadot/util'; import { DEFAULT_BITLENGTH } from '@polkadot/util/compact/defaults'; -import { typeToConstructor } from './utils'; +import typeToConstructor from './utils/typeToConstructor'; import { UIntBitLength } from './AbstractInt'; import Base from './Base'; diff --git a/packages/types/src/codec/HashMap.spec.ts b/packages/types/src/codec/HashMap.spec.ts new file mode 100644 index 0000000000..2a666c44de --- /dev/null +++ b/packages/types/src/codec/HashMap.spec.ts @@ -0,0 +1,21 @@ +// Copyright 2017-2020 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { TypeRegistry } from '../create'; +import Text from '../primitive/Text'; +import U32 from '../primitive/U32'; +import Struct from './Struct'; + +import HashMap from './HashMap'; + +const registry = new TypeRegistry(); + +describe('HashMap', (): void => { + it('generates sane toRawTypes', (): void => { + expect(new (HashMap.with(Text, U32))(registry).toRawType()).toBe('HashMap'); + expect(new (HashMap.with(Text, Text))(registry).toRawType()).toBe('HashMap'); + expect(new (HashMap.with(Text, Struct.with({ a: U32, b: Text })))(registry).toRawType()) + .toBe('HashMap'); + }); +}); diff --git a/packages/types/src/codec/HashMap.ts b/packages/types/src/codec/HashMap.ts new file mode 100644 index 0000000000..fc8f150a0c --- /dev/null +++ b/packages/types/src/codec/HashMap.ts @@ -0,0 +1,17 @@ +// Copyright 2017-2020 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Constructor, Codec, InterfaceTypes, Registry } from '../types'; + +import CodecMap from './Map'; + +export default class HashMap extends CodecMap { + public static with (keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes): Constructor> { + return class extends HashMap { + constructor (registry: Registry, value?: any) { + super(registry, 'HashMap', keyType, valType, value); + } + }; + } +} diff --git a/packages/types/src/codec/Map.spec.ts b/packages/types/src/codec/Map.spec.ts new file mode 100644 index 0000000000..64fd58d024 --- /dev/null +++ b/packages/types/src/codec/Map.spec.ts @@ -0,0 +1,73 @@ +// Copyright 2017-2020 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { TypeRegistry } from '../create'; +import Text from '../primitive/Text'; +import U32 from '../primitive/U32'; +import { CodecTo } from '../types'; + +import CodecMap from './Map'; + +const registry = new TypeRegistry(); +const mockU32TextMap = new Map(); +mockU32TextMap.set(new Text(registry, 'bazzing'), new U32(registry, 69)); +const mockU32TextMapString = '{"bazzing":69}'; +const mockU32TextMapObject = { bazzing: 69 }; +const mockU32TextMapHexString = '0x041c62617a7a696e6745000000'; +const mockU32TextMapUint8Array = Uint8Array.from([4, 28, 98, 97, 122, 122, 105, 110, 103, 69, 0, 0, 0]); + +const mockU32U32Map = new Map(); +mockU32U32Map.set(new U32(registry, 1), new U32(registry, 2)); +mockU32U32Map.set(new U32(registry, 23), new U32(registry, 24)); +mockU32U32Map.set(new U32(registry, 28), new U32(registry, 30)); +mockU32U32Map.set(new U32(registry, 45), new U32(registry, 80)); +const mockU32U32MapString = '{"1":2,"23":24,"28":30,"45":80}'; +const mockU32U32MapObject = { 1: 2, 23: 24, 28: 30, 45: 80 }; +const mockU32U32MapHexString = '0x10043102000000083233180000000832381e00000008343550000000'; +const mockU32U32MapUint8Array = Uint8Array.from([16, 4, 49, 2, 0, 0, 0, 8, 50, 51, 24, 0, 0, 0, 8, 50, 56, 30, 0, 0, 0, 8, 52, 53, 80, 0, 0, 0]); + +describe('CodecMap', (): void => { + describe('decoding', (): void => { + const testDecode = (type: string, input: any, output: string): void => + it(`can decode from ${type}`, (): void => { + const s = new CodecMap(registry, 'HashMap', Text, U32, input); + + expect(s.toString()).toBe(output); + }); + + testDecode('map', mockU32TextMap, mockU32TextMapString); + testDecode('hex', mockU32TextMapHexString, mockU32TextMapString); + testDecode('Uint8Array', mockU32TextMapUint8Array, mockU32TextMapString); + + testDecode('map', mockU32U32Map, mockU32U32MapString); + testDecode('hex', mockU32U32MapHexString, mockU32U32MapString); + testDecode('Uint8Array', mockU32U32MapUint8Array, mockU32U32MapString); + }); + + describe('encoding', (): void => { + const testEncode = (to: CodecTo, expected: any): void => + it(`can encode ${to}`, (): void => { + const s = new CodecMap(registry, 'BTreeMap', Text, U32, mockU32TextMap); + expect(s[to]()).toEqual(expected); + }); + + testEncode('toHex', mockU32TextMapHexString); + testEncode('toJSON', mockU32TextMapObject); + testEncode('toU8a', mockU32TextMapUint8Array); + testEncode('toString', mockU32TextMapString); + }); + + describe('encoding muple values', (): void => { + const testEncode = (to: CodecTo, expected: any): void => + it(`can encode ${to}`, (): void => { + const s = new CodecMap(registry, 'BTreeMap', Text, U32, mockU32U32Map); + expect(s[to]()).toEqual(expected); + }); + + testEncode('toHex', mockU32U32MapHexString); + testEncode('toJSON', mockU32U32MapObject); + testEncode('toU8a', mockU32U32MapUint8Array); + testEncode('toString', mockU32U32MapString); + }); +}); diff --git a/packages/types/src/codec/Map.ts b/packages/types/src/codec/Map.ts new file mode 100644 index 0000000000..3aff3a6f4e --- /dev/null +++ b/packages/types/src/codec/Map.ts @@ -0,0 +1,210 @@ +// Copyright 2017-2020 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { H256 } from '../interfaces/runtime'; +import { AnyJson, Constructor, Codec, InterfaceTypes, Registry } from '../types'; + +import { isHex, hexToU8a, isObject, isU8a, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util'; +import { blake2AsU8a } from '@polkadot/util-crypto'; + +import Compact from './Compact'; +import Raw from './Raw'; +import compareMap from './utils/compareMap'; +import decodeU8a from './utils/decodeU8a'; +import typeToConstructor from './utils/typeToConstructor'; + +/** @internal */ +function decodeMapFromU8a (registry: Registry, KeyClass: Constructor, ValClass: Constructor, u8a: Uint8Array): Map { + const output = new Map(); + const [offset, length] = Compact.decodeU8a(u8a); + const types = []; + + for (let i = 0; i < length.toNumber(); i++) { + types.push(KeyClass, ValClass); + } + + const values = decodeU8a(registry, u8a.subarray(offset), types); + + for (let i = 0; i < values.length; i += 2) { + output.set(values[i] as K, values[i + 1] as V); + } + + return output; +} + +/** @internal */ +function decodeMapFromMap (registry: Registry, KeyClass: Constructor, ValClass: Constructor, value: Map): Map { + const output = new Map(); + + value.forEach((val: any, key: any) => { + try { + output.set( + key instanceof KeyClass + ? key + : new KeyClass(registry, key), + val instanceof ValClass + ? val + : new ValClass(registry, val) + ); + } catch (error) { + console.error('Failed to decode Map key or value:', error.message); + + throw error; + } + }); + + return output; +} + +/** + * Decode input to pass into constructor. + * + * @param KeyClass - Type of the map key + * @param ValClass - Type of the map value + * @param value - Value to decode, one of: + * - null + * - undefined + * - hex + * - Uint8Array + * - Map, where both key and value types are either + * constructors or decodeable values for their types. + * @param jsonMap + * @internal + */ +function decodeMap (registry: Registry, keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes, value: Uint8Array | string | Map): Map { + const KeyClass = typeToConstructor(registry, keyType); + const ValClass = typeToConstructor(registry, valType); + + if (!value) { + return new Map(); + } else if (isHex(value)) { + return decodeMap(registry, KeyClass, ValClass, hexToU8a(value)); + } else if (isU8a(value)) { + return decodeMapFromU8a(registry, KeyClass, ValClass, u8aToU8a(value)); + } else if (value instanceof Map) { + return decodeMapFromMap(registry, KeyClass, ValClass, value); + } else if (isObject(value)) { + return decodeMapFromMap(registry, KeyClass, ValClass, new Map(Object.entries(value))); + } + + throw new Error('Map: cannot decode type'); +} + +export default class CodecMap extends Map implements Codec { + public readonly registry: Registry; + + readonly #KeyClass: Constructor; + + readonly #ValClass: Constructor; + + readonly #type: string; + + constructor (registry: Registry, type: 'BTreeMap' | 'HashMap', keyType: Constructor | keyof InterfaceTypes, valType: Constructor | keyof InterfaceTypes, rawValue: any) { + super(decodeMap(registry, keyType, valType, rawValue)); + + this.registry = registry; + this.#KeyClass = typeToConstructor(registry, keyType); + this.#ValClass = typeToConstructor(registry, valType); + this.#type = type; + } + + /** + * @description The length of the value when encoded as a Uint8Array + */ + public get encodedLength (): number { + let len = Compact.encodeU8a(this.size).length; + + this.forEach((v: V, k: K) => { + len += v.encodedLength + k.encodedLength; + }); + + return len; + } + + /** + * @description Returns a hash of the value + */ + public get hash (): H256 { + return new Raw(this.registry, blake2AsU8a(this.toU8a(), 256)); + } + + /** + * @description Checks if the value is an empty value + */ + public get isEmpty (): boolean { + return this.size === 0; + } + + /** + * @description Compares the value of the input to see if there is a match + */ + public eq (other?: any): boolean { + return compareMap(this, other); + } + + /** + * @description Returns a hex string representation of the value. isLe returns a LE (number-only) representation + */ + public toHex (): string { + return u8aToHex(this.toU8a()); + } + + /** + * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information + */ + public toHuman (isExtended?: boolean): AnyJson { + const json: any = {}; + + this.forEach((v: V, k: K) => { + json[k.toString()] = v.toHuman(isExtended); + }); + + return json; + } + + /** + * @description Converts the Object to JSON, typically used for RPC transfers + */ + public toJSON (): AnyJson { + const json: any = {}; + + this.forEach((v: V, k: K) => { + json[k.toString()] = v.toJSON(); + }); + + return json; + } + + /** + * @description Returns the base runtime type name for this instance + */ + public toRawType (): string { + return `${this.#type}<${new this.#KeyClass(this.registry).toRawType()},${new this.#ValClass(this.registry).toRawType()}>`; + } + + /** + * @description Returns the string representation of the value + */ + public toString (): string { + return JSON.stringify(this.toJSON()); + } + + /** + * @description Encodes the value as a Uint8Array as per the SCALE specifications + * @param isBare true when the value has none of the type-specific prefixes (internal) + */ + public toU8a (isBare?: boolean): Uint8Array { + const encoded = new Array(); + + if (!isBare) { + encoded.push(Compact.encodeU8a(this.size)); + } + + this.forEach((v: V, k: K) => { + encoded.push(k.toU8a(isBare), v.toU8a(isBare)); + }); + + return u8aConcat(...encoded); + } +} diff --git a/packages/types/src/codec/Struct.spec.ts b/packages/types/src/codec/Struct.spec.ts index 316dee6678..3e0608cfe5 100644 --- a/packages/types/src/codec/Struct.spec.ts +++ b/packages/types/src/codec/Struct.spec.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { ClassOf, TypeRegistry } from '../create'; +import { TypeRegistry } from '../create'; import AccountId from '../generic/AccountId'; import Text from '../primitive/Text'; import U32 from '../primitive/U32'; @@ -193,7 +193,7 @@ describe('Struct', (): void => { // replicate https://github.com/polkadot-js/api/issues/640 expect( new Struct(registry, { - blockNumber: ClassOf(registry, 'Option') + blockNumber: registry.createClass('Option') }, { blockNumber: '0x0000000010abcdef' }).toString() ).toEqual('{"blockNumber":279694831}'); }); @@ -202,10 +202,10 @@ describe('Struct', (): void => { expect( new Struct(registry, { accountId: AccountId, - balanceCompact: ClassOf(registry, 'Compact'), - blockNumber: ClassOf(registry, 'BlockNumber'), - compactNumber: ClassOf(registry, 'Compact'), - optionNumber: ClassOf(registry, 'Option'), + balanceCompact: registry.createClass('Compact'), + blockNumber: registry.createClass('BlockNumber'), + compactNumber: registry.createClass('Compact'), + optionNumber: registry.createClass('Option'), counter: U32, vector: Vec.with(AccountId) }).toRawType() @@ -223,7 +223,7 @@ describe('Struct', (): void => { it('generates sane toRawType (via with)', (): void => { const Type = Struct.with({ accountId: AccountId, - balance: ClassOf(registry, 'Balance') + balance: registry.createClass('Balance') }); expect( diff --git a/packages/types/src/codec/Tuple.spec.ts b/packages/types/src/codec/Tuple.spec.ts index aaf08493ce..fe55a2a82c 100644 --- a/packages/types/src/codec/Tuple.spec.ts +++ b/packages/types/src/codec/Tuple.spec.ts @@ -9,7 +9,7 @@ import { CodecTo } from '../types'; import Metadata from '@polkadot/metadata/Metadata'; import rpcMetadata from '@polkadot/metadata/Metadata/static'; -import { ClassOf, TypeRegistry } from '../create'; +import { TypeRegistry } from '../create'; import Text from '../primitive/Text'; import U32 from '../primitive/U32'; import U128 from '../primitive/U128'; @@ -71,7 +71,7 @@ describe('Tuple', (): void => { new Metadata(registry, rpcMetadata); const test = new (Tuple.with([ - ClassOf(registry, 'BlockNumber'), ClassOf(registry, 'VoteThreshold') + registry.createClass('BlockNumber'), registry.createClass('VoteThreshold') ] ))(registry, '0x6219000001'); @@ -85,8 +85,8 @@ describe('Tuple', (): void => { it('exposes the Types (object creation)', (): void => { const test = new Tuple(registry, { - BlockNumber: ClassOf(registry, 'BlockNumber'), - VoteThreshold: ClassOf(registry, 'VoteThreshold') + BlockNumber: registry.createClass('BlockNumber'), + VoteThreshold: registry.createClass('VoteThreshold') }, []); expect(test.Types).toEqual(['BlockNumber', 'VoteThreshold']); @@ -113,13 +113,13 @@ describe('Tuple', (): void => { describe('toRawType', (): void => { it('generates sane value with array types', (): void => { expect( - new Tuple(registry, [U128, ClassOf(registry, 'BlockNumber')]).toRawType() + new Tuple(registry, [U128, registry.createClass('BlockNumber')]).toRawType() ).toEqual('(u128,u32)'); }); it('generates sane value with object types', (): void => { expect( - new Tuple(registry, { number: U128, blockNumber: ClassOf(registry, 'BlockNumber') }).toRawType() + new Tuple(registry, { number: U128, blockNumber: registry.createClass('BlockNumber') }).toRawType() ).toEqual('(u128,u32)'); }); }); diff --git a/packages/types/src/codec/UInt.ts b/packages/types/src/codec/UInt.ts index 64ea442d1e..45039662a2 100644 --- a/packages/types/src/codec/UInt.ts +++ b/packages/types/src/codec/UInt.ts @@ -6,7 +6,6 @@ import { AnyNumber, Constructor, Registry } from '../types'; import { bnToHex, bnToU8a, formatBalance } from '@polkadot/util'; -import { ClassOf } from '../create/createClass'; import AbstractInt, { DEFAULT_UINT_BITS, UIntBitLength } from './AbstractInt'; /** @@ -53,7 +52,7 @@ export default class UInt extends AbstractInt { */ public toHuman (isExpanded?: boolean): any { // FIXME we need proper expansion here - return this instanceof ClassOf(this.registry, 'Balance') + return this instanceof this.registry.createClass('Balance') ? formatBalance(this, { decimals: this.registry.chainDecimals, withSi: true, withUnit: this.registry.chainToken }) : super.toHuman(isExpanded); } @@ -65,7 +64,7 @@ export default class UInt extends AbstractInt { // NOTE In the case of balances, which have a special meaning on the UI // and can be interpreted differently, return a specific value for it so // underlying it always matches (no matter which length it actually is) - return this instanceof ClassOf(this.registry, 'Balance') + return this instanceof this.registry.createClass('Balance') ? 'Balance' : `u${this.bitLength()}`; } diff --git a/packages/types/src/codec/index.ts b/packages/types/src/codec/index.ts index 28b9bb8ff9..f1e5559645 100644 --- a/packages/types/src/codec/index.ts +++ b/packages/types/src/codec/index.ts @@ -13,6 +13,7 @@ export { default as BTreeSet } from './BTreeSet'; export { default as Compact } from './Compact'; // export { default as Date } from './Date'; export { default as Enum } from './Enum'; +export { default as HashMap } from './HashMap'; export { default as Option } from './Option'; export { default as Result } from './Result'; export { default as Set } from './Set'; diff --git a/packages/types/src/codec/utils/encodeTypes.ts b/packages/types/src/codec/utils/encodeTypes.ts index 753bb42289..edca37afc0 100644 --- a/packages/types/src/codec/utils/encodeTypes.ts +++ b/packages/types/src/codec/utils/encodeTypes.ts @@ -27,6 +27,7 @@ function encodeWithParams (typeDef: Pick, outer = typeDef.displayN case TypeDefInfo.BTreeMap: case TypeDefInfo.BTreeSet: case TypeDefInfo.Compact: + case TypeDefInfo.HashMap: case TypeDefInfo.Linkage: case TypeDefInfo.Option: case TypeDefInfo.Result: @@ -122,6 +123,7 @@ const encoders: Record string> = { [TypeDefInfo.BTreeSet]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'BTreeSet'), [TypeDefInfo.Compact]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Compact'), [TypeDefInfo.Enum]: (typeDef: TypeDef): string => encodeEnum(typeDef), + [TypeDefInfo.HashMap]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'HashMap'), [TypeDefInfo.Int]: (typeDef: TypeDef): string => encodeUInt(typeDef, 'Int'), [TypeDefInfo.Linkage]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Linkage'), // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/types/src/codec/utils/index.ts b/packages/types/src/codec/utils/index.ts index 7768c98b93..dff82cbe09 100644 --- a/packages/types/src/codec/utils/index.ts +++ b/packages/types/src/codec/utils/index.ts @@ -6,5 +6,7 @@ export { default as compareArray } from './compareArray'; export { default as compareMap } from './compareMap'; export { default as compareSet } from './compareSet'; export { default as decodeU8a } from './decodeU8a'; +export { default as mapToTypeMap } from './mapToTypeMap'; +export { default as typeToConstructor } from './typeToConstructor'; + export * from './encodeTypes'; -export * from './mapToTypeMap'; diff --git a/packages/types/src/codec/utils/mapToTypeMap.ts b/packages/types/src/codec/utils/mapToTypeMap.ts index 5241ab5d0e..eca9131160 100644 --- a/packages/types/src/codec/utils/mapToTypeMap.ts +++ b/packages/types/src/codec/utils/mapToTypeMap.ts @@ -2,24 +2,14 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { Codec, Constructor, InterfaceTypes, Registry } from '../../types'; +import { Constructor, InterfaceTypes, Registry } from '../../types'; -import { isString } from '@polkadot/util'; - -import { ClassOf } from '../../create/createClass'; - -export function typeToConstructor (registry: Registry, type: keyof InterfaceTypes | Constructor): Constructor { - return ( - isString(type) - ? ClassOf(registry, type) - : type - ) as Constructor; -} +import typeToConstructor from './typeToConstructor'; /** * @description takes an input map of the form `{ [string]: string | Constructor }` and returns a map of `{ [string]: Constructor }` */ -export function mapToTypeMap (registry: Registry, input: Record): Record { +export default function mapToTypeMap (registry: Registry, input: Record): Record { return Object .entries(input) .reduce((output: Record, [key, type]): Record => { diff --git a/packages/types/src/codec/utils/typeToConstructor.ts b/packages/types/src/codec/utils/typeToConstructor.ts new file mode 100644 index 0000000000..b2bb6b93f2 --- /dev/null +++ b/packages/types/src/codec/utils/typeToConstructor.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2020 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Codec, Constructor, InterfaceTypes, Registry } from '../../types'; + +import { isString } from '@polkadot/util'; + +export default function typeToConstructor (registry: Registry, type: keyof InterfaceTypes | Constructor): Constructor { + return ( + isString(type) + ? registry.createClass(type) + : type + ) as Constructor; +} diff --git a/packages/types/src/create/createClass.ts b/packages/types/src/create/createClass.ts index 098dbed8a3..a3d82cc877 100644 --- a/packages/types/src/create/createClass.ts +++ b/packages/types/src/create/createClass.ts @@ -12,6 +12,7 @@ import BTreeMap from '../codec/BTreeMap'; import BTreeSet from '../codec/BTreeSet'; import Compact from '../codec/Compact'; import Enum from '../codec/Enum'; +import HashMap from '../codec/HashMap'; import Int from '../codec/Int'; import Option from '../codec/Option'; import Result from '../codec/Result'; @@ -83,13 +84,14 @@ function createInt ({ displayName, length }: TypeDef, Clazz: typeof Int | typeof return Clazz.with(length as UIntBitLength, displayName); } -const infoMapping: Record Constructor> = { - [TypeDefInfo.BTreeMap]: (registry: Registry, value: TypeDef): Constructor => { - const [keyType, valueType] = getTypeClassArray(value); +function createHashMap (value: TypeDef, Clazz: typeof BTreeMap | typeof HashMap): Constructor { + const [keyType, valueType] = getTypeClassArray(value); - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return BTreeMap.with(keyType, valueType); - }, + return Clazz.with(keyType, valueType); +} + +const infoMapping: Record Constructor> = { + [TypeDefInfo.BTreeMap]: (registry: Registry, value: TypeDef): Constructor => createHashMap(value, BTreeMap), [TypeDefInfo.BTreeSet]: (registry: Registry, value: TypeDef): Constructor => BTreeSet.with(getSubType(value)), @@ -97,6 +99,8 @@ const infoMapping: Record C [TypeDefInfo.Enum]: (registry: Registry, value: TypeDef): Constructor => Enum.with(getTypeClassMap(value)), + [TypeDefInfo.HashMap]: (registry: Registry, value: TypeDef): Constructor => createHashMap(value, HashMap), + [TypeDefInfo.Int]: (registry: Registry, value: TypeDef): Constructor => createInt(value, Int), // We have circular deps between Linkage & Struct @@ -112,7 +116,7 @@ const infoMapping: Record C }, // eslint-disable-next-line @typescript-eslint/no-unused-vars - [TypeDefInfo.Null]: (registry: Registry, _: TypeDef): Constructor => ClassOf(registry, 'Null'), + [TypeDefInfo.Null]: (registry: Registry, _: TypeDef): Constructor => createClass(registry, 'Null'), [TypeDefInfo.Option]: (registry: Registry, value: TypeDef): Constructor => Option.with(getSubType(value)), @@ -150,7 +154,7 @@ const infoMapping: Record C return ( subType === 'u8' - ? ClassOf(registry, 'Bytes') + ? createClass(registry, 'Bytes') : Vec.with(subType) ); }, diff --git a/packages/types/src/create/createType.spec.ts b/packages/types/src/create/createType.spec.ts index 90a9249d75..4ab8f46775 100644 --- a/packages/types/src/create/createType.spec.ts +++ b/packages/types/src/create/createType.spec.ts @@ -4,7 +4,7 @@ import Int from '../codec/Int'; import CodecSet from '../codec/Set'; -import { createClass, createTypeUnsafe, ClassOf, TypeRegistry } from '.'; +import { createClass, createTypeUnsafe, TypeRegistry } from '.'; describe('createType', (): void => { const registry = new TypeRegistry(); @@ -111,12 +111,12 @@ describe('createType', (): void => { it('instanceof should work (primitive type)', (): void => { const value = registry.createType('Balance', 1234); - expect(value instanceof ClassOf(registry, 'Balance')).toBe(true); + expect(value instanceof registry.createClass('Balance')).toBe(true); }); it('instanceof should work (srml type)', (): void => { const value = registry.createType('Gas', 1234); - const Gas = ClassOf(registry, 'Gas'); + const Gas = registry.createClass('Gas'); expect(value instanceof Gas).toBe(true); }); @@ -144,14 +144,14 @@ describe('createType', (): void => { it('allows for re-registration of a type', (): void => { const balDef = registry.createType('Balance'); - expect(balDef instanceof ClassOf(registry, 'Balance')); + expect(balDef instanceof registry.createClass('Balance')); expect(balDef.bitLength()).toEqual(128); registry.register({ Balance: 'u32' }); const balu32 = registry.createType('Balance'); - expect(balu32 instanceof ClassOf(registry, 'Balance')); + expect(balu32 instanceof registry.createClass('Balance')); expect(balu32.bitLength()).toEqual(32); }); diff --git a/packages/types/src/create/getTypeDef.ts b/packages/types/src/create/getTypeDef.ts index aab974e178..5d59102a61 100644 --- a/packages/types/src/create/getTypeDef.ts +++ b/packages/types/src/create/getTypeDef.ts @@ -135,6 +135,7 @@ const nestedExtraction: [string, string, TypeDefInfo, (value: TypeDef, type: str ['(', ')', TypeDefInfo.Tuple, _decodeTuple], // the inner for these are the same as tuple, multiple values ['BTreeMap<', '>', TypeDefInfo.BTreeMap, _decodeTuple], + ['HashMap<', '>', TypeDefInfo.HashMap, _decodeTuple], ['Int<', '>', TypeDefInfo.Int, _decodeInt], ['Result<', '>', TypeDefInfo.Result, _decodeTuple], ['UInt<', '>', TypeDefInfo.UInt, _decodeUInt] diff --git a/packages/types/src/create/registry.ts b/packages/types/src/create/registry.ts index 3669318996..359d77d62f 100644 --- a/packages/types/src/create/registry.ts +++ b/packages/types/src/create/registry.ts @@ -128,6 +128,13 @@ export class TypeRegistry implements Registry { : formatBalance.getDefaults().unit; } + /** + * @describe Creates an instance of the class + */ + public createClass (type: K): Constructor { + return createClass(this, type) as any; + } + /** * @description Creates an instance of a type as registered */ diff --git a/packages/types/src/create/sanitize.ts b/packages/types/src/create/sanitize.ts index 7cf2c06573..e911dd9aca 100644 --- a/packages/types/src/create/sanitize.ts +++ b/packages/types/src/create/sanitize.ts @@ -5,7 +5,7 @@ type Mapper = (value: string) => string; -const ALLOWED_BOXES = ['BTreeMap', 'BTreeSet', 'Compact', 'Int', 'Linkage', 'Result', 'Option', 'UInt', 'Vec']; +const ALLOWED_BOXES = ['BTreeMap', 'BTreeSet', 'Compact', 'HashMap', 'Int', 'Linkage', 'Result', 'Option', 'UInt', 'Vec']; const BOX_PRECEDING = ['<', '(', '[', '"', ',', ' ']; // start of vec, tuple, fixed array, part of struct def or in tuple const mappings: Mapper[] = [ diff --git a/packages/types/src/create/types.ts b/packages/types/src/create/types.ts index d20a03bfb8..93fad9091d 100644 --- a/packages/types/src/create/types.ts +++ b/packages/types/src/create/types.ts @@ -23,6 +23,7 @@ export enum TypeDefInfo { Tuple, Vec, VecFixed, + HashMap, Int, UInt, // anything not fully supported (keep this as the last entry) diff --git a/packages/types/src/extrinsic/Extrinsic.ts b/packages/types/src/extrinsic/Extrinsic.ts index b9b8e064bf..6edc15d155 100644 --- a/packages/types/src/extrinsic/Extrinsic.ts +++ b/packages/types/src/extrinsic/Extrinsic.ts @@ -9,7 +9,6 @@ import { AnyJson, AnyU8a, ArgsDef, Codec, ExtrinsicPayloadValue, IExtrinsic, IKe import { assert, isHex, isU8a, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util'; -import { ClassOf } from '../create'; import Base from '../codec/Base'; import Compact from '../codec/Compact'; import { ExtrinsicValueV1 } from './v1/Extrinsic'; @@ -74,7 +73,7 @@ export default class Extrinsic extends Base impl public static decodeExtrinsic (registry: Registry, value: Extrinsic | ExtrinsicValue | AnyU8a | Call | undefined, version: number = DEFAULT_VERSION): ExtrinsicVx | ExtrinsicUnknown { if (isU8a(value) || Array.isArray(value) || isHex(value)) { return Extrinsic.decodeU8a(registry, u8aToU8a(value), version); - } else if (value instanceof ClassOf(registry, 'Call')) { + } else if (value instanceof registry.createClass('Call')) { return Extrinsic.newFromValue(registry, { method: value }, version); } diff --git a/packages/types/src/extrinsic/v2/Extrinsic.ts b/packages/types/src/extrinsic/v2/Extrinsic.ts index 60ca2d204e..424dbdbb46 100644 --- a/packages/types/src/extrinsic/v2/Extrinsic.ts +++ b/packages/types/src/extrinsic/v2/Extrinsic.ts @@ -9,7 +9,6 @@ import { ExtrinsicOptions } from '../types'; import { isU8a } from '@polkadot/util'; -import { ClassOf } from '../../create'; import Struct from '../../codec/Struct'; const TRANSACTION_VERSION = 2; @@ -36,7 +35,7 @@ export default class ExtrinsicV2 extends Struct implements IExtrinsicImpl { public static decodeExtrinsic (registry: Registry, value?: Call | Uint8Array | ExtrinsicValueV2, isSigned = false): ExtrinsicValueV2 { if (value instanceof ExtrinsicV2) { return value; - } else if (value instanceof ClassOf(registry, 'Call')) { + } else if (value instanceof registry.createClass('Call')) { return { method: value }; } else if (isU8a(value)) { // here we decode manually since we need to pull through the version information diff --git a/packages/types/src/extrinsic/v3/Extrinsic.ts b/packages/types/src/extrinsic/v3/Extrinsic.ts index 73fb42b5f4..6d4b431d67 100644 --- a/packages/types/src/extrinsic/v3/Extrinsic.ts +++ b/packages/types/src/extrinsic/v3/Extrinsic.ts @@ -9,7 +9,6 @@ import { ExtrinsicOptions } from '../types'; import { isU8a } from '@polkadot/util'; -import { ClassOf } from '../../create'; import Struct from '../../codec/Struct'; const TRANSACTION_VERSION = 3; @@ -36,7 +35,7 @@ export default class ExtrinsicV3 extends Struct implements IExtrinsicImpl { public static decodeExtrinsic (registry: Registry, value?: Call | Uint8Array | ExtrinsicValueV3, isSigned = false): ExtrinsicValueV3 { if (value instanceof ExtrinsicV3) { return value; - } else if (value instanceof ClassOf(registry, 'Call')) { + } else if (value instanceof registry.createClass('Call')) { return { method: value }; } else if (isU8a(value)) { // here we decode manually since we need to pull through the version information diff --git a/packages/types/src/extrinsic/v4/Extrinsic.ts b/packages/types/src/extrinsic/v4/Extrinsic.ts index 94adbf5a03..6161451a35 100644 --- a/packages/types/src/extrinsic/v4/Extrinsic.ts +++ b/packages/types/src/extrinsic/v4/Extrinsic.ts @@ -9,7 +9,6 @@ import { ExtrinsicOptions } from '../types'; import { isU8a } from '@polkadot/util'; -import { ClassOf } from '../../create'; import Struct from '../../codec/Struct'; export const TRANSACTION_VERSION = 4; @@ -36,7 +35,7 @@ export default class ExtrinsicV4 extends Struct implements IExtrinsicImpl { public static decodeExtrinsic (registry: Registry, value?: Call | Uint8Array | ExtrinsicValueV4, isSigned = false): ExtrinsicValueV4 { if (value instanceof ExtrinsicV4) { return value; - } else if (value instanceof ClassOf(registry, 'Call')) { + } else if (value instanceof registry.createClass('Call')) { return { method: value }; } else if (isU8a(value)) { // here we decode manually since we need to pull through the version information diff --git a/packages/types/src/interfaces/rpc/definitions.ts b/packages/types/src/interfaces/rpc/definitions.ts index f5ffde15bc..d443602a26 100644 --- a/packages/types/src/interfaces/rpc/definitions.ts +++ b/packages/types/src/interfaces/rpc/definitions.ts @@ -41,9 +41,8 @@ export default { peerId: 'Text', listenedAddresses: 'Vec', externalAddresses: 'Vec', - // FIXME These are HashMap - connectedPeers: 'BTreeMap', - notConnectedPeers: 'BTreeMap', + connectedPeers: 'HashMap', + notConnectedPeers: 'HashMap', averageDownloadPerSec: 'u64', averageUploadPerSec: 'u64', peerset: 'NetworkStatePeerset' @@ -78,8 +77,7 @@ export default { }, NetworkStatePeerset: { messageQueue: 'u64', - // FIXME This is HashMap - nodes: 'BTreeMap' + nodes: 'HashMap' }, NetworkStatePeersetInfo: { connected: 'bool', diff --git a/packages/types/src/interfaces/rpc/types.ts b/packages/types/src/interfaces/rpc/types.ts index daadadb54a..efe0cf524d 100644 --- a/packages/types/src/interfaces/rpc/types.ts +++ b/packages/types/src/interfaces/rpc/types.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-empty-interface */ import { ITuple } from '@polkadot/types/types'; -import { BTreeMap, Enum, Option, Struct, U8aFixed, Vec } from '@polkadot/types/codec'; +import { Enum, HashMap, Option, Struct, U8aFixed, Vec } from '@polkadot/types/codec'; import { Bytes, StorageKey, Text, bool, u32, u64, u8 } from '@polkadot/types/primitive'; import { Balance, BlockNumber, DispatchClass, Hash, StorageData, Weight } from '@polkadot/types/interfaces/runtime'; @@ -62,8 +62,8 @@ export interface NetworkState extends Struct { readonly peerId: Text; readonly listenedAddresses: Vec; readonly externalAddresses: Vec; - readonly connectedPeers: BTreeMap; - readonly notConnectedPeers: BTreeMap; + readonly connectedPeers: HashMap; + readonly notConnectedPeers: HashMap; readonly averageDownloadPerSec: u64; readonly averageUploadPerSec: u64; readonly peerset: NetworkStatePeerset; @@ -72,7 +72,7 @@ export interface NetworkState extends Struct { /** @name NetworkStatePeerset */ export interface NetworkStatePeerset extends Struct { readonly messageQueue: u64; - readonly nodes: BTreeMap; + readonly nodes: HashMap; } /** @name NetworkStatePeersetInfo */ diff --git a/packages/types/src/types/registry.ts b/packages/types/src/types/registry.ts index 96f70791c0..483e30ff3e 100644 --- a/packages/types/src/types/registry.ts +++ b/packages/types/src/types/registry.ts @@ -92,6 +92,7 @@ export interface Registry { // keep this as a generic Codec, however the actual impl. returns the correct findMetaEvent (eventIndex: Uint8Array): Constructor; + createClass (type: K): Constructor; createType (type: K, ...params: any[]): InterfaceTypes[K]; get (name: string, withUnknown?: boolean): Constructor | undefined; getChainProperties (): ChainProperties | undefined;