Skip to main content
Module

x/polkadot/types/metadata/PortableRegistry/PortableRegistry.ts

Package publishing for deno.land/x/polkadot
Go to Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070

import type { Option, Text, Type, u32, Vec } from 'https://deno.land/x/polkadot@0.2.40/types-codec/mod.ts';import type { AnyString, LookupString, Registry } from 'https://deno.land/x/polkadot@0.2.40/types-codec/types/index.ts';import type { ILookup, TypeDef } from 'https://deno.land/x/polkadot@0.2.40/types-create/types/index.ts';import type { PortableType } from '../../interfaces/metadata/index.ts';import type { SiField, SiLookupTypeId, SiType, SiTypeDefArray, SiTypeDefBitSequence, SiTypeDefCompact, SiTypeDefComposite, SiTypeDefSequence, SiTypeDefTuple, SiTypeDefVariant, SiTypeParameter, SiVariant } from '../../interfaces/scaleInfo/index.ts';
import { sanitize, Struct } from 'https://deno.land/x/polkadot@0.2.40/types-codec/mod.ts';import { getTypeDef, TypeDefInfo, withTypeString } from 'https://deno.land/x/polkadot@0.2.40/types-create/mod.ts';import { assertUnreachable, isNumber, isString, logger, objectSpread, stringCamelCase, stringify, stringPascalCase } from 'https://deno.land/x/polkadot@0.2.40/util/mod.ts';
const l = logger('PortableRegistry');
interface ExtractBase { lookupIndex: number; name: string;}
interface Extract extends ExtractBase { lookupIndex: number; name: string; params: SiTypeParameter[];}
interface TypeInfo { lookups: Record<string, LookupString>; names: Record<number, string>; params: Record<string, SiTypeParameter[]>; types: Record<number, PortableType>;}
const TYPE_UNWRAP = { toNumber: () => -1 };
const PRIMITIVE_ALIAS: Record<string, string> = { 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: Text[]): string[] { const count = docs.length; const result = new Array<string>(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: string[]): string[][] { const count = values.length; const result = new Array<string[]>(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: string[], second: (string | Text)[]): boolean { 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 }: SiType): string | null { // 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: PortableType[], lookupIndex: number, params: SiTypeParameter[], path: AnyString[], isInternal = false): Extract | null { 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<string>(count); const lowers = new Array<string>(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: PortableType[], lookupIndex: number, { type: { params, path } }: PortableType): Extract | null { return extractNameFlat(portable, lookupIndex, params, path);}
/** @internal Check for dupes from a specific index onwards */function nextDupeMatches (name: string, startAt: number, names: Extract[]): Extract[] { 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: ExtractBase[], rewrite: Record<number, string>): boolean { 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: PortableRegistry, portable: PortableType[], names: Extract[]): Extract[] { const rewrite: Record<number, string> = {};
return names .map((original, startAt): Extract | null => { 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<ExtractBase>(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 is Extract => !!n) .map(({ lookupIndex, name, params }): Extract => ({ lookupIndex, name: rewrite[lookupIndex] || name, params }));}
/** @internal Detect on-chain types (AccountId/Signature) as set as the default */function registerTypes (lookup: PortableRegistry, lookups: Record<string, string>, names: Record<number, string>, params: Record<string, SiTypeParameter[]>): void { // 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: Record<string, SiTypeParameter[]>, isContract?: boolean): Record<number, string> { const hasParams = Object.keys(params).some((k) => !k.startsWith('Pallet')); const alias: Record<number, string> = {};
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: PortableRegistry, portable: PortableType[]): TypeInfo { const nameInfo: Extract[] = []; const types: Record<number, PortableType> = {};
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: Record<string, LookupString> = {}; const names: Record<number, string> = {}; const params: Record<string, SiTypeParameter[]> = {}; 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 implements ILookup { #alias: Record<number, string>; #lookups: Record<string, LookupString>; #names: Record<number, string>; #params: Record<string, SiTypeParameter[]>; #typeDefs: Record<number, TypeDef> = {}; #types: Record<number, PortableType>;
constructor (registry: Registry, value?: Uint8Array, isContract?: boolean) { // const timeStart = performance.now()
super(registry, { types: 'Vec<PortableType>' }, value);
const { lookups, names, params, types } = extractTypeInfo(this, this.types);
this.#alias = extractAliases(params, isContract); this.#lookups = lookups; this.#names = names; this.#params = params; this.#types = types;
// console.log('PortableRegistry', `${(performance.now() - timeStart).toFixed(2)}ms`) }
/** * @description Returns all the available type names for this chain **/ public get names (): string[] { return Object.values(this.#names).sort(); }
/** * @description The types of the registry */ public get types (): Vec<PortableType> { return this.getT('types'); }
/** * @description Register all available types into the registry (generally for internal usage) */ public register (): void { registerTypes(this, this.#lookups, this.#names, this.#params); }
/** * @description Returns the name for a specific lookup */ public getName (lookupId: SiLookupTypeId | LookupString | number): string | undefined { return this.#names[this.#getLookupId(lookupId)]; }
/** * @description Finds a specific type in the registry */ public getSiType (lookupId: SiLookupTypeId | LookupString | number): SiType { // 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.#types || this.types)[this.#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 */ public getTypeDef (lookupId: SiLookupTypeId | LookupString | number): TypeDef { const lookupIndex = this.#getLookupId(lookupId);
if (!this.#typeDefs[lookupIndex]) { const lookupName = this.#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.#typeDefs[lookupIndex] = empty; }
const extracted = this.#extract(this.getSiType(lookupId), lookupIndex);
// For non-named items, we only set this right at the end if (!lookupName) { this.#typeDefs[lookupIndex] = empty; }
Object.keys(extracted).forEach((k): void => { if (k !== 'lookupName' || extracted[k]) { // these are safe since we are looking through the keys as set this.#typeDefs[lookupIndex][k as 'info'] = extracted[k as 'info']; } });
// don't set lookupName on lower-level, we want to always direct to the type if (extracted.info === TypeDefInfo.Plain) { this.#typeDefs[lookupIndex].lookupNameRoot = this.#typeDefs[lookupIndex].lookupName; delete this.#typeDefs[lookupIndex].lookupName; } }
return this.#typeDefs[lookupIndex]; }
/** * @description For a specific field, perform adjustments to not have built-in conflicts */ public sanitizeField (name: Option<Text>): [string | null, string | null] { let nameField: string | null = null; let nameOrig: string | null = 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 */ #createSiDef (lookupId: SiLookupTypeId): TypeDef { 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.#names[lookupIndex], type: this.registry.createLookupType(lookupId) } : typeDef; }
/** @internal Converts a lookupId input to the actual lookup index */ #getLookupId (lookupId: SiLookupTypeId | LookupString | number): number { if (isString(lookupId)) { if (!this.registry.isLookupType(lookupId)) { throw new Error(`PortableRegistry: Expected a lookup string type, found ${lookupId as string}`); }
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 */ #extract (type: SiType, lookupIndex: number): TypeDef { const namespace = type.path.join('::'); let typeDef: TypeDef; const aliasType = this.#alias[lookupIndex] || getAliasPath(type);
try { if (aliasType) { typeDef = this.#extractAliasPath(lookupIndex, aliasType); } else { switch (type.def.type) { case 'Array': typeDef = this.#extractArray(lookupIndex, type.def.asArray); break; case 'BitSequence': typeDef = this.#extractBitSequence(lookupIndex, type.def.asBitSequence); break; case 'Compact': typeDef = this.#extractCompact(lookupIndex, type.def.asCompact); break; case 'Composite': typeDef = this.#extractComposite(lookupIndex, type, type.def.asComposite); break; case 'HistoricMetaCompat': typeDef = this.#extractHistoric(lookupIndex, type.def.asHistoricMetaCompat); break; case 'Primitive': typeDef = this.#extractPrimitive(lookupIndex, type); break; case 'Sequence': typeDef = this.#extractSequence(lookupIndex, type.def.asSequence); break; case 'Tuple': typeDef = this.#extractTuple(lookupIndex, type.def.asTuple); break; case 'Variant': typeDef = this.#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 as Error).message}`); }
return objectSpread({ docs: sanitizeDocs(type.docs), namespace }, typeDef); }
/** @internal Extracts a ScaleInfo Array into TypeDef.VecFixed */ #extractArray (_: number, { len, type }: SiTypeDefArray): TypeDef { 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.#createSiDef(type) }); }
/** @internal Extracts a ScaleInfo BitSequence into TypeDef.Plain */ #extractBitSequence (_: number, { bitOrderType, bitStoreType }: SiTypeDefBitSequence): TypeDef { // 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.#createSiDef(bitOrderType); const b = this.#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 */ #extractCompact (_: number, { type }: SiTypeDefCompact): TypeDef { return withTypeString(this.registry, { info: TypeDefInfo.Compact, sub: this.#createSiDef(type) }); }
/** @internal Extracts a ScaleInfo Composite into TypeDef.{BTree*, Range*, Wrapper*} */ #extractComposite (lookupIndex: number, { params, path }: SiType, { fields }: SiTypeDefComposite): TypeDef { 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.#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.#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.#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.#createSiDef(params[0].type.unwrap()), type: pathLast }); } }
return PATHS_SET.some((p) => matchParts(p, path)) ? this.#extractCompositeSet(lookupIndex, params, fields) : this.#extractFields(lookupIndex, fields); }
/** @internal Extracts a ScaleInfo CompositeSet into TypeDef.Set */ #extractCompositeSet (_: number, params: SiTypeParameter[], fields: SiField[]): TypeDef { 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<u32>(this.registry.createLookupType(fields[0].type), []).bitLength(), sub: this.getSiType(params[0].type.unwrap()).def.asVariant.variants.map(({ index, name }): TypeDef => ({ // 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} */ #extractFields (lookupIndex: number, fields: SiField[]): TypeDef { 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.#createSiDef(fields[0].type);
return objectSpread( {}, typeDef, lookupIndex === -1 ? null : { lookupIndex, lookupName: this.#names[lookupIndex], lookupNameRoot: typeDef.lookupName }, fields[0].typeName.isSome ? { typeName: sanitize(fields[0].typeName.unwrap()) } : null ); }
const [sub, alias] = this.#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.#names[lookupIndex] } )); }
/** @internal Apply field aliassed (with no JS conflicts) */ #extractFieldsAlias (fields: SiField[]): [TypeDef[], Map<string, string>] { const alias = new Map<string, string>(); const count = fields.length; const sub = new Array<TypeDef>(count);
for (let i = 0; i < count; i++) { const { docs, name, type, typeName } = fields[i]; const typeDef = this.#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 */ #extractHistoric (_: number, type: Type): TypeDef { return objectSpread({ displayName: type.toString(), isFromSi: true }, getTypeDef(type)); }
/** @internal Extracts a ScaleInfo Primitive into TypeDef.Plain */ #extractPrimitive (_: number, type: SiType): TypeDef { 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 */ #extractAliasPath (_: number, type: string): TypeDef { return { info: TypeDefInfo.Plain, type }; }
/** @internal Extracts a ScaleInfo Sequence into TypeDef.Vec (with Bytes shortcut) */ #extractSequence (lookupIndex: number, { type }: SiTypeDefSequence): TypeDef { const sub = this.#createSiDef(type);
if (sub.type === 'u8') { return { info: TypeDefInfo.Plain, type: 'Bytes' }; }
return withTypeString(this.registry, { info: TypeDefInfo.Vec, lookupIndex, lookupName: this.#names[lookupIndex], sub }); }
/** @internal Extracts a ScaleInfo Tuple into TypeDef.Tuple */ #extractTuple (lookupIndex: number, ids: SiTypeDefTuple): TypeDef { 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.#createSiDef(t));
return withTypeString(this.registry, { info: TypeDefInfo.Tuple, lookupIndex, lookupName: this.#names[lookupIndex], sub }); }
/** @internal Extracts a ScaleInfo Variant into TypeDef.{Option, Result, Enum} */ #extractVariant (lookupIndex: number, { params, path }: SiType, { variants }: SiTypeDefVariant): TypeDef { 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.#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.#createSiDef(type.unwrap())) ) }); } }
if (variants.length === 0) { return { info: TypeDefInfo.Null, type: 'Null' }; }
return this.#extractVariantEnum(lookupIndex, variants); }
/** @internal Extracts a ScaleInfo Variant into TypeDef.Enum */ #extractVariantEnum (lookupIndex: number, variants: SiVariant[]): TypeDef { const sub: (TypeDef & { name: string })[] = [];
// 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.#extractFields(-1, fields), { index, name: name.toString() }) ); });
return withTypeString(this.registry, { info: TypeDefInfo.Enum, lookupIndex, lookupName: this.#names[lookupIndex], sub }); }}