From 586c90208ca2da69197bdc94f4688cdeb9682c22 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 2 Mar 2020 15:52:14 +0100 Subject: [PATCH] Add typeAlias to API construction options (#1989) --- CHANGELOG.md | 1 + packages/api/src/base/Init.ts | 21 +++++--- packages/api/src/types/index.ts | 19 ++----- .../metadata/src/Metadata/v11/toLatest.ts | 2 +- packages/types/src/create/registry.ts | 12 ++++- packages/types/src/known/index.ts | 19 ++++--- packages/types/src/known/overrides.spec.ts | 49 +++++++++++++++++++ packages/types/src/types/registry.ts | 23 +++++++++ 8 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 packages/types/src/known/overrides.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 643998483d..88b1a2c51a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix storage parsing not resulting in rejected Promises (Fix from https://github.com/aniiantt applied) - Type generation will now handle the same sub-module name across packages, i.e. `@polkadot/types/interfaces/runtime` & `@mine/interfaces/runtime`) - Add `.range([from, to]: [Hash, Hash?], ...args: any[]): [Hash, Codec][]` on all storage entries +- Allow pre-module type aliasing with `typesAlias: { : { : } }`on API construction options - Allow `BTreeMap` to be initialized with a `Record` object (in addition to `Map`) - Allow for `HashMap` definitions - `Bool` will now correctly return `isEmpty` on false/default values diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index dc70556795..58c2b7c39c 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -31,11 +31,21 @@ export default abstract class Init extends Decorate extends Decorate extends Decorate): Promise { - const { typesChain, typesSpec } = this._options; const [runtimeVersion, chain, chainProps] = await Promise.all([ this._rpcCore.state.getRuntimeVersion().toPromise(), this._rpcCore.system.chain().toPromise(), @@ -152,8 +161,8 @@ export default abstract class Init extends Decorate extends Decorate { // inject types based on metadata, if applicable - this.registerTypes(getMetadataTypes(metadata.version)); + this.registerTypes(getMetadataTypes(this.registry, metadata.version)); const metaExtrinsic = metadata.asLatest.extrinsic; diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index d268cad446..cd0d1533b1 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -11,7 +11,7 @@ import { Constants } from '@polkadot/metadata/Decorated/types'; import { RpcInterface, UserRpc } from '@polkadot/rpc-core/types'; import { Metadata } from '@polkadot/types'; import { Hash, RuntimeVersion } from '@polkadot/types/interfaces'; -import { RegistryTypes, Signer, SignerResult, SignatureOptions, Registry } from '@polkadot/types/types'; +import { Signer, SignerResult, SignatureOptions, Registry, RegisteredTypes } from '@polkadot/types/types'; import ApiBase from '../base'; import { DeriveAllSections } from '../util/decorate'; @@ -26,13 +26,13 @@ export * from './rpc'; export * from './storage'; export * from './submittable'; -export interface ApiOptions { +export interface ApiOptions extends RegisteredTypes { /** * @description Add custom derives to be injected */ derives?: DeriveCustom; /** - * @description prebundles is a map of 'genesis hash and runtime spec version' as key to metadata's hex string + * @description pre-bundles is a map of 'genesis hash and runtime spec version' as key to a metadata hex string * if genesis hash and runtime spec version matches, then use metadata, else fetch it from chain */ metadata?: Record; @@ -57,19 +57,6 @@ export interface ApiOptions { * @description The source object to use for runtime information (only used when cloning) */ source?: ApiBase; - /** - * @description Additional types used by runtime modules. This is nessusary if the runtime modules - * uses types not available in the base Substrate runtime. - */ - types?: RegistryTypes; - /** - * @description Additional types that are injected based on the chain we are connecting to. There are keyed by the chain, i.e. `{ 'Kusama CC1': { ... } }` - */ - typesChain?: Record; - /** - * @description Additional types that are injected based on the type of node we are connecting to, as set via specName in the runtime version. There are keyed by the node, i.e. `{ 'edgeware': { ... } }` - */ - typesSpec?: Record; } // A smaller interface of ApiRx, used in derive and in SubmittableExtrinsic diff --git a/packages/metadata/src/Metadata/v11/toLatest.ts b/packages/metadata/src/Metadata/v11/toLatest.ts index d204490fc4..7b7218def0 100644 --- a/packages/metadata/src/Metadata/v11/toLatest.ts +++ b/packages/metadata/src/Metadata/v11/toLatest.ts @@ -71,7 +71,7 @@ export default function toLatest (registry: Registry, { modules, extrinsic }: Me modules: modules.map((mod): ModuleMetadataLatest => { const calls = mod.calls.unwrapOr(null); const storage = mod.storage.unwrapOr(null); - const sectionTypes = getModuleTypes(stringCamelCase(mod.name.toString())); + const sectionTypes = getModuleTypes(registry, stringCamelCase(mod.name.toString())); return registry.createType('ModuleMetadataLatest', { ...mod, diff --git a/packages/types/src/create/registry.ts b/packages/types/src/create/registry.ts index 359d77d62f..2cc2f420ad 100644 --- a/packages/types/src/create/registry.ts +++ b/packages/types/src/create/registry.ts @@ -4,7 +4,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { ChainProperties, DispatchErrorModule } from '../interfaces/types'; -import { CallFunction, Codec, Constructor, InterfaceTypes, RegistryError, RegistryTypes, Registry, RegistryMetadata, TypeDef } from '../types'; +import { CallFunction, Codec, Constructor, InterfaceTypes, RegistryError, RegistryTypes, Registry, RegistryMetadata, RegisteredTypes, TypeDef } from '../types'; import extrinsicsFromMeta from '@polkadot/metadata/Decorated/extrinsics/fromMetadata'; import { assert, formatBalance, isFunction, isString, isU8a, isUndefined, stringCamelCase, u8aToHex } from '@polkadot/util'; @@ -94,6 +94,8 @@ export class TypeRegistry implements Registry { #metadataExtensions: string[] = defaultExtensions; + #knownTypes: RegisteredTypes = {}; + constructor () { // we only want to import these on creation, i.e. we want to avoid types // weird side-effects from circular references. (Since registry is injected @@ -128,6 +130,10 @@ export class TypeRegistry implements Registry { : formatBalance.getDefaults().unit; } + public get knownTypes (): RegisteredTypes { + return this.#knownTypes; + } + /** * @describe Creates an instance of the class */ @@ -294,6 +300,10 @@ export class TypeRegistry implements Registry { } } + setKnownTypes (knownTypes: RegisteredTypes): void { + this.#knownTypes = knownTypes; + } + // sets the metadata public setMetadata (metadata: RegistryMetadata): void { decorateExtrinsics(this, metadata, this.#metadataCalls); diff --git a/packages/types/src/known/index.ts b/packages/types/src/known/index.ts index f2ff59c32b..3eee9d6fba 100644 --- a/packages/types/src/known/index.ts +++ b/packages/types/src/known/index.ts @@ -3,7 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { RuntimeVersion } from '../interfaces'; -import { RegistryTypes } from '../types'; +import { Registry, RegistryTypes } from '../types'; import { OverrideModuleType, OverrideVersionedType } from './types'; import { Text } from '@polkadot/types'; @@ -27,13 +27,13 @@ function filterVersions (versions: OverrideVersionedType[] = [], version: number // based on the metadata version, return the registry types /** @internal */ -export function getMetadataTypes (version: number): RegistryTypes { +export function getMetadataTypes (_registry: Registry, version: number): RegistryTypes { return filterVersions(TYPES_META, version); } // based on the chain and runtimeVersion, get the applicable types (ready for registration) /** @internal */ -export function getSpecTypes (chainName: Text | string, { specName, specVersion }: RuntimeVersion): RegistryTypes { +export function getSpecTypes (_registry: Registry, chainName: Text | string, { specName, specVersion }: RuntimeVersion): RegistryTypes { const _chainName = chainName.toString(); const _specName = specName.toString(); const _specVersion = specVersion.toNumber(); @@ -46,18 +46,21 @@ export function getSpecTypes (chainName: Text | string, { specName, specVersion // based on the spec and chain, get the specific user-defind types /** @internal */ -export function getUserTypes (chainName: Text | string, { specName }: RuntimeVersion, typesChain: Record = {}, typesSpec: Record = {}): RegistryTypes { +export function getUserTypes ({ knownTypes }: Registry, chainName: Text | string, { specName }: RuntimeVersion): RegistryTypes { const _chainName = chainName.toString(); const _specName = specName.toString(); return { - ...(typesSpec[_specName] || {}), - ...(typesChain[_chainName] || {}) + ...(knownTypes.typesSpec?.[_specName] || {}), + ...(knownTypes.typesChain?.[_chainName] || {}) }; } // get types for specific modules (metadata override) /** @internal */ -export function getModuleTypes (section: string): OverrideModuleType { - return TYPES_MODULES[section] || {}; +export function getModuleTypes ({ knownTypes }: Registry, section: string): OverrideModuleType { + return { + ...(TYPES_MODULES[section] || {}), + ...(knownTypes.typesAlias?.[section] || {}) + }; } diff --git a/packages/types/src/known/overrides.spec.ts b/packages/types/src/known/overrides.spec.ts new file mode 100644 index 0000000000..2f5c220dc7 --- /dev/null +++ b/packages/types/src/known/overrides.spec.ts @@ -0,0 +1,49 @@ +// Copyright 2017-2020 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { TypeRegistry } from '../create/registry'; +import { getModuleTypes } from './'; + +const registry = new TypeRegistry(); + +registry.setKnownTypes({ + typesAlias: { + identity: { + Id: 'IdentityId' + }, + testModule: { + Proposal: 'TestProposal' + }, + treasury: { + Proposal: 'TreasuryProposals2' + } + } +}); + +describe('getModuleTypes', (): void => { + it('collects the pre-defined types for contracts', (): void => { + expect(getModuleTypes(registry, 'contracts')).toEqual({ + StorageKey: 'ContractStorageKey' + }); + }); + + it('collects the user-defined types for testModule', (): void => { + expect(getModuleTypes(registry, 'testModule')).toEqual({ + Proposal: 'TestProposal' + }); + }); + + it('overrides pre-defined with user-defined for treasury', (): void => { + expect(getModuleTypes(registry, 'treasury')).toEqual({ + Proposal: 'TreasuryProposals2' + }); + }); + + it('merges pre-defined and user-defined for identity', (): void => { + expect(getModuleTypes(registry, 'identity')).toEqual({ + Judgement: 'IdentityJudgement', + Id: 'IdentityId' + }); + }); +}); diff --git a/packages/types/src/types/registry.ts b/packages/types/src/types/registry.ts index 483e30ff3e..6f1a3046d9 100644 --- a/packages/types/src/types/registry.ts +++ b/packages/types/src/types/registry.ts @@ -3,6 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { ChainProperties } from '../interfaces/rpc'; +import { OverrideModuleType } from '../known/types'; import BN from 'bn.js'; @@ -81,10 +82,31 @@ export interface RegistryMetadata { asLatest: RegistryMetadataLatest; } +export interface RegisteredTypes { + /** + * @description Additional types used by runtime modules. This is necessary if the runtime modules + * uses types not available in the base Substrate runtime. + */ + types?: RegistryTypes; + /** + * @description Alias an types, as received via the metadata, to a JS-specific type to avoid conflicts. For instance, you can rename the `Proposal` in the `treasury` module to `TreasuryProposal` as to not have conflicts with the one for democracy. + */ + typesAlias?: Record; + /** + * @description Additional types that are injected based on the chain we are connecting to. There are keyed by the chain, i.e. `{ 'Kusama CC1': { ... } }` + */ + typesChain?: Record; + /** + * @description Additional types that are injected based on the type of node we are connecting to, as set via specName in the runtime version. There are keyed by the node, i.e. `{ 'edgeware': { ... } }` + */ + typesSpec?: Record; +} + export interface Registry { readonly chainDecimals: number; readonly chainSS58: number | undefined; readonly chainToken: string; + readonly knownTypes: RegisteredTypes; findMetaCall (callIndex: Uint8Array): CallFunction; findMetaError (errorIndex: Uint8Array): any; @@ -99,6 +121,7 @@ export interface Registry { getDefinition (name: string): string | undefined; getOrThrow (name: string, msg?: string): Constructor; getOrUnknown (name: string): Constructor; + setKnownTypes (types: RegisteredTypes): void; getSignedExtensionExtra (): Record; getSignedExtensionTypes (): Record; hasClass (name: string): boolean;