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
This commit is contained in:
Jaco
2021-08-12 14:07:09 +02:00
committed by GitHub
parent e1f510f5e9
commit 76da6a6af5
13 changed files with 234 additions and 180 deletions
+15 -18
View File
@@ -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 = <T>(nullable: T): nullable is NonNullable<T> => !!nullable
// current (balances, vesting)
function queryCurrent (api: ApiInterfaceRx, accountId: AccountId, balanceInstances: string[] = ['balances']): Observable<ResultBalance> {
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<VestingInfo>, ...BalanceLocks]>([
[api.query.vesting.vesting, accountId],
...lockQueries
])
? api.queryMulti<[Option<VestingInfo>, ...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<BalanceLock>)[]]>(lockQueries).pipe(
map((locks): [Option<VestingInfo>, ...BalanceLocks] =>
[api.registry.createType('Option<VestingInfo>'), ...locks]
)
: queries.length
? api.queryMulti<[...(Vec<BalanceLock>)[]]>(queries).pipe(
map((r): [Option<VestingInfo>, ...BalanceLocks] => [api.registry.createType('Option<VestingInfo>'), ...r])
)
: of([api.registry.createType('Option<VestingInfo>')] as [Option<VestingInfo>])
).pipe(
map(([optVesting, ...locks]): ResultBalance => {
map(([opt, ...locks]): ResultBalance => {
let offset = -1;
return [optVesting.unwrapOr(null), lockEmpty.map((e) => e ? api.registry.createType('Vec<BalanceLock>') : locks[++offset])];
return [
opt.unwrapOr(null),
lockEmpty.map((e) => e ? api.registry.createType('Vec<BalanceLock>') : locks[++offset])
];
})
);
}
+24 -9
View File
@@ -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<ValidatorIndex>, 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<u8>> > >');
const vt = registry.createType('Vec<(u8, u16)>');
// nested stuff from all-over
const vn = registry.createType('Vec<(u32, (u32, u64), Vec<u8>, 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<u128>
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<u64>, u128, Codec]>
const tt1 = registry.createType('(u32, Compact< u64 >, u128 , Something)');
const tt1 = registry.createType('(u32, Compact<u64>, 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<u8>, 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');
+3 -1
View File
@@ -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<ITuple<[Codec, Linkage<Codec>]>>(
'(ValidatorPrefsWithCommission, Linkage<AccountId>)',
'0x0284d7170001da30b68f54f686f586ddb29de12b682dd8bd1404566fb8a8db5dec20aa5b6b36'
).toHuman()
+2 -2
View File
@@ -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<string> | 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<string> implements Codec {
export class CodecSet extends Set<string> implements ISet {
public readonly registry: Registry;
public createdAtHash?: Hash;
+1 -1
View File
@@ -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<keyof S, Codec> implements IStruct {
E extends { [K in keyof S]: string } = { [K in keyof S]: string }> extends Map<keyof S, Codec> implements IStruct<keyof S> {
public readonly registry: Registry;
public createdAtHash?: Hash;
+13 -13
View File
@@ -12,15 +12,6 @@ import { Bytes, Null } from '../primitive';
import { getTypeDef } from './getTypeDef';
import { TypeDefInfo } from './types';
export function createClass<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K): DetectConstructor<T, K> {
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<TypeDefInfo, (registry: Registry, value: TypeDef) => C
// Returns the type Class for construction
export function getTypeClass<T extends Codec = Codec> (registry: Registry, typeDef: TypeDef): Constructor<T> {
let Type = registry.get<T>(typeDef.type) as Constructor<T> | undefined;
let Type = registry.get(typeDef.type);
if (Type) {
return Type;
return Type as Constructor<T>;
}
try {
Type = infoMapping[typeDef.info](registry, typeDef) as Constructor<T>;
Type = infoMapping[typeDef.info](registry, typeDef);
assert(Type, 'No class created');
@@ -198,8 +189,17 @@ export function getTypeClass<T extends Codec = Codec> (registry: Registry, typeD
Type.__fallbackType = typeDef.fallbackType;
}
return Type;
return Type as Constructor<T>;
} catch (error) {
throw new Error(`Unable to construct class from ${stringify(typeDef)}: ${(error as Error).message}`);
}
}
export function createClass<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K): DetectConstructor<T, K> {
return getTypeClass(
registry,
registry.isLookupType(type)
? registry.lookup.getTypeDef(type)
: getTypeDef(type)
);
}
+23 -23
View File
@@ -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<Text,u32>', ['0x041c62617a7a696e6745000000']).toString()
registry.createTypeUnsafe('BTreeMap<Text,u32>', ['0x041c62617a7a696e6745000000']).toString()
).toEqual('{"bazzing":69}');
});
it('allows creation of a BTreeSet', (): void => {
expect(
createTypeUnsafe(registry, 'BTreeSet<u32>', ['0x1002000000180000001e00000050000000']).toString()
registry.createTypeUnsafe('BTreeSet<u32>', ['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<u32,Text>', ['0x011064656667']).toJSON()
registry.createTypeUnsafe('Result<u32,Text>', ['0x011064656667']).toJSON()
).toEqual({ err: 'defg' });
});
it('allows creation of a Set', (): void => {
expect(
createTypeUnsafe<CodecSet>(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<CodecSet>('{"_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<bitLength>', (): void => {
expect(
createTypeUnsafe(registry, 'UInt<2048>').toRawType()
registry.createType('UInt<2048>').toRawType()
).toEqual('u2048');
});
it('fails creation for a UInt<bitLength> 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<bitLength>, 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<u8> -> 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);
+4 -4
View File
@@ -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<T extends Codec = Codec, K extends string = string> (registry: Registry, Type: Constructor, params: unknown[] = [], { blockHash, isOptional, isPedantic }: CreateOptions = {}): DetectCodec<T, K> {
function initType<T extends Codec> (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<T extends Codec = Codec, K extends string = str
let firstError: Error | null = null;
try {
Clazz = createClass<T, K>(registry, type);
Clazz = createClass(registry, type);
return initType(registry, Clazz, params, options);
} catch (error) {
@@ -68,7 +68,7 @@ export function createTypeUnsafe<T extends Codec = Codec, K extends string = str
if (Clazz && Clazz.__fallbackType) {
try {
Clazz = createClass<T, K>(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<T extends Codec = Codec, K extends string = str
* @param params - The value to instantiate the type with
*/
export function createType<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K, ...params: unknown[]): DetectCodec<T, K> {
return createTypeUnsafe<T, K>(registry, type, params);
return createTypeUnsafe(registry, type, params);
}
@@ -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<keyof typeof allExtensions> = [
export const fallbackExtensions = [
'CheckVersion',
'CheckGenesis',
'CheckEra',
+3 -3
View File
@@ -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();
@@ -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<ValidatorIndex>
@@ -181,15 +181,15 @@ describe('CompactAssignments', (): void => {
it('has a valid vote1 ([Type; <number>] 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<ValidatorIndex>, CompactAssignmentsTo257, PhragmenScore, EraIndex)', [ACTUAL]).toHex()
registry.createType('(Vec<ValidatorIndex>, CompactAssignmentsTo257, PhragmenScore, EraIndex)', ACTUAL).toHex()
).toEqual(ACTUAL);
});
});
+124 -89
View File
@@ -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<T extends Codec, K extends string> = __Expand<__Unspace<K>, T>;
export type DetectCodec<T extends Codec, K extends string> =
// 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<K>>[0]>>;
export type DetectConstructor<T extends Codec, K extends string> = Constructor<DetectCodec<T, K>>;
// trim leading and trailing spaces
export type __Unspace<K extends string> =
K extends ` ${infer X} ` | ` ${infer X}` | `${infer X} `
? __Unspace<X>
: K extends `${infer A} ${infer B}`
? __Unspace<`${A}${B}`>
: K;
export type __ExtractCodec<V extends Codec[]> =
V[0] extends Codec
? V[0]
: Codec;
export type __Expand<K extends string, T extends Codec = Codec> =
// trim leading and trailing spaces and unused portions, e.g `...;<length>]`
export type __Sanitize<K extends string> =
K extends ` ${infer X}` | `${infer X} ` | ` ${infer X} `
? __Sanitize<X>
: 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<X>
: K;
export type __Value = string | Record<string, unknown> | __Value[];
export type __ToCodec<K extends unknown, C extends Codec[], N extends unknown[]> =
K extends keyof InterfaceTypes
? InterfaceTypes[K]
: T extends ICompact | IEnum | INumber | IOption | IStruct | ITuple | IU8a | IVec
? T
: __Unwrap<K, T>;
: K extends unknown[]
? __ToTuple<__ToCodecs<K>>
: K extends Record<string, unknown>
? K['_enum'] extends Record<string, unknown>
? IEnum
: K['_set'] extends Record<string, unknown>
? ISet
: IStruct
: K extends Set<unknown>
? ISet
: K extends '['
? N[0] extends 'u8'
? Raw
: VecFixed<C[0]>
: K extends 'Vec<'
? N[0] extends 'u8'
? Bytes
: Vec<C[0]>
: K extends 'Option<'
? Option<C[0]>
: K extends 'Compact<'
? C[0] extends INumber
? Compact<C[0]>
: Codec
: Codec;
// ensure whatever we wrap is always Compact-capable
export type __Compact<X extends string> = __CompactImpl<__Expand<X>>;
export type __CompactImpl<T extends Codec> =
T extends INumber
? Compact<T>
: Codec;
export type __ToTuple<O extends Codec[]> =
O[0] extends Codec
? O[1] extends Codec
? ITuple<O>
: O[0]
: Null;
// handle option types
export type __Option<X extends string> = __OptionImpl<__Expand<X>>;
export type __OptionImpl<T extends Codec> =
T extends Codec
? Option<T>
: Codec;
export type __ToCodecsInner<K extends unknown, N extends unknown[], C extends Codec[]> =
K extends '[' | 'Vec<' | 'Option<' | 'Compact<'
? C extends [Codec, ...infer X]
? [__ToCodec<K, C, N>, ...X]
: never
: [__ToCodec<K, C, N>, ...C];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type __Struct<_ extends string> = Codec;
export type __ToCodecs<T extends unknown[]> =
T extends [infer K, ...infer O]
? __ToCodecsInner<K, O, __ToCodecs<O>>
: [];
export type __TupleParams<X extends string> =
X extends `Vec<${infer A}>,${infer B}`
? [__Vec<A>, ...__TupleParams<B>]
: X extends `Vec<${infer A}>`
? [__Vec<A>]
: X extends `Option<${infer A}>,${infer B}`
? [__Option<A>, ...__TupleParams<B>]
: X extends `Option<${infer A}>`
? [__Option<A>]
: X extends `Compact<${infer A}>,${infer B}`
? [__Compact<A>, ...__TupleParams<B>]
: X extends `Compact<${infer A}>`
? [__Compact<A>]
: X extends `[${infer A}],${infer B}`
? [__VecFixed<A>, ...__TupleParams<B>]
: X extends `[${infer A}]`
? [__VecFixed<A>]
: X extends `{${infer A}},${infer B}`
? [__Struct<A>, ...__TupleParams<B>]
: X extends `{${infer A}}`
? [__Struct<A>]
// FIXME This also matches ((x, y), ... as (x, y
: X extends `(${infer A}),${infer B}`
? [__Tuple<A>, ...__TupleParams<B>]
: X extends `(${infer A})`
? [__Tuple<A>]
: X extends `${infer A},${infer B}`
? [__Expand<A>, ...__TupleParams<B>]
: [__Expand<X>];
export type __RemoveEmpty<T extends string> =
T extends ''
? []
: [T];
export type __Tuple<X extends string> = __TupleWrap<__TupleParams<X>>;
export type __Combine<V extends __Value[], I extends string = '', T extends string = ''> =
[...V, ...__RemoveEmpty<I>, ...__RemoveEmpty<T>];
export type __TupleWrap<X extends Codec[]> =
X[1] extends Codec
? ITuple<X>
: X[0];
export type __CombineInner<V extends __Value[], I extends string, X extends __Value> =
[...__RemoveEmpty<I>, ...V, X];
// vec support with short-circuit for u8
export type __Vec<X extends string> =
X extends 'u8'
? Bytes
: Vec<__Expand<X>>;
// TODO At this point structs are fully empty, no field indicators
export type __TokenizeStruct<T extends [__Value[], string], V extends __Value[], I extends string, R extends string> =
__Tokenize<T[1], __CombineInner<V, I,
R extends `"_set"${string}`
// eslint-disable-next-line @typescript-eslint/ban-types
? { _set: {} }
: R extends `"_enum"${string}`
// eslint-disable-next-line @typescript-eslint/ban-types
? { _enum: {} }
// eslint-disable-next-line @typescript-eslint/ban-types
: { data: R }
>>;
// fixed vec support
export type __VecFixed<X extends string> =
// eslint-disable-next-line @typescript-eslint/no-unused-vars
X extends `${infer T};${infer _}`
? T extends 'u8'
? Raw
: VecFixed<__Expand<T>>
: Codec;
export type __TokenizeTuple<T extends [__Value[], string], V extends __Value[], I extends string> =
__Tokenize<T[1], __CombineInner<V, I, T[0]>>;
export type __Unwrap<K extends string, T extends Codec> =
K extends `Vec<${infer X}>`
? __Vec<X>
: K extends `Option<${infer X}>`
? __Option<X>
: K extends `Compact<${infer X}>`
? __Compact<X>
: K extends `[${infer X}]`
? __VecFixed<X>
: K extends `(${infer X})`
? __Tuple<X>
: K extends `{${infer X}}`
? __Struct<X>
: T;
export type __TokenizeWrapper<K extends string, V extends __Value[], I extends string, R extends string> =
K extends `${infer X}${R}`
? __Tokenize<R, __Combine<V, `${I}${X}`>>
: never;
export type __TokenizeKnown<K extends string, V extends __Value[], I extends string, R extends string> =
K extends `${infer X}${',' | '>'}${R}`
? __Tokenize<R, __Combine<V, `${I}${X}`>>
: 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 string, V extends __Value[] = [], I extends string = ''> =
K extends '' | ')' | '>' | '}'
? [__Combine<V, I>, '']
: K extends `${'Vec<' | 'Option<' | 'Compact<'}${infer R}`
? __TokenizeWrapper<K, V, I, R>
: K extends `${',' | '>'}${infer R}`
? __Tokenize<R, __Combine<V, I>>
: K extends `[${infer R}`
? __Tokenize<R, __Combine<V, I, '['>>
: K extends `)${infer R}` | `}${infer R}`
? [__Combine<V, I>, R]
: K extends `(${infer R}`
? __TokenizeTuple<__Tokenize<R>, V, I>
: K extends `{${infer R}`
? __TokenizeStruct<__Tokenize<R>, V, I, R>
: K extends `${keyof InterfaceTypes}${',' | '>'}${infer R}`
? __TokenizeKnown<K, V, I, R>
: K extends `${infer C}${infer R}`
? __Tokenize<R, V, `${I}${C}`>
: [__Combine<V, I>, ''];
+16 -11
View File
@@ -15,10 +15,10 @@ export interface ICompact<T extends INumber = INumber> 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<T extends Codec = Codec> extends Codec {
isNone: boolean;
isSome: boolean;
value: Codec;
readonly isNone: boolean;
readonly isSome: boolean;
readonly value: Codec;
unwrap (): T;
unwrapOr <O>(other: O): T | O;
unwrapOrDefault (): T;
}
export interface IStruct extends Map<unknown, Codec>, Codec {}
export interface ISet<K extends string = string> extends Set<K>, Codec {
readonly strings: string[];
readonly valueEncoded: BN;
}
export interface IStruct<K extends string | number | symbol = string, V extends Codec = Codec> extends Map<K, V>, Codec {}
// A type alias for [Type1, Type2] & Codec, representing a tuple (Type1, Type2)
// FIXME Implement this generic <Sub> on Tuple.ts itself.
@@ -55,9 +60,9 @@ export interface IU8a extends Uint8Array, Codec {
export interface IVec<S extends Codec = Codec> extends Array<S>, 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;
}