Skip to content

Commit b8cdfd2

Browse files
authored
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
1 parent a5725d7 commit b8cdfd2

File tree

4 files changed

+147
-4
lines changed

4 files changed

+147
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Added `derive.eras*` interfaces for queries to new Substrate staking interfaces
99
- Update `derive.account` to cater for new indices module storage (detected, fallbacks)
1010
- Adjust derive queries for session move away from module prefix (DoubleMap -> Map), detected on use
11+
- Add runtime validation for map arguments to `api.query.*`
1112

1213
# 1.5.1 Mar 06, 2020
1314

packages/api/src/base/Decorate.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import StorageKey, { StorageEntry, unwrapStorageType } from '@polkadot/types/pri
2525
import { assert, compactStripLength, isNull, isUndefined, u8aConcat, u8aToHex } from '@polkadot/util';
2626

2727
import { createSubmittable } from '../submittable';
28-
import { decorateSections, DeriveAllSections } from '../util/decorate';
2928
import augmentObject from '../util/augmentObject';
29+
import { decorateSections, DeriveAllSections } from '../util/decorate';
30+
import { extractStorageArgs } from '../util/validate';
3031
import Events from './Events';
3132

3233
interface MetaDecoration {
@@ -292,9 +293,7 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
292293

293294
private decorateStorageEntry<ApiType extends ApiTypes> (creator: StorageEntry, decorateMethod: DecorateMethod<ApiType>): QueryableStorageEntry<ApiType> {
294295
// get the storage arguments, with DoubleMap as an array entry, otherwise spread
295-
const getArgs = (...args: any[]): any[] => creator.meta.type.isDoubleMap
296-
? [creator, args]
297-
: [creator, ...args];
296+
const getArgs = (...args: any[]): any[] => extractStorageArgs(creator, args);
298297

299298
// FIXME We probably want to be able to query the full list with non-subs as well
300299
const decorated = this.hasSubscriptions && creator.iterKey && creator.meta.type.isMap && creator.meta.type.asMap.linked.isTrue
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2017-2019 @polkadot/api authors & contributors
2+
// This software may be modified and distributed under the terms
3+
// of the Apache-2.0 license. See the LICENSE file for details.
4+
5+
import { Storage } from '@polkadot/metadata/Decorated/types';
6+
7+
import storageFromMeta from '@polkadot/metadata/Decorated/storage/fromMetadata';
8+
import Metadata from '@polkadot/metadata/Metadata';
9+
import metaStatic from '@polkadot/metadata/Metadata/static';
10+
import { TypeRegistry } from '@polkadot/types';
11+
12+
import { extractStorageArgs } from './validate';
13+
14+
describe('extractStorageArgs', (): void => {
15+
const registry = new TypeRegistry();
16+
let storage: Storage;
17+
18+
beforeEach((): void => {
19+
const metadata = new Metadata(registry, metaStatic);
20+
21+
storage = storageFromMeta(registry, metadata);
22+
});
23+
24+
it('validates no-arg plain', (): void => {
25+
expect(
26+
extractStorageArgs(storage.timestamp.now, [])
27+
).toEqual([storage.timestamp.now]);
28+
});
29+
30+
it('validates no-arg plain (failing when there are args)', (): void => {
31+
expect(
32+
(): any[] =>
33+
extractStorageArgs(storage.timestamp.now, [123, 456])
34+
).toThrow('timestamp.now() does not take any arguments, 2 found');
35+
});
36+
37+
it('validates map, 1 arg', (): void => {
38+
expect(
39+
extractStorageArgs(storage.staking.payee, ['abc'])
40+
).toEqual([storage.staking.payee, 'abc']);
41+
});
42+
43+
it('validates map, 1 arg (failing with no args)', (): void => {
44+
expect(
45+
(): any =>
46+
extractStorageArgs(storage.staking.payee, [])
47+
).toThrow('staking.payee(AccountId) is a map, requiring 1 argument, 0 found');
48+
});
49+
50+
it('validates map, 1 arg (failing with no args)', (): void => {
51+
expect(
52+
(): any =>
53+
extractStorageArgs(storage.staking.payee, ['abc', 'def'])
54+
).toThrow('staking.payee(AccountId) is a map, requiring 1 argument, 2 found');
55+
});
56+
57+
it('validates doublemap, 2 args', (): void => {
58+
expect(
59+
extractStorageArgs(storage.staking.erasStakers, [1, '0x1234'])
60+
).toEqual([storage.staking.erasStakers, [1, '0x1234']]);
61+
});
62+
63+
it('validates doublemap, 2 args (failing with no args)', (): void => {
64+
expect(
65+
(): any =>
66+
extractStorageArgs(storage.staking.erasStakers, [])
67+
).toThrow('staking.erasStakers(EraIndex, AccountId) is a doublemap, requiring 2 arguments, 0 found');
68+
});
69+
70+
it('validates doublemap, 2 args (failing with 1 arg)', (): void => {
71+
expect(
72+
(): any =>
73+
extractStorageArgs(storage.staking.erasStakers, [123])
74+
).toThrow('staking.erasStakers(EraIndex, AccountId) is a doublemap, requiring 2 arguments, 1 found');
75+
});
76+
77+
it('validates linked map, no args', (): void => {
78+
expect(
79+
extractStorageArgs(storage.staking.validators, [])
80+
).toEqual([storage.staking.validators]);
81+
});
82+
83+
it('validates linked map, single arg', (): void => {
84+
expect(
85+
extractStorageArgs(storage.staking.validators, [123])
86+
).toEqual([storage.staking.validators, 123]);
87+
});
88+
89+
it('validates linked map (failing with extra args)', (): void => {
90+
expect(
91+
(): any[] =>
92+
extractStorageArgs(storage.staking.validators, [123, 456])
93+
).toThrow('staking.validators(AccountId) is a linked map, requiring either 0 arguments (retrieving all) or 1 argument, 2 found');
94+
});
95+
});

packages/api/src/util/validate.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2017-2019 @polkadot/api authors & contributors
2+
// This software may be modified and distributed under the terms
3+
// of the Apache-2.0 license. See the LICENSE file for details.
4+
5+
import { StorageEntry } from '@polkadot/types/primitive/StorageKey';
6+
import { Type } from '@polkadot/types';
7+
import { assert } from '@polkadot/util';
8+
9+
function sig ({ method, section }: StorageEntry, ...args: Type[]): string {
10+
return `${section}.${method}(${args.join(', ')})`;
11+
}
12+
13+
function doDoubleMap (creator: StorageEntry, args: any[]): [StorageEntry, [any, any]] {
14+
const { key1, key2 } = creator.meta.type.asDoubleMap;
15+
16+
assert(args.length === 2, `${sig(creator, key1, key2)} is a doublemap, requiring 2 arguments, ${args.length} found`);
17+
18+
// pass as tuple
19+
return [creator, args as [any, any]];
20+
}
21+
22+
function doMap (creator: StorageEntry, args: any[]): [StorageEntry] | [StorageEntry, any] {
23+
const { key, linked } = creator.meta.type.asMap;
24+
25+
linked.isTrue
26+
? assert(args.length <= 1, `${sig(creator, key)} is a linked map, requiring either 0 arguments (retrieving all) or 1 argument, ${args.length} found`)
27+
: assert(args.length === 1, `${sig(creator, key)} is a map, requiring 1 argument, ${args.length} found`);
28+
29+
// expand
30+
return args.length
31+
? [creator, args[0]]
32+
: [creator];
33+
}
34+
35+
// sets up the arguments in the form of [creator, args] ready to be used in a storage
36+
// call. Additionally, it verifies that the correct number of arguments have been passed
37+
export function extractStorageArgs (creator: StorageEntry, args: any[]): any[] {
38+
if (creator.meta.type.isDoubleMap) {
39+
return doDoubleMap(creator, args);
40+
} else if (creator.meta.type.isMap) {
41+
return doMap(creator, args);
42+
}
43+
44+
assert(args.length === 0, `${sig(creator)} does not take any arguments, ${args.length} found`);
45+
46+
// no args
47+
return [creator];
48+
}

0 commit comments

Comments
 (0)