From 441148889de2dae981ca4202ef4d03b2d69e3cb5 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 26 Jul 2019 14:00:25 +0200 Subject: [PATCH] CreateType is typed, add createTypeUnsafe just in case (#1199) * Use unsafe internally * Add comments * Remove <> in createType * Fix lint * Fix classOf * Remove createType from Text * Fix lint * Fix Fallback type * Fix fallback in createType --- packages/api-derive/src/balances/all.ts | 6 +- .../src/democracy/referendumVotesFor.ts | 4 +- .../src/elections/voterPositions.ts | 2 +- packages/api-derive/src/staking/info.ts | 2 +- .../src/type/ReferendumInfoExtended.ts | 4 +- .../src/consts/fromMetadata/index.ts | 4 +- .../storage/fromMetadata/createFunction.ts | 11 +-- packages/api/src/SubmittableExtrinsic.ts | 16 ++--- packages/api/src/checkTypes.manual.ts | 10 ++- .../test/e2e/api/promise-queries-dev.spec.ts | 2 +- .../api/test/e2e/api/promise-queries.spec.ts | 2 +- packages/rpc-core/src/index.ts | 15 ++-- packages/types/src/Metadata/util/testUtil.ts | 4 +- packages/types/src/codec/Compact.spec.ts | 8 +-- packages/types/src/codec/Vec.spec.ts | 4 +- packages/types/src/codec/createType.spec.ts | 35 +++------- packages/types/src/codec/createType.ts | 69 ++++++++++++------- .../src/interfaces/runtime/KeyValue.spec.ts | 4 +- packages/types/src/primitive/Event.ts | 2 +- .../Extrinsic/v1/ExtrinsicSignature.ts | 2 +- .../Extrinsic/v2/ExtrinsicSignature.ts | 2 +- packages/types/src/primitive/Method.ts | 2 +- packages/types/src/primitive/Vote.ts | 4 +- packages/types/src/types.ts | 2 +- 24 files changed, 112 insertions(+), 104 deletions(-) diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts index 5e0e3a8746..ec87cfd15d 100644 --- a/packages/api-derive/src/balances/all.ts +++ b/packages/api-derive/src/balances/all.ts @@ -20,9 +20,9 @@ import { drr } from '../util/drr'; type Result = [AccountId | undefined, BlockNumber | undefined, [Balance?, Balance?, BalanceLock[]?, Option?, Index?]]; const EMPTY_ACCOUNT = new AccountId(); -const ZERO = createType('Balance', 0); +const ZERO = createType('Balance', 0); -function calcBalances ([accountId = EMPTY_ACCOUNT, bestNumber = ZERO, [freeBalance = ZERO, reservedBalance = ZERO, locks = [], vesting = new Option(ClassOf('VestingSchedule'), null), accountNonce = ZERO]]: Result): DerivedBalances { +function calcBalances ([accountId = EMPTY_ACCOUNT, bestNumber = ZERO, [freeBalance = ZERO, reservedBalance = ZERO, locks = [], vesting = new Option(ClassOf('VestingSchedule'), null), accountNonce = ZERO]]: Result): DerivedBalances { let lockedBalance = ZERO; if (Array.isArray(locks)) { @@ -35,7 +35,7 @@ function calcBalances ([accountId = EMPTY_ACCOUNT, bestNumber = ZERO, [freeBalan } // offset = balance locked at genesis, perBlock is the unlock amount - const { offset, perBlock } = vesting.unwrapOr(createType('VestingSchedule')); + const { offset, perBlock } = vesting.unwrapOr(createType('VestingSchedule')); const vestedNow: BN = perBlock.mul(bestNumber); const vestedBalance: BN = vestedNow.gt(offset) ? freeBalance diff --git a/packages/api-derive/src/democracy/referendumVotesFor.ts b/packages/api-derive/src/democracy/referendumVotesFor.ts index 99374d2408..5f84243778 100644 --- a/packages/api-derive/src/democracy/referendumVotesFor.ts +++ b/packages/api-derive/src/democracy/referendumVotesFor.ts @@ -2,8 +2,6 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { Balance } from '@polkadot/types/interfaces'; - import BN from 'bn.js'; import { combineLatest, Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; @@ -28,7 +26,7 @@ export function referendumVotesFor (api: ApiInterfaceRx): (referendumId: BN | nu map(([votersFor, votes, balances]): DerivedReferendumVote[] => votersFor.map((accountId, index): DerivedReferendumVote => ({ accountId, - balance: balances[index].votingBalance || createType('Balance', 0), + balance: balances[index].votingBalance || createType('Balance', 0), vote: votes[index] || new Vote(0) } as unknown as DerivedReferendumVote)) ), diff --git a/packages/api-derive/src/elections/voterPositions.ts b/packages/api-derive/src/elections/voterPositions.ts index 530a259b26..f3343b4437 100644 --- a/packages/api-derive/src/elections/voterPositions.ts +++ b/packages/api-derive/src/elections/voterPositions.ts @@ -44,7 +44,7 @@ export function voterPositions (api: ApiInterfaceRx): () => Observable('SetIndex', setIndex) + setIndex: createType('SetIndex', setIndex) }; } }); diff --git a/packages/api-derive/src/staking/info.ts b/packages/api-derive/src/staking/info.ts index 4a5ff9d3f8..125b700588 100644 --- a/packages/api-derive/src/staking/info.ts +++ b/packages/api-derive/src/staking/info.ts @@ -58,7 +58,7 @@ function calculateUnlocking (stakingLedger: StakingLedger | undefined, eraLength const groupedResult = groupByEra(unlockingChunks); const results = Object.entries(groupedResult).map(([eraString, value]): { value: BN; remainingBlocks: BN } => ({ value, - remainingBlocks: remainingBlocks(createType('BlockNumber', eraString), eraLength, bestNumber) + remainingBlocks: remainingBlocks(createType('BlockNumber', eraString), eraLength, bestNumber) })); return results.length ? results : undefined; diff --git a/packages/api-derive/src/type/ReferendumInfoExtended.ts b/packages/api-derive/src/type/ReferendumInfoExtended.ts index 615fddd7ef..ab552d5ebd 100644 --- a/packages/api-derive/src/type/ReferendumInfoExtended.ts +++ b/packages/api-derive/src/type/ReferendumInfoExtended.ts @@ -13,7 +13,7 @@ import { AnyJsonObject } from '@polkadot/types/types'; * @description * A [[ReferendumInfo]] with an additional `index` field */ -export default class ReferendumInfoExtended extends ClassOf('ReferendumInfo') { +export default class ReferendumInfoExtended extends ClassOf('ReferendumInfo') { private _index: ReferendumIndex; public constructor (value: ReferendumInfo | ReferendumInfoExtended, index?: BN | number) { @@ -21,7 +21,7 @@ export default class ReferendumInfoExtended extends ClassOf('Ref this._index = value instanceof ReferendumInfoExtended ? value.index - : createType('ReferendumIndex', index); + : createType('ReferendumIndex', index); } /** diff --git a/packages/api-metadata/src/consts/fromMetadata/index.ts b/packages/api-metadata/src/consts/fromMetadata/index.ts index b732c5cb7c..4b1bf3b003 100644 --- a/packages/api-metadata/src/consts/fromMetadata/index.ts +++ b/packages/api-metadata/src/consts/fromMetadata/index.ts @@ -4,7 +4,7 @@ import { Constants, ConstantCodec, ModuleConstants } from '../types'; -import { createType } from '@polkadot/types'; +import { createTypeUnsafe } from '@polkadot/types/codec/createType'; import Metadata from '@polkadot/types/Metadata'; import { stringCamelCase } from '@polkadot/util'; @@ -23,7 +23,7 @@ export default function fromMetadata (metadata: Metadata): Constants { // For access, we change the index names, i.e. Democracy.EnactmentPeriod -> democracy.enactmentPeriod result[stringCamelCase(name.toString())] = moduleMetadata.constants.reduce((newModule, meta): ModuleConstants => { - const codec = createType(meta.type, meta.value); + const codec = createTypeUnsafe(meta.type.toString(), meta.value); // This is not a perfect idea, however as it stands with number-only constants on the metadata // does not have any effect. However, this could become problematic in cases where items are diff --git a/packages/api-metadata/src/storage/fromMetadata/createFunction.ts b/packages/api-metadata/src/storage/fromMetadata/createFunction.ts index 0ef17ba8dc..fc4fe0c69a 100644 --- a/packages/api-metadata/src/storage/fromMetadata/createFunction.ts +++ b/packages/api-metadata/src/storage/fromMetadata/createFunction.ts @@ -5,7 +5,8 @@ import { Codec } from '@polkadot/types/types'; import BN from 'bn.js'; -import { createType, Bytes, Compact, StorageKey, U8a } from '@polkadot/types'; +import { Bytes, Compact, StorageKey, U8a } from '@polkadot/types'; +import { createTypeUnsafe } from '@polkadot/types/codec/createType'; import { PlainType, StorageEntryMetadata, StorageEntryModifier, StorageEntryType } from '@polkadot/types/Metadata/v6/Storage'; import { StorageEntry } from '@polkadot/types/primitive/StorageKey'; import { assert, isNull, isUndefined, stringLowerFirst, stringToU8a, u8aConcat } from '@polkadot/util'; @@ -69,9 +70,9 @@ export default function createFunction ({ meta, method, prefix, section }: Creat const [key1, key2] = arg as [CreateArgType, CreateArgType]; const type1 = meta.type.asDoubleMap.key1.toString(); const type2 = meta.type.asDoubleMap.key2.toString(); - const param1Encoded = u8aConcat(key, createType(type1, key1).toU8a(true)); + const param1Encoded = u8aConcat(key, createTypeUnsafe(type1, key1).toU8a(true)); const param1Hashed = hasher(param1Encoded); - const param2Hashed = key2Hasher(createType(type2, key2).toU8a(true)); + const param2Hashed = key2Hasher(createTypeUnsafe(type2, key2).toU8a(true)); return Compact.addLengthPrefix(u8aConcat(param1Hashed, param2Hashed)); } @@ -80,7 +81,7 @@ export default function createFunction ({ meta, method, prefix, section }: Creat assert(!isUndefined(arg) && !isNull(arg), `${meta.name} expects one argument`); const type = meta.type.asMap.key.toString(); - const param = createType(type, arg).toU8a(); + const param = createTypeUnsafe(type, arg).toU8a(); key = u8aConcat(key, param); } @@ -111,7 +112,7 @@ export default function createFunction ({ meta, method, prefix, section }: Creat name: meta.name, modifier: new StorageEntryModifier('Required'), type: new StorageEntryType(new PlainType(meta.type.asMap.key), 0), - fallback: new Bytes(createType(meta.type.asMap.key).toHex()), + fallback: new Bytes(createTypeUnsafe(meta.type.asMap.key.toString()).toHex()), documentation: meta.documentation }); diff --git a/packages/api/src/SubmittableExtrinsic.ts b/packages/api/src/SubmittableExtrinsic.ts index 1856579a52..1bf51066c4 100644 --- a/packages/api/src/SubmittableExtrinsic.ts +++ b/packages/api/src/SubmittableExtrinsic.ts @@ -24,7 +24,7 @@ export interface ISubmittableResult { readonly isError: boolean; readonly isFinalized: boolean; - findRecord (section: string, method: string): EventRecord | undefined; + findRecord(section: string, method: string): EventRecord | undefined; } export type SumbitableResultResult = @@ -93,17 +93,17 @@ export class SubmittableResult implements ISubmittableResult { } export interface SubmittableExtrinsic extends IExtrinsic { - send (): SumbitableResultResult; + send(): SumbitableResultResult; - send (statusCb: Callback): SumbitableResultSubscription; + send(statusCb: Callback): SumbitableResultSubscription; - sign (account: IKeyringPair, _options: Partial): this; + sign(account: IKeyringPair, _options: Partial): this; - signAndSend (account: IKeyringPair | string | AccountId | Address, options?: Partial): SumbitableResultResult; + signAndSend(account: IKeyringPair | string | AccountId | Address, options?: Partial): SumbitableResultResult; - signAndSend (account: IKeyringPair | string | AccountId | Address, statusCb: Callback): SumbitableResultSubscription; + signAndSend(account: IKeyringPair | string | AccountId | Address, statusCb: Callback): SumbitableResultSubscription; - signAndSend (account: IKeyringPair | string | AccountId | Address, options: Partial, statusCb?: Callback): SumbitableResultSubscription; + signAndSend(account: IKeyringPair | string | AccountId | Address, options: Partial, statusCb?: Callback): SumbitableResultSubscription; } export default function createSubmittableExtrinsic ( @@ -251,7 +251,7 @@ export default function createSubmittableExtrinsic ( // if we have a nonce already, don't retrieve the latest, use what is there isUndefined(options.nonce) ? api.query.system.accountNonce(address) - : of(createType('Index', options.nonce)), + : of(createType('Index', options.nonce)), // if we have an era provided already or eraLength is <= 0 (immortal) // don't get the latest block, just pass null, handle in mergeMap (isUndefined(options.era) || (isNumber(options.era) && options.era > 0)) diff --git a/packages/api/src/checkTypes.manual.ts b/packages/api/src/checkTypes.manual.ts index baf9398b23..2f99ea1784 100644 --- a/packages/api/src/checkTypes.manual.ts +++ b/packages/api/src/checkTypes.manual.ts @@ -12,6 +12,7 @@ import { ConstantCodec } from '@polkadot/api-metadata/consts/types'; import testKeyring from '@polkadot/keyring/testingPairs'; import { IExtrinsic, IMethod } from '@polkadot/types/types'; import { createType, Header } from '@polkadot/types'; +import { createTypeUnsafe } from '@polkadot/types/codec/createType'; import { SubmittableResult } from './'; @@ -95,8 +96,11 @@ export default async function test (): Promise { // check correct types with `createType` const balance = createType('Balance', 2); - const overridden = createType('Gas', 2); const gas = createType('Gas', 2); - const vec = createType('Vec', 2); - console.log(balance, overridden, gas, vec); + const compact = createType('Compact', 2); + // const random = createType('RandomType', 2); // This one should deliberately show a TS error + + const gasUnsafe = createTypeUnsafe('Gas', 2); + const overriddenUnsafe = createTypeUnsafe
('Gas', 2); + console.log(balance, gas, compact, gasUnsafe, overriddenUnsafe); } diff --git a/packages/api/test/e2e/api/promise-queries-dev.spec.ts b/packages/api/test/e2e/api/promise-queries-dev.spec.ts index a409fec798..c52e3342a4 100644 --- a/packages/api/test/e2e/api/promise-queries-dev.spec.ts +++ b/packages/api/test/e2e/api/promise-queries-dev.spec.ts @@ -159,7 +159,7 @@ describeE2E({ // assume the account Alice is only used in test(the balance of Alice does not change in this test case) const key = api.query.balances.freeBalance.key(keyring.alice_stash.address); const balanceData = await api.rpc.state.getStorage(key) as Option; - const balanceRPC = createType('Balance', balanceData.unwrapOr(undefined)); + const balanceRPC = createType('Balance', balanceData.unwrapOr(undefined)); const balance = await api.query.balances.freeBalance(keyring.alice_stash.address); diff --git a/packages/api/test/e2e/api/promise-queries.spec.ts b/packages/api/test/e2e/api/promise-queries.spec.ts index 9ac5d97eee..3c121d1476 100644 --- a/packages/api/test/e2e/api/promise-queries.spec.ts +++ b/packages/api/test/e2e/api/promise-queries.spec.ts @@ -128,7 +128,7 @@ describeE2E()('Promise e2e queries', (wsUrl): void => { it('gets correct key', async (): Promise => { const key = api.query.session.currentIndex.key(); const sessionIndexData = await api.rpc.state.getStorage(key) as Option; - const sessionIndexRPC = createType('SessionIndex', sessionIndexData.unwrapOr(undefined)); + const sessionIndexRPC = createType('SessionIndex', sessionIndexData.unwrapOr(undefined)); expect(sessionIndexRPC.toNumber()).toBeGreaterThanOrEqual(0); }); diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index a1ba216890..c34d833a80 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -12,7 +12,8 @@ import memoizee from 'memoizee'; import { combineLatest, from, Observable, Observer, of, throwError } from 'rxjs'; import { catchError, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import interfaces from '@polkadot/jsonrpc'; -import { ClassOf, Option, StorageKey, Vec, createClass, createType } from '@polkadot/types'; +import { ClassOf, Option, StorageKey, Vec, createClass } from '@polkadot/types'; +import { createTypeUnsafe } from '@polkadot/types/codec/createType'; import { ExtError, assert, isFunction, isNull, isNumber, logger } from '@polkadot/util'; const l = logger('rpc-core'); @@ -261,12 +262,12 @@ export default class Rpc implements RpcInterface { assert(inputs.length >= reqArgCount && inputs.length <= method.params.length, `Expected ${method.params.length} parameters${optText}, ${inputs.length} found instead`); return inputs.map((input, index): Codec => - createType(method.params[index].type, input) + createTypeUnsafe(method.params[index].type, input) ); } private formatOutput (method: RpcMethod, params: Codec[], result?: any): Codec | (Codec | null | undefined)[] { - const base = createType(method.type as string, result); + const base = createTypeUnsafe(method.type as string, result); if (method.type === 'StorageData') { const key = params[0] as StorageKey; @@ -309,11 +310,11 @@ export default class Rpc implements RpcInterface { if (meta.modifier.isOptional) { return new Option( createClass(type), - isNull ? null : createType(type, base, true) + isNull ? null : createTypeUnsafe(type, base, true) ); } - return createType(type, isNull ? meta.fallback : base, true); + return createTypeUnsafe(type, isNull ? meta.fallback : base, true); } private formatStorageSet (key: StorageKey, base: StorageChangeSet): Codec { @@ -336,10 +337,10 @@ export default class Rpc implements RpcInterface { if (meta.modifier.isOptional) { return new Option( createClass(type), - value.isNone ? null : createType(type, value.unwrap(), true) + value.isNone ? null : createTypeUnsafe(type, value.unwrap(), true) ); } - return createType(type, value.unwrapOr(meta.fallback), true); + return createTypeUnsafe(type, value.unwrapOr(meta.fallback), true); } } diff --git a/packages/types/src/Metadata/util/testUtil.ts b/packages/types/src/Metadata/util/testUtil.ts index 3199f30e3a..059bb2438f 100644 --- a/packages/types/src/Metadata/util/testUtil.ts +++ b/packages/types/src/Metadata/util/testUtil.ts @@ -6,7 +6,7 @@ import '../../injector'; import extrinsicsFromMeta from '@polkadot/api-metadata/extrinsics/fromMetadata'; -import createType from '../../codec/createType'; +import { createTypeUnsafe } from '../../codec/createType'; import Metadata from '../Metadata'; import Method from '../../primitive/Method'; import { MetadataInterface } from '../types'; @@ -62,7 +62,7 @@ export function defaultValues (rpcData: string): void { mod.storage.unwrap().items.forEach(({ fallback, name, type }): void => { it(`creates default types for ${mod.name}.${name}, type ${type}`, (): void => { expect( - (): Codec => createType(type.toString(), fallback) + (): Codec => createTypeUnsafe(type.toString(), fallback) ).not.toThrow(); }); }); diff --git a/packages/types/src/codec/Compact.spec.ts b/packages/types/src/codec/Compact.spec.ts index b950c4f3df..c053f8d459 100644 --- a/packages/types/src/codec/Compact.spec.ts +++ b/packages/types/src/codec/Compact.spec.ts @@ -4,8 +4,6 @@ import '../injector'; -import { Balance, BlockNumber } from '../interfaces/runtime'; - import BN from 'bn.js'; import createType, { ClassOf } from '../codec/createType'; @@ -66,7 +64,7 @@ describe('Compact', (): void => { it('encondes a large balance', (): void => { expect( - Compact.encodeU8a(createType('Balance', '0x5af3107a4000')) + Compact.encodeU8a(createType('Balance', '0x5af3107a4000')) ).toEqual( new Uint8Array([ 3 + ((6 - 4) << 2), @@ -111,13 +109,13 @@ describe('Compact', (): void => { describe('constructor', (): void => { it('has the correct bitLength for constructor values (BlockNumber)', (): void => { expect( - new Compact(ClassOf('BlockNumber'), 0xfffffff9).bitLength() + new Compact(ClassOf('BlockNumber'), 0xfffffff9).bitLength() ).toEqual(64); }); it('has the correct encodedLength for constructor values (BlockNumber)', (): void => { expect( - new Compact(ClassOf('BlockNumber'), 0xfffffff9).encodedLength + new Compact(ClassOf('BlockNumber'), 0xfffffff9).encodedLength ).toEqual(5); }); diff --git a/packages/types/src/codec/Vec.spec.ts b/packages/types/src/codec/Vec.spec.ts index 169758ca65..e6116ee882 100644 --- a/packages/types/src/codec/Vec.spec.ts +++ b/packages/types/src/codec/Vec.spec.ts @@ -10,7 +10,7 @@ import extrinsics from '@polkadot/api-metadata/extrinsics/static'; import AccountId from '../primitive/AccountId'; import Method from '../primitive/Method'; import Text from '../primitive/Text'; -import createType from './createType'; +import { createTypeUnsafe } from './createType'; import Vec from './Vec'; import Tuple from './Tuple'; @@ -53,7 +53,7 @@ describe('Vec', (): void => { }); it.skip('decodes a complex type via construction', (): void => { - const test = createType('Vec<(PropIndex, Proposal, AccountId)>', new Uint8Array([ + const test = createTypeUnsafe('Vec<(PropIndex, Proposal, AccountId)>', new Uint8Array([ 4, 10, 0, 0, 0, 0, 3, 80, 123, 10, 9, 34, 48, 120, 52, 50, 34, 58, 32, 34, 48, 120, 52, 51, 34, 10, 125, 10, 209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79 ])); const first = (test as Vec)[0] as Tuple; diff --git a/packages/types/src/codec/createType.spec.ts b/packages/types/src/codec/createType.spec.ts index f6805f4d29..88f5ba98f4 100644 --- a/packages/types/src/codec/createType.spec.ts +++ b/packages/types/src/codec/createType.spec.ts @@ -6,8 +6,7 @@ import '../injector'; import { Codec, Constructor } from '../types'; -import createType, { TypeDef, TypeDefInfo, createClass, getTypeClass, getTypeDef, typeSplit, ClassOf } from './createType'; -import Text from '../primitive/Text'; +import createType, { TypeDef, TypeDefInfo, createClass, createTypeUnsafe, getTypeClass, getTypeDef, typeSplit, ClassOf } from './createType'; import CodecSet from './Set'; describe('typeSplit', (): void => { @@ -221,26 +220,12 @@ describe('createClass', (): void => { expect(a).toBe(b); }); - - it('should memoize from Text', (): void => { - const a = createClass(new Text('TrieId')); - const b = createClass(new Text('TrieId')); - - expect(a).toBe(b); - }); - - it('should memoize from string/Text combo', (): void => { - const a = createClass(new Text('AuthorityWeight')); - const b = createClass('AuthorityWeight'); - - expect(a).toBe(b); - }); }); describe('createType', (): void => { it('allows creation of a Struct', (): void => { expect( - createType('{"balance":"Balance","index":"u32"}', { + createTypeUnsafe('{"balance":"Balance","index":"u32"}', { balance: 1234, index: '0x10' }).toJSON() @@ -252,31 +237,31 @@ describe('createType', (): void => { it('allows creation of a Enum (simple)', (): void => { expect( - createType('{"_enum": ["A", "B", "C"]}', 1).toJSON() + createTypeUnsafe('{"_enum": ["A", "B", "C"]}', 1).toJSON() ).toEqual({ B: null }); }); it('allows creation of a Enum (parametrised)', (): void => { expect( - createType('{"_enum": {"A": null, "B": "u32", "C": null} }', 1).toJSON() + createTypeUnsafe('{"_enum": {"A": null, "B": "u32", "C": null} }', 1).toJSON() ).toEqual({ B: 0 }); }); it('allows creation of a Set', (): void => { expect( - createType('{"_set": { "A": 1, "B": 2, "C": 4, "D": 8, "E": 16, "G": 32, "H": 64, "I": 128 } }', 1 + 4 + 16 + 64).strings + 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 [u8; 8]', (): void => { expect( - createType('[u8; 8]', [0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67, 0x00]).toHex() + createTypeUnsafe('[u8; 8]', [0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67, 0x00]).toHex() ).toEqual('0x1200230045006700'); }); it('allows creation of a [u16; 4]', (): void => { expect( - createType('[u16; 4]', [0x1200, 0x2300, 0x4500, 0x6700]).toU8a() + createTypeUnsafe('[u16; 4]', [0x1200, 0x2300, 0x4500, 0x6700]).toU8a() ).toEqual(new Uint8Array([0x00, 0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67])); }); @@ -284,7 +269,7 @@ describe('createType', (): void => { const base = createType('StorageData', null); expect( - (): Codec => createType('DoubleMap>', base, true) + (): Codec => createTypeUnsafe('DoubleMap>', base, true) ).toThrow(/ Input doesn't match output, received 0x, created 0x00/); }); @@ -292,7 +277,7 @@ describe('createType', (): void => { const base = createType('StorageData', null); expect( - (): Codec => createType('Vec<(BlockNumber,EventIndex)>', base, true) + (): Codec => createTypeUnsafe('Vec<(BlockNumber,EventIndex)>', base, true) ).toThrow(/Input doesn't match output, received 0x, created 0x00/); }); @@ -313,7 +298,7 @@ describe('createType', (): void => { it('instanceof should work (complex type)', (): void => { const complexType = '{"balance":"Balance","account_id":"AccountId","log":"(u64, u32)","fromSrml":"Gas"}'; - const value = createType(complexType, { + const value = createTypeUnsafe(complexType, { balance: 123, accountId: '', log: [456, 789], diff --git a/packages/types/src/codec/createType.ts b/packages/types/src/codec/createType.ts index 812ba262b6..b3bf01ec72 100644 --- a/packages/types/src/codec/createType.ts +++ b/packages/types/src/codec/createType.ts @@ -8,7 +8,6 @@ import { assert } from '@polkadot/util'; import { InterfaceRegistry } from '../interfaceRegistry'; import { Codec, Constructor } from '../types'; import Null from '../primitive/Null'; -import Text from '../primitive/Text'; import Compact from './Compact'; import Enum from './Enum'; import Linkage from './Linkage'; @@ -22,6 +21,9 @@ import Vec from './Vec'; import VecFixed from './VecFixed'; import getRegistry from './typeRegistry'; +// Type which says: if `K` is in the InterfaceRegistry, then return InterfaceRegistry[K], else fallback to T +type FromReg = K extends keyof InterfaceRegistry ? InterfaceRegistry[K] : T + export enum TypeDefInfo { Compact, DoubleMap, @@ -135,7 +137,7 @@ function _decodeSet (value: TypeDef, details: Record): TypeDef { return value; } -export function getTypeDef (_type: Text | string, name?: string): TypeDef { +export function getTypeDef (_type: string, name?: string): TypeDef { const type = _type.toString().trim(); const value: TypeDef = { info: TypeDefInfo.Plain, @@ -202,27 +204,33 @@ export function getTypeDef (_type: Text | string, name?: string): TypeDef { } // Memoized helper of the `createClass` function below -const memoizedCreateClass = memoizee(( +const memoizedCreateClass = memoizee(( type: K -): Constructor => { +): Constructor> => { // eslint-disable-next-line @typescript-eslint/no-use-before-define - return getTypeClass( + return getTypeClass>( getTypeDef(type) ); -}, { - length: 1, - // Normalize args so that different args that should be cached - // together are cached together. - // E.g.: `createClass('abc') === createClass(new Text('abc'));` - normalizer: JSON.stringify -}); +}, { length: 1 }); -export function createClass ( +export function createClass ( type: K -): Constructor { +): Constructor> { return memoizedCreateClass(type); } +// An unsafe version of the `createType` below. It's unsafe because the `type` +// argument here can be any string, which, if not parseable, will yield a +// runtime error. +export function ClassOfUnsafe (name: K): Constructor> { + return createClass(name); +} + +// alias for createClass +export function ClassOf (name: K): Constructor { + return ClassOfUnsafe(name) as Constructor; +} + // create an array of constructors from the input export function getTypeClassMap (defs: TypeDef[]): { [index: string]: Constructor } { return defs.reduce((result, sub): Record => { @@ -320,12 +328,7 @@ export function getTypeClass (value: TypeDef, Fallback? throw new Error(`Unable to determine type from ${JSON.stringify(value)}`); } -// alias for createClass -export function ClassOf (name: string): Constructor { - return createClass(name); -} - -function initType (Type: Constructor, value?: any, isPedantic?: boolean): T { +function initType (Type: Constructor>, value?: any, isPedantic?: boolean): FromReg { try { const created = new Type(value); @@ -349,21 +352,41 @@ function initType>, value, isPedantic); } throw error; } } -export default function createType ( +// An unsafe version of the `createType` below. It's unsafe because the `type` +// argument here can be any string, which, if not parseable, will yield a +// runtime error. +export function createTypeUnsafe ( type: K, value?: any, isPedantic?: boolean -): K extends keyof InterfaceRegistry ? InterfaceRegistry[K] : T { +): FromReg { try { return initType(createClass(type), value, isPedantic); } catch (error) { throw new Error(`createType(${type}):: ${error.message}`); } } + +/** + * Create an instance of a `type` with a given `value`. + * @param type - A recognizable string representing the type to create an + * instance from + * @param value - The value to instantiate the type with + * @param isPedantic - Double-check that the input's encoding matches the + * output's one. Slower, but ensures that we have a 100% grasp on the actual + * provided value + */ +export default function createType ( + type: K, + value?: any, + isPedantic?: boolean +): InterfaceRegistry[K] { + return createTypeUnsafe(type, value, isPedantic) as InterfaceRegistry[K]; +} diff --git a/packages/types/src/interfaces/runtime/KeyValue.spec.ts b/packages/types/src/interfaces/runtime/KeyValue.spec.ts index 836b026cf6..0eef7d33ba 100644 --- a/packages/types/src/interfaces/runtime/KeyValue.spec.ts +++ b/packages/types/src/interfaces/runtime/KeyValue.spec.ts @@ -4,8 +4,6 @@ import '../../injector'; -import { KeyValue } from './types'; - import createType from '../../codec/createType'; describe('KeyValue', (): void => { @@ -34,7 +32,7 @@ describe('KeyValue', (): void => { }); it('exposes the properties for key/value', (): void => { - const [key, value] = createType('KeyValue', ['0x11223344', '0x998877665544332211']); + const [key, value] = createType('KeyValue', ['0x11223344', '0x998877665544332211']); expect(key.toHex()).toEqual('0x11223344'); expect(value.toHex()).toEqual('0x998877665544332211'); diff --git a/packages/types/src/primitive/Event.ts b/packages/types/src/primitive/Event.ts index 1393d79368..2a170f457b 100644 --- a/packages/types/src/primitive/Event.ts +++ b/packages/types/src/primitive/Event.ts @@ -119,7 +119,7 @@ export default class Event extends Struct { section.events.unwrap().forEach((meta, methodIndex): void => { const methodName = meta.name.toString(); const eventIndex = new Uint8Array([sectionIndex, methodIndex]); - const typeDef = meta.args.map((arg): TypeDef => getTypeDef(arg)); + const typeDef = meta.args.map((arg): TypeDef => getTypeDef(arg.toString())); const Types = typeDef.map((typeDef): Constructor => getTypeClass(typeDef, Unconstructable.with(typeDef))); EventTypes[eventIndex.toString()] = class extends EventData { diff --git a/packages/types/src/primitive/Extrinsic/v1/ExtrinsicSignature.ts b/packages/types/src/primitive/Extrinsic/v1/ExtrinsicSignature.ts index eeaf6fc02d..71d89efd5d 100644 --- a/packages/types/src/primitive/Extrinsic/v1/ExtrinsicSignature.ts +++ b/packages/types/src/primitive/Extrinsic/v1/ExtrinsicSignature.ts @@ -123,7 +123,7 @@ export default class ExtrinsicSignatureV1 extends Struct implements IExtrinsicSi era: era || IMMORTAL_ERA, blockHash }); - const signature = createType('Signature', payload.sign(account)); + const signature = createType('Signature', payload.sign(account)); return this.injectSignature(signer, signature, payload); } diff --git a/packages/types/src/primitive/Extrinsic/v2/ExtrinsicSignature.ts b/packages/types/src/primitive/Extrinsic/v2/ExtrinsicSignature.ts index c7b78a02c8..7d5950b7ad 100644 --- a/packages/types/src/primitive/Extrinsic/v2/ExtrinsicSignature.ts +++ b/packages/types/src/primitive/Extrinsic/v2/ExtrinsicSignature.ts @@ -128,7 +128,7 @@ export default class ExtrinsicSignatureV2 extends Struct implements IExtrinsicSi nonce, tip: tip || 0 }); - const signature = createType('Signature', payload.sign(account)); + const signature = createType('Signature', payload.sign(account)); return this.injectSignature(signer, signature, payload); } diff --git a/packages/types/src/primitive/Method.ts b/packages/types/src/primitive/Method.ts index 4c68d743a2..82aba376a8 100644 --- a/packages/types/src/primitive/Method.ts +++ b/packages/types/src/primitive/Method.ts @@ -157,7 +157,7 @@ export default class Method extends Struct implements IMethod { private static getArgsDef (meta: FunctionMetadataV7): ArgsDef { return Method.filterOrigin(meta).reduce((result, { name, type }): ArgsDef => { const Type = getTypeClass( - getTypeDef(type) + getTypeDef(type.toString()) ); result[name.toString()] = Type; diff --git a/packages/types/src/primitive/Vote.ts b/packages/types/src/primitive/Vote.ts index 517afd5371..d73b5af50a 100644 --- a/packages/types/src/primitive/Vote.ts +++ b/packages/types/src/primitive/Vote.ts @@ -32,7 +32,7 @@ export default class Vote extends U8a { const conviction = decoded[0] & 0b01111111; this._aye = new Bool(msb); - this._conviction = createType('Conviction', conviction); + this._conviction = createType('Conviction', conviction); } private static decodeVote (value?: any): Uint8Array { @@ -48,7 +48,7 @@ export default class Vote extends U8a { : new Uint8Array([0b0]); } else if (isObject(value) && !isUndefined(value.aye) && !isUndefined(value.conviction)) { const aye = new Bool(value.aye); - const convictionIndex = createType('Conviction', value.conviction).index; + const convictionIndex = createType('Conviction', value.conviction).index; const result = convictionIndex | (aye.eq(true) ? 0b1 << 7 : 0b0); diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index acd41879e9..5c3fa8849a 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -105,7 +105,7 @@ export interface IHash extends U8a { } export type CodecTo = 'toHex' | 'toJSON' | 'toString' | 'toU8a'; export interface Constructor { - Fallback?: Constructor; + Fallback?: Constructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any new(...value: any[]): T;