diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 249c2b3d61..a48ad17bcd 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -15,14 +15,13 @@ import { map, switchMap, take, tap, toArray } from 'rxjs/operators'; import decorateDerive, { ExactDerive } from '@polkadot/api-derive'; import { memo } from '@polkadot/api-derive/util'; import DecoratedMeta from '@polkadot/metadata/Decorated'; -import getHasher from '@polkadot/metadata/Decorated/storage/fromMetadata/getHasher'; import RpcCore from '@polkadot/rpc-core'; import { WsProvider } from '@polkadot/rpc-provider'; import { Metadata, Null, Option, Raw, Text, TypeRegistry, u64 } from '@polkadot/types'; import Linkage, { LinkageResult } from '@polkadot/types/codec/Linkage'; import { DEFAULT_VERSION as EXTRINSIC_DEFAULT_VERSION } from '@polkadot/types/extrinsic/constants'; import StorageKey, { StorageEntry, unwrapStorageType } from '@polkadot/types/primitive/StorageKey'; -import { assert, compactStripLength, isNull, isUndefined, logger, u8aConcat, u8aToHex } from '@polkadot/util'; +import { assert, compactStripLength, logger, u8aToHex } from '@polkadot/util'; import { createSubmittable } from '../submittable'; import augmentObject from '../util/augmentObject'; @@ -344,8 +343,8 @@ export default abstract class Decorate extends Events decorated.key = (arg1?: Arg, arg2?: Arg): string => u8aToHex(compactStripLength(creator(creator.meta.type.isDoubleMap ? [arg1, arg2] : arg1))[1]); - decorated.keyPrefix = (): string => - u8aToHex(creator.keyPrefix); + decorated.keyPrefix = (key1?: Arg): string => + u8aToHex(creator.keyPrefix(key1)); decorated.range = decorateMethod((range: [Hash, Hash?], arg1?: Arg, arg2?: Arg): Observable<[Hash, Codec][]> => this._decorateStorageRange(decorated, [arg1, arg2], range)); @@ -392,6 +391,9 @@ export default abstract class Decorate extends Events const result: Map]> | null> = new Map(); let subject: BehaviorSubject; let head: Codec | null = null; + const iterKey = creator.iterKey; + + assert(iterKey, 'iterKey field is missing'); // retrieve a value based on the key, iterating if it has a next entry. Since // entries can be re-linked in the middle of a list, we subscribe here to make @@ -455,7 +457,7 @@ export default abstract class Decorate extends Events .subscribeStorage<[[Codec, Linkage]]>([[creator, ...args]]) .pipe(map(([data]): [Codec, Linkage] => data)) : this._rpcCore.state - .subscribeStorage<[LinkageResult]>([creator.iterKey]) + .subscribeStorage<[LinkageResult]>([iterKey()]) .pipe(switchMap(([key]): Observable => getNext(head = key))) ); } @@ -463,14 +465,7 @@ export default abstract class Decorate extends Events private _retrieveMapKeys ({ iterKey, meta }: StorageEntry, arg?: Arg): Observable { assert(iterKey && (meta.type.isMap || meta.type.isDoubleMap), 'keys can only be retrieved on maps, linked maps and double maps'); - const headKey = this.createType('Raw', u8aConcat( - iterKey, - meta.type.isDoubleMap && !isUndefined(arg) && !isNull(arg) - ? getHasher(meta.type.asDoubleMap.hasher)( - this.createType(meta.type.asDoubleMap.key1.toString() as 'Raw', arg).toU8a() - ) - : new Uint8Array([]) - )).toHex(); + const headKey = iterKey(arg).toHex(); const startSubject = new BehaviorSubject(headKey); return this._rpcCore.state.getKeysPaged diff --git a/packages/api/src/types/storage.ts b/packages/api/src/types/storage.ts index f9ab07d4f4..be043e8cc1 100644 --- a/packages/api/src/types/storage.ts +++ b/packages/api/src/types/storage.ts @@ -40,6 +40,7 @@ export interface StorageEntryBase extends StorageEntryBase { entries: >>(arg?: Parameters[0]) => PromiseOrObs; + keyPrefix: (key1?: Parameters[0]) => string; } interface StorageEntryObservableMulti { diff --git a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts index 463a211721..82686393b8 100644 --- a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts +++ b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts @@ -4,7 +4,7 @@ import { Text, TypeRegistry } from '@polkadot/types'; import { StorageEntry } from '@polkadot/types/primitive/StorageKey'; -import { stringToU8a, u8aConcat, u8aToHex } from '@polkadot/util'; +import { assert, stringToU8a, u8aConcat, u8aToHex } from '@polkadot/util'; import createFunction from './createFunction'; @@ -104,6 +104,17 @@ describe('createFunction', (): void => { (): Uint8Array => storageFn(['5DXUeE5N5LtkW97F2PzqYPyqNkxqSWESdGSPTX6AvkUAhwKP']) ).toThrow(/requires two arguments/); }); + + it('accepts an optional parameter for key construction', (): void => { + const iterKey = storageFn.iterKey; + + assert(iterKey, 'storageFn.iterKey is undefined'); + + const result = iterKey('5DXUeE5N5LtkW97F2PzqYPyqNkxqSWESdGSPTX6AvkUAhwKP'); + + expect(u8aToHex(result)).toEqual('0x223416315e3dddca3b5a47fd0ac8e4916482b9ade7bc6657aaca787ba1add3b4c4303117deb55aad9858c8a873273280f78d172b398d5e77e43a2db5e42163e9'); + expect(u8aToHex(iterKey())).toEqual('0x223416315e3dddca3b5a47fd0ac8e4916482b9ade7bc6657aaca787ba1add3b4'); + }); }); it('allows creates double map function with a Null type key', (): void => { diff --git a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts index a892cac4c3..19454c23d5 100644 --- a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts +++ b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts @@ -142,7 +142,7 @@ function expandWithMeta ({ meta, method, prefix, section }: CreateItemFn, storag } /** @internal */ -function extendHeadMeta (registry: Registry, { meta: { documentation, name, type }, section }: CreateItemFn, { method }: StorageEntry, iterFn: (arg?: any) => Raw): StorageKey { +function extendHeadMeta (registry: Registry, { meta: { documentation, name, type }, section }: CreateItemFn, { method }: StorageEntry, iterFn: (arg?: any) => Raw): (arg?: any) => StorageKey { const outputType = type.isMap ? type.asMap.key.toString() : type.asDoubleMap.key1.toString(); @@ -157,7 +157,12 @@ function extendHeadMeta (registry: Registry, { meta: { documentation, name, type type: registry.createType('StorageEntryTypeLatest', registry.createType('PlainTypeLatest', type.isMap ? type.asMap.key : type.asDoubleMap.key1), 0) }); - return registry.createType('StorageKey', iterFn, { method, section }); + const prefixKey = registry.createType('StorageKey', iterFn, { method, section }); + + return (arg?: any): StorageKey => + !isUndefined(arg) && !isNull(arg) + ? registry.createType('StorageKey', iterFn(arg), { method, section }) + : prefixKey; } // attach the head key hashing for linked maps @@ -177,8 +182,12 @@ function extendLinkedMap (registry: Registry, itemFn: CreateItemFn, storageFn: S // attach the full list hashing for prefixed maps /** @internal */ function extendPrefixedMap (registry: Registry, itemFn: CreateItemFn, storageFn: StorageEntry): StorageEntry { - storageFn.iterKey = extendHeadMeta(registry, itemFn, storageFn, (): Raw => - new Raw(registry, createPrefixedKey(itemFn)) + const { meta: { type } } = itemFn; + + storageFn.iterKey = extendHeadMeta(registry, itemFn, storageFn, (arg?: any): Raw => + type.isDoubleMap && !isUndefined(arg) && !isNull(arg) + ? new Raw(registry, u8aConcat(createPrefixedKey(itemFn), getHasher(type.asDoubleMap.hasher)(registry.createType(type.asDoubleMap.key1.toString() as 'Raw', arg).toU8a()))) + : new Raw(registry, createPrefixedKey(itemFn)) ); return storageFn; @@ -219,7 +228,8 @@ export default function createFunction (registry: Registry, itemFn: CreateItemFn extendDoubleMap(registry, itemFn, storageFn); } - storageFn.keyPrefix = storageFn.iterKey || compactStripLength(storageFn())[1]; + storageFn.keyPrefix = (arg?: any): Uint8Array => + (storageFn.iterKey && storageFn.iterKey(arg)) || compactStripLength(storageFn())[1]; return storageFn; } diff --git a/packages/rpc-core/src/formatting.spec.FIXME-ts b/packages/rpc-core/src/formatting.spec.FIXME-ts index b85c19fba8..151538f738 100644 --- a/packages/rpc-core/src/formatting.spec.FIXME-ts +++ b/packages/rpc-core/src/formatting.spec.FIXME-ts @@ -153,7 +153,7 @@ function formattingTests (version: string, decorated: Metadata, encodedValues: [ } api.state - .subscribeStorage([iterKey]) + .subscribeStorage([iterKey()]) .subscribe((value: Codec[]): void => { expect(value).toHaveLength(1); // console.error('head fallback', value); diff --git a/packages/types/src/primitive/StorageKey.ts b/packages/types/src/primitive/StorageKey.ts index a59c9923ef..769b7061d7 100644 --- a/packages/types/src/primitive/StorageKey.ts +++ b/packages/types/src/primitive/StorageKey.ts @@ -12,8 +12,8 @@ import Bytes from './Bytes'; export interface StorageEntry { (arg?: any): Uint8Array; - iterKey?: Uint8Array & Codec; - keyPrefix: Uint8Array; + iterKey?: (arg?: any) => Uint8Array & Codec; + keyPrefix: (arg?: any) => Uint8Array; meta: StorageEntryMetadataLatest; method: string; prefix: string;