Lazy decoration (part 1, metadata) (#4139)

* Make decoration lazy

* Extract common

* w/ Extrinsics

* Storage lazy

* V14 -> Latest

* Util

* Adjust to use create/lazy

* Adjust

* Add tests for errors & events (long-time outstanding)
This commit is contained in:
Jaco
2021-11-02 09:49:16 +02:00
committed by GitHub
parent 7c17baa5be
commit bb61b1e6ac
9 changed files with 292 additions and 131 deletions
+1 -3
View File
@@ -18,9 +18,7 @@ export function lazyMethod <T, K> (result: Record<string, T>, item: K, creator:
});
}
export function lazyMethods <T, K> (items: K[], creator: (v: K) => T, getName?: (m: K) => string): Record<string, T> {
const result: Record<string, T> = {};
export function lazyMethods <T, K> (items: K[], creator: (v: K) => T, getName?: (m: K) => string, result: Record<string, T> = {}): Record<string, T> {
for (let i = 0; i < items.length; i++) {
lazyMethod(result, items[i], creator, getName);
}
@@ -1,33 +1,48 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataLatest } from '../../../interfaces';
import type { MetadataLatest, PalletConstantMetadataLatest, PalletMetadataLatest } from '../../../interfaces';
import type { Registry } from '../../../types';
import type { ConstantCodec, Constants, ModuleConstants } from '../types';
import type { ConstantCodec, Constants } from '../types';
import { hexToU8a, stringCamelCase } from '@polkadot/util';
import { lazyMethod, lazyMethods } from '../../../create/lazy';
import { objectNameToCamel } from '../util';
function createConstantCodec (registry: Registry, constant: PalletConstantMetadataLatest): ConstantCodec {
const codec = registry.createTypeUnsafe(registry.createLookupType(constant.type), [hexToU8a(constant.value.toHex())]) as ConstantCodec;
(codec as unknown as Record<string, unknown>).meta = constant;
return codec;
}
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function decorateConstants (registry: Registry, { pallets }: MetadataLatest, _metaVersion: number): Constants {
export function decorateConstants (registry: Registry, { pallets }: MetadataLatest, _version: number): Constants {
const result: Constants = {};
const lazySection = ({ constants, name }: PalletMetadataLatest): void => {
lazyMethod(
result,
constants,
(constants: PalletConstantMetadataLatest[]) =>
lazyMethods(
constants,
(constant: PalletConstantMetadataLatest) =>
createConstantCodec(registry, constant),
objectNameToCamel
),
() => stringCamelCase(name)
);
};
for (let p = 0; p < pallets.length; p++) {
const { constants, name } = pallets[p];
const pallet = pallets[p];
if (!constants.isEmpty) {
const newModule: ModuleConstants = {};
for (let c = 0; c < constants.length; c++) {
const meta = constants[c];
const codec = registry.createTypeUnsafe(registry.createLookupType(meta.type), [hexToU8a(meta.value.toHex())]) as unknown;
(codec as Record<string, unknown>).meta = meta;
newModule[stringCamelCase(meta.name)] = codec as ConstantCodec;
}
// For access, we change the index names, i.e. Democracy.EnactmentPeriod -> democracy.enactmentPeriod
result[stringCamelCase(name)] = newModule;
if (!pallet.constants.isEmpty) {
lazySection(pallet);
}
}
@@ -0,0 +1,44 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import rpcMetadata from '@polkadot/types-support/metadata/static-substrate';
import { TypeRegistry } from '../../../create';
import { Metadata } from '../../Metadata';
import { decorateErrors } from '..';
const registry = new TypeRegistry();
const metadata = new Metadata(registry, rpcMetadata);
registry.setMetadata(metadata);
const errors = decorateErrors(registry, metadata.asLatest, metadata.version);
describe('decorateErrors', (): void => {
it('should return known errors', (): void => {
expect(errors.balances.InsufficientBalance).toBeDefined();
});
it('has the correct metadata for known errors', (): void => {
expect(
errors.proxy.NotProxy.meta.toJSON()
).toEqual({
args: [],
docs: ['Sender is not a proxy of the account to be proxied.'],
fields: [],
index: 2,
name: 'NotProxy'
});
});
it('should check against a specific error', (): void => {
expect(
errors.system.InvalidSpecName.is(
registry.createType('DispatchErrorModule', {
error: 0,
index: 0
})
)
).toBe(true);
});
});
@@ -1,13 +1,16 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DispatchErrorModule, MetadataLatest, PortableRegistry, SiField, SiVariant } from '../../../interfaces';
import type { DispatchErrorModule, MetadataLatest, PalletMetadataLatest, PortableRegistry, SiField, SiVariant } from '../../../interfaces';
import type { Text, u8 } from '../../../primitive';
import type { Registry } from '../../../types';
import type { Errors, ModuleErrors } from '../types';
import type { Errors, IsError } from '../types';
import { stringCamelCase } from '@polkadot/util';
import { lazyMethod, lazyMethods } from '../../../create/lazy';
import { objectNameToString } from '../util';
interface ItemMeta {
args: string[];
name: Text;
@@ -25,33 +28,39 @@ export function variantToMeta (lookup: PortableRegistry, variant: SiVariant): It
};
}
function createIsError (registry: Registry, lookup: PortableRegistry, variant: SiVariant, sectionIndex: number): IsError {
return {
is: ({ error, index }: DispatchErrorModule) =>
index.eq(sectionIndex) &&
error.eq(variant.index),
meta: registry.createType('ErrorMetadataLatest', variantToMeta(lookup, variant))
};
}
/** @internal */
export function decorateErrors (registry: Registry, { lookup, pallets }: MetadataLatest, metaVersion: number): Errors {
export function decorateErrors (registry: Registry, { lookup, pallets }: MetadataLatest, version: number): Errors {
const result: Errors = {};
const lazySection = ({ errors, name }: PalletMetadataLatest, sectionIndex: number): void => {
lazyMethod(
result,
lookup.getSiType(errors.unwrap().type).def.asVariant.variants,
(variants: SiVariant[]) =>
lazyMethods(
variants,
(variant: SiVariant) =>
createIsError(registry, lookup, variant, sectionIndex),
objectNameToString
),
() => stringCamelCase(name)
);
};
for (let p = 0; p < pallets.length; p++) {
const { errors, index, name } = pallets[p];
const pallet = pallets[p];
if (errors.isSome) {
const sectionIndex = metaVersion >= 12
? index.toNumber()
: p;
const newModule: ModuleErrors = {};
const { variants } = lookup.getSiType(errors.unwrap().type).def.asVariant;
for (let v = 0; v < variants.length; v++) {
const variant = variants[v];
// we don't camelCase the error name
newModule[variant.name.toString()] = {
is: ({ error, index }: DispatchErrorModule) =>
index.eq(sectionIndex) &&
error.eq(variant.index),
meta: registry.createType('ErrorMetadataLatest', variantToMeta(lookup, variant))
};
}
result[stringCamelCase(name)] = newModule;
if (pallet.errors.isSome) {
lazySection(pallet, version >= 12 ? pallet.index.toNumber() : p);
}
}
@@ -0,0 +1,46 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AnyTuple, IEvent } from '../../../types';
import rpcMetadata from '@polkadot/types-support/metadata/static-substrate';
import { TypeRegistry } from '../../../create';
import { Metadata } from '../../Metadata';
import { decorateEvents } from '..';
const registry = new TypeRegistry();
const metadata = new Metadata(registry, rpcMetadata);
registry.setMetadata(metadata);
const events = decorateEvents(registry, metadata.asLatest, metadata.version);
describe('decorateEvents', (): void => {
it('should return known errors', (): void => {
expect(events.balances.Transfer).toBeDefined();
});
it('has the correct metadata for known errors', (): void => {
expect(
events.staking.Rewarded.meta.toJSON()
).toEqual({
args: ['AccountId32', 'u128'],
docs: [
'The nominator has been rewarded by this amount. \\[stash, amount\\]'
],
fields: [
{ docs: [], name: null, type: 0, typeName: 'T::AccountId' },
{ docs: [], name: null, type: 6, typeName: 'BalanceOf<T>' }
],
index: 1,
name: 'Rewarded'
});
});
it('should check against a specific error', (): void => {
expect(
events.system.ExtrinsicSuccess.is({ index: [0, 0] } as unknown as IEvent<AnyTuple>)
).toBe(true);
});
});
@@ -1,44 +1,54 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataLatest, PalletMetadataV14 } from '../../../interfaces';
import type { MetadataLatest, PalletMetadataLatest, PortableRegistry, SiVariant } from '../../../interfaces';
import type { AnyTuple, IEvent, Registry } from '../../../types';
import type { Events, ModuleEvents } from '../types';
import type { Events, IsEvent } from '../types';
import { stringCamelCase } from '@polkadot/util';
import { lazyMethod, lazyMethods } from '../../../create/lazy';
import { variantToMeta } from '../errors';
import { objectNameToString } from '../util';
export function filterEventsSome ({ events }: PalletMetadataV14): boolean {
export function filterEventsSome ({ events }: PalletMetadataLatest): boolean {
return events.isSome;
}
function createIsEvent (registry: Registry, lookup: PortableRegistry, variant: SiVariant, sectionIndex: number): IsEvent<AnyTuple> {
return {
is: <T extends AnyTuple> (eventRecord: IEvent<AnyTuple>): eventRecord is IEvent<T> =>
sectionIndex === eventRecord.index[0] &&
variant.index.eq(eventRecord.index[1]),
meta: registry.createType('EventMetadataLatest', variantToMeta(lookup, variant))
};
}
/** @internal */
export function decorateEvents (registry: Registry, { lookup, pallets }: MetadataLatest, metaVersion: number): Events {
const filtered = pallets.filter(filterEventsSome);
export function decorateEvents (registry: Registry, { lookup, pallets }: MetadataLatest, version: number): Events {
const result: Events = {};
const lazySection = ({ events, name }: PalletMetadataLatest, sectionIndex: number): void => {
lazyMethod(
result,
lookup.getSiType(events.unwrap().type).def.asVariant.variants,
(variants: SiVariant[]) =>
lazyMethods(
variants,
(variant: SiVariant) =>
createIsEvent(registry, lookup, variant, sectionIndex),
objectNameToString
),
() => stringCamelCase(name)
);
};
const filtered = pallets.filter(filterEventsSome);
for (let p = 0; p < filtered.length; p++) {
const { events, index, name } = filtered[p];
const sectionIndex = metaVersion >= 12
? index.toNumber()
: p;
const newModule: ModuleEvents = {};
const { variants } = lookup.getSiType(events.unwrap().type).def.asVariant;
const pallet = filtered[p];
for (let v = 0; v < variants.length; v++) {
const variant = variants[v];
// we don't camelCase the event name
newModule[variant.name.toString()] = {
is: <T extends AnyTuple> (eventRecord: IEvent<AnyTuple>): eventRecord is IEvent<T> =>
eventRecord.index[0] === sectionIndex &&
variant.index.eq(eventRecord.index[1]),
meta: registry.createType('EventMetadataLatest', variantToMeta(lookup, variant))
};
}
result[stringCamelCase(name)] = newModule;
lazySection(pallet, version >= 12 ? pallet.index.toNumber() : p);
}
return result;
@@ -1,56 +1,73 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataLatest, PalletMetadataV14 } from '../../../interfaces';
import type { Registry } from '../../../types';
import type { Extrinsics, ModuleExtrinsics } from '../types';
import type { MetadataLatest, PalletMetadataLatest, PortableRegistry, SiVariant } from '../../../interfaces';
import type { CallFunction, Registry } from '../../../types';
import type { Extrinsics } from '../types';
import { stringCamelCase } from '@polkadot/util';
import { lazyMethod, lazyMethods } from '../../../create/lazy';
import { getSiName } from '../../util';
import { objectNameToCamel } from '../util';
import { createUnchecked } from './createUnchecked';
export function filterCallsSome ({ calls }: PalletMetadataV14): boolean {
export function filterCallsSome ({ calls }: PalletMetadataLatest): boolean {
return calls.isSome;
}
function createCallFunction (registry: Registry, lookup: PortableRegistry, variant: SiVariant, sectionIndex: number, sectionName: string): CallFunction {
const { fields, index } = variant;
const args = new Array<Record<string, unknown>>(fields.length);
for (let a = 0; a < fields.length; a++) {
const { name, type, typeName } = fields[a];
args[a] = {
name: stringCamelCase(name.unwrapOr(`param${a}`)),
type: getSiName(lookup, type),
...(typeName.isSome
? { typeName: typeName.unwrap() }
: {}
)
};
}
return createUnchecked(
registry,
sectionName,
new Uint8Array([sectionIndex, index.toNumber()]),
registry.createType('FunctionMetadataLatest', { ...variant, args })
);
}
/** @internal */
export function decorateExtrinsics (registry: Registry, { lookup, pallets }: MetadataLatest, metaVersion: number): Extrinsics {
const filtered = pallets.filter(filterCallsSome);
export function decorateExtrinsics (registry: Registry, { lookup, pallets }: MetadataLatest, version: number): Extrinsics {
const result: Extrinsics = {};
for (let p = 0; p < filtered.length; p++) {
const { calls, index, name } = filtered[p];
const lazySection = ({ calls, name }: PalletMetadataLatest, sectionIndex: number): void => {
const sectionName = stringCamelCase(name);
const sectionIndex = metaVersion >= 12
? index.toNumber()
: p;
const newModule: ModuleExtrinsics = {};
const { variants } = lookup.getSiType(calls.unwrap().type).def.asVariant;
for (let v = 0; v < variants.length; v++) {
const variant = variants[v];
const args = new Array<Record<string, unknown>>(variant.fields.length);
lazyMethod(
result,
lookup.getSiType(calls.unwrap().type).def.asVariant.variants,
(variants: SiVariant[]) =>
lazyMethods(
variants,
(variant: SiVariant) =>
createCallFunction(registry, lookup, variant, sectionIndex, sectionName),
objectNameToCamel
),
() => sectionName
);
};
for (let a = 0; a < variant.fields.length; a++) {
const { name, type, typeName } = variant.fields[a];
const filtered = pallets.filter(filterCallsSome);
args[a] = {
name: stringCamelCase(name.unwrapOr(`param${a}`)),
type: getSiName(lookup, type),
...(typeName.isSome
? { typeName: typeName.unwrap() }
: {}
)
};
}
for (let p = 0; p < filtered.length; p++) {
const pallet = filtered[p];
const callMetadata = registry.createType('FunctionMetadataLatest', { ...variant, args });
newModule[stringCamelCase(callMetadata.name)] = createUnchecked(registry, sectionName, new Uint8Array([sectionIndex, callMetadata.index.toNumber()]), callMetadata);
}
result[sectionName] = newModule;
lazySection(pallet, version >= 12 ? pallet.index.toNumber() : p);
}
return result;
@@ -1,12 +1,14 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataLatest } from '../../../interfaces';
import type { MetadataLatest, PalletMetadataLatest, StorageEntryMetadataLatest } from '../../../interfaces';
import type { Registry } from '../../../types';
import type { ModuleStorage, Storage } from '../types';
import type { Storage } from '../types';
import { stringCamelCase, stringLowerFirst } from '@polkadot/util';
import { stringCamelCase } from '@polkadot/util';
import { lazyMethod, lazyMethods } from '../../../create/lazy';
import { objectNameFirstLower } from '../util';
import { createFunction, createKeyRaw } from './createFunction';
import { getStorage } from './getStorage';
import { createRuntimeFunction } from './util';
@@ -16,35 +18,37 @@ import { createRuntimeFunction } from './util';
export function decorateStorage (registry: Registry, { pallets }: MetadataLatest, _metaVersion: number): Storage {
const result: Storage = getStorage(registry);
const lazySection = ({ name, storage }: PalletMetadataLatest): void => {
const section = stringCamelCase(name);
const { items, prefix: _prefix } = storage.unwrap();
const prefix = _prefix.toString();
lazyMethod(
result,
items,
(items: StorageEntryMetadataLatest[]) =>
lazyMethods(
items,
(meta: StorageEntryMetadataLatest) =>
createFunction(registry, { meta, method: meta.name.toString(), prefix, section }, {}),
objectNameFirstLower,
{
palletVersion: createRuntimeFunction(
{ method: 'palletVersion', prefix, section },
createKeyRaw(registry, { method: ':__STORAGE_VERSION__:', prefix: name.toString() }, [], [], []),
{ docs: 'Returns the current pallet version from storage', type: 'u16' }
)(registry)
}
),
() => section
);
};
for (let p = 0; p < pallets.length; p++) {
const { name, storage } = pallets[p];
const pallet = pallets[p];
if (storage.isSome) {
const section = stringCamelCase(name);
const { items, prefix: _prefix } = storage.unwrap();
const prefix = _prefix.toString();
const newModule: ModuleStorage = {
palletVersion: createRuntimeFunction(
{ method: 'palletVersion', prefix, section },
createKeyRaw(registry, { method: ':__STORAGE_VERSION__:', prefix: name.toString() }, [], [], []),
{ docs: 'Returns the current pallet version from storage', type: 'u16' }
)(registry)
};
for (let i = 0; i < items.length; i++) {
const meta = items[i];
const method = meta.name.toString();
// For access, we change the index names, i.e. System.Account -> system.account
newModule[stringLowerFirst(method)] = createFunction(registry, {
meta,
method,
prefix,
section
}, {});
}
result[section] = newModule;
if (pallet.storage.isSome) {
lazySection(pallet);
}
}
@@ -0,0 +1,18 @@
// Copyright 2017-2021 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Text } from '../../primitive';
import { stringCamelCase, stringLowerFirst } from '@polkadot/util';
export function objectNameFirstLower ({ name }: { name: string | Text }): string {
return stringLowerFirst(name);
}
export function objectNameToCamel ({ name }: { name: string | Text }): string {
return stringCamelCase(name);
}
export function objectNameToString ({ name }: { name: string | Text }): string {
return name.toString();
}