Skip to main content
Module

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

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

import type { Option, Text, Type, Vec } from 'https://deno.land/x/polkadot@0.2.33/types-codec/mod.ts';import type { AnyString, LookupString, Registry } from 'https://deno.land/x/polkadot@0.2.33/types-codec/types/index.ts';import type { ILookup, TypeDef } from 'https://deno.land/x/polkadot@0.2.33/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, u32 } from 'https://deno.land/x/polkadot@0.2.33/types-codec/mod.ts';import { getTypeDef, TypeDefInfo, withTypeString } from 'https://deno.land/x/polkadot@0.2.33/types-create/mod.ts';import { assertUnreachable, isNumber, isString, logger, objectSpread, stringCamelCase, stringify, stringPascalCase } from 'https://deno.land/x/polkadot@0.2.33/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 result = new Array<string>(docs.length);
for (let i = 0; i < docs.length; i++) { result[i] = docs[i].toString(); }
return result;}
/** @internal Split a namespace with :: into individual parts */function splitNamespace (values: string[]): string[][] { const result = new Array<string[]>(values.length);
for (let i = 0; i < values.length; 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; i < names.length; 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 adjusted = new Array<ExtractBase>(allSame.length);
// loop through all, specifically checking that index where the // first param yields differences for (let i = 0; i < allSame.length; 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 < allSame.length; 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({ AccountId: ['sp_core::crypto::AccountId32'].includes(nsAccountId) ? 'AccountId32' : ['account::AccountId20', 'primitive_types::H160'].includes(nsAccountId) ? 'AccountId20' : 'AccountId32', // other, default to 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> = {}; const porCount = portable.length;
for (let i = 0; i < porCount; 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); const dedupCount = dedup.length;
for (let i = 0; i < dedupCount; 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];
// NOTE: Currently the BitVec type is one-way only, i.e. we only use it to decode, not // re-encode stuff. As such we ignore the msb/lsb identifier given by bitOrderType, or rather // we don't pass it though at all (all displays in LSB) if (!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}`); }
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;
for (let f = 0; f < fields.length; 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 (fields.length === 0) { return { info: TypeDefInfo.Null, type: 'Null' }; } else if (isTuple && fields.length === 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 sub = new Array<TypeDef>(fields.length);
for (let i = 0; i < fields.length; 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 }); }}