Allow storage queries with default keys values (#3578)
* Allow storage queries with default keys values * Cleanups * Adjust * Tuple test
This commit is contained in:
@@ -3,34 +3,33 @@
|
||||
|
||||
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
|
||||
function createApi (): Promise<ApiPromise> {
|
||||
jest.setTimeout(30000);
|
||||
process.env.NODE_ENV = 'test';
|
||||
describe('misc online tests', (): void => {
|
||||
let api: ApiPromise;
|
||||
|
||||
const provider = new WsProvider('wss://kusama-rpc.polkadot.io');
|
||||
// const provider = new WsProvider('wss://westend-rpc.polkadot.io/');
|
||||
// const provider = new WsProvider('ws://127.0.0.1:9944/');
|
||||
beforeEach(async () => {
|
||||
jest.setTimeout(30000);
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
return new ApiPromise({ provider }).isReady;
|
||||
}
|
||||
// const provider = new WsProvider('wss://kusama-rpc.polkadot.io');
|
||||
const provider = new WsProvider('wss://westend-rpc.polkadot.io/');
|
||||
|
||||
api = await ApiPromise.create({ provider });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
describe('misc quick tests', (): void => {
|
||||
it.skip('retrieves balances correctly', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
|
||||
console.error(JSON.stringify(
|
||||
await api.query.system.account('FPzukZw2mphWsXDqdkNzLaxnanjZEWH9i2vqwobTdtde5me')
|
||||
));
|
||||
console.error(JSON.stringify(
|
||||
await api.query.system.account('HUewJvzVuEeyaxH2vx9XiyAPKrpu1Zj5r5Pi9VrGiBVty7q')
|
||||
));
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
it.skip('handles map keys', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
|
||||
console.time('map.keys');
|
||||
|
||||
const keys = await api.query.system.account.keys();
|
||||
@@ -38,13 +37,9 @@ describe('misc quick tests', (): void => {
|
||||
console.error('# keys', keys.length);
|
||||
|
||||
console.timeEnd('map.keys');
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
it.skip('handles map entries', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
|
||||
console.time('map.entries');
|
||||
|
||||
const entries = await api.query.system.account.entries(); // api.query.indices.accounts.entries();
|
||||
@@ -52,50 +47,25 @@ describe('misc quick tests', (): void => {
|
||||
console.error('# entries', entries.length);
|
||||
|
||||
console.timeEnd('map.entries');
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
it.skip('handles doublemap entries', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
const activeEra = await api.query.staking.activeEra();
|
||||
|
||||
console.error(JSON.stringify(
|
||||
await api.query.staking.erasStakers.entries(activeEra.unwrapOrDefault().index)
|
||||
));
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
it.skip('does something in society', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
|
||||
console.error(JSON.stringify(
|
||||
await api.query.society.defenderVotes('Dab4bfYTZRUDMWjYAUQuFbDreQ9mt7nULWu3Dw7jodbzVe9')
|
||||
));
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
it.skip('allows for range queries', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
const header = await api.rpc.chain.getHeader();
|
||||
|
||||
console.error(JSON.stringify(
|
||||
(await api.query.staking.activeEra.range([header.parentHash, header.hash], 'Dab4bfYTZRUDMWjYAUQuFbDreQ9mt7nULWu3Dw7jodbzVe9'))
|
||||
.map(([block, value]) => [block, value.toRawType(), value.toHuman()])
|
||||
));
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
it('expose rpc and rx definition', async (): Promise<void> => {
|
||||
const api = await createApi();
|
||||
|
||||
it('expose rpc and rx definition', (): void => {
|
||||
console.error(api.rpc.payment.queryFeeDetails.meta);
|
||||
|
||||
console.error(api.rx.rpc.chain.getBlock.meta);
|
||||
|
||||
await api.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2021 @polkadot/metadata authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { StorageEntryMetadataLatest } from '@polkadot/types/interfaces/metadata';
|
||||
import type { StorageEntryMetadataLatest, StorageHasher } from '@polkadot/types/interfaces/metadata';
|
||||
import type { StorageEntry } from '@polkadot/types/primitive/types';
|
||||
import type { Codec, Registry } from '@polkadot/types/types';
|
||||
|
||||
@@ -9,7 +9,7 @@ import BN from 'bn.js';
|
||||
|
||||
import { Raw } from '@polkadot/types/codec';
|
||||
import { StorageKey, Type } from '@polkadot/types/primitive';
|
||||
import { assert, compactAddLength, compactStripLength, isNull, isUndefined, stringCamelCase, stringify, stringLowerFirst, u8aConcat, u8aToU8a } from '@polkadot/util';
|
||||
import { assert, compactAddLength, compactStripLength, isUndefined, stringCamelCase, stringLowerFirst, u8aConcat, u8aToU8a } from '@polkadot/util';
|
||||
import { xxhashAsU8a } from '@polkadot/util-crypto';
|
||||
|
||||
import { getHasher } from './getHasher';
|
||||
@@ -31,33 +31,27 @@ interface IterFn {
|
||||
meta: StorageEntryMetadataLatest;
|
||||
}
|
||||
|
||||
type CreateArgType = boolean | string | number | null | BN | BigInt | Uint8Array | Codec;
|
||||
|
||||
type U8aHasher = (input: Uint8Array) => Uint8Array;
|
||||
|
||||
function createPrefixedKey ({ method, prefix }: CreateItemFn): Uint8Array {
|
||||
return u8aConcat(xxhashAsU8a(prefix, 128), xxhashAsU8a(method, 128));
|
||||
}
|
||||
|
||||
function createKeyRaw (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: U8aHasher[], args: CreateArgType[]): Uint8Array {
|
||||
const { method, section } = itemFn;
|
||||
type Arg = boolean | string | number | null | BN | BigInt | Uint8Array | Codec;
|
||||
|
||||
/** @internal */
|
||||
function createKeyRaw (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: StorageHasher[], args: Arg[]): Uint8Array {
|
||||
return u8aConcat(
|
||||
createPrefixedKey(itemFn),
|
||||
...keys.map((type, index): Uint8Array => {
|
||||
const arg = args[index];
|
||||
|
||||
assert(!isUndefined(arg) && !isNull(arg), () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} has a null or undefined argument at position ${index}`);
|
||||
|
||||
return hashers[index](registry.createType(type.toString() as 'Raw', arg).toU8a());
|
||||
})
|
||||
xxhashAsU8a(itemFn.prefix, 128),
|
||||
xxhashAsU8a(itemFn.method, 128),
|
||||
...keys.map((type, index) =>
|
||||
getHasher(hashers[index])(
|
||||
registry.createType(type.toString() as 'Raw', args[index]).toU8a()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function createKey (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: U8aHasher[], args: CreateArgType[]): Uint8Array {
|
||||
/** @internal */
|
||||
function createKey (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: StorageHasher[], args: Arg[]): Uint8Array {
|
||||
const { method, section } = itemFn;
|
||||
|
||||
assert(Array.isArray(args) && args.length === keys.length, () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, found ${stringify(args)}`);
|
||||
assert(Array.isArray(args), () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, provided in tuple format`);
|
||||
assert(args.filter((a) => !isUndefined(a)).length === keys.length, () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, found [${args.join(', ')}]`);
|
||||
|
||||
// as per createKey, always add the length prefix (underlying it is Bytes)
|
||||
return compactAddLength(
|
||||
@@ -65,9 +59,8 @@ function createKey (registry: Registry, itemFn: CreateItemFn, keys: Type[], hash
|
||||
);
|
||||
}
|
||||
|
||||
// attach the metadata to expand to a StorageFunction
|
||||
/** @internal */
|
||||
function expandWithMeta ({ meta, method, prefix, section }: CreateItemFn, _storageFn: (arg?: CreateArgType | CreateArgType[]) => Uint8Array): StorageEntry {
|
||||
function expandWithMeta ({ meta, method, prefix, section }: CreateItemFn, _storageFn: (arg?: Arg | Arg[]) => Uint8Array): StorageEntry {
|
||||
const storageFn = _storageFn as StorageEntry;
|
||||
|
||||
storageFn.meta = meta;
|
||||
@@ -107,7 +100,6 @@ function extendHeadMeta (registry: Registry, { meta: { documentation, name, type
|
||||
registry.createType('StorageKey', iterFn(...args), { method, section });
|
||||
}
|
||||
|
||||
// attach the full list hashing for prefixed maps
|
||||
/** @internal */
|
||||
function extendPrefixedMap (registry: Registry, itemFn: CreateItemFn, storageFn: StorageEntry): StorageEntry {
|
||||
const { meta: { type }, method, section } = itemFn;
|
||||
@@ -124,16 +116,16 @@ function extendPrefixedMap (registry: Registry, itemFn: CreateItemFn, storageFn:
|
||||
|
||||
if (args.length) {
|
||||
if (type.isDoubleMap) {
|
||||
return new Raw(registry, createKeyRaw(registry, itemFn, [type.asDoubleMap.key1], [getHasher(type.asDoubleMap.hasher)], args as CreateArgType[]));
|
||||
return new Raw(registry, createKeyRaw(registry, itemFn, [type.asDoubleMap.key1], [type.asDoubleMap.hasher], args as Arg[]));
|
||||
} else if (type.isNMap) {
|
||||
const keys = [...type.asNMap.keyVec];
|
||||
const hashers = type.asNMap.hashers.map((h) => getHasher(h));
|
||||
const hashers = [...type.asNMap.hashers];
|
||||
|
||||
// remove the last entry
|
||||
keys.pop();
|
||||
hashers.pop();
|
||||
|
||||
return new Raw(registry, createKeyRaw(registry, itemFn, keys, hashers, args as CreateArgType[]));
|
||||
return new Raw(registry, createKeyRaw(registry, itemFn, keys, hashers, args as Arg[]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,16 +143,16 @@ export function createFunction (registry: Registry, itemFn: CreateItemFn, option
|
||||
// - storage.system.account(address)
|
||||
// - storage.timestamp.blockPeriod()
|
||||
// For higher-map queries the params are passed in as an tuple, [key1, key2]
|
||||
const storageFn = expandWithMeta(itemFn, (arg?: CreateArgType | CreateArgType[]) =>
|
||||
const storageFn = expandWithMeta(itemFn, (arg?: Arg | Arg[]) =>
|
||||
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])
|
||||
? createKey(registry, itemFn, [type.asMap.key], [type.asMap.hasher], [arg as Arg])
|
||||
: 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[])
|
||||
? createKey(registry, itemFn, [type.asDoubleMap.key1, type.asDoubleMap.key2], [type.asDoubleMap.hasher, type.asDoubleMap.key2Hasher], arg as Arg[])
|
||||
: createKey(registry, itemFn, type.asNMap.keyVec, type.asNMap.hashers, arg as Arg[])
|
||||
);
|
||||
|
||||
if (type.isMap || type.isDoubleMap || type.isNMap) {
|
||||
|
||||
@@ -12,34 +12,36 @@ import { decorateStorage } from '..';
|
||||
const keyring = createTestPairs({ type: 'ed25519' });
|
||||
|
||||
describe('decorateStorage', (): void => {
|
||||
describe('latest', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
const query = decorateStorage(registry, metadata.asLatest, metadata.version);
|
||||
const query = decorateStorage(registry, metadata.asLatest, metadata.version);
|
||||
|
||||
it('should throw if the storage function expects an argument', (): void => {
|
||||
expect(() => query.balances.account()).toThrowError('Call to balances.account has a null or undefined argument at position 0');
|
||||
});
|
||||
it('should throw if the storage function expects an argument', (): void => {
|
||||
expect(() => query.balances.account()).toThrowError('Call to balances.account needs 1 arguments, found []');
|
||||
});
|
||||
|
||||
it('should throw if the storage function expects multiple arguments', (): void => {
|
||||
expect(() => query.staking.erasStakers([1])).toThrowError('Call to staking.erasStakers needs 2 arguments, found [1]');
|
||||
});
|
||||
it('should throw if the storage function expects multiple arguments', (): void => {
|
||||
expect(() => query.staking.erasStakers([1])).toThrowError('Call to staking.erasStakers needs 2 arguments, found [1]');
|
||||
});
|
||||
|
||||
it('should return a value if the storage function does not expect an argument', (): void => {
|
||||
expect(() => query.timestamp.now()).not.toThrow();
|
||||
});
|
||||
it('should throw if the storage function expects tuple arguments', (): void => {
|
||||
expect(() => query.staking.erasStakers(1)).toThrowError('Call to staking.erasStakers needs 2 arguments, provided in tuple format');
|
||||
});
|
||||
|
||||
it('should return the correct length-prefixed storage key', (): void => {
|
||||
expect(
|
||||
u8aToHex(
|
||||
query.system.account(keyring.alice.address)
|
||||
)
|
||||
).toEqual(
|
||||
'0x410126aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'
|
||||
);
|
||||
});
|
||||
it('should return a value if the storage function does not expect an argument', (): void => {
|
||||
expect(() => query.timestamp.now()).not.toThrow();
|
||||
});
|
||||
|
||||
it('should return the correct length-prefixed storage key', (): void => {
|
||||
expect(
|
||||
u8aToHex(
|
||||
query.system.account(keyring.alice.address)
|
||||
)
|
||||
).toEqual(
|
||||
'0x410126aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,30 +32,23 @@ const HASHER_MAP: Record<keyof typeof AllHashers, [number, boolean]> = {
|
||||
Twox64Concat: [8, true]
|
||||
};
|
||||
|
||||
function getStorageType (type: StorageEntryTypeLatest): [boolean, string] {
|
||||
if (type.isMap) {
|
||||
return [false, type.asMap.value.toString()];
|
||||
} else if (type.isDoubleMap) {
|
||||
return [false, type.asDoubleMap.value.toString()];
|
||||
} else if (type.isNMap) {
|
||||
return [false, type.asNMap.value.toString()];
|
||||
}
|
||||
|
||||
return [false, type.asPlain.toString()];
|
||||
}
|
||||
|
||||
// we unwrap the type here, turning into an output usable for createType
|
||||
/** @internal */
|
||||
export function unwrapStorageType (type: StorageEntryTypeLatest, isOptional?: boolean): keyof InterfaceTypes {
|
||||
const [hasWrapper, outputType] = getStorageType(type);
|
||||
const outputType = type.isPlain
|
||||
? type.asPlain.toString()
|
||||
: type.isMap
|
||||
? type.asMap.value.toString()
|
||||
: type.isDoubleMap
|
||||
? type.asDoubleMap.value.toString()
|
||||
: type.asNMap.value.toString();
|
||||
|
||||
return isOptional && !hasWrapper
|
||||
return isOptional
|
||||
? `Option<${outputType}>` as keyof InterfaceTypes
|
||||
: outputType as keyof InterfaceTypes;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any]): Decoded {
|
||||
function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, unknown]): Decoded {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
if (value instanceof StorageKey) {
|
||||
return {
|
||||
@@ -73,12 +66,12 @@ function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [Storage
|
||||
section: value.section
|
||||
};
|
||||
} else if (Array.isArray(value)) {
|
||||
const [fn, ...arg] = value as [StorageEntry, ...any[]];
|
||||
const [fn, arg] = value;
|
||||
|
||||
assert(isFunction(fn), 'Expected function input for key construction');
|
||||
|
||||
return {
|
||||
key: fn(...arg),
|
||||
key: fn(arg),
|
||||
method: fn.method,
|
||||
section: fn.section
|
||||
};
|
||||
@@ -134,7 +127,7 @@ function decodeArgsFromMeta <A extends AnyTuple> (registry: Registry, value: Uin
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function getMeta (value: StorageKey | StorageEntry | [StorageEntry, any]): StorageEntryMetadataLatest | undefined {
|
||||
function getMeta (value: StorageKey | StorageEntry | [StorageEntry, unknown]): StorageEntryMetadataLatest | undefined {
|
||||
if (value instanceof StorageKey) {
|
||||
return value.meta;
|
||||
} else if (isFunction(value)) {
|
||||
@@ -149,7 +142,7 @@ function getMeta (value: StorageKey | StorageEntry | [StorageEntry, any]): Stora
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function getType (value: StorageKey | StorageEntry | [StorageEntry, any]): string {
|
||||
function getType (value: StorageKey | StorageEntry | [StorageEntry, unknown]): string {
|
||||
if (value instanceof StorageKey) {
|
||||
return value.outputType;
|
||||
} else if (isFunction(value)) {
|
||||
@@ -185,7 +178,7 @@ export class StorageKey<A extends AnyTuple = AnyTuple> extends Bytes implements
|
||||
|
||||
private _section?: string;
|
||||
|
||||
constructor (registry: Registry, value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any], override: Partial<StorageKeyExtra> = {}) {
|
||||
constructor (registry: Registry, value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, unknown], override: Partial<StorageKeyExtra> = {}) {
|
||||
const { key, method, section } = decodeStorageKey(value);
|
||||
|
||||
super(registry, key);
|
||||
|
||||
Reference in New Issue
Block a user