1 | import { Raw } from '@polkadot/types-codec';
|
2 | import { compactAddLength, compactStripLength, isUndefined, objectSpread, stringCamelCase, u8aConcat, u8aToU8a } from '@polkadot/util';
|
3 | import { xxhashAsU8a } from '@polkadot/util-crypto';
|
4 | import { getSiName } from '../../util/index.js';
|
5 | import { getHasher } from './getHasher.js';
|
6 | export const NO_RAW_ARGS = {
|
7 | args: [],
|
8 | hashers: [],
|
9 | keys: []
|
10 | };
|
11 |
|
12 | function filterDefined(a) {
|
13 | return !isUndefined(a);
|
14 | }
|
15 |
|
16 | function 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 |
|
25 | export 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 |
|
40 | export 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 |
|
59 | export function createKeyRaw(registry, itemFn, args) {
|
60 | const [prefix, extra] = createKeyRawParts(registry, itemFn, args);
|
61 | return u8aConcat(...prefix, ...extra);
|
62 | }
|
63 |
|
64 | function createKey(registry, itemFn, args) {
|
65 | assertArgs(itemFn, args);
|
66 |
|
67 | return compactAddLength(createKeyRaw(registry, itemFn, args));
|
68 | }
|
69 |
|
70 | function 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 |
|
85 | function createStorageFn(registry, itemFn, options) {
|
86 | const { meta: { type } } = itemFn;
|
87 | let cacheKey = null;
|
88 |
|
89 |
|
90 |
|
91 |
|
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 |
|
108 | function 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 |
|
117 |
|
118 | storageFn.toJSON = () => objectSpread({ storage: { method, prefix, section } }, meta.toJSON());
|
119 | return storageFn;
|
120 | }
|
121 |
|
122 | function extendHeadMeta(registry, { meta: { docs, name, type }, section }, { method }, iterFn) {
|
123 |
|
124 |
|
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 |
|
138 | function 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 |
|
158 | export 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 | }
|