From 76da6a6af55168f3c491be73f6859b7732cb7cf7 Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 12 Aug 2021 15:07:09 +0300 Subject: [PATCH] Adjust createType with inference parser (#3866) * Adjust createType with inference parser * Santize fixed vecs * __Tokenize * Shortcut ending sequences * Update tests & checks * greedy matching * Additional linting * s/extends Codec/extends Codec | undefined/ * Adjust * Shortcut interfacetypes * Cleanups * set & enum * Adjust getTypeClass fallback set * CC * Adjust * Adjust createTypeUnsafe --- packages/api-derive/src/balances/all.ts | 33 ++- packages/types/src/checkTypes.manual.ts | 33 ++- packages/types/src/codec/Linkage.spec.ts | 4 +- packages/types/src/codec/Set.ts | 4 +- packages/types/src/codec/Struct.ts | 2 +- packages/types/src/create/createClass.ts | 26 +-- packages/types/src/create/createType.spec.ts | 46 ++-- packages/types/src/create/createType.ts | 8 +- .../src/extrinsic/signedExtensions/index.ts | 2 +- packages/types/src/index.spec.ts | 6 +- .../staking/CompactAssignments.spec.ts | 10 +- packages/types/src/types/detect.ts | 213 ++++++++++-------- packages/types/src/types/interfaces.ts | 27 ++- 13 files changed, 234 insertions(+), 180 deletions(-) diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts index e88103f2d7..ba44ab1c03 100644 --- a/packages/api-derive/src/balances/all.ts +++ b/packages/api-derive/src/balances/all.ts @@ -73,7 +73,8 @@ function calcBalances (api: ApiInterfaceRx, [data, bestNumber, [vesting, allLock // Calculate the vesting balances, // - offset = balance locked at startingBlock // - perBlock is the unlock amount - const { locked: vestingTotal, perBlock, startingBlock } = vesting || api.registry.createType('VestingInfo'); + const emptyVest = api.registry.createType('VestingInfo'); + const { locked: vestingTotal, perBlock, startingBlock } = vesting || emptyVest; const isStarted = bestNumber.gt(startingBlock); const vestedNow = isStarted ? perBlock.mul(bestNumber.sub(startingBlock)) : new BN(0); const vestedBalance = vestedNow.gt(vestingTotal) ? vestingTotal : api.registry.createType('Balance', vestedNow); @@ -117,33 +118,29 @@ const isNonNullable = (nullable: T): nullable is NonNullable => !!nullable // current (balances, vesting) function queryCurrent (api: ApiInterfaceRx, accountId: AccountId, balanceInstances: string[] = ['balances']): Observable { - const lockCalls = balanceInstances.map( - (m): ApiInterfaceRx['query']['balances']['locks'] | undefined => - (api.derive as DeriveCustomLocks)[m]?.customLocks || api.query[m as 'balances']?.locks + const calls = balanceInstances.map((m): ApiInterfaceRx['query']['balances']['locks'] | undefined => + (api.derive as DeriveCustomLocks)[m]?.customLocks || api.query[m as 'balances']?.locks ); - - const lockEmpty = lockCalls.map((c) => !c); - const lockQueries = lockCalls.filter(isNonNullable).map((c): QueryableStorageMultiArg<'rxjs'> => [c, accountId]); + const lockEmpty = calls.map((c) => !c); + const queries = calls.filter(isNonNullable).map((c): QueryableStorageMultiArg<'rxjs'> => [c, accountId]); return ( api.query.vesting?.vesting - ? api.queryMulti<[Option, ...BalanceLocks]>([ - [api.query.vesting.vesting, accountId], - ...lockQueries - ]) + ? api.queryMulti<[Option, ...BalanceLocks]>([[api.query.vesting.vesting, accountId], ...queries]) // TODO We need to check module instances here as well, not only the balances module - : lockQueries.length - ? api.queryMulti<[...(Vec)[]]>(lockQueries).pipe( - map((locks): [Option, ...BalanceLocks] => - [api.registry.createType('Option'), ...locks] - ) + : queries.length + ? api.queryMulti<[...(Vec)[]]>(queries).pipe( + map((r): [Option, ...BalanceLocks] => [api.registry.createType('Option'), ...r]) ) : of([api.registry.createType('Option')] as [Option]) ).pipe( - map(([optVesting, ...locks]): ResultBalance => { + map(([opt, ...locks]): ResultBalance => { let offset = -1; - return [optVesting.unwrapOr(null), lockEmpty.map((e) => e ? api.registry.createType('Vec') : locks[++offset])]; + return [ + opt.unwrapOr(null), + lockEmpty.map((e) => e ? api.registry.createType('Vec') : locks[++offset]) + ]; }) ); } diff --git a/packages/types/src/checkTypes.manual.ts b/packages/types/src/checkTypes.manual.ts index 93ab23e682..e14ca8cf77 100644 --- a/packages/types/src/checkTypes.manual.ts +++ b/packages/types/src/checkTypes.manual.ts @@ -29,8 +29,10 @@ assert(oo0.divn(123) && [...oo1.values()] && oo2[6].isAscii && oo3.divn(3) && oo const aa0 = registry.createType(' AccountId'); const aa1 = registry.createType('BlockAttestations'); const aa2 = registry.createType('ExtrinsicEra'); +const aa3 = registry.createType('VestingInfo'); +const aa4 = registry.createType('(Vec, CompactAssignmentsTo257, PhragmenScore, EraIndex)'); -assert(aa0.isAscii && aa1.receipt && aa2.isMortalEra, 'All ok'); +assert(aa0.isAscii && aa1.receipt && aa2.isMortalEra && aa3.toHuman() && aa4[3].toNumber(), 'All ok'); // Should be Codec, we don't know this one const bb = registry.createType('Something'); @@ -47,13 +49,24 @@ const vv = registry.createType('Vec< Vec< Vec< Vec> > >'); const vt = registry.createType('Vec<(u8, u16)>'); // nested stuff from all-over const vn = registry.createType('Vec<(u32, (u32, u64), Vec, Vec<(u32, u64)>, [u8;32], [u128;32])>'); +// nested fixed +const nf = registry.createType('[[[u8;32];5];3]'); -assert(ee[0].unwrap().unwrap().divn(123) && vb.unwrap().bitLength() && vv.toHuman() && vn.toHuman() && vt.toHuman(), 'All ok'); +assert(ee[0].unwrap().unwrap().divn(123) && vb.unwrap().bitLength() && vv.toHuman() && vn.toHuman() && vt.toHuman() && nf.toHuman(), 'All ok'); + +// tuple & struct +const vs = registry.createType('(u8, {"a":"u32","b":"(u32,u64)"},(u8,u16),{"foo":"Bar"},u16)'); +// set +const st = registry.createType('{"_set": { "A": 1, "B": 2, "C": 4 } }'); +// enum +const en = registry.createType('{"_enum": { "A": 1, "B": 2, "C": 4 } }'); + +assert(vs.toHuman() && st.strings && en.index, 'All ok'); // Should end up as Raw const gg = registry.createType('[ u8 ;678]'); -assert(gg.bitLength, 'All ok'); +assert(gg.subarray(1), 'All ok'); // Should end up as VecFixed const hh = registry.createType('[u128; 32]'); @@ -61,20 +74,22 @@ const hh = registry.createType('[u128; 32]'); assert(hh[0].bitLength(), 'All ok'); // tuple! ITuple<[u32, Compact, u128, Codec]> -const tt1 = registry.createType('(u32, Compact< u64 >, u128 , Something)'); +const tt1 = registry.createType('(u32, Compact, u128 , Something)'); // unwraps into a u32 -const tt2 = registry.createType('( u32 )'); -// lots and lots of params -const tt4 = registry.createType('(u8,u16,u32,u64,u128,u256,Break,u256,u128,u64,u32,u16,u8)'); +const tt2 = registry.createType('(((u32)))'); +// lots and lots of params (indicates recursion limit) +const tt4 = registry.createType('(u8,u16,u32,u64,u128,u256,u8,u16,u32,u64,u128,u256,u8,u16,u32,u64,u128,u256)'); // empty const tt5 = registry.createType('()'); // nested tuples -const tt6 = registry.createType('(u8, (u16, (u32, u64, u32)), (u64, u64))'); +const tt6 = registry.createType('(u8, (u16, (u32, u64, u128)), (u64, u128))'); // more nested tuples const tt7 = registry.createType('(((u8, u16, u32), (u32, u16, u8)), u128, u256)'); // nested tuples with a wrapper const tt8 = registry.createType('(u8, Vec<(u16, u32)>, Option<(u128, u128)>)'); // same example as above const tt9 = registry.createType('(u32, (u32, u64), Vec, Vec<(u32, u64)>, [u8;32], [u128;32])'); +// tuple with nested fixed +const tta = registry.createType('([u8;32], [u16;5])'); -assert(tt1[2].bitLength() && tt2.bitLength() && tt4[3].bitLength() && tt5.isEmpty && tt6[1].toHuman() && tt7.toHuman() && tt8.toHuman() && tt9.toHuman(), 'All ok'); +assert(tt1[2].bitLength() && tt2.bitLength() && tt4[3].bitLength() && tt5.isEmpty && tt6[1].toHuman() && tt7.toHuman() && tt8.toHuman() && tt9.toHuman() && tta[1][1].bitLength(), 'All ok'); diff --git a/packages/types/src/codec/Linkage.spec.ts b/packages/types/src/codec/Linkage.spec.ts index 3bf66ca70e..710d7bcb18 100644 --- a/packages/types/src/codec/Linkage.spec.ts +++ b/packages/types/src/codec/Linkage.spec.ts @@ -1,6 +1,8 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { Codec, ITuple } from '../types'; + import { TypeRegistry } from '../create/registry'; import { Linkage } from '.'; @@ -32,7 +34,7 @@ describe('Linkage', (): void => { // actual check expect( - registry.createType( + registry.createType]>>( '(ValidatorPrefsWithCommission, Linkage)', '0x0284d7170001da30b68f54f686f586ddb29de12b682dd8bd1404566fb8a8db5dec20aa5b6b36' ).toHuman() diff --git a/packages/types/src/codec/Set.ts b/packages/types/src/codec/Set.ts index 5abace0225..24b798c63b 100644 --- a/packages/types/src/codec/Set.ts +++ b/packages/types/src/codec/Set.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { CodecHash, Hash } from '../interfaces/runtime'; -import type { Codec, Constructor, Registry } from '../types'; +import type { Constructor, ISet, Registry } from '../types'; import { assert, BN, bnToBn, bnToU8a, isBn, isNumber, isString, isU8a, isUndefined, stringCamelCase, stringify, stringUpperFirst, u8aToBn, u8aToHex, u8aToU8a } from '@polkadot/util'; @@ -75,7 +75,7 @@ function decodeSet (setValues: SetValues, value: string[] | Set | Uint8A * a bitwise representation of the values. */ // FIXME This is a prime candidate to extend the JavaScript built-in Set -export class CodecSet extends Set implements Codec { +export class CodecSet extends Set implements ISet { public readonly registry: Registry; public createdAtHash?: Hash; diff --git a/packages/types/src/codec/Struct.ts b/packages/types/src/codec/Struct.ts index eed2d4ff27..448bf9d045 100644 --- a/packages/types/src/codec/Struct.ts +++ b/packages/types/src/codec/Struct.ts @@ -136,7 +136,7 @@ export class Struct< // input values, mapped by key can be anything (construction) V extends { [K in keyof S]: any } = { [K in keyof S]: any }, // type names, mapped by key, name of Class in S - E extends { [K in keyof S]: string } = { [K in keyof S]: string }> extends Map implements IStruct { + E extends { [K in keyof S]: string } = { [K in keyof S]: string }> extends Map implements IStruct { public readonly registry: Registry; public createdAtHash?: Hash; diff --git a/packages/types/src/create/createClass.ts b/packages/types/src/create/createClass.ts index e574fdf7ad..946e2cf28b 100644 --- a/packages/types/src/create/createClass.ts +++ b/packages/types/src/create/createClass.ts @@ -12,15 +12,6 @@ import { Bytes, Null } from '../primitive'; import { getTypeDef } from './getTypeDef'; import { TypeDefInfo } from './types'; -export function createClass (registry: Registry, type: K): DetectConstructor { - return getTypeClass( - registry, - registry.isLookupType(type) - ? registry.lookup.getTypeDef(type) - : getTypeDef(type) - ); -} - function getSubDefArray (value: TypeDef): TypeDef[] { assert(value.sub && Array.isArray(value.sub), () => `Expected subtype as TypeDef[] in ${stringify(value)}`); @@ -180,14 +171,14 @@ const infoMapping: Record C // Returns the type Class for construction export function getTypeClass (registry: Registry, typeDef: TypeDef): Constructor { - let Type = registry.get(typeDef.type) as Constructor | undefined; + let Type = registry.get(typeDef.type); if (Type) { - return Type; + return Type as Constructor; } try { - Type = infoMapping[typeDef.info](registry, typeDef) as Constructor; + Type = infoMapping[typeDef.info](registry, typeDef); assert(Type, 'No class created'); @@ -198,8 +189,17 @@ export function getTypeClass (registry: Registry, typeD Type.__fallbackType = typeDef.fallbackType; } - return Type; + return Type as Constructor; } catch (error) { throw new Error(`Unable to construct class from ${stringify(typeDef)}: ${(error as Error).message}`); } } + +export function createClass (registry: Registry, type: K): DetectConstructor { + return getTypeClass( + registry, + registry.isLookupType(type) + ? registry.lookup.getTypeDef(type) + : getTypeDef(type) + ); +} diff --git a/packages/types/src/create/createType.spec.ts b/packages/types/src/create/createType.spec.ts index 7d1c04e534..2a1fb365c1 100644 --- a/packages/types/src/create/createType.spec.ts +++ b/packages/types/src/create/createType.spec.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { CodecSet, Int } from '../codec'; -import { createClass, createTypeUnsafe, TypeRegistry } from '.'; +import { createClass, TypeRegistry } from '.'; describe('createType', (): void => { const registry = new TypeRegistry(); @@ -27,7 +27,7 @@ describe('createType', (): void => { it('allows creation of a Struct', (): void => { const raw = '{"balance":"Balance","index":"u32"}'; - const struct = createTypeUnsafe(registry, raw, [{ + const struct = registry.createTypeUnsafe(raw, [{ balance: 1234, index: '0x10' }]); @@ -41,49 +41,49 @@ describe('createType', (): void => { it('allows creation of a BTreeMap', (): void => { expect( - createTypeUnsafe(registry, 'BTreeMap', ['0x041c62617a7a696e6745000000']).toString() + registry.createTypeUnsafe('BTreeMap', ['0x041c62617a7a696e6745000000']).toString() ).toEqual('{"bazzing":69}'); }); it('allows creation of a BTreeSet', (): void => { expect( - createTypeUnsafe(registry, 'BTreeSet', ['0x1002000000180000001e00000050000000']).toString() + registry.createTypeUnsafe('BTreeSet', ['0x1002000000180000001e00000050000000']).toString() ).toEqual('[2,24,30,80]'); }); it('allows creation of a Enum (simple)', (): void => { expect( - createTypeUnsafe(registry, '{"_enum": ["A", "B", "C"]}', [1]).toJSON() + registry.createTypeUnsafe('{"_enum": ["A", "B", "C"]}', [1]).toJSON() ).toEqual('B'); }); it('allows creation of a Enum (with types)', (): void => { expect( - createTypeUnsafe(registry, '{"_enum": {"A": null, "B": "u32", "C": null} }', [1]).toJSON() + registry.createTypeUnsafe('{"_enum": {"A": null, "B": "u32", "C": null} }', [1]).toJSON() ).toEqual({ b: 0 }); }); it('allows creation of a Enum (with indexes)', (): void => { expect( - createTypeUnsafe(registry, '{"_enum": {"A": 42, "B": 69, "C": 255} }', [69]).toJSON() + registry.createTypeUnsafe('{"_enum": {"A": 42, "B": 69, "C": 255} }', [69]).toJSON() ).toEqual('B'); }); it('allows creation of a Result', (): void => { expect( - createTypeUnsafe(registry, 'Result', ['0x011064656667']).toJSON() + registry.createTypeUnsafe('Result', ['0x011064656667']).toJSON() ).toEqual({ err: 'defg' }); }); it('allows creation of a Set', (): void => { expect( - createTypeUnsafe(registry, '{"_set": { "A": 1, "B": 2, "C": 4, "D": 8, "E": 16, "G": 32, "H": 64, "I": 128 } }', [1 + 4 + 16 + 64]).strings + registry.createTypeUnsafe('{"_set": { "A": 1, "B": 2, "C": 4, "D": 8, "E": 16, "G": 32, "H": 64, "I": 128 } }', [1 + 4 + 16 + 64]).strings ).toEqual(['A', 'C', 'E', 'H']); }); it('allows creation of a Tuple', (): void => { expect( - createTypeUnsafe(registry, '(Balance,u32)', [[1234, 5678]]).toJSON() + registry.createTypeUnsafe('(Balance,u32)', [[1234, 5678]]).toJSON() ).toEqual([ 1234, // '0x000000000000000000000000000004d2', 5678 @@ -92,13 +92,13 @@ describe('createType', (): void => { it('allows creation for a UInt', (): void => { expect( - createTypeUnsafe(registry, 'UInt<2048>').toRawType() + registry.createType('UInt<2048>').toRawType() ).toEqual('u2048'); }); it('fails creation for a UInt where bitLength is not power of 8', (): void => { expect( - () => createTypeUnsafe(registry, 'UInt<20>').toRawType() + () => registry.createType('UInt<20>').toRawType() ).toThrow('UInt<20>: Only support for UInt, where length <= 8192 and a power of 8'); }); @@ -112,44 +112,44 @@ describe('createType', (): void => { it('allows creation of a [u8; 8]', (): void => { expect( - createTypeUnsafe(registry, '[u8; 8]', [[0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67, 0x00]]).toHex() + registry.createTypeUnsafe('[u8; 8]', [[0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67, 0x00]]).toHex() ).toEqual('0x1200230045006700'); }); it('allows creation of a [u16; 4]', (): void => { expect( - createTypeUnsafe(registry, '[u16; 4]', [[0x1200, 0x2300, 0x4500, 0x6700]]).toU8a() + registry.createTypeUnsafe('[u16; 4]', [[0x1200, 0x2300, 0x4500, 0x6700]]).toU8a() ).toEqual(new Uint8Array([0x00, 0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67])); }); describe('isPedantic', (): void => { it('correctly decodes Bytes', (): void => { expect( - createTypeUnsafe(registry, 'Bytes', ['0x12345678'], { isPedantic: true }).toHex() + registry.createTypeUnsafe('Bytes', ['0x12345678'], { isPedantic: true }).toHex() ).toEqual('0x12345678'); }); it('correctly decodes Bytes (prefixed)', (): void => { expect( - createTypeUnsafe(registry, 'Bytes', [new Uint8Array([4 << 2, 0x12, 0x34, 0x56, 0x78])], { isPedantic: true }).toHex() + registry.createTypeUnsafe('Bytes', [new Uint8Array([4 << 2, 0x12, 0x34, 0x56, 0x78])], { isPedantic: true }).toHex() ).toEqual('0x12345678'); }); it('correctly decodes Text', (): void => { expect( - createTypeUnsafe(registry, 'Text', ['0x70726f7669646572'], { isPedantic: true }).toString() + registry.createTypeUnsafe('Text', ['0x70726f7669646572'], { isPedantic: true }).toString() ).toEqual('provider'); }); it('correctly decodes Type', (): void => { expect( - createTypeUnsafe(registry, 'Type', ['0x5665633c75383e'], { isPedantic: true }).toString() + registry.createTypeUnsafe('Type', ['0x5665633c75383e'], { isPedantic: true }).toString() ).toEqual('Bytes'); // Vec -> Bytes }); it('correctly decodes [u16; 4]', (): void => { expect( - createTypeUnsafe(registry, '[u16; 4]', ['0x0012002300450067'], { isPedantic: true }).toHex() + registry.createTypeUnsafe('[u16; 4]', ['0x0012002300450067'], { isPedantic: true }).toHex() ).toEqual('0x0012002300450067'); }); }); @@ -180,12 +180,12 @@ describe('createType', (): void => { } }); - const value = createTypeUnsafe(registry, 'TestComplex', [{ + const value = registry.createType('TestComplex', { accountId: '0x1234567812345678123456781234567812345678123456781234567812345678', balance: 123, fromSrml: 0, log: [456, 789] - }]); + }); expect(value instanceof createClass(registry, 'TestComplex')).toBe(true); }); @@ -218,7 +218,7 @@ describe('createType', (): void => { }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const cmpDef: any = createTypeUnsafe(registry, 'TestComplex'); + const cmpDef: any = registry.createType('TestComplex'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call expect(cmpDef.balance.bitLength()).toEqual(128); @@ -226,7 +226,7 @@ describe('createType', (): void => { registry.register({ Balance: 'u32' }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const cmpu32: any = createTypeUnsafe(registry, 'TestComplex'); + const cmpu32: any = registry.createType('TestComplex'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call expect(cmpu32.balance.bitLength()).toEqual(32); diff --git a/packages/types/src/create/createType.ts b/packages/types/src/create/createType.ts index 20f56d41e4..976d7ca347 100644 --- a/packages/types/src/create/createType.ts +++ b/packages/types/src/create/createType.ts @@ -38,7 +38,7 @@ function checkPedantic (created: Codec, [value]: unknown[], isPedantic = false): // Initializes a type with a value. This also checks for fallbacks and in the cases // where isPedantic is specified (storage decoding), also check the format/structure -function initType (registry: Registry, Type: Constructor, params: unknown[] = [], { blockHash, isOptional, isPedantic }: CreateOptions = {}): DetectCodec { +function initType (registry: Registry, Type: Constructor, params: unknown[] = [], { blockHash, isOptional, isPedantic }: CreateOptions = {}): T { const created = new (isOptional ? Option.with(Type) : Type)(registry, ...params); checkPedantic(created, params, isPedantic); @@ -59,7 +59,7 @@ export function createTypeUnsafe(registry, type); + Clazz = createClass(registry, type); return initType(registry, Clazz, params, options); } catch (error) { @@ -68,7 +68,7 @@ export function createTypeUnsafe(registry, Clazz.__fallbackType as unknown as K); + Clazz = createClass(registry, Clazz.__fallbackType as unknown as K); return initType(registry, Clazz, params, options); } catch { @@ -86,5 +86,5 @@ export function createTypeUnsafe (registry: Registry, type: K, ...params: unknown[]): DetectCodec { - return createTypeUnsafe(registry, type, params); + return createTypeUnsafe(registry, type, params); } diff --git a/packages/types/src/extrinsic/signedExtensions/index.ts b/packages/types/src/extrinsic/signedExtensions/index.ts index 97695fb674..5b5a54ea51 100644 --- a/packages/types/src/extrinsic/signedExtensions/index.ts +++ b/packages/types/src/extrinsic/signedExtensions/index.ts @@ -23,7 +23,7 @@ export const allExtensions: ExtDef = { // This may not match 100% with the current defaults and are used when not specified // in the metadata (which is for very old chains). The order is important here, as // applied by default -export const fallbackExtensions: Array = [ +export const fallbackExtensions = [ 'CheckVersion', 'CheckGenesis', 'CheckEra', diff --git a/packages/types/src/index.spec.ts b/packages/types/src/index.spec.ts index 14ae2d5eab..f4ef839849 100644 --- a/packages/types/src/index.spec.ts +++ b/packages/types/src/index.spec.ts @@ -6,7 +6,7 @@ import type { Codec } from './types'; import metadataStatic from '@polkadot/types-support/metadata/static-substrate'; import * as definitions from './interfaces/definitions'; -import { createTypeUnsafe, TypeRegistry } from './create'; +import { TypeRegistry } from './create'; import * as exported from './index.types'; import { Metadata } from './metadata'; @@ -34,7 +34,7 @@ function testTypes (type: string, typeNames: string[]): void { typeNames.forEach((name): void => { it(`creates an empty ${name}`, (): void => { const constructFn = (): Codec => - createTypeUnsafe(registry, name); + registry.createType(name); if (UNCONSTRUCTABLE.includes(name.toLowerCase())) { expect(constructFn).toThrow(); @@ -49,7 +49,7 @@ function testTypes (type: string, typeNames: string[]): void { typeNames.forEach((name): void => { it(`creates an empty ${name} (from empty bytes)`, (): void => { const constructFn = (): Codec => - createTypeUnsafe(registry, name, [createTypeUnsafe(registry, 'Bytes')]); + registry.createType(name, registry.createType('Bytes')); if (UNCONSTRUCTABLE.includes(name.toLowerCase())) { expect(constructFn).toThrow(); diff --git a/packages/types/src/interfaces/staking/CompactAssignments.spec.ts b/packages/types/src/interfaces/staking/CompactAssignments.spec.ts index 678bacc1de..7c8462b861 100644 --- a/packages/types/src/interfaces/staking/CompactAssignments.spec.ts +++ b/packages/types/src/interfaces/staking/CompactAssignments.spec.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { createTypeUnsafe, TypeRegistry } from '../../create'; +import { TypeRegistry } from '../../create'; const ACTUAL = '0x' + // Vec @@ -181,15 +181,15 @@ describe('CompactAssignments', (): void => { it('has a valid vote1 ([Type; ] equivalency)', (): void => { expect(test.votes1.toHex()).toEqual( - createTypeUnsafe(registry, 'Vec<(u32, u16)>', [[ + registry.createType('Vec<(u32, u16)>', [ [1, 3] - ]]).toHex() + ]).toHex() ); }); it('hash valid vote2 (actual tuple values)', (): void => { expect(test.votes2.toHex()).toEqual( - createTypeUnsafe(registry, 'Vec<(u32, [(u16, u16); 1], u16)>', [votes2]).toHex() + registry.createType('Vec<(u32, [(u16, u16); 1], u16)>', votes2).toHex() ); }); @@ -199,7 +199,7 @@ describe('CompactAssignments', (): void => { it('decodes an actual solution from the chain', (): void => { expect( - createTypeUnsafe(registry, '(Vec, CompactAssignmentsTo257, PhragmenScore, EraIndex)', [ACTUAL]).toHex() + registry.createType('(Vec, CompactAssignmentsTo257, PhragmenScore, EraIndex)', ACTUAL).toHex() ).toEqual(ACTUAL); }); }); diff --git a/packages/types/src/types/detect.ts b/packages/types/src/types/detect.ts index cd881e287b..c1bbde832d 100644 --- a/packages/types/src/types/detect.ts +++ b/packages/types/src/types/detect.ts @@ -2,110 +2,145 @@ // SPDX-License-Identifier: Apache-2.0 import type { Compact, Option, Raw, Vec, VecFixed } from '../codec'; -import type { Bytes } from '../primitive'; +import type { Bytes, Null } from '../primitive'; import type { Codec, Constructor } from './codec'; -import type { ICompact, IEnum, INumber, IOption, IStruct, ITuple, IU8a, IVec } from './interfaces'; +import type { ICompact, IEnum, INumber, IOption, ISet, IStruct, ITuple, IU8a, IVec } from './interfaces'; import type { InterfaceTypes } from './registry'; -export type DetectCodec = __Expand<__Unspace, T>; +export type DetectCodec = + // This is weird - it looks the wrong way around (i.e. T check should be done first), however + // it does work (as evident with checkTypes) and is problematic the other way around + K extends keyof InterfaceTypes + ? InterfaceTypes[K] + : T extends ICompact | IEnum | INumber | IOption | ISet | IStruct | ITuple | IU8a | IVec + ? T + : __ExtractCodec<__ToCodecs<__Tokenize<__Sanitize>[0]>>; export type DetectConstructor = Constructor>; -// trim leading and trailing spaces -export type __Unspace = - K extends ` ${infer X} ` | ` ${infer X}` | `${infer X} ` - ? __Unspace - : K extends `${infer A} ${infer B}` - ? __Unspace<`${A}${B}`> - : K; +export type __ExtractCodec = + V[0] extends Codec + ? V[0] + : Codec; -export type __Expand = +// trim leading and trailing spaces and unused portions, e.g `...;]` +export type __Sanitize = + K extends ` ${infer X}` | `${infer X} ` | ` ${infer X} ` + ? __Sanitize + : K extends `${infer A} ${infer B}` + ? __Sanitize<`${A}${B}`> + : K extends `${infer A};${string}]${infer B}` + ? __Sanitize<`${A}${B}`> + : K extends `${infer X};${string}]` + ? __Sanitize + : K; + +export type __Value = string | Record | __Value[]; + +export type __ToCodec = K extends keyof InterfaceTypes ? InterfaceTypes[K] - : T extends ICompact | IEnum | INumber | IOption | IStruct | ITuple | IU8a | IVec - ? T - : __Unwrap; + : K extends unknown[] + ? __ToTuple<__ToCodecs> + : K extends Record + ? K['_enum'] extends Record + ? IEnum + : K['_set'] extends Record + ? ISet + : IStruct + : K extends Set + ? ISet + : K extends '[' + ? N[0] extends 'u8' + ? Raw + : VecFixed + : K extends 'Vec<' + ? N[0] extends 'u8' + ? Bytes + : Vec + : K extends 'Option<' + ? Option + : K extends 'Compact<' + ? C[0] extends INumber + ? Compact + : Codec + : Codec; -// ensure whatever we wrap is always Compact-capable -export type __Compact = __CompactImpl<__Expand>; -export type __CompactImpl = - T extends INumber - ? Compact - : Codec; +export type __ToTuple = + O[0] extends Codec + ? O[1] extends Codec + ? ITuple + : O[0] + : Null; -// handle option types -export type __Option = __OptionImpl<__Expand>; -export type __OptionImpl = - T extends Codec - ? Option - : Codec; +export type __ToCodecsInner = + K extends '[' | 'Vec<' | 'Option<' | 'Compact<' + ? C extends [Codec, ...infer X] + ? [__ToCodec, ...X] + : never + : [__ToCodec, ...C]; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export type __Struct<_ extends string> = Codec; +export type __ToCodecs = + T extends [infer K, ...infer O] + ? __ToCodecsInner> + : []; -export type __TupleParams = - X extends `Vec<${infer A}>,${infer B}` - ? [__Vec, ...__TupleParams] - : X extends `Vec<${infer A}>` - ? [__Vec] - : X extends `Option<${infer A}>,${infer B}` - ? [__Option, ...__TupleParams] - : X extends `Option<${infer A}>` - ? [__Option] - : X extends `Compact<${infer A}>,${infer B}` - ? [__Compact, ...__TupleParams] - : X extends `Compact<${infer A}>` - ? [__Compact] - : X extends `[${infer A}],${infer B}` - ? [__VecFixed, ...__TupleParams] - : X extends `[${infer A}]` - ? [__VecFixed] - : X extends `{${infer A}},${infer B}` - ? [__Struct, ...__TupleParams] - : X extends `{${infer A}}` - ? [__Struct] - // FIXME This also matches ((x, y), ... as (x, y - : X extends `(${infer A}),${infer B}` - ? [__Tuple, ...__TupleParams] - : X extends `(${infer A})` - ? [__Tuple] - : X extends `${infer A},${infer B}` - ? [__Expand, ...__TupleParams] - : [__Expand]; +export type __RemoveEmpty = + T extends '' + ? [] + : [T]; -export type __Tuple = __TupleWrap<__TupleParams>; +export type __Combine = + [...V, ...__RemoveEmpty, ...__RemoveEmpty]; -export type __TupleWrap = - X[1] extends Codec - ? ITuple - : X[0]; +export type __CombineInner = + [...__RemoveEmpty, ...V, X]; -// vec support with short-circuit for u8 -export type __Vec = - X extends 'u8' - ? Bytes - : Vec<__Expand>; +// TODO At this point structs are fully empty, no field indicators +export type __TokenizeStruct = + __Tokenize>; -// fixed vec support -export type __VecFixed = - // eslint-disable-next-line @typescript-eslint/no-unused-vars - X extends `${infer T};${infer _}` - ? T extends 'u8' - ? Raw - : VecFixed<__Expand> - : Codec; +export type __TokenizeTuple = + __Tokenize>; -export type __Unwrap = - K extends `Vec<${infer X}>` - ? __Vec - : K extends `Option<${infer X}>` - ? __Option - : K extends `Compact<${infer X}>` - ? __Compact - : K extends `[${infer X}]` - ? __VecFixed - : K extends `(${infer X})` - ? __Tuple - : K extends `{${infer X}}` - ? __Struct - : T; +export type __TokenizeWrapper = + K extends `${infer X}${R}` + ? __Tokenize> + : never; + +export type __TokenizeKnown = + K extends `${infer X}${',' | '>'}${R}` + ? __Tokenize> + : never; + +// NOTE For recursion limits, it is more optimal to use __Sanitize with conjunction with __Tokenize +// below, even while we do more matching (Number of characters iterated through is the most problematic) +export type __Tokenize = + K extends '' | ')' | '>' | '}' + ? [__Combine, ''] + : K extends `${'Vec<' | 'Option<' | 'Compact<'}${infer R}` + ? __TokenizeWrapper + : K extends `${',' | '>'}${infer R}` + ? __Tokenize> + : K extends `[${infer R}` + ? __Tokenize> + : K extends `)${infer R}` | `}${infer R}` + ? [__Combine, R] + : K extends `(${infer R}` + ? __TokenizeTuple<__Tokenize, V, I> + : K extends `{${infer R}` + ? __TokenizeStruct<__Tokenize, V, I, R> + : K extends `${keyof InterfaceTypes}${',' | '>'}${infer R}` + ? __TokenizeKnown + : K extends `${infer C}${infer R}` + ? __Tokenize + : [__Combine, '']; diff --git a/packages/types/src/types/interfaces.ts b/packages/types/src/types/interfaces.ts index 9a74665206..79be8ffb14 100644 --- a/packages/types/src/types/interfaces.ts +++ b/packages/types/src/types/interfaces.ts @@ -15,10 +15,10 @@ export interface ICompact extends Codec { } export interface IEnum extends Codec { - index: number; - isBasic: boolean - type: string; - value: Codec; + readonly index: number; + readonly isBasic: boolean + readonly type: string; + readonly value: Codec; toNumber (): number; } @@ -31,16 +31,21 @@ export interface INumber extends Codec { } export interface IOption extends Codec { - isNone: boolean; - isSome: boolean; - value: Codec; + readonly isNone: boolean; + readonly isSome: boolean; + readonly value: Codec; unwrap (): T; unwrapOr (other: O): T | O; unwrapOrDefault (): T; } -export interface IStruct extends Map, Codec {} +export interface ISet extends Set, Codec { + readonly strings: string[]; + readonly valueEncoded: BN; +} + +export interface IStruct extends Map, Codec {} // A type alias for [Type1, Type2] & Codec, representing a tuple (Type1, Type2) // FIXME Implement this generic on Tuple.ts itself. @@ -55,9 +60,9 @@ export interface IU8a extends Uint8Array, Codec { export interface IVec extends Array, Codec {} export interface IKeyringPair { - address: string; - addressRaw: Uint8Array; - publicKey: Uint8Array; + readonly address: string; + readonly addressRaw: Uint8Array; + readonly publicKey: Uint8Array; sign: (data: Uint8Array, options?: SignOptions) => Uint8Array; }