UNPKG

20.9 kBJavaScriptView Raw
1import { DoNotConstruct, Json, Raw } from '@polkadot/types-codec';
2import { constructTypeClass, createClassUnsafe, createTypeUnsafe } from '@polkadot/types-create';
3import { assertReturn, BN_ZERO, formatBalance, isBn, isFunction, isNumber, isString, isU8a, lazyMethod, logger, objectSpread, stringCamelCase, stringify } from '@polkadot/util';
4import { blake2AsU8a } from '@polkadot/util-crypto';
5import { expandExtensionTypes, fallbackExtensions, findUnknownExtensions } from '../extrinsic/signedExtensions/index.js';
6import { GenericEventData } from '../generic/Event.js';
7import * as baseTypes from '../index.types.js';
8import * as definitions from '../interfaces/definitions.js';
9import { createCallFunction } from '../metadata/decorate/extrinsics/index.js';
10import { decorateConstants, filterCallsSome, filterEventsSome } from '../metadata/decorate/index.js';
11import { Metadata } from '../metadata/Metadata.js';
12import { PortableRegistry } from '../metadata/PortableRegistry/index.js';
13import { lazyVariants } from './lazy.js';
14const DEFAULT_FIRST_CALL_IDX = new Uint8Array(2);
15const l = logger('registry');
16function sortDecimalStrings(a, b) {
17 return parseInt(a, 10) - parseInt(b, 10);
18}
19function valueToString(v) {
20 return v.toString();
21}
22function getFieldArgs(lookup, fields) {
23 const count = fields.length;
24 const args = new Array(count);
25 for (let i = 0; i < count; i++) {
26 args[i] = lookup.getTypeDef(fields[i].type).type;
27 }
28 return args;
29}
30function clearRecord(record) {
31 const keys = Object.keys(record);
32 for (let i = 0, count = keys.length; i < count; i++) {
33 delete record[keys[i]];
34 }
35}
36function getVariantStringIdx({ index }) {
37 return index.toString();
38}
39function injectErrors(_, { lookup, pallets }, version, result) {
40 clearRecord(result);
41 for (let i = 0, count = pallets.length; i < count; i++) {
42 const { errors, index, name } = pallets[i];
43 if (errors.isSome) {
44 const sectionName = stringCamelCase(name);
45 lazyMethod(result, version >= 12 ? index.toNumber() : i, () => lazyVariants(lookup, errors.unwrap(), getVariantStringIdx, ({ docs, fields, index, name }) => ({
46 args: getFieldArgs(lookup, fields),
47 docs: docs.map(valueToString),
48 fields,
49 index: index.toNumber(),
50 method: name.toString(),
51 name: name.toString(),
52 section: sectionName
53 })));
54 }
55 }
56}
57function injectEvents(registry, { lookup, pallets }, version, result) {
58 const filtered = pallets.filter(filterEventsSome);
59 clearRecord(result);
60 for (let i = 0, count = filtered.length; i < count; i++) {
61 const { events, index, name } = filtered[i];
62 lazyMethod(result, version >= 12 ? index.toNumber() : i, () => lazyVariants(lookup, events.unwrap(), getVariantStringIdx, (variant) => {
63 const meta = registry.createType('EventMetadataLatest', objectSpread({}, variant, { args: getFieldArgs(lookup, variant.fields) }));
64 return class extends GenericEventData {
65 constructor(registry, value) {
66 super(registry, value, meta, stringCamelCase(name), variant.name.toString());
67 }
68 };
69 }));
70 }
71}
72function injectExtrinsics(registry, { lookup, pallets }, version, result, mapping) {
73 const filtered = pallets.filter(filterCallsSome);
74 clearRecord(result);
75 clearRecord(mapping);
76 for (let i = 0, count = filtered.length; i < count; i++) {
77 const { calls, index, name } = filtered[i];
78 const sectionIndex = version >= 12 ? index.toNumber() : i;
79 const sectionName = stringCamelCase(name);
80 const allCalls = calls.unwrap();
81 lazyMethod(result, sectionIndex, () => lazyVariants(lookup, allCalls, getVariantStringIdx, (variant) => createCallFunction(registry, lookup, variant, sectionName, sectionIndex)));
82 const { path } = registry.lookup.getSiType(allCalls.type);
83 // frame_system::pallet::Call / pallet_balances::pallet::Call / polkadot_runtime_parachains::configuration::pallet::Call /
84 const palletIdx = path.findIndex((v) => v.eq('pallet'));
85 if (palletIdx !== -1) {
86 const name = stringCamelCase(path
87 .slice(0, palletIdx)
88 .map((p, i) => i === 0
89 // frame_system || pallet_balances
90 ? p.replace(/^(frame|pallet)_/, '')
91 : p)
92 .join(' '));
93 if (!mapping[name]) {
94 mapping[name] = [sectionName];
95 }
96 else {
97 mapping[name].push(sectionName);
98 }
99 }
100 }
101}
102function extractProperties(registry, metadata) {
103 const original = registry.getChainProperties();
104 const constants = decorateConstants(registry, metadata.asLatest, metadata.version);
105 const ss58Format = constants['system'] && (constants['system']['sS58Prefix'] || constants['system']['ss58Prefix']);
106 if (!ss58Format) {
107 return original;
108 }
109 const { isEthereum, tokenDecimals, tokenSymbol } = original || {};
110 return registry.createTypeUnsafe('ChainProperties', [{ isEthereum, ss58Format, tokenDecimals, tokenSymbol }]);
111}
112export class TypeRegistry {
113 __internal__chainProperties;
114 __internal__classes = new Map();
115 __internal__definitions = new Map();
116 __internal__firstCallIndex = null;
117 __internal__hasher = blake2AsU8a;
118 __internal__knownTypes = {};
119 __internal__lookup;
120 __internal__metadata;
121 __internal__metadataVersion = 0;
122 __internal__signedExtensions = fallbackExtensions;
123 __internal__unknownTypes = new Map();
124 __internal__userExtensions;
125 __internal__knownDefaults;
126 __internal__knownDefaultsEntries;
127 __internal__knownDefinitions;
128 __internal__metadataCalls = {};
129 __internal__metadataErrors = {};
130 __internal__metadataEvents = {};
131 __internal__moduleMap = {};
132 createdAtHash;
133 constructor(createdAtHash) {
134 this.__internal__knownDefaults = objectSpread({ Json, Metadata, PortableRegistry, Raw }, baseTypes);
135 this.__internal__knownDefaultsEntries = Object.entries(this.__internal__knownDefaults);
136 this.__internal__knownDefinitions = definitions;
137 const allKnown = Object.values(this.__internal__knownDefinitions);
138 for (let i = 0, count = allKnown.length; i < count; i++) {
139 this.register(allKnown[i].types);
140 }
141 if (createdAtHash) {
142 this.createdAtHash = this.createType('BlockHash', createdAtHash);
143 }
144 }
145 get chainDecimals() {
146 if (this.__internal__chainProperties?.tokenDecimals.isSome) {
147 const allDecimals = this.__internal__chainProperties.tokenDecimals.unwrap();
148 if (allDecimals.length) {
149 return allDecimals.map((b) => b.toNumber());
150 }
151 }
152 return [12];
153 }
154 get chainIsEthereum() {
155 return this.__internal__chainProperties?.isEthereum.isTrue || false;
156 }
157 get chainSS58() {
158 return this.__internal__chainProperties?.ss58Format.isSome
159 ? this.__internal__chainProperties.ss58Format.unwrap().toNumber()
160 : undefined;
161 }
162 get chainTokens() {
163 if (this.__internal__chainProperties?.tokenSymbol.isSome) {
164 const allTokens = this.__internal__chainProperties.tokenSymbol.unwrap();
165 if (allTokens.length) {
166 return allTokens.map(valueToString);
167 }
168 }
169 return [formatBalance.getDefaults().unit];
170 }
171 get firstCallIndex() {
172 return this.__internal__firstCallIndex || DEFAULT_FIRST_CALL_IDX;
173 }
174 /**
175 * @description Returns true if the type is in a Compat format
176 */
177 isLookupType(value) {
178 return /Lookup\d+$/.test(value);
179 }
180 /**
181 * @description Creates a lookup string from the supplied id
182 */
183 createLookupType(lookupId) {
184 return `Lookup${typeof lookupId === 'number' ? lookupId : lookupId.toNumber()}`;
185 }
186 get knownTypes() {
187 return this.__internal__knownTypes;
188 }
189 get lookup() {
190 return assertReturn(this.__internal__lookup, 'PortableRegistry has not been set on this registry');
191 }
192 get metadata() {
193 return assertReturn(this.__internal__metadata, 'Metadata has not been set on this registry');
194 }
195 get unknownTypes() {
196 return [...this.__internal__unknownTypes.keys()];
197 }
198 get signedExtensions() {
199 return this.__internal__signedExtensions;
200 }
201 clearCache() {
202 this.__internal__classes = new Map();
203 }
204 /**
205 * @describe Creates an instance of the class
206 */
207 createClass(type) {
208 return createClassUnsafe(this, type);
209 }
210 /**
211 * @describe Creates an instance of the class
212 */
213 createClassUnsafe(type) {
214 return createClassUnsafe(this, type);
215 }
216 /**
217 * @description Creates an instance of a type as registered
218 */
219 createType(type, ...params) {
220 return createTypeUnsafe(this, type, params);
221 }
222 /**
223 * @description Creates an instance of a type as registered
224 */
225 createTypeUnsafe(type, params, options) {
226 return createTypeUnsafe(this, type, params, options);
227 }
228 // find a specific call
229 findMetaCall(callIndex) {
230 const [section, method] = [callIndex[0], callIndex[1]];
231 return assertReturn(this.__internal__metadataCalls[`${section}`] && this.__internal__metadataCalls[`${section}`][`${method}`], () => `findMetaCall: Unable to find Call with index [${section}, ${method}]/[${callIndex.toString()}]`);
232 }
233 // finds an error
234 findMetaError(errorIndex) {
235 const [section, method] = isU8a(errorIndex)
236 ? [errorIndex[0], errorIndex[1]]
237 : [
238 errorIndex.index.toNumber(),
239 isU8a(errorIndex.error)
240 ? errorIndex.error[0]
241 : errorIndex.error.toNumber()
242 ];
243 return assertReturn(this.__internal__metadataErrors[`${section}`] && this.__internal__metadataErrors[`${section}`][`${method}`], () => `findMetaError: Unable to find Error with index [${section}, ${method}]/[${errorIndex.toString()}]`);
244 }
245 findMetaEvent(eventIndex) {
246 const [section, method] = [eventIndex[0], eventIndex[1]];
247 return assertReturn(this.__internal__metadataEvents[`${section}`] && this.__internal__metadataEvents[`${section}`][`${method}`], () => `findMetaEvent: Unable to find Event with index [${section}, ${method}]/[${eventIndex.toString()}]`);
248 }
249 get(name, withUnknown, knownTypeDef) {
250 return this.getUnsafe(name, withUnknown, knownTypeDef);
251 }
252 getUnsafe(name, withUnknown, knownTypeDef) {
253 let Type = this.__internal__classes.get(name) || this.__internal__knownDefaults[name];
254 // we have not already created the type, attempt it
255 if (!Type) {
256 const definition = this.__internal__definitions.get(name);
257 let BaseType;
258 // we have a definition, so create the class now (lazily)
259 if (definition) {
260 BaseType = createClassUnsafe(this, definition);
261 }
262 else if (knownTypeDef) {
263 BaseType = constructTypeClass(this, knownTypeDef);
264 }
265 else if (withUnknown) {
266 l.warn(`Unable to resolve type ${name}, it will fail on construction`);
267 this.__internal__unknownTypes.set(name, true);
268 BaseType = DoNotConstruct.with(name);
269 }
270 if (BaseType) {
271 // NOTE If we didn't extend here, we would have strange artifacts. An example is
272 // Balance, with this, new Balance() instanceof u128 is true, but Balance !== u128
273 // Additionally, we now pass through the registry, which is a link to ourselves
274 Type = class extends BaseType {
275 };
276 this.__internal__classes.set(name, Type);
277 // In the case of lookups, we also want to store the actual class against
278 // the lookup name, instad of having to traverse again
279 if (knownTypeDef && isNumber(knownTypeDef.lookupIndex)) {
280 this.__internal__classes.set(this.createLookupType(knownTypeDef.lookupIndex), Type);
281 }
282 }
283 }
284 return Type;
285 }
286 getChainProperties() {
287 return this.__internal__chainProperties;
288 }
289 getClassName(Type) {
290 // we cannot rely on export order (anymore, since babel/core 7.15.8), so in the case of
291 // items such as u32 & U32, we get the lowercase versions here... not quite as optimal
292 // (previously this used to be a simple find & return)
293 const names = [];
294 for (const [name, Clazz] of this.__internal__knownDefaultsEntries) {
295 if (Type === Clazz) {
296 names.push(name);
297 }
298 }
299 for (const [name, Clazz] of this.__internal__classes.entries()) {
300 if (Type === Clazz) {
301 names.push(name);
302 }
303 }
304 return names.length
305 // both sort and reverse are done in-place
306 // ['U32', 'u32'] -> ['u32', 'U32']
307 ? names.sort().reverse()[0]
308 : undefined;
309 }
310 getDefinition(typeName) {
311 return this.__internal__definitions.get(typeName);
312 }
313 getModuleInstances(specName, moduleName) {
314 return this.__internal__knownTypes?.typesBundle?.spec?.[specName.toString()]?.instances?.[moduleName] || this.__internal__moduleMap[moduleName];
315 }
316 getOrThrow(name) {
317 const Clazz = this.get(name);
318 if (!Clazz) {
319 throw new Error(`type ${name} not found`);
320 }
321 return Clazz;
322 }
323 getOrUnknown(name) {
324 return this.get(name, true);
325 }
326 // Only used in extrinsic version 5
327 getTransactionExtensionVersion() {
328 return 0;
329 }
330 getSignedExtensionExtra() {
331 return expandExtensionTypes(this.__internal__signedExtensions, 'payload', this.__internal__userExtensions);
332 }
333 getSignedExtensionTypes() {
334 return expandExtensionTypes(this.__internal__signedExtensions, 'extrinsic', this.__internal__userExtensions);
335 }
336 hasClass(name) {
337 return this.__internal__classes.has(name) || !!this.__internal__knownDefaults[name];
338 }
339 hasDef(name) {
340 return this.__internal__definitions.has(name);
341 }
342 hasType(name) {
343 return !this.__internal__unknownTypes.get(name) && (this.hasClass(name) || this.hasDef(name));
344 }
345 hash(data) {
346 return this.createType('CodecHash', this.__internal__hasher(data));
347 }
348 // eslint-disable-next-line no-dupe-class-members
349 register(arg1, arg2) {
350 // NOTE Constructors appear as functions here
351 if (isFunction(arg1)) {
352 this.__internal__classes.set(arg1.name, arg1);
353 }
354 else if (isString(arg1)) {
355 if (!isFunction(arg2)) {
356 throw new Error(`Expected class definition passed to '${arg1}' registration`);
357 }
358 else if (arg1 === arg2.toString()) {
359 throw new Error(`Unable to register circular ${arg1} === ${arg1}`);
360 }
361 this.__internal__classes.set(arg1, arg2);
362 }
363 else {
364 this.__internal__registerObject(arg1);
365 }
366 }
367 __internal__registerObject = (obj) => {
368 const entries = Object.entries(obj);
369 for (let e = 0, count = entries.length; e < count; e++) {
370 const [name, type] = entries[e];
371 if (isFunction(type)) {
372 // This _looks_ a bit funny, but `typeof Clazz === 'function'
373 this.__internal__classes.set(name, type);
374 }
375 else {
376 const def = isString(type)
377 ? type
378 : stringify(type);
379 if (name === def) {
380 throw new Error(`Unable to register circular ${name} === ${def}`);
381 }
382 // we already have this type, remove the classes registered for it
383 if (this.__internal__classes.has(name)) {
384 this.__internal__classes.delete(name);
385 }
386 this.__internal__definitions.set(name, def);
387 }
388 }
389 };
390 // sets the chain properties
391 setChainProperties(properties) {
392 if (properties) {
393 this.__internal__chainProperties = properties;
394 }
395 }
396 setHasher(hasher) {
397 this.__internal__hasher = hasher || blake2AsU8a;
398 }
399 setKnownTypes(knownTypes) {
400 this.__internal__knownTypes = knownTypes;
401 }
402 setLookup(lookup) {
403 this.__internal__lookup = lookup;
404 // register all applicable types found
405 lookup.register();
406 }
407 // register alias types alongside the portable/lookup setup
408 // (we don't combine this into setLookup since that would/could
409 // affect stand-along lookups, such as ABIs which don't have
410 // actual on-chain metadata)
411 __internal__registerLookup = (lookup) => {
412 // attach the lookup before we register any types
413 this.setLookup(lookup);
414 // we detect based on runtime configuration
415 let Weight = null;
416 if (this.hasType('SpWeightsWeightV2Weight')) {
417 // detection for WeightV2 type based on latest naming
418 const weightv2 = this.createType('SpWeightsWeightV2Weight');
419 Weight = weightv2.refTime && weightv2.proofSize
420 // with both refTime & proofSize we use as-is (WeightV2)
421 ? 'SpWeightsWeightV2Weight'
422 // fallback to WeightV1 (WeightV1.5 is a struct, single field)
423 : 'WeightV1';
424 }
425 else if (!isBn(this.createType('Weight'))) {
426 // where we have an already-supplied BN override, we don't clobber
427 // it with our detected value (This protects against pre-defines
428 // where Weight may be aliassed to WeightV0, e.g. in early Kusama chains)
429 Weight = 'WeightV1';
430 }
431 if (Weight) {
432 // we have detected a version, adjust the definition
433 this.register({ Weight });
434 }
435 };
436 // sets the metadata
437 setMetadata(metadata, signedExtensions, userExtensions, noInitWarn) {
438 this.__internal__metadata = metadata.asLatest;
439 this.__internal__metadataVersion = metadata.version;
440 this.__internal__firstCallIndex = null;
441 // attach the lookup at this point and register relevant types (before injecting)
442 this.__internal__registerLookup(this.__internal__metadata.lookup);
443 injectExtrinsics(this, this.__internal__metadata, this.__internal__metadataVersion, this.__internal__metadataCalls, this.__internal__moduleMap);
444 injectErrors(this, this.__internal__metadata, this.__internal__metadataVersion, this.__internal__metadataErrors);
445 injectEvents(this, this.__internal__metadata, this.__internal__metadataVersion, this.__internal__metadataEvents);
446 // set the default call index (the lowest section, the lowest method)
447 // in most chains this should be 0,0
448 const [defSection] = Object
449 .keys(this.__internal__metadataCalls)
450 .sort(sortDecimalStrings);
451 if (defSection) {
452 const [defMethod] = Object
453 .keys(this.__internal__metadataCalls[defSection])
454 .sort(sortDecimalStrings);
455 if (defMethod) {
456 this.__internal__firstCallIndex = new Uint8Array([parseInt(defSection, 10), parseInt(defMethod, 10)]);
457 }
458 }
459 // setup the available extensions
460 this.setSignedExtensions(signedExtensions || (this.__internal__metadata.extrinsic.version.gt(BN_ZERO)
461 // FIXME Use the extension and their injected types
462 ? this.__internal__metadata.extrinsic.signedExtensions.map(({ identifier }) => identifier.toString())
463 : fallbackExtensions), userExtensions, noInitWarn);
464 // setup the chain properties with format overrides
465 this.setChainProperties(extractProperties(this, metadata));
466 }
467 // sets the available signed extensions
468 setSignedExtensions(signedExtensions = fallbackExtensions, userExtensions, noInitWarn) {
469 this.__internal__signedExtensions = signedExtensions;
470 this.__internal__userExtensions = userExtensions;
471 if (!noInitWarn) {
472 const unknown = findUnknownExtensions(this.__internal__signedExtensions, this.__internal__userExtensions);
473 if (unknown.length) {
474 l.warn(`Unknown signed extensions ${unknown.join(', ')} found, treating them as no-effect`);
475 }
476 }
477 }
478}