Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/api/src/base/Decorate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,16 @@ export abstract class Decorate<ApiType extends ApiTypes> extends Events {
decorated.is = <A extends AnyTuple> (key: IStorageKey<AnyTuple>): key is IStorageKey<A> =>
key.section === creator.section && key.method === creator.method;

decorated.key = (arg1?: Arg, arg2?: Arg): string =>
u8aToHex(compactStripLength(creator(creator.meta.type.isDoubleMap ? [arg1, arg2] : arg1))[1]);
decorated.key = (...args: Arg[]): string =>
u8aToHex(compactStripLength(creator(
creator.meta.type.isPlain
? undefined
: creator.meta.type.isMap
? args[0]
: creator.meta.type.isDoubleMap
? [args[0], args[1]]
: args
))[1]);

decorated.keyPrefix = (key1?: Arg): string =>
u8aToHex(creator.keyPrefix(key1));
Expand All @@ -393,6 +401,7 @@ export abstract class Decorate<ApiType extends ApiTypes> extends Events {
decorated.sizeAt = decorateMethod((hash: Hash | Uint8Array | string, arg1?: Arg, arg2?: Arg): Observable<u64> =>
this._rpcCore.state.getStorageSize(getArgs(arg1, arg2), hash));

// FIXME NMap support
// .keys() & .entries() only available on map types
if (creator.iterKey && (creator.meta.type.isMap || creator.meta.type.isDoubleMap)) {
decorated.entries = decorateMethod(
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/util/validate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ describe('extractStorageArgs', (): void => {
expect(
(): any =>
extractStorageArgs(storage.staking.erasStakers, [])
).toThrow('staking.erasStakers(EraIndex, AccountId) is a doublemap, requiring 2 arguments, 0 found');
).toThrow('staking.erasStakers(EraIndex, AccountId) is a double map, requiring 2 arguments, 0 found');
});

it('validates doublemap, 2 args (failing with 1 arg)', (): void => {
expect(
(): any =>
extractStorageArgs(storage.staking.erasStakers, [123])
).toThrow('staking.erasStakers(EraIndex, AccountId) is a doublemap, requiring 2 arguments, 1 found');
).toThrow('staking.erasStakers(EraIndex, AccountId) is a double map, requiring 2 arguments, 1 found');
});

// Linked maps have been removed
Expand Down
21 changes: 16 additions & 5 deletions packages/api/src/util/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import type { StorageEntry } from '@polkadot/types/primitive/types';

import { assert, isUndefined } from '@polkadot/util';

function sig ({ method, section }: StorageEntry, ...args: Type[]): string {
function sig ({ method, section }: StorageEntry, args: Type[]): string {
return `${section}.${method}(${args.join(', ')})`;
}

function doDoubleMap (creator: StorageEntry, args: unknown[]): [StorageEntry, [any, any]] {
const { key1, key2 } = creator.meta.type.asDoubleMap;

assert(args.length === 2, () => `${sig(creator, key1, key2)} is a doublemap, requiring 2 arguments, ${args.length} found`);
assert(args.length === 2, () => `${sig(creator, [key1, key2])} is a double map, requiring 2 arguments, ${args.length} found`);

// pass as tuple
return [creator, args as [any, any]];
Expand All @@ -22,26 +22,37 @@ function doDoubleMap (creator: StorageEntry, args: unknown[]): [StorageEntry, [a
function doMap (creator: StorageEntry, args: unknown[]): [StorageEntry] | [StorageEntry, any] {
const { key } = creator.meta.type.asMap;

assert(args.length === 1, () => `${sig(creator, key)} is a map, requiring 1 argument, ${args.length} found`);
assert(args.length === 1, () => `${sig(creator, [key])} is a map, requiring 1 argument, ${args.length} found`);

// expand
return args.length
? [creator, args[0]]
: [creator];
}

function doNMap (creator: StorageEntry, args: unknown[]): [StorageEntry, any[]] {
const { keyVec } = creator.meta.type.asNMap;

assert(args.length === keyVec.length, () => `${sig(creator, keyVec)} is a multi map, requiring ${keyVec.length} arguments, ${args.length} found`);

// pass as tuple
return [creator, args];
}

// sets up the arguments in the form of [creator, args] ready to be used in a storage
// call. Additionally, it verifies that the correct number of arguments have been passed
export function extractStorageArgs (creator: StorageEntry, _args: unknown[]): [StorageEntry, [any, any]] | [StorageEntry] | [StorageEntry, any] {
export function extractStorageArgs (creator: StorageEntry, _args: unknown[]): [StorageEntry, any[]] | [StorageEntry] | [StorageEntry, any] {
const args = _args.filter((arg) => !isUndefined(arg));

if (creator.meta.type.isDoubleMap) {
return doDoubleMap(creator, args);
} else if (creator.meta.type.isMap) {
return doMap(creator, args);
} else if (creator.meta.type.isNMap) {
return doNMap(creator, args);
}

assert(args.length === 0, () => `${sig(creator)} does not take any arguments, ${args.length} found`);
assert(args.length === 0, () => `${sig(creator, [])} does not take any arguments, ${args.length} found`);

// no args
return [creator];
Expand Down
14 changes: 12 additions & 2 deletions packages/metadata/src/MagicNumber.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ describe('MagicNumber', (): void => {
const registry = new TypeRegistry();

it('succeeds when the magic number matches', (): void => {
expect((): MagicNumber => new MagicNumber(registry, MAGIC_NUMBER)).not.toThrow();
expect(
() => new MagicNumber(registry, MAGIC_NUMBER)
).not.toThrow();
});

it('succeeds when the magic number is empty', (): void => {
expect(
() => new MagicNumber(registry)
).not.toThrow();
});

it('fails when the magic number mismatches', (): void => {
expect((): MagicNumber => new MagicNumber(registry, 0x12345)).toThrow(/MagicNumber/);
expect(
() => new MagicNumber(registry, 0x12345678)
).toThrow(/MagicNumber mismatch/);
});
});
4 changes: 1 addition & 3 deletions packages/metadata/src/MagicNumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ export class MagicNumber extends U32 {
super(registry, value);

if (!this.isEmpty) {
const magic = registry.createType('u32', MAGIC_NUMBER);

assert(this.eq(magic), () => `MagicNumber mismatch: expected ${magic.toHex()}, found ${this.toHex()}`);
assert(this.eq(MAGIC_NUMBER), () => `MagicNumber mismatch: expected ${registry.createType('u32', MAGIC_NUMBER).toHex()}, found ${this.toHex()}`);
}
}
}
38 changes: 25 additions & 13 deletions packages/metadata/src/MetadataVersioned.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2017-2021 @polkadot/metadata authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { MetadataAll, MetadataLatest, MetadataV9, MetadataV10, MetadataV11, MetadataV12 } from '@polkadot/types/interfaces/metadata';
import type { MetadataAll, MetadataLatest, MetadataV9, MetadataV10, MetadataV11, MetadataV12, MetadataV13 } from '@polkadot/types/interfaces/metadata';
import type { AnyJson, Registry } from '@polkadot/types/types';

import { Struct } from '@polkadot/types/codec';
Expand All @@ -10,21 +10,24 @@ import { assert } from '@polkadot/util';
import { toV10 } from './v9/toV10';
import { toV11 } from './v10/toV11';
import { toV12 } from './v11/toV12';
import { toLatest } from './v12/toLatest';
import { toV13 } from './v12/toV13';
import { toLatest } from './v13/toLatest';
import { MagicNumber } from './MagicNumber';
import { getUniqTypes, toCallsOnly } from './util';

type MetaMapped = MetadataV9 | MetadataV10 | MetadataV11 | MetadataV12;
type MetaVersions = 9 | 10 | 11 | 12 | 13;
type MetaAsX = 'asV9' | 'asV10' | 'asV11' | 'asV12';
type MetaMapped = MetadataV9 | MetadataV10 | MetadataV11 | MetadataV12 | MetadataV13;
type MetaAsX = 'asV9' | 'asV10' | 'asV11' | 'asV12' | 'asV13';
type MetaVersions = 'latest' | 9 | 10 | 11 | 12 | 13;

const LATEST_VERSION = 13;

/**
* @name MetadataVersioned
* @description
* The versioned runtime metadata as a decoded structure
*/
export class MetadataVersioned extends Struct {
readonly #converted = new Map<number, MetaMapped>();
readonly #converted = new Map<MetaVersions, MetaMapped>();

constructor (registry: Registry, value?: unknown) {
super(registry, {
Expand All @@ -34,16 +37,18 @@ export class MetadataVersioned extends Struct {
}

#assertVersion = (version: number): boolean => {
assert(this.version <= version, () => `Cannot convert metadata from v${this.version} to v${version}`);
assert(this.version <= version, () => `Cannot convert metadata from version ${this.version} to ${version}`);

return this.version === version;
};

#getVersion = <T extends MetaMapped, F extends MetaMapped>(version: MetaVersions, fromPrev: (registry: Registry, input: F, metaVersion: number) => T): T => {
const asCurr = `asV${version}` as MetaAsX;
const asPrev = `asV${version - 1}` as MetaAsX;
const asPrev = version === 'latest'
? `asV${LATEST_VERSION}` as MetaAsX
: `asV${version - 1}` as MetaAsX;

if (this.#assertVersion(version)) {
if (version !== 'latest' && this.#assertVersion(version)) {
return this.#metadata()[asCurr] as T;
}

Expand Down Expand Up @@ -101,16 +106,23 @@ export class MetadataVersioned extends Struct {
return this.#getVersion(12, toV12);
}

/**
* @description Returns the wrapped values as a V13 object
*/
public get asV13 (): MetadataV13 {
return this.#getVersion(13, toV13);
}

/**
* @description Returns the wrapped values as a latest version object
*/
public get asLatest (): MetadataLatest {
// This is non-existent & latest - applied here to do the module-specific type conversions
return this.#getVersion(13, toLatest);
return this.#getVersion('latest', toLatest);
}

/**
* @description
* @description The magicNumber for the Metadata (known constant)
*/
public get magicNumber (): MagicNumber {
return this.get('magicNumber') as MagicNumber;
Expand All @@ -131,8 +143,8 @@ export class MetadataVersioned extends Struct {
* @description Converts the Object to JSON, typically used for RPC transfers
*/
public toJSON (): Record<string, AnyJson> {
// HACK(y): ensure that we apply the aliasses if we have not done so already, this is
// needed to ensure we have the overrides as intended (only applied in toLatest)
// HACK(y): ensure that we apply the aliases if we have not done so already, this is
// needed to ensure we have the correct overrides (which is only applied in toLatest)
// eslint-disable-next-line no-unused-expressions
this.asLatest;

Expand Down
48 changes: 48 additions & 0 deletions packages/metadata/src/decorate/storage/createFunction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,52 @@ describe('createFunction', (): void => {
'5eb36b60f0fc4b9177116eba3e5cd57fea6289a57f5f5b9ffeb0475c66e7a521' // blake2
);
});

it('allows creating of NMap keys (with Bytes)', (): void => {
const storageFn = createFunction(registry, {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
meta: {
type: {
asNMap: {
hashers: [
registry.createType('StorageHasher', 'Twox64Concat'),
registry.createType('StorageHasher', 'Blake2_256'),
registry.createType('StorageHasher', 'Blake2_256')
],
keyVec: [
new Text(registry, 'Bytes'),
new Text(registry, 'AccountId'),
new Text(registry, 'AccountId')
],
value: new Text(registry, 'SessionKeys5')
},
isNMap: true
}
} as any,
method: 'NextKeys',
prefix: 'Session',
section: 'session'
}, {});

expect(
u8aToHex(
storageFn([
// hex, without length prefix
'0x3a73657373696f6e3a6b657973',
// addresses
'DB2mp5nNhbFN86J9hxoAog8JALMhDXgwvWMxrRMLNUFMEY4',
'DB2mp5nNhbFN86J9hxoAog8JALMhDXgwvWMxrRMLNUFMEY4'
])
)
).toEqual(
'0x' +
'd901' + // length
'cec5070d609dd3497f72bde07fc96ba0' + // twox 128
'4c014e6bf8b8c2c011e7290b85696bb3' + // twox 128
'9fe6329cc0b39e09' + // twox 64
'343a73657373696f6e3a6b657973' + // twox 64 (concat, with length)
'5eb36b60f0fc4b9177116eba3e5cd57fea6289a57f5f5b9ffeb0475c66e7a521' + // blake2
'5eb36b60f0fc4b9177116eba3e5cd57fea6289a57f5f5b9ffeb0475c66e7a521' // blake2
);
});
});
14 changes: 9 additions & 5 deletions packages/metadata/src/decorate/storage/createFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function extendHeadMeta (registry: Registry, { meta: { documentation, name, type
function extendPrefixedMap (registry: Registry, itemFn: CreateItemFn, storageFn: StorageEntry): StorageEntry {
const { meta: { type } } = itemFn;

// FIXME NMap support
storageFn.iterKey = extendHeadMeta(registry, itemFn, storageFn, (arg?: any): Raw => {
assert(type.isDoubleMap || isUndefined(arg), 'Filtering arguments for keys/entries are only valid on double maps');

Expand Down Expand Up @@ -136,15 +137,18 @@ export function createFunction (registry: Registry, itemFn: CreateItemFn, option
// - storage.timestamp.blockPeriod()
// For higher-map queries the params are passed in as an tuple, [key1, key2]
const storageFn = expandWithMeta(itemFn, (arg?: CreateArgType | CreateArgType[]) =>
type.isDoubleMap
? createKey(registry, itemFn, [type.asDoubleMap.key1, type.asDoubleMap.key2], [getHasher(type.asDoubleMap.hasher), getHasher(type.asDoubleMap.key2Hasher)], arg as CreateArgType[])
type.isPlain
? options.skipHashing
? compactAddLength(u8aToU8a(options.key))
: createKey(registry, itemFn, [], [], [])
: type.isMap
? createKey(registry, itemFn, [type.asMap.key], [getHasher(type.asMap.hasher)], [arg as CreateArgType])
: options.skipHashing
? compactAddLength(u8aToU8a(options.key))
: createKey(registry, itemFn, [], [], [])
: type.isDoubleMap
? createKey(registry, itemFn, [type.asDoubleMap.key1, type.asDoubleMap.key2], [getHasher(type.asDoubleMap.hasher), getHasher(type.asDoubleMap.key2Hasher)], arg as CreateArgType[])
: createKey(registry, itemFn, type.asNMap.keyVec, type.asNMap.hashers.map((h) => getHasher(h)), arg as CreateArgType[])
);

// FIXME NMap support
if (type.isMap || type.isDoubleMap) {
extendPrefixedMap(registry, itemFn, storageFn);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/metadata/src/static.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2017-2021 @polkadot/metadata authors & contributors
// SPDX-License-Identifier: Apache-2.0

import metadata from './v12/static';
import metadata from './v13/static';

export default metadata;
32 changes: 21 additions & 11 deletions packages/metadata/src/util/getUniqTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ type Arg = { type: Type } & Codec;

type Item = {
type: {
isDoubleMap?: boolean;
isDoubleMap: boolean;
isMap: boolean;
isNMap: boolean;
isPlain: boolean;
asDoubleMap?: {
asDoubleMap: {
key1: Text;
key2: Text;
value: Text;
Expand All @@ -24,6 +25,10 @@ type Item = {
key: Text;
value: Text;
};
asNMap: {
keyVec: Text[];
value: Text;
},
asPlain: Text;
};
} & Codec;
Expand Down Expand Up @@ -106,20 +111,25 @@ function unwrapStorage (storage?: Storage): Item[] {
function getStorageNames ({ modules }: ExtractionMetadata): string[][][] {
return modules.map(({ storage }): string[][] =>
unwrapStorage(storage).map(({ type }) =>
type.isDoubleMap && type.asDoubleMap
type.isPlain
? [
type.asDoubleMap.key1.toString(),
type.asDoubleMap.key2.toString(),
type.asDoubleMap.value.toString()
type.asPlain.toString()
]
: type.isMap
? [
type.asMap.key.toString(),
type.asMap.value.toString()
]
: [
type.asPlain.toString()
type.asMap.value.toString(),
type.asMap.key.toString()
]
: type.isDoubleMap
? [
type.asDoubleMap.value.toString(),
type.asDoubleMap.key1.toString(),
type.asDoubleMap.key2.toString()
]
: [
type.asNMap.value.toString(),
...type.asNMap.keyVec.map((k) => k.toString())
]
)
);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/metadata/src/v12/toV13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2017-2021 @polkadot/metadata authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { MetadataV12, MetadataV13 } from '@polkadot/types/interfaces/metadata';
import type { Registry } from '@polkadot/types/types';

/**
* @internal
**/
export function toV13 (registry: Registry, metadata: MetadataV12): MetadataV13 {
return registry.createType('MetadataV13', metadata);
}
Loading