UNPKG

7.26 kBJavaScriptView Raw
1import { Raw } from '@polkadot/types-codec';
2import { compactAddLength, compactStripLength, isUndefined, objectSpread, stringCamelCase, u8aConcat, u8aToU8a } from '@polkadot/util';
3import { xxhashAsU8a } from '@polkadot/util-crypto';
4import { getSiName } from '../../util/index.js';
5import { getHasher } from './getHasher.js';
6export const NO_RAW_ARGS = {
7 args: [],
8 hashers: [],
9 keys: []
10};
11/** @internal */
12function filterDefined(a) {
13 return !isUndefined(a);
14}
15/** @internal */
16function assertArgs({ method, section }, { args, keys }) {
17 if (!Array.isArray(args)) {
18 throw new Error(`Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments`);
19 }
20 else if (args.filter(filterDefined).length !== keys.length) {
21 throw new Error(`Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, found [${args.join(', ')}]`);
22 }
23}
24/** @internal */
25export function createKeyRawParts(registry, itemFn, { args, hashers, keys }) {
26 const count = keys.length;
27 const extra = new Array(count);
28 for (let i = 0; i < count; i++) {
29 extra[i] = getHasher(hashers[i])(registry.createTypeUnsafe(registry.createLookupType(keys[i]), [args[i]]).toU8a());
30 }
31 return [
32 [
33 xxhashAsU8a(itemFn.prefix, 128),
34 xxhashAsU8a(itemFn.method, 128)
35 ],
36 extra
37 ];
38}
39/** @internal */
40export function createKeyInspect(registry, itemFn, args) {
41 assertArgs(itemFn, args);
42 const { meta } = itemFn;
43 const [prefix, extra] = createKeyRawParts(registry, itemFn, args);
44 let types = [];
45 if (meta.type.isMap) {
46 const { hashers, key } = meta.type.asMap;
47 types = hashers.length === 1
48 ? [`${hashers[0].type}(${getSiName(registry.lookup, key)})`]
49 : registry.lookup.getSiType(key).def.asTuple.map((k, i) => `${hashers[i].type}(${getSiName(registry.lookup, k)})`);
50 }
51 const names = ['module', 'method'].concat(...args.args.map((_, i) => types[i]));
52 return {
53 inner: prefix
54 .concat(...extra)
55 .map((v, i) => ({ name: names[i], outer: [v] }))
56 };
57}
58/** @internal */
59export function createKeyRaw(registry, itemFn, args) {
60 const [prefix, extra] = createKeyRawParts(registry, itemFn, args);
61 return u8aConcat(...prefix, ...extra);
62}
63/** @internal */
64function createKey(registry, itemFn, args) {
65 assertArgs(itemFn, args);
66 // always add the length prefix (underlying it is Bytes)
67 return compactAddLength(createKeyRaw(registry, itemFn, args));
68}
69/** @internal */
70function createStorageInspect(registry, itemFn, options) {
71 const { meta: { type } } = itemFn;
72 return (...args) => {
73 if (type.isPlain) {
74 return options.skipHashing
75 ? { inner: [], name: 'wellKnown', outer: [u8aToU8a(options.key)] }
76 : createKeyInspect(registry, itemFn, NO_RAW_ARGS);
77 }
78 const { hashers, key } = type.asMap;
79 return hashers.length === 1
80 ? createKeyInspect(registry, itemFn, { args, hashers, keys: [key] })
81 : createKeyInspect(registry, itemFn, { args, hashers, keys: registry.lookup.getSiType(key).def.asTuple });
82 };
83}
84/** @internal */
85function createStorageFn(registry, itemFn, options) {
86 const { meta: { type } } = itemFn;
87 let cacheKey = null;
88 // Can only have zero or one argument:
89 // - storage.system.account(address)
90 // - storage.timestamp.blockPeriod()
91 // For higher-map queries the params are passed in as an tuple, [key1, key2]
92 return (...args) => {
93 if (type.isPlain) {
94 if (!cacheKey) {
95 cacheKey = options.skipHashing
96 ? compactAddLength(u8aToU8a(options.key))
97 : createKey(registry, itemFn, NO_RAW_ARGS);
98 }
99 return cacheKey;
100 }
101 const { hashers, key } = type.asMap;
102 return hashers.length === 1
103 ? createKey(registry, itemFn, { args, hashers, keys: [key] })
104 : createKey(registry, itemFn, { args, hashers, keys: registry.lookup.getSiType(key).def.asTuple });
105 };
106}
107/** @internal */
108function createWithMeta(registry, itemFn, options) {
109 const { meta, method, prefix, section } = itemFn;
110 const storageFn = createStorageFn(registry, itemFn, options);
111 storageFn.inspect = createStorageInspect(registry, itemFn, options);
112 storageFn.meta = meta;
113 storageFn.method = stringCamelCase(method);
114 storageFn.prefix = prefix;
115 storageFn.section = section;
116 // explicitly add the actual method in the toJSON, this gets used to determine caching and without it
117 // instances (e.g. collective) will not work since it is only matched on param meta
118 storageFn.toJSON = () => objectSpread({ storage: { method, prefix, section } }, meta.toJSON());
119 return storageFn;
120}
121/** @internal */
122function extendHeadMeta(registry, { meta: { docs, name, type }, section }, { method }, iterFn) {
123 // metadata with a fallback value using the type of the key, the normal
124 // meta fallback only applies to actual entry values, create one for head
125 const meta = registry.createTypeUnsafe('StorageEntryMetadataLatest', [{
126 docs,
127 fallback: registry.createTypeUnsafe('Bytes', []),
128 modifier: registry.createTypeUnsafe('StorageEntryModifierLatest', [1]),
129 name,
130 type: registry.createTypeUnsafe('StorageEntryTypeLatest', [type.asMap.key, 0])
131 }]);
132 iterFn.meta = meta;
133 const fn = (...args) => registry.createTypeUnsafe('StorageKey', [iterFn(...args), { method, section }]);
134 fn.meta = meta;
135 return fn;
136}
137/** @internal */
138function extendPrefixedMap(registry, itemFn, storageFn) {
139 const { meta: { type }, method, section } = itemFn;
140 storageFn.iterKey = extendHeadMeta(registry, itemFn, storageFn, (...args) => {
141 if (args.length && (type.isPlain || (args.length >= type.asMap.hashers.length))) {
142 throw new Error(`Iteration of ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs arguments to be at least one less than the full arguments, found [${args.join(', ')}]`);
143 }
144 if (args.length) {
145 if (type.isMap) {
146 const { hashers, key } = type.asMap;
147 const keysVec = hashers.length === 1
148 ? [key]
149 : registry.lookup.getSiType(key).def.asTuple;
150 return new Raw(registry, createKeyRaw(registry, itemFn, { args, hashers: hashers.slice(0, args.length), keys: keysVec.slice(0, args.length) }));
151 }
152 }
153 return new Raw(registry, createKeyRaw(registry, itemFn, NO_RAW_ARGS));
154 });
155 return storageFn;
156}
157/** @internal */
158export function createFunction(registry, itemFn, options) {
159 const { meta: { type } } = itemFn;
160 const storageFn = createWithMeta(registry, itemFn, options);
161 if (type.isMap) {
162 extendPrefixedMap(registry, itemFn, storageFn);
163 }
164 storageFn.keyPrefix = (...args) => (storageFn.iterKey && storageFn.iterKey(...args)) ||
165 compactStripLength(storageFn())[1];
166 return storageFn;
167}