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:
Jaco Greeff
2020-03-12 07:29:18 +01:00
committed by GitHub
parent a5725d7bd6
commit b8cdfd226a
4 changed files with 147 additions and 4 deletions
+1
View File
@@ -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
+3 -4
View File
@@ -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
+95
View File
@@ -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');
});
});
+48
View File
@@ -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];
}