UNPKG

36.7 kBJavaScriptView Raw
1import { sanitize, Struct } from '@polkadot/types-codec';
2import { getTypeDef, TypeDefInfo, withTypeString } from '@polkadot/types-create';
3import { assertUnreachable, isNumber, isString, logger, objectSpread, stringCamelCase, stringify, stringPascalCase } from '@polkadot/util';
4const l = logger('PortableRegistry');
5const TYPE_UNWRAP = { toNumber: () => -1 };
6const PRIMITIVE_ALIAS = {
7 Char: 'u32',
8 Str: 'Text'
9};
10const PATHS_ALIAS = splitNamespace([
11 // full matching on exact names...
12 // these are well-known types with additional encoding
13 'sp_core::crypto::AccountId32',
14 'sp_runtime::generic::era::Era',
15 'sp_runtime::multiaddress::MultiAddress',
16 // ethereum overrides (Frontier, Moonbeam, Polkadot claims)
17 'fp_account::AccountId20',
18 'account::AccountId20',
19 'polkadot_runtime_common::claims::EthereumAddress',
20 // weights 2 is a structure, however for 1.5. with a single field it
21 // should be flatenned (can appear in Compact<Weight> extrinsics)
22 'frame_support::weights::weight_v2::Weight',
23 'sp_weights::weight_v2::Weight',
24 // wildcard matching in place...
25 // these have a specific encoding or logic, use a wildcard for {pallet, darwinia}_democracy
26 '*_democracy::vote::Vote',
27 '*_conviction_voting::vote::Vote',
28 '*_identity::types::Data',
29 // these are opaque Vec<u8> wrappers
30 'sp_core::OpaqueMetadata',
31 'sp_core::OpaquePeerId',
32 'sp_core::offchain::OpaqueMultiaddr',
33 // shorten some well-known types
34 'primitive_types::*',
35 'sp_arithmetic::per_things::*',
36 // runtime
37 '*_runtime::RuntimeCall',
38 '*_runtime::RuntimeEvent',
39 // ink!
40 'ink::env::types::*',
41 'ink::primitives::types::*',
42 'ink_env::types::*',
43 'ink_primitives::types::*'
44]);
45const PATHS_SET = splitNamespace([
46 'pallet_identity::types::BitFlags'
47]);
48const BITVEC_NS_LSB = ['bitvec::order::Lsb0', 'BitOrderLsb0'];
49const BITVEC_NS_MSB = ['bitvec::order::Msb0', 'BitOrderMsb0'];
50const BITVEC_NS = [...BITVEC_NS_LSB, ...BITVEC_NS_MSB];
51const WRAPPERS = ['BoundedBTreeMap', 'BoundedBTreeSet', 'BoundedVec', 'Box', 'BTreeMap', 'BTreeSet', 'Cow', 'Option', 'Range', 'RangeInclusive', 'Result', 'WeakBoundedVec', 'WrapperKeepOpaque', 'WrapperOpaque'];
52const RESERVED = [
53 // JS reserved words
54 'entries', 'keys', 'new', 'size',
55 // exposed by all Codec objects
56 'hash', 'registry'
57];
58const PATH_RM_INDEX_1 = ['generic', 'misc', 'pallet', 'traits', 'types'];
59/** @internal Converts a Text[] into string[] (used as part of definitions) */
60function sanitizeDocs(docs) {
61 const count = docs.length;
62 const result = new Array(count);
63 for (let i = 0; i < count; i++) {
64 result[i] = docs[i].toString();
65 }
66 return result;
67}
68/** @internal Split a namespace with :: into individual parts */
69function splitNamespace(values) {
70 const count = values.length;
71 const result = new Array(count);
72 for (let i = 0; i < count; i++) {
73 result[i] = values[i].split('::');
74 }
75 return result;
76}
77/** @internal Match a namespace based on parts (alongside wildcards) */
78function matchParts(first, second) {
79 return first.length === second.length && first.every((a, index) => {
80 const b = second[index].toString();
81 if ((a === '*') || (a === b)) {
82 return true;
83 }
84 if (a.includes('*') && a.includes('_') && b.includes('_')) {
85 let suba = a.split('_');
86 let subb = b.split('_');
87 // match initial *'s to multiples if we have a match for the other
88 if (suba[0] === '*') {
89 const indexOf = subb.indexOf(suba[1]);
90 if (indexOf !== -1) {
91 suba = suba.slice(1);
92 subb = subb.slice(indexOf);
93 }
94 }
95 // check for * matches at the end, adjust accordingly
96 if ((suba.length === 2) && (suba[1] === '*') && (suba[0] === subb[0])) {
97 return true;
98 }
99 return matchParts(suba, subb);
100 }
101 return false;
102 });
103}
104/** @internal check if the path matches the PATHS_ALIAS (with wildcards) */
105function getAliasPath({ def, path }) {
106 // specific logic for weights - we override when non-complex struct
107 // (as applied in Weight 1.5 where we also have `Compact<{ refTime: u64 }>)
108 if (['frame_support::weights::weight_v2::Weight', 'sp_weights::weight_v2::Weight'].includes(path.join('::'))) {
109 return !def.isComposite || def.asComposite.fields.length === 1
110 ? 'WeightV1'
111 : null;
112 }
113 // TODO We need to handle ink! Balance in some way
114 return path.length && PATHS_ALIAS.some((a) => matchParts(a, path))
115 ? path[path.length - 1].toString()
116 : null;
117}
118/** @internal Converts a type name into a JS-API compatible name */
119function extractNameFlat(portable, lookupIndex, params, path, isInternal = false) {
120 const count = path.length;
121 // if we have no path or determined as a wrapper, we just skip it
122 if (count === 0 || WRAPPERS.includes(path[count - 1].toString())) {
123 return null;
124 }
125 const camels = new Array(count);
126 const lowers = new Array(count);
127 // initially just create arrays of the camelCase and lowercase path
128 // parts - we will check these to extract the final values. While
129 // we have 2 loops here, we also don't do the same operation twice
130 for (let i = 0; i < count; i++) {
131 const c = stringPascalCase(isInternal
132 ? path[i].replace('pallet_', '')
133 : path[i]);
134 const l = c.toLowerCase();
135 camels[i] = c;
136 lowers[i] = l;
137 }
138 let name = '';
139 for (let i = 0; i < count; i++) {
140 const l = lowers[i];
141 // Remove ::{generic, misc, pallet, traits, types}::
142 if (i !== 1 || !PATH_RM_INDEX_1.includes(l)) {
143 // sp_runtime::generic::digest::Digest -> sp_runtime::generic::Digest
144 // sp_runtime::multiaddress::MultiAddress -> sp_runtime::MultiAddress
145 if (l !== lowers[i + 1]) {
146 name += camels[i];
147 }
148 }
149 }
150 // do magic for RawOrigin lookup, e.g. pallet_collective::RawOrigin
151 if (camels[1] === 'RawOrigin' && count === 2 && params.length === 2 && params[1].type.isSome) {
152 const instanceType = portable[params[1].type.unwrap().toNumber()];
153 if (instanceType.type.path.length === 2) {
154 name = `${name}${instanceType.type.path[1].toString()}`;
155 }
156 }
157 return { lookupIndex, name, params };
158}
159/** @internal Alias for extractNameFlat with PortableType as a last parameter */
160function extractName(portable, lookupIndex, { type: { params, path } }) {
161 return extractNameFlat(portable, lookupIndex, params, path);
162}
163/** @internal Check for dupes from a specific index onwards */
164function nextDupeMatches(name, startAt, names) {
165 const result = [names[startAt]];
166 for (let i = startAt + 1, count = names.length; i < count; i++) {
167 const v = names[i];
168 if (v.name === name) {
169 result.push(v);
170 }
171 }
172 return result;
173}
174/** @internal Checks to see if a type is a full duplicate (with all params matching) */
175function rewriteDupes(input, rewrite) {
176 const count = input.length;
177 for (let i = 0; i < count; i++) {
178 const a = input[i];
179 for (let j = i + 1; j < count; j++) {
180 const b = input[j];
181 // if the indexes are not the same and the names match, we have a dupe
182 if (a.lookupIndex !== b.lookupIndex && a.name === b.name) {
183 return false;
184 }
185 }
186 }
187 // add all the adjusted values to the rewite map
188 for (let i = 0; i < count; i++) {
189 const p = input[i];
190 rewrite[p.lookupIndex] = p.name;
191 }
192 return true;
193}
194/** @internal Find duplicates and adjust the names based on parameters */
195function removeDupeNames(lookup, portable, names) {
196 const rewrite = {};
197 return names
198 .map((original, startAt) => {
199 const { lookupIndex, name, params } = original;
200 if (!name) {
201 // the name is empty (this is not expected, but have a failsafe)
202 return null;
203 }
204 else if (rewrite[lookupIndex]) {
205 // we have already rewritten this one, we can skip it
206 return original;
207 }
208 // those where the name is matching starting from this index
209 const allSame = nextDupeMatches(name, startAt, names);
210 // we only have one, so all ok
211 if (allSame.length === 1) {
212 return original;
213 }
214 // are there param differences between matching names
215 const anyDiff = allSame.some((o) => params.length !== o.params.length ||
216 params.some((p, index) => !p.name.eq(o.params[index].name) ||
217 p.type.unwrapOr(TYPE_UNWRAP).toNumber() !== o.params[index].type.unwrapOr(TYPE_UNWRAP).toNumber()));
218 // everything matches, we can combine these
219 if (!anyDiff) {
220 return original;
221 }
222 // TODO We probably want to attach all the indexes with differences,
223 // not just the first
224 // find the first parameter that yields differences
225 const paramIdx = params.findIndex(({ type }, index) => allSame.every(({ params }, aIndex) => params[index].type.isSome && (aIndex === 0 ||
226 !params[index].type.eq(type))));
227 // No param found that is different
228 if (paramIdx === -1) {
229 return original;
230 }
231 // see if using the param type helps
232 const sameCount = allSame.length;
233 const adjusted = new Array(sameCount);
234 // loop through all, specifically checking that index where the
235 // first param yields differences
236 for (let i = 0; i < sameCount; i++) {
237 const { lookupIndex, name, params } = allSame[i];
238 const { def, path } = lookup.getSiType(params[paramIdx].type.unwrap());
239 // if it is not a primitive and it doesn't have a path, we really cannot
240 // do anything at this point
241 if (!def.isPrimitive && !path.length) {
242 return null;
243 }
244 adjusted[i] = {
245 lookupIndex,
246 name: def.isPrimitive
247 ? `${name}${def.asPrimitive.toString()}`
248 : `${name}${path[path.length - 1].toString()}`
249 };
250 }
251 // check to see if the adjusted names have no issues
252 if (rewriteDupes(adjusted, rewrite)) {
253 return original;
254 }
255 // TODO This is duplicated from the section just above...
256 // ... we certainly need a better solution here
257 //
258 // Last-ditch effort to use the full type path - ugly
259 // loop through all, specifically checking that index where the
260 // first param yields differences
261 for (let i = 0; i < sameCount; i++) {
262 const { lookupIndex, name, params } = allSame[i];
263 const { def, path } = lookup.getSiType(params[paramIdx].type.unwrap());
264 const flat = extractNameFlat(portable, lookupIndex, params, path, true);
265 if (def.isPrimitive || !flat) {
266 return null;
267 }
268 adjusted[i] = {
269 lookupIndex,
270 name: `${name}${flat.name}`
271 };
272 }
273 // check to see if the adjusted names have no issues
274 if (rewriteDupes(adjusted, rewrite)) {
275 return original;
276 }
277 return null;
278 })
279 .filter((n) => !!n)
280 .map(({ lookupIndex, name, params }) => ({
281 lookupIndex,
282 name: rewrite[lookupIndex] || name,
283 params
284 }));
285}
286/** @internal Detect on-chain types (AccountId/Signature) as set as the default */
287function registerTypes(lookup, lookups, names, params) {
288 // Register the types we extracted
289 lookup.registry.register(lookups);
290 // Try and extract the AccountId/Address/Signature type from UncheckedExtrinsic
291 if (params.SpRuntimeUncheckedExtrinsic) {
292 // Address, Call, Signature, Extra
293 const [addrParam, , sigParam] = params.SpRuntimeUncheckedExtrinsic;
294 const siAddress = lookup.getSiType(addrParam.type.unwrap());
295 const siSignature = lookup.getSiType(sigParam.type.unwrap());
296 const nsSignature = siSignature.path.join('::');
297 let nsAccountId = siAddress.path.join('::');
298 const isMultiAddress = nsAccountId === 'sp_runtime::multiaddress::MultiAddress';
299 // With multiaddress, we check the first type param again
300 if (isMultiAddress) {
301 // AccountId, AccountIndex
302 const [idParam] = siAddress.params;
303 nsAccountId = lookup.getSiType(idParam.type.unwrap()).path.join('::');
304 }
305 lookup.registry.register({
306 // known: account::AccountId20, fp_account::AccountId20, primitive_types::H160
307 AccountId: nsAccountId.endsWith('::AccountId20') || nsAccountId.endsWith('::H160')
308 ? 'AccountId20'
309 : 'AccountId32',
310 Address: isMultiAddress
311 ? 'MultiAddress'
312 : 'AccountId',
313 ExtrinsicSignature: ['sp_runtime::MultiSignature'].includes(nsSignature)
314 ? 'MultiSignature'
315 : names[sigParam.type.unwrap().toNumber()] || 'MultiSignature'
316 });
317 }
318}
319/**
320 * @internal Extracts aliases based on what we know the runtime config looks like in a
321 * Substrate chain. Specifically we want to have access to the Call and Event params
322 **/
323function extractAliases(params, isContract) {
324 const hasParams = Object.keys(params).some((k) => !k.startsWith('Pallet'));
325 const alias = {};
326 if (params.SpRuntimeUncheckedExtrinsic) {
327 // Address, Call, Signature, Extra
328 const [, { type }] = params.SpRuntimeUncheckedExtrinsic;
329 alias[type.unwrap().toNumber()] = 'Call';
330 }
331 else if (hasParams && !isContract) {
332 l.warn('Unable to determine runtime Call type, cannot inspect sp_runtime::generic::unchecked_extrinsic::UncheckedExtrinsic');
333 }
334 if (params.FrameSystemEventRecord) {
335 // Event, Topic
336 const [{ type }] = params.FrameSystemEventRecord;
337 alias[type.unwrap().toNumber()] = 'Event';
338 }
339 else if (hasParams && !isContract) {
340 l.warn('Unable to determine runtime Event type, cannot inspect frame_system::EventRecord');
341 }
342 return alias;
343}
344/** @internal Extracts all the intreresting type information for this registry */
345function extractTypeInfo(lookup, portable) {
346 const nameInfo = [];
347 const types = {};
348 for (let i = 0, count = portable.length; i < count; i++) {
349 const type = portable[i];
350 const lookupIndex = type.id.toNumber();
351 const extracted = extractName(portable, lookupIndex, portable[i]);
352 if (extracted) {
353 nameInfo.push(extracted);
354 }
355 types[lookupIndex] = type;
356 }
357 const lookups = {};
358 const names = {};
359 const params = {};
360 const dedup = removeDupeNames(lookup, portable, nameInfo);
361 for (let i = 0, count = dedup.length; i < count; i++) {
362 const { lookupIndex, name, params: p } = dedup[i];
363 names[lookupIndex] = name;
364 lookups[name] = lookup.registry.createLookupType(lookupIndex);
365 params[name] = p;
366 }
367 return { lookups, names, params, types };
368}
369export class PortableRegistry extends Struct {
370 constructor(registry, value, isContract) {
371 // const timeStart = performance.now()
372 super(registry, {
373 types: 'Vec<PortableType>'
374 }, value);
375 this.__internal__typeDefs = {};
376 const { lookups, names, params, types } = extractTypeInfo(this, this.types);
377 this.__internal__alias = extractAliases(params, isContract);
378 this.__internal__lookups = lookups;
379 this.__internal__names = names;
380 this.__internal__params = params;
381 this.__internal__types = types;
382 // console.log('PortableRegistry', `${(performance.now() - timeStart).toFixed(2)}ms`)
383 }
384 /**
385 * @description Returns all the available type names for this chain
386 **/
387 get names() {
388 return Object.values(this.__internal__names).sort();
389 }
390 /**
391 * @description The types of the registry
392 */
393 get types() {
394 return this.getT('types');
395 }
396 /**
397 * @description Register all available types into the registry (generally for internal usage)
398 */
399 register() {
400 registerTypes(this, this.__internal__lookups, this.__internal__names, this.__internal__params);
401 }
402 /**
403 * @description Returns the name for a specific lookup
404 */
405 getName(lookupId) {
406 return this.__internal__names[this.__internal__getLookupId(lookupId)];
407 }
408 /**
409 * @description Finds a specific type in the registry
410 */
411 getSiType(lookupId) {
412 // NOTE catch-22 - this may already be used as part of the constructor, so
413 // ensure that we have actually initialized it correctly
414 const found = (this.__internal__types || this.types)[this.__internal__getLookupId(lookupId)];
415 if (!found) {
416 throw new Error(`PortableRegistry: Unable to find type with lookupId ${lookupId.toString()}`);
417 }
418 return found.type;
419 }
420 /**
421 * @description Lookup the type definition for the index
422 */
423 getTypeDef(lookupId) {
424 const lookupIndex = this.__internal__getLookupId(lookupId);
425 if (!this.__internal__typeDefs[lookupIndex]) {
426 const lookupName = this.__internal__names[lookupIndex];
427 const empty = {
428 info: TypeDefInfo.DoNotConstruct,
429 lookupIndex,
430 lookupName,
431 type: this.registry.createLookupType(lookupIndex)
432 };
433 // Set named items since we will get into circular lookups along the way
434 if (lookupName) {
435 this.__internal__typeDefs[lookupIndex] = empty;
436 }
437 const extracted = this.__internal__extract(this.getSiType(lookupId), lookupIndex);
438 // For non-named items, we only set this right at the end
439 if (!lookupName) {
440 this.__internal__typeDefs[lookupIndex] = empty;
441 }
442 Object.keys(extracted).forEach((k) => {
443 if (k !== 'lookupName' || extracted[k]) {
444 // these are safe since we are looking through the keys as set
445 this.__internal__typeDefs[lookupIndex][k] = extracted[k];
446 }
447 });
448 // don't set lookupName on lower-level, we want to always direct to the type
449 if (extracted.info === TypeDefInfo.Plain) {
450 this.__internal__typeDefs[lookupIndex].lookupNameRoot = this.__internal__typeDefs[lookupIndex].lookupName;
451 delete this.__internal__typeDefs[lookupIndex].lookupName;
452 }
453 }
454 return this.__internal__typeDefs[lookupIndex];
455 }
456 /**
457 * @description For a specific field, perform adjustments to not have built-in conflicts
458 */
459 sanitizeField(name) {
460 let nameField = null;
461 let nameOrig = null;
462 if (name.isSome) {
463 nameField = stringCamelCase(name.unwrap());
464 if (nameField.includes('#')) {
465 nameOrig = nameField;
466 nameField = nameOrig.replace(/#/g, '_');
467 }
468 else if (RESERVED.includes(nameField)) {
469 nameOrig = nameField;
470 nameField = `${nameField}_`;
471 }
472 }
473 return [nameField, nameOrig];
474 }
475 /** @internal Creates a TypeDef based on an internal lookupId */
476 __internal__createSiDef(lookupId) {
477 const typeDef = this.getTypeDef(lookupId);
478 const lookupIndex = lookupId.toNumber();
479 // Setup for a lookup on complex types
480 return [TypeDefInfo.DoNotConstruct, TypeDefInfo.Enum, TypeDefInfo.Struct].includes(typeDef.info) && typeDef.lookupName
481 ? {
482 docs: typeDef.docs,
483 info: TypeDefInfo.Si,
484 lookupIndex,
485 lookupName: this.__internal__names[lookupIndex],
486 type: this.registry.createLookupType(lookupId)
487 }
488 : typeDef;
489 }
490 /** @internal Converts a lookupId input to the actual lookup index */
491 __internal__getLookupId(lookupId) {
492 if (isString(lookupId)) {
493 if (!this.registry.isLookupType(lookupId)) {
494 throw new Error(`PortableRegistry: Expected a lookup string type, found ${lookupId}`);
495 }
496 return parseInt(lookupId.replace('Lookup', ''), 10);
497 }
498 else if (isNumber(lookupId)) {
499 return lookupId;
500 }
501 return lookupId.toNumber();
502 }
503 /** @internal Converts a type into a TypeDef for Codec usage */
504 __internal__extract(type, lookupIndex) {
505 const namespace = type.path.join('::');
506 let typeDef;
507 const aliasType = this.__internal__alias[lookupIndex] || getAliasPath(type);
508 try {
509 if (aliasType) {
510 typeDef = this.__internal__extractAliasPath(lookupIndex, aliasType);
511 }
512 else {
513 switch (type.def.type) {
514 case 'Array':
515 typeDef = this.__internal__extractArray(lookupIndex, type.def.asArray);
516 break;
517 case 'BitSequence':
518 typeDef = this.__internal__extractBitSequence(lookupIndex, type.def.asBitSequence);
519 break;
520 case 'Compact':
521 typeDef = this.__internal__extractCompact(lookupIndex, type.def.asCompact);
522 break;
523 case 'Composite':
524 typeDef = this.__internal__extractComposite(lookupIndex, type, type.def.asComposite);
525 break;
526 case 'HistoricMetaCompat':
527 typeDef = this.__internal__extractHistoric(lookupIndex, type.def.asHistoricMetaCompat);
528 break;
529 case 'Primitive':
530 typeDef = this.__internal__extractPrimitive(lookupIndex, type);
531 break;
532 case 'Sequence':
533 typeDef = this.__internal__extractSequence(lookupIndex, type.def.asSequence);
534 break;
535 case 'Tuple':
536 typeDef = this.__internal__extractTuple(lookupIndex, type.def.asTuple);
537 break;
538 case 'Variant':
539 typeDef = this.__internal__extractVariant(lookupIndex, type, type.def.asVariant);
540 break;
541 default: assertUnreachable(type.def.type);
542 }
543 }
544 }
545 catch (error) {
546 throw new Error(`PortableRegistry: ${lookupIndex}${namespace ? ` (${namespace})` : ''}: Error extracting ${stringify(type)}: ${error.message}`);
547 }
548 return objectSpread({
549 docs: sanitizeDocs(type.docs),
550 namespace
551 }, typeDef);
552 }
553 /** @internal Extracts a ScaleInfo Array into TypeDef.VecFixed */
554 __internal__extractArray(_, { len, type }) {
555 const length = len.toNumber();
556 if (length > 2048) {
557 throw new Error('Only support for [Type; <length>], where length <= 2048');
558 }
559 return withTypeString(this.registry, {
560 info: TypeDefInfo.VecFixed,
561 length,
562 sub: this.__internal__createSiDef(type)
563 });
564 }
565 /** @internal Extracts a ScaleInfo BitSequence into TypeDef.Plain */
566 __internal__extractBitSequence(_, { bitOrderType, bitStoreType }) {
567 // With the v3 of scale-info this swapped around, but obviously the decoder cannot determine
568 // the order. With that in-mind, we apply a detection for LSb0/Msb and set accordingly
569 const a = this.__internal__createSiDef(bitOrderType);
570 const b = this.__internal__createSiDef(bitStoreType);
571 const [bitOrder, bitStore] = BITVEC_NS.includes(a.namespace || '')
572 ? [a, b]
573 : [b, a];
574 if (!bitOrder.namespace || !BITVEC_NS.includes(bitOrder.namespace)) {
575 throw new Error(`Unexpected bitOrder found as ${bitOrder.namespace || '<unknown>'}`);
576 }
577 else if (bitStore.info !== TypeDefInfo.Plain || bitStore.type !== 'u8') {
578 throw new Error(`Only u8 bitStore is currently supported, found ${bitStore.type}`);
579 }
580 const isLsb = BITVEC_NS_LSB.includes(bitOrder.namespace);
581 if (!isLsb) {
582 // TODO To remove this limitation, we need to pass an extra info flag
583 // through to the TypeDef (Here we could potentially re-use something
584 // like index (???) to indicate and ensure we use it to pass to the
585 // BitVec constructor - which does handle this type)
586 //
587 // See https://github.com/polkadot-js/api/issues/5588
588 // throw new Error(`Only LSB BitVec is currently supported, found ${bitOrder.namespace}`);
589 }
590 return {
591 info: TypeDefInfo.Plain,
592 type: 'BitVec'
593 };
594 }
595 /** @internal Extracts a ScaleInfo Compact into TypeDef.Compact */
596 __internal__extractCompact(_, { type }) {
597 return withTypeString(this.registry, {
598 info: TypeDefInfo.Compact,
599 sub: this.__internal__createSiDef(type)
600 });
601 }
602 /** @internal Extracts a ScaleInfo Composite into TypeDef.{BTree*, Range*, Wrapper*} */
603 __internal__extractComposite(lookupIndex, { params, path }, { fields }) {
604 if (path.length) {
605 const pathFirst = path[0].toString();
606 const pathLast = path[path.length - 1].toString();
607 if (path.length === 1 && pathFirst === 'BTreeMap') {
608 if (params.length !== 2) {
609 throw new Error(`BTreeMap requires 2 parameters, found ${params.length}`);
610 }
611 return withTypeString(this.registry, {
612 info: TypeDefInfo.BTreeMap,
613 sub: params.map(({ type }) => this.__internal__createSiDef(type.unwrap()))
614 });
615 }
616 else if (path.length === 1 && pathFirst === 'BTreeSet') {
617 if (params.length !== 1) {
618 throw new Error(`BTreeSet requires 1 parameter, found ${params.length}`);
619 }
620 return withTypeString(this.registry, {
621 info: TypeDefInfo.BTreeSet,
622 sub: this.__internal__createSiDef(params[0].type.unwrap())
623 });
624 }
625 else if (['Range', 'RangeInclusive'].includes(pathFirst)) {
626 if (params.length !== 1) {
627 throw new Error(`Range requires 1 parameter, found ${params.length}`);
628 }
629 return withTypeString(this.registry, {
630 info: pathFirst === 'Range'
631 ? TypeDefInfo.Range
632 : TypeDefInfo.RangeInclusive,
633 sub: this.__internal__createSiDef(params[0].type.unwrap()),
634 type: pathFirst
635 });
636 }
637 else if (['WrapperKeepOpaque', 'WrapperOpaque'].includes(pathLast)) {
638 if (params.length !== 1) {
639 throw new Error(`WrapperOpaque requires 1 parameter, found ${params.length}`);
640 }
641 return withTypeString(this.registry, {
642 info: pathLast === 'WrapperKeepOpaque'
643 ? TypeDefInfo.WrapperKeepOpaque
644 : TypeDefInfo.WrapperOpaque,
645 sub: this.__internal__createSiDef(params[0].type.unwrap()),
646 type: pathLast
647 });
648 }
649 }
650 return PATHS_SET.some((p) => matchParts(p, path))
651 ? this.__internal__extractCompositeSet(lookupIndex, params, fields)
652 : this.__internal__extractFields(lookupIndex, fields);
653 }
654 /** @internal Extracts a ScaleInfo CompositeSet into TypeDef.Set */
655 __internal__extractCompositeSet(_, params, fields) {
656 if (params.length !== 1 || fields.length !== 1) {
657 throw new Error('Set handling expects param/field as single entries');
658 }
659 return withTypeString(this.registry, {
660 info: TypeDefInfo.Set,
661 length: this.registry.createTypeUnsafe(this.registry.createLookupType(fields[0].type), []).bitLength(),
662 sub: this.getSiType(params[0].type.unwrap()).def.asVariant.variants.map(({ index, name }) => ({
663 // This will be an issue > 2^53 - 1 ... don't have those (yet)
664 index: index.toNumber(),
665 info: TypeDefInfo.Plain,
666 name: name.toString(),
667 type: 'Null'
668 }))
669 });
670 }
671 /** @internal Extracts ScaleInfo enum/struct fields into TypeDef.{Struct, Tuple} */
672 __internal__extractFields(lookupIndex, fields) {
673 let isStruct = true;
674 let isTuple = true;
675 const count = fields.length;
676 for (let f = 0; f < count; f++) {
677 const { name } = fields[f];
678 isStruct = isStruct && name.isSome;
679 isTuple = isTuple && name.isNone;
680 }
681 if (!isTuple && !isStruct) {
682 throw new Error('Invalid fields type detected, expected either Tuple (all unnamed) or Struct (all named)');
683 }
684 if (count === 0) {
685 return {
686 info: TypeDefInfo.Null,
687 type: 'Null'
688 };
689 }
690 else if (isTuple && count === 1) {
691 const typeDef = this.__internal__createSiDef(fields[0].type);
692 return objectSpread({}, typeDef, lookupIndex === -1
693 ? null
694 : {
695 lookupIndex,
696 lookupName: this.__internal__names[lookupIndex],
697 lookupNameRoot: typeDef.lookupName
698 }, fields[0].typeName.isSome
699 ? { typeName: sanitize(fields[0].typeName.unwrap()) }
700 : null);
701 }
702 const [sub, alias] = this.__internal__extractFieldsAlias(fields);
703 return withTypeString(this.registry, objectSpread({
704 info: isTuple // Tuple check first
705 ? TypeDefInfo.Tuple
706 : TypeDefInfo.Struct,
707 sub
708 }, alias.size
709 ? { alias }
710 : null, lookupIndex === -1
711 ? null
712 : {
713 lookupIndex,
714 lookupName: this.__internal__names[lookupIndex]
715 }));
716 }
717 /** @internal Apply field aliassed (with no JS conflicts) */
718 __internal__extractFieldsAlias(fields) {
719 const alias = new Map();
720 const count = fields.length;
721 const sub = new Array(count);
722 for (let i = 0; i < count; i++) {
723 const { docs, name, type, typeName } = fields[i];
724 const typeDef = this.__internal__createSiDef(type);
725 if (name.isNone) {
726 sub[i] = typeDef;
727 }
728 else {
729 const [nameField, nameOrig] = this.sanitizeField(name);
730 if (nameField && nameOrig) {
731 alias.set(nameField, nameOrig);
732 }
733 sub[i] = objectSpread({
734 docs: sanitizeDocs(docs),
735 name: nameField
736 }, typeDef, typeName.isSome
737 ? { typeName: sanitize(typeName.unwrap()) }
738 : null);
739 }
740 }
741 return [sub, alias];
742 }
743 /** @internal Extracts an internal Historic (pre V14) type */
744 __internal__extractHistoric(_, type) {
745 return objectSpread({
746 displayName: type.toString(),
747 isFromSi: true
748 }, getTypeDef(type));
749 }
750 /** @internal Extracts a ScaleInfo Primitive into TypeDef.Plain */
751 __internal__extractPrimitive(_, type) {
752 const typeStr = type.def.asPrimitive.type.toString();
753 return {
754 info: TypeDefInfo.Plain,
755 type: PRIMITIVE_ALIAS[typeStr] || typeStr.toLowerCase()
756 };
757 }
758 /** @internal Applies an alias path onto the TypeDef */
759 __internal__extractAliasPath(_, type) {
760 return {
761 info: TypeDefInfo.Plain,
762 type
763 };
764 }
765 /** @internal Extracts a ScaleInfo Sequence into TypeDef.Vec (with Bytes shortcut) */
766 __internal__extractSequence(lookupIndex, { type }) {
767 const sub = this.__internal__createSiDef(type);
768 if (sub.type === 'u8') {
769 return {
770 info: TypeDefInfo.Plain,
771 type: 'Bytes'
772 };
773 }
774 return withTypeString(this.registry, {
775 info: TypeDefInfo.Vec,
776 lookupIndex,
777 lookupName: this.__internal__names[lookupIndex],
778 sub
779 });
780 }
781 /** @internal Extracts a ScaleInfo Tuple into TypeDef.Tuple */
782 __internal__extractTuple(lookupIndex, ids) {
783 if (ids.length === 0) {
784 return {
785 info: TypeDefInfo.Null,
786 type: 'Null'
787 };
788 }
789 else if (ids.length === 1) {
790 return this.getTypeDef(ids[0]);
791 }
792 const sub = ids.map((t) => this.__internal__createSiDef(t));
793 return withTypeString(this.registry, {
794 info: TypeDefInfo.Tuple,
795 lookupIndex,
796 lookupName: this.__internal__names[lookupIndex],
797 sub
798 });
799 }
800 /** @internal Extracts a ScaleInfo Variant into TypeDef.{Option, Result, Enum} */
801 __internal__extractVariant(lookupIndex, { params, path }, { variants }) {
802 if (path.length) {
803 const specialVariant = path[0].toString();
804 if (specialVariant === 'Option') {
805 if (params.length !== 1) {
806 throw new Error(`Option requires 1 parameter, found ${params.length}`);
807 }
808 // NOTE This is opt-in (unhandled), not by default
809 // if (sub.type === 'bool') {
810 // return withTypeString(this.registry, {
811 // info: TypeDefInfo.Plain,
812 // type: 'OptionBool'
813 // });
814 // }
815 return withTypeString(this.registry, {
816 info: TypeDefInfo.Option,
817 sub: this.__internal__createSiDef(params[0].type.unwrap())
818 });
819 }
820 else if (specialVariant === 'Result') {
821 if (params.length !== 2) {
822 throw new Error(`Result requires 2 parameters, found ${params.length}`);
823 }
824 return withTypeString(this.registry, {
825 info: TypeDefInfo.Result,
826 sub: params.map(({ type }, index) => objectSpread({
827 name: ['Ok', 'Error'][index]
828 }, this.__internal__createSiDef(type.unwrap())))
829 });
830 }
831 }
832 if (variants.length === 0) {
833 return {
834 info: TypeDefInfo.Null,
835 type: 'Null'
836 };
837 }
838 return this.__internal__extractVariantEnum(lookupIndex, variants);
839 }
840 /** @internal Extracts a ScaleInfo Variant into TypeDef.Enum */
841 __internal__extractVariantEnum(lookupIndex, variants) {
842 const sub = [];
843 // we may get entries out of order, arrange them first before creating with gaps filled
844 // NOTE: Since we mutate, use a copy of the array as an input
845 variants
846 .slice()
847 .sort((a, b) => a.index.cmp(b.index))
848 .forEach(({ fields, index: bnIndex, name }) => {
849 const index = bnIndex.toNumber();
850 while (sub.length !== index) {
851 sub.push({
852 index: sub.length,
853 info: TypeDefInfo.Null,
854 name: `__Unused${sub.length}`,
855 type: 'Null'
856 });
857 }
858 sub.push(objectSpread(this.__internal__extractFields(-1, fields), {
859 index,
860 name: name.toString()
861 }));
862 });
863 return withTypeString(this.registry, {
864 info: TypeDefInfo.Enum,
865 lookupIndex,
866 lookupName: this.__internal__names[lookupIndex],
867 sub
868 });
869 }
870}