UNPKG

@polkadot/types

Version:
882 lines (881 loc) • 37 kB
import { sanitize, Struct } from '@polkadot/types-codec'; import { getTypeDef, TypeDefInfo, withTypeString } from '@polkadot/types-create'; import { assertUnreachable, isNumber, isString, logger, objectSpread, stringCamelCase, stringify, stringPascalCase } from '@polkadot/util'; const l = logger('PortableRegistry'); const TYPE_UNWRAP = { toNumber: () => -1 }; const PRIMITIVE_ALIAS = { Char: 'u32', // Rust char is 4-bytes Str: 'Text' }; const PATHS_ALIAS = splitNamespace([ // full matching on exact names... // these are well-known types with additional encoding 'sp_core::crypto::AccountId32', 'sp_runtime::generic::era::Era', 'sp_runtime::multiaddress::MultiAddress', // ethereum overrides (Frontier, Moonbeam, Polkadot claims) 'fp_account::AccountId20', 'account::AccountId20', 'polkadot_runtime_common::claims::EthereumAddress', // weights 2 is a structure, however for 1.5. with a single field it // should be flatenned (can appear in Compact<Weight> extrinsics) 'frame_support::weights::weight_v2::Weight', 'sp_weights::weight_v2::Weight', // wildcard matching in place... // these have a specific encoding or logic, use a wildcard for {pallet, darwinia}_democracy '*_democracy::vote::Vote', '*_conviction_voting::vote::Vote', '*_identity::types::Data', // these are opaque Vec<u8> wrappers 'sp_core::OpaqueMetadata', 'sp_core::OpaquePeerId', 'sp_core::offchain::OpaqueMultiaddr', // shorten some well-known types 'primitive_types::*', 'sp_arithmetic::per_things::*', // runtime '*_runtime::RuntimeCall', '*_runtime::RuntimeEvent', // ink! 'ink::env::types::*', 'ink::primitives::types::*', 'ink_env::types::*', 'ink_primitives::types::*' ]); const PATHS_SET = splitNamespace([ 'pallet_identity::types::BitFlags' ]); const BITVEC_NS_LSB = ['bitvec::order::Lsb0', 'BitOrderLsb0']; const BITVEC_NS_MSB = ['bitvec::order::Msb0', 'BitOrderMsb0']; const BITVEC_NS = [...BITVEC_NS_LSB, ...BITVEC_NS_MSB]; const WRAPPERS = ['BoundedBTreeMap', 'BoundedBTreeSet', 'BoundedVec', 'Box', 'BTreeMap', 'BTreeSet', 'Cow', 'Option', 'Range', 'RangeInclusive', 'Result', 'WeakBoundedVec', 'WrapperKeepOpaque', 'WrapperOpaque']; const RESERVED = [ // JS reserved words 'entries', 'keys', 'new', 'size', // exposed by all Codec objects 'hash', 'registry' ]; const PATH_RM_INDEX_1 = ['generic', 'misc', 'pallet', 'traits', 'types']; /** @internal Converts a Text[] into string[] (used as part of definitions) */ function sanitizeDocs(docs) { const count = docs.length; const result = new Array(count); for (let i = 0; i < count; i++) { result[i] = docs[i].toString(); } return result; } /** @internal Split a namespace with :: into individual parts */ function splitNamespace(values) { const count = values.length; const result = new Array(count); for (let i = 0; i < count; i++) { result[i] = values[i].split('::'); } return result; } /** @internal Match a namespace based on parts (alongside wildcards) */ function matchParts(first, second) { return first.length === second.length && first.every((a, index) => { const b = second[index].toString(); if ((a === '*') || (a === b)) { return true; } if (a.includes('*') && a.includes('_') && b.includes('_')) { let suba = a.split('_'); let subb = b.split('_'); // match initial *'s to multiples if we have a match for the other if (suba[0] === '*') { const indexOf = subb.indexOf(suba[1]); if (indexOf !== -1) { suba = suba.slice(1); subb = subb.slice(indexOf); } } // check for * matches at the end, adjust accordingly if ((suba.length === 2) && (suba[1] === '*') && (suba[0] === subb[0])) { return true; } return matchParts(suba, subb); } return false; }); } /** @internal check if the path matches the PATHS_ALIAS (with wildcards) */ function getAliasPath({ def, path }) { // specific logic for weights - we override when non-complex struct // (as applied in Weight 1.5 where we also have `Compact<{ refTime: u64 }>) if (['frame_support::weights::weight_v2::Weight', 'sp_weights::weight_v2::Weight'].includes(path.join('::'))) { return !def.isComposite || def.asComposite.fields.length === 1 ? 'WeightV1' : null; } // TODO We need to handle ink! Balance in some way return path.length && PATHS_ALIAS.some((a) => matchParts(a, path)) ? path[path.length - 1].toString() : null; } /** @internal Converts a type name into a JS-API compatible name */ function extractNameFlat(portable, lookupIndex, params, path, isInternal = false) { const count = path.length; // if we have no path or determined as a wrapper, we just skip it if (count === 0 || WRAPPERS.includes(path[count - 1].toString())) { return null; } const camels = new Array(count); const lowers = new Array(count); // initially just create arrays of the camelCase and lowercase path // parts - we will check these to extract the final values. While // we have 2 loops here, we also don't do the same operation twice for (let i = 0; i < count; i++) { const c = stringPascalCase(isInternal ? path[i].replace('pallet_', '') : path[i]); const l = c.toLowerCase(); camels[i] = c; lowers[i] = l; } let name = ''; for (let i = 0; i < count; i++) { const l = lowers[i]; // Remove ::{generic, misc, pallet, traits, types}:: if (i !== 1 || !PATH_RM_INDEX_1.includes(l)) { // sp_runtime::generic::digest::Digest -> sp_runtime::generic::Digest // sp_runtime::multiaddress::MultiAddress -> sp_runtime::MultiAddress if (l !== lowers[i + 1]) { name += camels[i]; } } } // do magic for RawOrigin lookup, e.g. pallet_collective::RawOrigin if (camels[1] === 'RawOrigin' && count === 2 && params.length === 2 && params[1].type.isSome) { const instanceType = portable[params[1].type.unwrap().toNumber()]; if (instanceType.type.path.length === 2) { name = `${name}${instanceType.type.path[1].toString()}`; } } return { lookupIndex, name, params }; } /** @internal Alias for extractNameFlat with PortableType as a last parameter */ function extractName(portable, lookupIndex, { type: { params, path } }) { return extractNameFlat(portable, lookupIndex, params, path); } /** @internal Check for dupes from a specific index onwards */ function nextDupeMatches(name, startAt, names) { const result = [names[startAt]]; for (let i = startAt + 1, count = names.length; i < count; i++) { const v = names[i]; if (v.name === name) { result.push(v); } } return result; } /** @internal Checks to see if a type is a full duplicate (with all params matching) */ function rewriteDupes(input, rewrite) { const count = input.length; for (let i = 0; i < count; i++) { const a = input[i]; for (let j = i + 1; j < count; j++) { const b = input[j]; // if the indexes are not the same and the names match, we have a dupe if (a.lookupIndex !== b.lookupIndex && a.name === b.name) { return false; } } } // add all the adjusted values to the rewite map for (let i = 0; i < count; i++) { const p = input[i]; rewrite[p.lookupIndex] = p.name; } return true; } /** @internal Find duplicates and adjust the names based on parameters */ function removeDupeNames(lookup, portable, names) { const rewrite = {}; return names .map((original, startAt) => { const { lookupIndex, name, params } = original; if (!name) { // the name is empty (this is not expected, but have a failsafe) return null; } else if (rewrite[lookupIndex]) { // we have already rewritten this one, we can skip it return original; } // those where the name is matching starting from this index const allSame = nextDupeMatches(name, startAt, names); // we only have one, so all ok if (allSame.length === 1) { return original; } // are there param differences between matching names const anyDiff = allSame.some((o) => params.length !== o.params.length || params.some((p, index) => !p.name.eq(o.params[index].name) || p.type.unwrapOr(TYPE_UNWRAP).toNumber() !== o.params[index].type.unwrapOr(TYPE_UNWRAP).toNumber())); // everything matches, we can combine these if (!anyDiff) { return original; } // TODO We probably want to attach all the indexes with differences, // not just the first // find the first parameter that yields differences const paramIdx = params.findIndex(({ type }, index) => allSame.every(({ params }, aIndex) => params[index].type.isSome && (aIndex === 0 || !params[index].type.eq(type)))); // No param found that is different if (paramIdx === -1) { return original; } // see if using the param type helps const sameCount = allSame.length; const adjusted = new Array(sameCount); // loop through all, specifically checking that index where the // first param yields differences for (let i = 0; i < sameCount; i++) { const { lookupIndex, name, params } = allSame[i]; const { def, path } = lookup.getSiType(params[paramIdx].type.unwrap()); // if it is not a primitive and it doesn't have a path, we really cannot // do anything at this point if (!def.isPrimitive && !path.length) { return null; } adjusted[i] = { lookupIndex, name: def.isPrimitive ? `${name}${def.asPrimitive.toString()}` : `${name}${path[path.length - 1].toString()}` }; } // check to see if the adjusted names have no issues if (rewriteDupes(adjusted, rewrite)) { return original; } // TODO This is duplicated from the section just above... // ... we certainly need a better solution here // // Last-ditch effort to use the full type path - ugly // loop through all, specifically checking that index where the // first param yields differences for (let i = 0; i < sameCount; i++) { const { lookupIndex, name, params } = allSame[i]; const { def, path } = lookup.getSiType(params[paramIdx].type.unwrap()); const flat = extractNameFlat(portable, lookupIndex, params, path, true); if (def.isPrimitive || !flat) { return null; } adjusted[i] = { lookupIndex, name: `${name}${flat.name}` }; } // check to see if the adjusted names have no issues if (rewriteDupes(adjusted, rewrite)) { return original; } return null; }) .filter((n) => !!n) .map(({ lookupIndex, name, params }) => ({ lookupIndex, name: rewrite[lookupIndex] || name, params })); } /** @internal Detect on-chain types (AccountId/Signature) as set as the default */ function registerTypes(lookup, lookups, names, params) { // Register the types we extracted lookup.registry.register(lookups); // Try and extract the AccountId/Address/Signature type from UncheckedExtrinsic if (params.SpRuntimeUncheckedExtrinsic) { // Address, Call, Signature, Extra const [addrParam, , sigParam] = params.SpRuntimeUncheckedExtrinsic; const siAddress = lookup.getSiType(addrParam.type.unwrap()); const siSignature = lookup.getSiType(sigParam.type.unwrap()); const nsSignature = siSignature.path.join('::'); let nsAccountId = siAddress.path.join('::'); const isMultiAddress = nsAccountId === 'sp_runtime::multiaddress::MultiAddress'; // With multiaddress, we check the first type param again if (isMultiAddress) { // AccountId, AccountIndex const [idParam] = siAddress.params; nsAccountId = lookup.getSiType(idParam.type.unwrap()).path.join('::'); } lookup.registry.register({ // known: account::AccountId20, fp_account::AccountId20, primitive_types::H160 AccountId: nsAccountId.endsWith('::AccountId20') || nsAccountId.endsWith('::H160') ? 'AccountId20' : 'AccountId32', Address: isMultiAddress ? 'MultiAddress' : 'AccountId', ExtrinsicSignature: ['sp_runtime::MultiSignature'].includes(nsSignature) ? 'MultiSignature' : names[sigParam.type.unwrap().toNumber()] || 'MultiSignature' }); } } /** * @internal Extracts aliases based on what we know the runtime config looks like in a * Substrate chain. Specifically we want to have access to the Call and Event params **/ function extractAliases(params, isContract) { const hasParams = Object.keys(params).some((k) => !k.startsWith('Pallet')); const alias = {}; if (params.SpRuntimeUncheckedExtrinsic) { // Address, Call, Signature, Extra const [, { type }] = params.SpRuntimeUncheckedExtrinsic; alias[type.unwrap().toNumber()] = 'Call'; } else if (hasParams && !isContract) { l.warn('Unable to determine runtime Call type, cannot inspect sp_runtime::generic::unchecked_extrinsic::UncheckedExtrinsic'); } if (params.FrameSystemEventRecord) { // Event, Topic const [{ type }] = params.FrameSystemEventRecord; alias[type.unwrap().toNumber()] = 'Event'; } else if (hasParams && !isContract) { l.warn('Unable to determine runtime Event type, cannot inspect frame_system::EventRecord'); } return alias; } /** @internal Extracts all the intreresting type information for this registry */ function extractTypeInfo(lookup, portable) { const nameInfo = []; const types = {}; for (let i = 0, count = portable.length; i < count; i++) { const type = portable[i]; const lookupIndex = type.id.toNumber(); const extracted = extractName(portable, lookupIndex, portable[i]); if (extracted) { nameInfo.push(extracted); } types[lookupIndex] = type; } const lookups = {}; const names = {}; const params = {}; const dedup = removeDupeNames(lookup, portable, nameInfo); for (let i = 0, count = dedup.length; i < count; i++) { const { lookupIndex, name, params: p } = dedup[i]; names[lookupIndex] = name; lookups[name] = lookup.registry.createLookupType(lookupIndex); params[name] = p; } return { lookups, names, params, types }; } export class PortableRegistry extends Struct { __internal__alias; __internal__lookups; __internal__names; __internal__params; __internal__typeDefs = {}; __internal__types; constructor(registry, value, isContract) { // const timeStart = performance.now() super(registry, { types: 'Vec<PortableType>' }, value); const { lookups, names, params, types } = extractTypeInfo(this, this.types); this.__internal__alias = extractAliases(params, isContract); this.__internal__lookups = lookups; this.__internal__names = names; this.__internal__params = params; this.__internal__types = types; // console.log('PortableRegistry', `${(performance.now() - timeStart).toFixed(2)}ms`) } /** * @description Returns all the available type names for this chain **/ get names() { return Object.values(this.__internal__names).sort(); } /** * @description Returns all the available parameterized types for this chain **/ get paramTypes() { return this.__internal__params; } /** * @description The types of the registry */ get types() { return this.getT('types'); } /** * @description Register all available types into the registry (generally for internal usage) */ register() { registerTypes(this, this.__internal__lookups, this.__internal__names, this.__internal__params); } /** * @description Returns the name for a specific lookup */ getName(lookupId) { return this.__internal__names[this.__internal__getLookupId(lookupId)]; } /** * @description Finds a specific type in the registry */ getSiType(lookupId) { // NOTE catch-22 - this may already be used as part of the constructor, so // ensure that we have actually initialized it correctly const found = (this.__internal__types || this.types)[this.__internal__getLookupId(lookupId)]; if (!found) { throw new Error(`PortableRegistry: Unable to find type with lookupId ${lookupId.toString()}`); } return found.type; } /** * @description Lookup the type definition for the index */ getTypeDef(lookupId) { const lookupIndex = this.__internal__getLookupId(lookupId); if (!this.__internal__typeDefs[lookupIndex]) { const lookupName = this.__internal__names[lookupIndex]; const empty = { info: TypeDefInfo.DoNotConstruct, lookupIndex, lookupName, type: this.registry.createLookupType(lookupIndex) }; // Set named items since we will get into circular lookups along the way if (lookupName) { this.__internal__typeDefs[lookupIndex] = empty; } const extracted = this.__internal__extract(this.getSiType(lookupId), lookupIndex); // For non-named items, we only set this right at the end if (!lookupName) { this.__internal__typeDefs[lookupIndex] = empty; } Object.keys(extracted).forEach((k) => { if (k !== 'lookupName' || extracted[k]) { // these are safe since we are looking through the keys as set this.__internal__typeDefs[lookupIndex][k] = extracted[k]; } }); // don't set lookupName on lower-level, we want to always direct to the type if (extracted.info === TypeDefInfo.Plain) { this.__internal__typeDefs[lookupIndex].lookupNameRoot = this.__internal__typeDefs[lookupIndex].lookupName; delete this.__internal__typeDefs[lookupIndex].lookupName; } } return this.__internal__typeDefs[lookupIndex]; } /** * @description For a specific field, perform adjustments to not have built-in conflicts */ sanitizeField(name) { let nameField = null; let nameOrig = null; if (name.isSome) { nameField = stringCamelCase(name.unwrap()); if (nameField.includes('#')) { nameOrig = nameField; nameField = nameOrig.replace(/#/g, '_'); } else if (RESERVED.includes(nameField)) { nameOrig = nameField; nameField = `${nameField}_`; } } return [nameField, nameOrig]; } /** @internal Creates a TypeDef based on an internal lookupId */ __internal__createSiDef(lookupId) { const typeDef = this.getTypeDef(lookupId); const lookupIndex = lookupId.toNumber(); // Setup for a lookup on complex types return [TypeDefInfo.DoNotConstruct, TypeDefInfo.Enum, TypeDefInfo.Struct].includes(typeDef.info) && typeDef.lookupName ? { docs: typeDef.docs, info: TypeDefInfo.Si, lookupIndex, lookupName: this.__internal__names[lookupIndex], type: this.registry.createLookupType(lookupId) } : typeDef; } /** @internal Converts a lookupId input to the actual lookup index */ __internal__getLookupId(lookupId) { if (isString(lookupId)) { if (!this.registry.isLookupType(lookupId)) { throw new Error(`PortableRegistry: Expected a lookup string type, found ${lookupId}`); } return parseInt(lookupId.replace('Lookup', ''), 10); } else if (isNumber(lookupId)) { return lookupId; } return lookupId.toNumber(); } /** @internal Converts a type into a TypeDef for Codec usage */ __internal__extract(type, lookupIndex) { const namespace = type.path.join('::'); let typeDef; const aliasType = this.__internal__alias[lookupIndex] || getAliasPath(type); try { if (aliasType) { typeDef = this.__internal__extractAliasPath(lookupIndex, aliasType); } else { switch (type.def.type) { case 'Array': typeDef = this.__internal__extractArray(lookupIndex, type.def.asArray); break; case 'BitSequence': typeDef = this.__internal__extractBitSequence(lookupIndex, type.def.asBitSequence); break; case 'Compact': typeDef = this.__internal__extractCompact(lookupIndex, type.def.asCompact); break; case 'Composite': typeDef = this.__internal__extractComposite(lookupIndex, type, type.def.asComposite); break; case 'HistoricMetaCompat': typeDef = this.__internal__extractHistoric(lookupIndex, type.def.asHistoricMetaCompat); break; case 'Primitive': typeDef = this.__internal__extractPrimitive(lookupIndex, type); break; case 'Sequence': typeDef = this.__internal__extractSequence(lookupIndex, type.def.asSequence); break; case 'Tuple': typeDef = this.__internal__extractTuple(lookupIndex, type.def.asTuple); break; case 'Variant': typeDef = this.__internal__extractVariant(lookupIndex, type, type.def.asVariant); break; default: assertUnreachable(type.def.type); } } } catch (error) { throw new Error(`PortableRegistry: ${lookupIndex}${namespace ? ` (${namespace})` : ''}: Error extracting ${stringify(type)}: ${error.message}`); } return objectSpread({ docs: sanitizeDocs(type.docs), namespace }, typeDef); } /** @internal Extracts a ScaleInfo Array into TypeDef.VecFixed */ __internal__extractArray(_, { len, type }) { const length = len.toNumber(); if (length > 2048) { throw new Error('Only support for [Type; <length>], where length <= 2048'); } return withTypeString(this.registry, { info: TypeDefInfo.VecFixed, length, sub: this.__internal__createSiDef(type) }); } /** @internal Extracts a ScaleInfo BitSequence into TypeDef.Plain */ __internal__extractBitSequence(_, { bitOrderType, bitStoreType }) { // With the v3 of scale-info this swapped around, but obviously the decoder cannot determine // the order. With that in-mind, we apply a detection for LSb0/Msb and set accordingly const a = this.__internal__createSiDef(bitOrderType); const b = this.__internal__createSiDef(bitStoreType); const [bitOrder, bitStore] = BITVEC_NS.includes(a.namespace || '') ? [a, b] : [b, a]; if (!bitOrder.namespace || !BITVEC_NS.includes(bitOrder.namespace)) { throw new Error(`Unexpected bitOrder found as ${bitOrder.namespace || '<unknown>'}`); } else if (bitStore.info !== TypeDefInfo.Plain || bitStore.type !== 'u8') { throw new Error(`Only u8 bitStore is currently supported, found ${bitStore.type}`); } const isLsb = BITVEC_NS_LSB.includes(bitOrder.namespace); if (!isLsb) { // TODO To remove this limitation, we need to pass an extra info flag // through to the TypeDef (Here we could potentially re-use something // like index (???) to indicate and ensure we use it to pass to the // BitVec constructor - which does handle this type) // // See https://github.com/polkadot-js/api/issues/5588 // throw new Error(`Only LSB BitVec is currently supported, found ${bitOrder.namespace}`); } return { info: TypeDefInfo.Plain, type: 'BitVec' }; } /** @internal Extracts a ScaleInfo Compact into TypeDef.Compact */ __internal__extractCompact(_, { type }) { return withTypeString(this.registry, { info: TypeDefInfo.Compact, sub: this.__internal__createSiDef(type) }); } /** @internal Extracts a ScaleInfo Composite into TypeDef.{BTree*, Range*, Wrapper*} */ __internal__extractComposite(lookupIndex, { params, path }, { fields }) { if (path.length) { const pathFirst = path[0].toString(); const pathLast = path[path.length - 1].toString(); if (path.length === 1 && pathFirst === 'BTreeMap') { if (params.length !== 2) { throw new Error(`BTreeMap requires 2 parameters, found ${params.length}`); } return withTypeString(this.registry, { info: TypeDefInfo.BTreeMap, sub: params.map(({ type }) => this.__internal__createSiDef(type.unwrap())) }); } else if (path.length === 1 && pathFirst === 'BTreeSet') { if (params.length !== 1) { throw new Error(`BTreeSet requires 1 parameter, found ${params.length}`); } return withTypeString(this.registry, { info: TypeDefInfo.BTreeSet, sub: this.__internal__createSiDef(params[0].type.unwrap()) }); } else if (['Range', 'RangeInclusive'].includes(pathFirst)) { if (params.length !== 1) { throw new Error(`Range requires 1 parameter, found ${params.length}`); } return withTypeString(this.registry, { info: pathFirst === 'Range' ? TypeDefInfo.Range : TypeDefInfo.RangeInclusive, sub: this.__internal__createSiDef(params[0].type.unwrap()), type: pathFirst }); } else if (['WrapperKeepOpaque', 'WrapperOpaque'].includes(pathLast)) { if (params.length !== 1) { throw new Error(`WrapperOpaque requires 1 parameter, found ${params.length}`); } return withTypeString(this.registry, { info: pathLast === 'WrapperKeepOpaque' ? TypeDefInfo.WrapperKeepOpaque : TypeDefInfo.WrapperOpaque, sub: this.__internal__createSiDef(params[0].type.unwrap()), type: pathLast }); } } return PATHS_SET.some((p) => matchParts(p, path)) ? this.__internal__extractCompositeSet(lookupIndex, params, fields) : this.__internal__extractFields(lookupIndex, fields); } /** @internal Extracts a ScaleInfo CompositeSet into TypeDef.Set */ __internal__extractCompositeSet(_, params, fields) { if (params.length !== 1 || fields.length !== 1) { throw new Error('Set handling expects param/field as single entries'); } return withTypeString(this.registry, { info: TypeDefInfo.Set, length: this.registry.createTypeUnsafe(this.registry.createLookupType(fields[0].type), []).bitLength(), sub: this.getSiType(params[0].type.unwrap()).def.asVariant.variants.map(({ index, name }) => ({ // This will be an issue > 2^53 - 1 ... don't have those (yet) index: index.toNumber(), info: TypeDefInfo.Plain, name: name.toString(), type: 'Null' })) }); } /** @internal Extracts ScaleInfo enum/struct fields into TypeDef.{Struct, Tuple} */ __internal__extractFields(lookupIndex, fields) { let isStruct = true; let isTuple = true; const count = fields.length; for (let f = 0; f < count; f++) { const { name } = fields[f]; isStruct = isStruct && name.isSome; isTuple = isTuple && name.isNone; } if (!isTuple && !isStruct) { throw new Error('Invalid fields type detected, expected either Tuple (all unnamed) or Struct (all named)'); } if (count === 0) { return { info: TypeDefInfo.Null, type: 'Null' }; } else if (isTuple && count === 1) { const typeDef = this.__internal__createSiDef(fields[0].type); return objectSpread({}, typeDef, lookupIndex === -1 ? null : { lookupIndex, lookupName: this.__internal__names[lookupIndex], lookupNameRoot: typeDef.lookupName }, fields[0].typeName.isSome ? { typeName: sanitize(fields[0].typeName.unwrap()) } : null); } const [sub, alias] = this.__internal__extractFieldsAlias(fields); return withTypeString(this.registry, objectSpread({ info: isTuple // Tuple check first ? TypeDefInfo.Tuple : TypeDefInfo.Struct, sub }, alias.size ? { alias } : null, lookupIndex === -1 ? null : { lookupIndex, lookupName: this.__internal__names[lookupIndex] })); } /** @internal Apply field aliassed (with no JS conflicts) */ __internal__extractFieldsAlias(fields) { const alias = new Map(); const count = fields.length; const sub = new Array(count); for (let i = 0; i < count; i++) { const { docs, name, type, typeName } = fields[i]; const typeDef = this.__internal__createSiDef(type); if (name.isNone) { sub[i] = typeDef; } else { const [nameField, nameOrig] = this.sanitizeField(name); if (nameField && nameOrig) { alias.set(nameField, nameOrig); } sub[i] = objectSpread({ docs: sanitizeDocs(docs), name: nameField }, typeDef, typeName.isSome ? { typeName: sanitize(typeName.unwrap()) } : null); } } return [sub, alias]; } /** @internal Extracts an internal Historic (pre V14) type */ __internal__extractHistoric(_, type) { return objectSpread({ displayName: type.toString(), isFromSi: true }, getTypeDef(type)); } /** @internal Extracts a ScaleInfo Primitive into TypeDef.Plain */ __internal__extractPrimitive(_, type) { const typeStr = type.def.asPrimitive.type.toString(); return { info: TypeDefInfo.Plain, type: PRIMITIVE_ALIAS[typeStr] || typeStr.toLowerCase() }; } /** @internal Applies an alias path onto the TypeDef */ __internal__extractAliasPath(_, type) { return { info: TypeDefInfo.Plain, type }; } /** @internal Extracts a ScaleInfo Sequence into TypeDef.Vec (with Bytes shortcut) */ __internal__extractSequence(lookupIndex, { type }) { const sub = this.__internal__createSiDef(type); if (sub.type === 'u8') { return { info: TypeDefInfo.Plain, type: 'Bytes' }; } return withTypeString(this.registry, { info: TypeDefInfo.Vec, lookupIndex, lookupName: this.__internal__names[lookupIndex], sub }); } /** @internal Extracts a ScaleInfo Tuple into TypeDef.Tuple */ __internal__extractTuple(lookupIndex, ids) { if (ids.length === 0) { return { info: TypeDefInfo.Null, type: 'Null' }; } else if (ids.length === 1) { return this.getTypeDef(ids[0]); } const sub = ids.map((t) => this.__internal__createSiDef(t)); return withTypeString(this.registry, { info: TypeDefInfo.Tuple, lookupIndex, lookupName: this.__internal__names[lookupIndex], sub }); } /** @internal Extracts a ScaleInfo Variant into TypeDef.{Option, Result, Enum} */ __internal__extractVariant(lookupIndex, { params, path }, { variants }) { if (path.length) { const specialVariant = path[0].toString(); if (specialVariant === 'Option') { if (params.length !== 1) { throw new Error(`Option requires 1 parameter, found ${params.length}`); } // NOTE This is opt-in (unhandled), not by default // if (sub.type === 'bool') { // return withTypeString(this.registry, { // info: TypeDefInfo.Plain, // type: 'OptionBool' // }); // } return withTypeString(this.registry, { info: TypeDefInfo.Option, sub: this.__internal__createSiDef(params[0].type.unwrap()) }); } else if (specialVariant === 'Result') { if (params.length !== 2) { throw new Error(`Result requires 2 parameters, found ${params.length}`); } return withTypeString(this.registry, { info: TypeDefInfo.Result, sub: params.map(({ type }, index) => objectSpread({ name: ['Ok', 'Error'][index] }, this.__internal__createSiDef(type.unwrap()))) }); } } if (variants.length === 0) { return { info: TypeDefInfo.Null, type: 'Null' }; } return this.__internal__extractVariantEnum(lookupIndex, variants); } /** @internal Extracts a ScaleInfo Variant into TypeDef.Enum */ __internal__extractVariantEnum(lookupIndex, variants) { const sub = []; // we may get entries out of order, arrange them first before creating with gaps filled // NOTE: Since we mutate, use a copy of the array as an input variants .slice() .sort((a, b) => a.index.cmp(b.index)) .forEach(({ fields, index: bnIndex, name }) => { const index = bnIndex.toNumber(); while (sub.length !== index) { sub.push({ index: sub.length, info: TypeDefInfo.Null, name: `__Unused${sub.length}`, type: 'Null' }); } sub.push(objectSpread(this.__internal__extractFields(-1, fields), { index, name: name.toString() })); }); return withTypeString(this.registry, { info: TypeDefInfo.Enum, lookupIndex, lookupName: this.__internal__names[lookupIndex], sub }); } }