Validate storage args (#1507)
* Validate storage args * Fix linting * Update imports * Update type extraction * Move storage extraction to util * Re-add CHANGELOG entry * Add DoubleMap tests * Adjust error for linked * formatting * Split map/doublemap * Cleanups * Update error sigs * Just join
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- Added `derive.eras*` interfaces for queries to new Substrate staking interfaces
|
||||
- Update `derive.account` to cater for new indices module storage (detected, fallbacks)
|
||||
- Adjust derive queries for session move away from module prefix (DoubleMap -> Map), detected on use
|
||||
- Add runtime validation for map arguments to `api.query.*`
|
||||
|
||||
# 1.5.1 Mar 06, 2020
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@ import StorageKey, { StorageEntry, unwrapStorageType } from '@polkadot/types/pri
|
||||
import { assert, compactStripLength, isNull, isUndefined, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { createSubmittable } from '../submittable';
|
||||
import { decorateSections, DeriveAllSections } from '../util/decorate';
|
||||
import augmentObject from '../util/augmentObject';
|
||||
import { decorateSections, DeriveAllSections } from '../util/decorate';
|
||||
import { extractStorageArgs } from '../util/validate';
|
||||
import Events from './Events';
|
||||
|
||||
interface MetaDecoration {
|
||||
@@ -292,9 +293,7 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
|
||||
|
||||
private decorateStorageEntry<ApiType extends ApiTypes> (creator: StorageEntry, decorateMethod: DecorateMethod<ApiType>): QueryableStorageEntry<ApiType> {
|
||||
// get the storage arguments, with DoubleMap as an array entry, otherwise spread
|
||||
const getArgs = (...args: any[]): any[] => creator.meta.type.isDoubleMap
|
||||
? [creator, args]
|
||||
: [creator, ...args];
|
||||
const getArgs = (...args: any[]): any[] => extractStorageArgs(creator, args);
|
||||
|
||||
// FIXME We probably want to be able to query the full list with non-subs as well
|
||||
const decorated = this.hasSubscriptions && creator.iterKey && creator.meta.type.isMap && creator.meta.type.asMap.linked.isTrue
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Storage } from '@polkadot/metadata/Decorated/types';
|
||||
|
||||
import storageFromMeta from '@polkadot/metadata/Decorated/storage/fromMetadata';
|
||||
import Metadata from '@polkadot/metadata/Metadata';
|
||||
import metaStatic from '@polkadot/metadata/Metadata/static';
|
||||
import { TypeRegistry } from '@polkadot/types';
|
||||
|
||||
import { extractStorageArgs } from './validate';
|
||||
|
||||
describe('extractStorageArgs', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
let storage: Storage;
|
||||
|
||||
beforeEach((): void => {
|
||||
const metadata = new Metadata(registry, metaStatic);
|
||||
|
||||
storage = storageFromMeta(registry, metadata);
|
||||
});
|
||||
|
||||
it('validates no-arg plain', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(storage.timestamp.now, [])
|
||||
).toEqual([storage.timestamp.now]);
|
||||
});
|
||||
|
||||
it('validates no-arg plain (failing when there are args)', (): void => {
|
||||
expect(
|
||||
(): any[] =>
|
||||
extractStorageArgs(storage.timestamp.now, [123, 456])
|
||||
).toThrow('timestamp.now() does not take any arguments, 2 found');
|
||||
});
|
||||
|
||||
it('validates map, 1 arg', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(storage.staking.payee, ['abc'])
|
||||
).toEqual([storage.staking.payee, 'abc']);
|
||||
});
|
||||
|
||||
it('validates map, 1 arg (failing with no args)', (): void => {
|
||||
expect(
|
||||
(): any =>
|
||||
extractStorageArgs(storage.staking.payee, [])
|
||||
).toThrow('staking.payee(AccountId) is a map, requiring 1 argument, 0 found');
|
||||
});
|
||||
|
||||
it('validates map, 1 arg (failing with no args)', (): void => {
|
||||
expect(
|
||||
(): any =>
|
||||
extractStorageArgs(storage.staking.payee, ['abc', 'def'])
|
||||
).toThrow('staking.payee(AccountId) is a map, requiring 1 argument, 2 found');
|
||||
});
|
||||
|
||||
it('validates doublemap, 2 args', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(storage.staking.erasStakers, [1, '0x1234'])
|
||||
).toEqual([storage.staking.erasStakers, [1, '0x1234']]);
|
||||
});
|
||||
|
||||
it('validates doublemap, 2 args (failing with no args)', (): void => {
|
||||
expect(
|
||||
(): any =>
|
||||
extractStorageArgs(storage.staking.erasStakers, [])
|
||||
).toThrow('staking.erasStakers(EraIndex, AccountId) is a doublemap, 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');
|
||||
});
|
||||
|
||||
it('validates linked map, no args', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(storage.staking.validators, [])
|
||||
).toEqual([storage.staking.validators]);
|
||||
});
|
||||
|
||||
it('validates linked map, single arg', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(storage.staking.validators, [123])
|
||||
).toEqual([storage.staking.validators, 123]);
|
||||
});
|
||||
|
||||
it('validates linked map (failing with extra args)', (): void => {
|
||||
expect(
|
||||
(): any[] =>
|
||||
extractStorageArgs(storage.staking.validators, [123, 456])
|
||||
).toThrow('staking.validators(AccountId) is a linked map, requiring either 0 arguments (retrieving all) or 1 argument, 2 found');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { StorageEntry } from '@polkadot/types/primitive/StorageKey';
|
||||
import { Type } from '@polkadot/types';
|
||||
import { assert } from '@polkadot/util';
|
||||
|
||||
function sig ({ method, section }: StorageEntry, ...args: Type[]): string {
|
||||
return `${section}.${method}(${args.join(', ')})`;
|
||||
}
|
||||
|
||||
function doDoubleMap (creator: StorageEntry, args: any[]): [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`);
|
||||
|
||||
// pass as tuple
|
||||
return [creator, args as [any, any]];
|
||||
}
|
||||
|
||||
function doMap (creator: StorageEntry, args: any[]): [StorageEntry] | [StorageEntry, any] {
|
||||
const { key, linked } = creator.meta.type.asMap;
|
||||
|
||||
linked.isTrue
|
||||
? assert(args.length <= 1, `${sig(creator, key)} is a linked map, requiring either 0 arguments (retrieving all) or 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];
|
||||
}
|
||||
|
||||
// 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: any[]): any[] {
|
||||
if (creator.meta.type.isDoubleMap) {
|
||||
return doDoubleMap(creator, args);
|
||||
} else if (creator.meta.type.isMap) {
|
||||
return doMap(creator, args);
|
||||
}
|
||||
|
||||
assert(args.length === 0, `${sig(creator)} does not take any arguments, ${args.length} found`);
|
||||
|
||||
// no args
|
||||
return [creator];
|
||||
}
|
||||
Reference in New Issue
Block a user