From 77ffd7c1533661202d2cbae2855676644dcc77fd Mon Sep 17 00:00:00 2001 From: Jaco Date: Sat, 24 Jul 2021 16:50:06 +0300 Subject: [PATCH] Metadata v13 to v14 conversion (#3807) --- packages/types-known/src/bundle.ts | 1 + packages/types-known/src/knownOrigins.ts | 27 ++ packages/types/src/metadata/v13/toLatest.ts | 22 +- packages/types/src/metadata/v13/toV14.ts | 325 ++++++++++++++++++++ 4 files changed, 355 insertions(+), 20 deletions(-) create mode 100644 packages/types-known/src/knownOrigins.ts create mode 100644 packages/types/src/metadata/v13/toV14.ts diff --git a/packages/types-known/src/bundle.ts b/packages/types-known/src/bundle.ts index ed54cfd5c8..8ad7ea67b4 100644 --- a/packages/types-known/src/bundle.ts +++ b/packages/types-known/src/bundle.ts @@ -14,6 +14,7 @@ import typesModules from './modules'; import typesSpec from './spec'; import upgrades from './upgrades'; +export { knownOrigins } from './knownOrigins'; export { packageInfo } from './packageInfo'; // flatten a VersionedType[] into a Record diff --git a/packages/types-known/src/knownOrigins.ts b/packages/types-known/src/knownOrigins.ts new file mode 100644 index 0000000000..0c3ddffc86 --- /dev/null +++ b/packages/types-known/src/knownOrigins.ts @@ -0,0 +1,27 @@ +// Copyright 2017-2021 @polkadot/types authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +// We want predictive ordering (manually managed) +/* eslint-disable sort-keys */ + +// FIXME: Need some sort of solution for specifying these +// Since we don't have insight into the origin specification, we can only define what we know about +// in a pure Substrate/Polkadot implementation, any other custom origins won't be handled at all +export const knownOrigins: Record = { + // + // (1) Defaults from Substrate + // + Council: 'CollectiveOrigin', + System: 'SystemOrigin', + TechnicalCommittee: 'CollectiveOrigin', + // + // (2) Defaults from Polkadot + // + Xcm: 'XcmOrigin', + XcmPallet: 'XcmOrigin', + // + // (3) Defaults from Acala + // + Authority: 'AuthorityOrigin', + GeneralCouncil: 'CollectiveOrigin' +}; diff --git a/packages/types/src/metadata/v13/toLatest.ts b/packages/types/src/metadata/v13/toLatest.ts index 4ec6dc2699..63000718a6 100644 --- a/packages/types/src/metadata/v13/toLatest.ts +++ b/packages/types/src/metadata/v13/toLatest.ts @@ -4,29 +4,11 @@ import type { EventMetadataLatest, EventMetadataV13, FunctionMetadataLatest, FunctionMetadataV13, MetadataLatest, MetadataV13, ModuleConstantMetadataLatest, ModuleConstantMetadataV13, ModuleMetadataLatest, ModuleMetadataV13, StorageEntryMetadataLatest, StorageMetadataLatest, StorageMetadataV13 } from '../../interfaces/metadata'; import type { OverrideModuleType, Registry } from '../../types'; -import { getModuleTypes } from '@polkadot/types-known'; +import { getModuleTypes, knownOrigins } from '@polkadot/types-known'; import { stringCamelCase } from '@polkadot/util'; import { Type } from '../../primitive'; -// FIXME: Need some sort of solution for specifying these -// Since we don't have insight into the origin specification, we can only define what we know about -// in a pure Substrate/Polkadot implementation, any other custom origins won't be handled at all -const KNOWN_ORIGINS: Record = { - /* eslint-disable sort-keys */ - // Substrate - Council: 'CollectiveOrigin', - System: 'SystemOrigin', - TechnicalCommittee: 'CollectiveOrigin', - // Polkadot - Xcm: 'XcmOrigin', - XcmPallet: 'XcmOrigin', - // Acala - Authority: 'AuthorityOrigin', - GeneralCouncil: 'CollectiveOrigin' - /* eslint-enable sort-keys */ -}; - const BOXES = [['<', '>'], ['<', ','], [',', '>'], ['(', ')'], ['(', ','], [',', ','], [',', ')']]; /** @@ -131,7 +113,7 @@ function registerOriginCaller (registry: Registry, modules: ModuleMetadataV13[], result[`Empty${i}`] = 'Null'; } - result[name] = KNOWN_ORIGINS[name] || 'Null'; + result[name] = knownOrigins[name] || 'Null'; return result; }, {}) diff --git a/packages/types/src/metadata/v13/toV14.ts b/packages/types/src/metadata/v13/toV14.ts new file mode 100644 index 0000000000..0735f72402 --- /dev/null +++ b/packages/types/src/metadata/v13/toV14.ts @@ -0,0 +1,325 @@ +// Copyright 2017-2021 @polkadot/types authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ErrorMetadataV13, EventMetadataV13, ExtrinsicMetadataV13, ExtrinsicMetadataV14, FunctionMetadataV13, MetadataV13, MetadataV14, ModuleConstantMetadataV13, ModuleMetadataV13, PalletCallMetadataV14, PalletConstantMetadataV14, PalletErrorMetadataV14, PalletEventMetadataV14, PalletMetadataV14, PalletStorageMetadataV14, StorageEntryMetadataV14, StorageEntryTypeV14, StorageMetadataV13 } from '../../interfaces/metadata'; +import type { SiVariant } from '../../interfaces/scaleInfo'; +import type { Text } from '../../primitive/Text'; +import type { Type } from '../../primitive/Type'; +import type { OverrideModuleType, Registry } from '../../types'; + +import { getModuleTypes, knownOrigins } from '@polkadot/types-known'; +import { stringCamelCase } from '@polkadot/util'; + +interface TypeSpec { + def: { + HistoricMetaCompat?: string; + Tuple?: number[]; + Variant?: { variants: SiVariant[] } + }, + path?: string[]; +} + +const BOXES = [['<', '>'], ['<', ','], [',', '>'], ['(', ')'], ['(', ','], [',', ','], [',', ')']]; + +/** + * Creates a compatible type mapping + * @internal + **/ +function compatType (types: TypeSpec[], _type: Text | string): number { + const type = _type.toString(); + const index = types.findIndex(({ def }) => + def.HistoricMetaCompat === type + ); + + if (index !== -1) { + return index; + } + + return types.push({ + def: { HistoricMetaCompat: type } + }) - 1; +} + +function variantType (modName: Text, variantType: string, types: TypeSpec[], variants: SiVariant[]): number { + return types.push({ + def: { + Variant: { variants } + }, + path: [`pallet_${modName.toString()}`, 'pallet', variantType] + }) - 1; +} + +/** + * @internal + * generate & register the OriginCaller type + **/ +function registerOriginCaller (registry: Registry, modules: ModuleMetadataV13[], metaVersion: number): void { + registry.register({ + OriginCaller: { + _enum: modules + .map((mod, index): [string, number] => [ + mod.name.toString(), + metaVersion >= 12 + ? mod.index.toNumber() + : index + ]) + .sort((a, b) => a[1] - b[1]) + .reduce((result: Record, [name, index]): Record => { + for (let i = Object.keys(result).length; i < index; i++) { + result[`Empty${i}`] = 'Null'; + } + + result[name] = knownOrigins[name] || 'Null'; + + return result; + }, {}) + } + }); +} + +/** + * Find and apply the correct type override + * @internal + **/ +function setTypeOverride (sectionTypes: OverrideModuleType, types: Type[]): void { + types.forEach((type): void => { + const override = Object.keys(sectionTypes).find((aliased) => type.eq(aliased)); + + if (override) { + type.setOverride(sectionTypes[override]); + } else { + // FIXME: NOT happy with this approach, but gets over the initial hump cased by (Vec,BalanceOf) + const orig = type.toString(); + const alias = Object + .entries(sectionTypes) + .reduce((result: string, [from, to]) => + BOXES.reduce((result, [one, two]) => + result.replace(`${one}${from}${two}`, `${one}${to}${two}`), result), orig); + + if (orig !== alias) { + type.setOverride(alias); + } + } + }); +} + +/** + * Apply module-specific type overrides (always be done as part of toV14) + * @internal + **/ +function convertCalls (registry: Registry, types: TypeSpec[], modName: Text, calls: FunctionMetadataV13[], sectionTypes: OverrideModuleType): PalletCallMetadataV14 { + const variants = calls.map(({ args, docs, name }, index): SiVariant => { + setTypeOverride(sectionTypes, args.map(({ type }) => type)); + + return registry.createType('SiVariant', { + docs, + fields: args.map(({ name, type }) => + registry.createType('SiField', { name, type: compatType(types, type) }) + ), + index, + name + }); + }); + + return registry.createType('PalletCallMetadataV14', { + type: variantType(modName, 'Call', types, variants) + }); +} + +/** + * Apply module-specific type overrides (always be done as part of toV14) + * @internal + */ +function convertConstants (registry: Registry, types: TypeSpec[], constants: ModuleConstantMetadataV13[], sectionTypes: OverrideModuleType): PalletConstantMetadataV14[] { + return constants.map(({ docs, name, type, value }): PalletConstantMetadataV14 => { + setTypeOverride(sectionTypes, [type]); + + return registry.createType('PalletConstantMetadataV14', { + docs, + name, + type: compatType(types, type), + value + }); + }); +} + +/** + * Apply module-specific type overrides (always be done as part of toV14) + * @internal + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function convertErrors (registry: Registry, types: TypeSpec[], modName: Text, errors: ErrorMetadataV13[], _sectionTypes: OverrideModuleType): PalletErrorMetadataV14 { + const variants = errors.map(({ docs, name }, index): SiVariant => + registry.createType('SiVariant', { + docs, + fields: [], + index, + name + }) + ); + + return registry.createType('PalletErrorMetadataV14', { + type: variantType(modName, 'Error', types, variants) + }); +} + +/** + * Apply module-specific type overrides (always be done as part of toV14) + * @internal + **/ +function convertEvents (registry: Registry, types: TypeSpec[], modName: Text, events: EventMetadataV13[], sectionTypes: OverrideModuleType): PalletEventMetadataV14 { + const variants = events.map(({ args, docs, name }, index): SiVariant => { + setTypeOverride(sectionTypes, args); + + return registry.createType('SiVariant', { + docs, + fields: args.map((type) => + registry.createType('SiField', { type: compatType(types, type) }) + ), + index, + name + }); + }); + + return registry.createType('PalletEventMetadataV14', { + type: variantType(modName, 'Event', types, variants) + }); +} + +/** + * Apply module-specific storage type overrides (always part of toV14) + * @internal + **/ +function convertStorage (registry: Registry, types: TypeSpec[], { items, prefix }: StorageMetadataV13, sectionTypes: OverrideModuleType): PalletStorageMetadataV14 { + return registry.createType('PalletStorageMetadataV14', { + items: items.map(({ docs, fallback, modifier, name, type }): StorageEntryMetadataV14 => { + let entryType: StorageEntryTypeV14; + + if (type.isPlain) { + const plain = type.asPlain; + + setTypeOverride(sectionTypes, [plain]); + + entryType = registry.createType('StorageEntryTypeV14', { + Plain: compatType(types, plain) + }); + } else if (type.isMap) { + const map = type.asMap; + + setTypeOverride(sectionTypes, [map.value, map.key]); + + entryType = registry.createType('StorageEntryTypeV14', { + Map: { + hasher: map.hasher, + key: compatType(types, map.key), + value: compatType(types, map.value) + } + }); + } else if (type.isDoubleMap) { + const dm = type.asDoubleMap; + + setTypeOverride(sectionTypes, [dm.value, dm.key1, dm.key2]); + + entryType = registry.createType('StorageEntryTypeV14', { + DoubleMap: { + hasher: dm.hasher, + key1: compatType(types, dm.key1), + key2: compatType(types, dm.key2), + key2Hasher: dm.key2Hasher, + value: compatType(types, dm.value) + } + }); + } else { + const nm = type.asNMap; + + setTypeOverride(sectionTypes, [nm.value, ...nm.keyVec]); + + const key = types.push({ + def: { + Tuple: nm.keyVec.map((type) => + compatType(types, type) + ) + } + }) - 1; + + entryType = registry.createType('StorageEntryTypeV14', { + NMap: { + hashers: nm.hashers, + key, + value: compatType(types, nm.value) + } + }); + } + + return registry.createType('StorageEntryMetadataV14', { + docs, + fallback, + modifier, + name, + type: entryType + }); + }), + prefix + }); +} + +/** @internal */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function convertExtrinsic (registry: Registry, _types: TypeSpec[], { signedExtensions, version }: ExtrinsicMetadataV13): ExtrinsicMetadataV14 { + return registry.createType('ExtrinsicMetadataV14', { + signedExtensions: signedExtensions.map((identifier) => ({ + identifier, + type: 0 // we don't map the fields at all + })), + type: 0, // Map to extrinsic like in v14? + version: version + }); +} + +/** @internal */ +function createPallet (registry: Registry, types: TypeSpec[], mod: ModuleMetadataV13, { calls, constants, errors, events, storage }: { calls: FunctionMetadataV13[] | null, constants: ModuleConstantMetadataV13[], errors: ErrorMetadataV13[] | null, events: EventMetadataV13[] | null, storage: StorageMetadataV13 | null }): PalletMetadataV14 { + const sectionTypes = getModuleTypes(registry, stringCamelCase(mod.name)); + + return registry.createType('PalletMetadataV14', { + calls: calls && convertCalls(registry, types, mod.name, calls, sectionTypes), + constants: convertConstants(registry, types, constants, sectionTypes), + errors: errors && convertErrors(registry, types, mod.name, errors, sectionTypes), + events: events && convertEvents(registry, types, mod.name, events, sectionTypes), + index: mod.index, + name: mod.name, + storage: storage && convertStorage(registry, types, storage, sectionTypes) + }); +} + +/** + * Convert the Metadata to v14 + * @internal + **/ +export function toV14 (registry: Registry, v13: MetadataV13, metaVersion: number): MetadataV14 { + // the types that we will pass + const types: TypeSpec[] = []; + + compatType(types, 'Null'); // position 0 always has Null + registerOriginCaller(registry, v13.modules, metaVersion); + + const extrinsic = convertExtrinsic(registry, types, v13.extrinsic); + const pallets = v13.modules.map((mod) => + createPallet(registry, types, mod, { + calls: mod.calls.unwrapOr(null), + constants: mod.constants, + errors: mod.errors.length ? mod.errors : null, + events: mod.events.unwrapOr(null), + storage: mod.storage.unwrapOr(null) + }) + ); + + return registry.createType('MetadataV14', { + extrinsic, + lookup: { + types: types.map((type, id) => + registry.createType('PortableType', { id, type }) + ) + }, + pallets + }); +}