@polkadot/types
Version:
Implementation of the Parity codec
882 lines (881 loc) • 37 kB
JavaScript
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
});
}
}