diff --git a/packages/typegen/src/generate/tsDef.ts b/packages/typegen/src/generate/tsDef.ts index 1f8912a738..ad36eafedf 100644 --- a/packages/typegen/src/generate/tsDef.ts +++ b/packages/typegen/src/generate/tsDef.ts @@ -46,6 +46,7 @@ const tsHashMap = tsExport; const tsOption = tsExport; const tsPlain = tsExport; const tsTuple = tsExport; +const tsWrapperOpaque = tsExport; /** @internal */ function tsEnum (registry: Registry, definitions: Record, { lookupIndex, name: enumName, sub }: TypeDef, imports: TypeImports): string { @@ -72,6 +73,7 @@ function tsEnum (registry: Registry, definitions: Record, { case TypeDefInfo.Vec: case TypeDefInfo.Option: case TypeDefInfo.VecFixed: + case TypeDefInfo.WrapperOpaque: return `${isGetter}${asGetter}`; case TypeDefInfo.DoNotConstruct: @@ -111,11 +113,12 @@ function tsResultGetter (registry: Registry, definitions: Record, imports: TypeImports, withShortcut: boolean): string { + const sub = (typeDef.sub as TypeDef); + + setImports(definitions, imports, [wrapper, sub.lookupName]); + + return paramsNotation(wrapper, sub.lookupName || formatType(registry, definitions, sub.type, imports, withShortcut)); +} + +function dualParamsNotation (registry: Registry, wrapper: string, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean): string { + const [a, b] = (typeDef.sub as TypeDef[]); + + setImports(definitions, imports, [wrapper, a.lookupName, b.lookupName]); + + return paramsNotation(wrapper, [ + a.lookupName || formatType(registry, definitions, a.type, imports, withShortcut), + b.lookupName || formatType(registry, definitions, b.type, imports, withShortcut) + ]); +} + const formatters: Record, imports: TypeImports, withShortcut: boolean) => string> = { [TypeDefInfo.Compact]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { - const sub = typeDef.sub as TypeDef; - - setImports(definitions, imports, ['Compact', sub.lookupName]); - - return paramsNotation('Compact', sub.lookupName || formatType(registry, definitions, sub.type, imports, withShortcut)); + return singleParamNotation(registry, 'Compact', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.DoNotConstruct]: (registry: Registry, { lookupName }: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { setImports(definitions, imports, ['DoNotConstruct']); return 'DoNotConstruct'; }, + [TypeDefInfo.Enum]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { if (typeDef.lookupName) { return typeDef.lookupName; @@ -106,38 +123,43 @@ const formatters: Record, imports: TypeImports, withShortcut: boolean) => { throw new Error(`TypeDefInfo.Int: Not implemented on ${stringify(typeDef)}`); }, + [TypeDefInfo.UInt]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { throw new Error(`TypeDefInfo.UInt: Not implemented on ${stringify(typeDef)}`); }, + [TypeDefInfo.Null]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { setImports(definitions, imports, ['Null']); return 'Null'; }, + [TypeDefInfo.Option]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { - const sub = (typeDef.sub as TypeDef); - - setImports(definitions, imports, ['Option', sub.lookupName]); - - return paramsNotation('Option', sub.lookupName || formatType(registry, definitions, sub.type, imports, withShortcut)); + return singleParamNotation(registry, 'Option', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.Plain]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { setImports(definitions, imports, [typeDef.type]); return typeDef.type; }, + [TypeDefInfo.Range]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { throw new Error(`TypeDefInfo.Range: Not implemented on ${stringify(typeDef)}`); }, + [TypeDefInfo.Set]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { throw new Error(`TypeDefInfo.Set: Not implemented on ${stringify(typeDef)}`); }, + [TypeDefInfo.Si]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { return formatType(registry, definitions, registry.lookup.getTypeDef(typeDef.type), imports, withShortcut); }, + [TypeDefInfo.Struct]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { if (typeDef.lookupName) { return typeDef.lookupName; @@ -154,6 +176,7 @@ const formatters: Record `${withShortcut ? '' : ' readonly '}${k}: ${t};`).join(withShortcut ? ' ' : '\n') }${withShortcut ? ' ' : '\n '}} & Struct`; }, + [TypeDefInfo.Tuple]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { const sub = typeDef.sub as TypeDef[]; @@ -166,13 +189,11 @@ const formatters: Record, imports: TypeImports, withShortcut: boolean) => { - const sub = (typeDef.sub as TypeDef); - - setImports(definitions, imports, ['Vec', sub.lookupName]); - - return paramsNotation('Vec', sub.lookupName || formatType(registry, definitions, sub.type, imports, withShortcut)); + return singleParamNotation(registry, 'Vec', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.VecFixed]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { const sub = (typeDef.sub as TypeDef); @@ -182,44 +203,31 @@ const formatters: Record, imports: TypeImports, withShortcut: boolean) => { - const [keyDef, valDef] = (typeDef.sub as TypeDef[]); - - setImports(definitions, imports, ['BTreeMap', keyDef.lookupName, valDef.lookupName]); - - return `BTreeMap<${keyDef.lookupName || formatType(registry, definitions, keyDef.type, imports, withShortcut)}, ${valDef.lookupName || formatType(registry, definitions, valDef.type, imports, withShortcut)}>`; + return dualParamsNotation(registry, 'BTreeMap', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.BTreeSet]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { - const valDef = typeDef.sub as TypeDef; - - setImports(definitions, imports, ['BTreeSet', valDef.lookupName]); - - return `BTreeSet<${valDef.lookupName || formatType(registry, definitions, valDef.type, imports, withShortcut)}>`; + return singleParamNotation(registry, 'BTreeSet', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.HashMap]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { - const [keyDef, valDef] = (typeDef.sub as TypeDef[]); - - setImports(definitions, imports, ['HashMap', keyDef.lookupName, valDef.lookupName]); - - return `HashMap<${keyDef.lookupName || formatType(registry, definitions, keyDef.type, imports, withShortcut)}, ${valDef.lookupName || formatType(registry, definitions, valDef.type, imports, withShortcut)}>`; + return dualParamsNotation(registry, 'HashMap', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.Linkage]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { - const sub = (typeDef.sub as TypeDef); - - setImports(definitions, imports, ['Linkage', sub.lookupName]); - - return paramsNotation('Linkage', sub.lookupName || formatType(registry, definitions, sub.type, imports, withShortcut)); + return singleParamNotation(registry, 'Linkage', typeDef, definitions, imports, withShortcut); }, + [TypeDefInfo.Result]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { - const [okDef, errDef] = (typeDef.sub as TypeDef[]); + return dualParamsNotation(registry, 'Result', typeDef, definitions, imports, withShortcut); + }, - setImports(definitions, imports, ['Result', okDef.lookupName, errDef.lookupName]); - - return `Result<${okDef.lookupName || formatType(registry, definitions, okDef.type, imports, withShortcut)}, ${errDef.lookupName || formatType(registry, definitions, errDef.type, imports, withShortcut)}>`; + [TypeDefInfo.WrapperOpaque]: (registry: Registry, typeDef: TypeDef, definitions: Record, imports: TypeImports, withShortcut: boolean) => { + return singleParamNotation(registry, 'WrapperOpaque', typeDef, definitions, imports, withShortcut); } }; diff --git a/packages/types/src/codec/WrapperOpaque.spec.ts b/packages/types/src/codec/WrapperOpaque.spec.ts new file mode 100644 index 0000000000..7f8b272afd --- /dev/null +++ b/packages/types/src/codec/WrapperOpaque.spec.ts @@ -0,0 +1,40 @@ +// Copyright 2017-2021 @polkadot/types authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { TypeRegistry } from '../create'; +import { WrapperOpaque } from '.'; + +describe('WrapperOpaque', (): void => { + const registry = new TypeRegistry(); + const u8au32 = new Uint8Array([4 << 2, 135, 214, 18, 0]); + + it('u8a encodes a wrapped u32 correctly', (): void => { + expect( + new WrapperOpaque(registry, 'u32', 1234567).toU8a() + ).toEqual(u8au32); + }); + + it('u8a decodes a wrapped u32 correctly', (): void => { + expect( + new WrapperOpaque(registry, 'u32', u8au32).toU8a() + ).toEqual(u8au32); + }); + + it('hex encodes a wrapped u32 correctly', (): void => { + expect( + new WrapperOpaque(registry, 'u32', '0x12345678').toHex() + ).toEqual('0x12345678'); + }); + + it('has the correct unwrap', (): void => { + expect( + new WrapperOpaque(registry, 'u32', '0x12345678').unwrap().toHex() + ).toEqual('0x12345678'); + }); + + it('has the correct toRawType', (): void => { + expect( + new WrapperOpaque(registry, 'u32').toRawType() + ).toEqual('WrapperOpaque'); + }); +}); diff --git a/packages/types/src/codec/WrapperOpaque.ts b/packages/types/src/codec/WrapperOpaque.ts new file mode 100644 index 0000000000..783d9865fd --- /dev/null +++ b/packages/types/src/codec/WrapperOpaque.ts @@ -0,0 +1,60 @@ +// Copyright 2017-2021 @polkadot/types authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Codec, Constructor, Registry } from '../types'; + +import { compactAddLength, compactStripLength, isU8a } from '@polkadot/util'; + +import { Base } from './Base'; +import { typeToConstructor } from './utils'; + +function decodeRaw (registry: Registry, Type: Constructor | string, value?: unknown): T { + const Clazz = typeToConstructor(registry, Type); + + if (isU8a(value)) { + const [, u8a] = compactStripLength(value); + + return new Clazz(registry, u8a); + } + + return new Clazz(registry, value); +} + +export class WrapperOpaque extends Base { + constructor (registry: Registry, Type: Constructor | string, value?: unknown) { + super(registry, decodeRaw(registry, Type, value)); + } + + public static with (Type: Constructor | string): Constructor> { + return class extends WrapperOpaque { + constructor (registry: Registry, value?: unknown) { + super(registry, Type, value); + } + }; + } + + /** + * @description Returns the base runtime type name for this instance + */ + public override toRawType (): string { + return `WrapperOpaque<${this._raw.toRawType()}>`; + } + + /** + * @description The length of the value when encoded as a Uint8Array + */ + public override toU8a (isBare?: boolean): Uint8Array { + const u8a = super.toU8a(isBare); + + return isBare + ? u8a + : compactAddLength(u8a); + } + + /** + * @description Return the inner value for the wrapped type + */ + public unwrap (): T { + return this._raw; + } +} diff --git a/packages/types/src/codec/index.ts b/packages/types/src/codec/index.ts index 53e35d292a..dee5ab6f6e 100644 --- a/packages/types/src/codec/index.ts +++ b/packages/types/src/codec/index.ts @@ -30,3 +30,4 @@ export { U8aFixed } from './U8aFixed'; export { Vec } from './Vec'; // export { VecAny } from './VecAny'; export { VecFixed } from './VecFixed'; +export { WrapperOpaque } from './WrapperOpaque'; diff --git a/packages/types/src/create/createClass.ts b/packages/types/src/create/createClass.ts index 44860c89e1..04206bdcb5 100644 --- a/packages/types/src/create/createClass.ts +++ b/packages/types/src/create/createClass.ts @@ -7,7 +7,7 @@ import type { TypeDef } from './types'; import { assert, isNumber, isUndefined, stringify } from '@polkadot/util'; -import { BTreeMap, BTreeSet, CodecSet, Compact, DoNotConstruct, Enum, HashMap, Int, Option, Range, RangeInclusive, Result, Struct, Tuple, U8aFixed, UInt, Vec, VecFixed } from '../codec'; +import { BTreeMap, BTreeSet, CodecSet, Compact, DoNotConstruct, Enum, HashMap, Int, Option, Range, RangeInclusive, Result, Struct, Tuple, U8aFixed, UInt, Vec, VecFixed, WrapperOpaque } from '../codec'; import { Bytes, Null } from '../primitive'; import { getTypeDef } from './getTypeDef'; import { TypeDefInfo } from './types'; @@ -169,7 +169,10 @@ const infoMapping: Record C ? U8aFixed.with((length * 8) as U8aBitLength, displayName) : VecFixed.with(subType, length) ); - } + }, + + [TypeDefInfo.WrapperOpaque]: (registry: Registry, value: TypeDef): Constructor => + WrapperOpaque.with(getSubType(value)) }; // Returns the type Class for construction diff --git a/packages/types/src/create/encodeTypes.ts b/packages/types/src/create/encodeTypes.ts index 6c8c6f37cb..978c8644fb 100644 --- a/packages/types/src/create/encodeTypes.ts +++ b/packages/types/src/create/encodeTypes.ts @@ -32,6 +32,7 @@ function encodeWithParams (registry: Registry, typeDef: TypeDef, outer: string): case TypeDefInfo.Option: case TypeDefInfo.Result: case TypeDefInfo.Vec: + case TypeDefInfo.WrapperOpaque: return paramsNotation(outer, sub, (p) => encodeTypeDef(registry, p)); } @@ -134,7 +135,9 @@ const encoders: Record st assert(isNumber(length) && !isUndefined(sub) && !Array.isArray(sub), 'Unable to encode VecFixed type'); return `[${sub.type};${length}]`; - } + }, + [TypeDefInfo.WrapperOpaque]: (registry: Registry, typeDef: TypeDef) => + encodeWithParams(registry, typeDef, 'WrapperOpaque') }; function encodeType (registry: Registry, typeDef: TypeDef, withLookup = true): string { diff --git a/packages/types/src/create/getTypeDef.ts b/packages/types/src/create/getTypeDef.ts index 7a4f41c72e..de7cbae251 100644 --- a/packages/types/src/create/getTypeDef.ts +++ b/packages/types/src/create/getTypeDef.ts @@ -216,7 +216,8 @@ const wrappedExtraction: [string, string, TypeDefInfo][] = [ ['Compact<', '>', TypeDefInfo.Compact], ['Linkage<', '>', TypeDefInfo.Linkage], ['Option<', '>', TypeDefInfo.Option], - ['Vec<', '>', TypeDefInfo.Vec] + ['Vec<', '>', TypeDefInfo.Vec], + ['WrapperOpaque<', '>', TypeDefInfo.WrapperOpaque] ]; function extractSubType (type: string, [start, end]: [string, string, TypeDefInfo, any?]): string { diff --git a/packages/types/src/create/sanitize.ts b/packages/types/src/create/sanitize.ts index 9c7db2b775..7b687a9736 100644 --- a/packages/types/src/create/sanitize.ts +++ b/packages/types/src/create/sanitize.ts @@ -8,7 +8,7 @@ interface SanitizeOptions { type Mapper = (value: string, options?: SanitizeOptions) => string; const BOUNDED = ['BTreeMap', 'BTreeSet', 'HashMap', 'Vec']; -const ALLOWED_BOXES = BOUNDED.concat(['Compact', 'DoNotConstruct', 'Int', 'Linkage', 'Range', 'RangeInclusive', 'Result', 'Option', 'UInt']); +const ALLOWED_BOXES = BOUNDED.concat(['Compact', 'DoNotConstruct', 'Int', 'Linkage', 'Range', 'RangeInclusive', 'Result', 'Option', 'UInt', 'WrapperOpaque']); const BOX_PRECEDING = ['<', '(', '[', '"', ',', ' ']; // start of vec, tuple, fixed array, part of struct def or in tuple const mappings: Mapper[] = [ diff --git a/packages/types/src/create/types.ts b/packages/types/src/create/types.ts index 39cc242353..798cf12a71 100644 --- a/packages/types/src/create/types.ts +++ b/packages/types/src/create/types.ts @@ -21,7 +21,8 @@ export enum TypeDefInfo { Tuple, UInt, Vec, - VecFixed + VecFixed, + WrapperOpaque } export interface TypeDef { diff --git a/packages/types/src/generic/PortableRegistry/PortableRegistry.ts b/packages/types/src/generic/PortableRegistry/PortableRegistry.ts index 64026b43ac..248ec15378 100644 --- a/packages/types/src/generic/PortableRegistry/PortableRegistry.ts +++ b/packages/types/src/generic/PortableRegistry/PortableRegistry.ts @@ -48,7 +48,7 @@ const SETS = [ ].map((p) => p.split('::')); // These we never use these as top-level names, they are wrappers -const WRAPPERS = ['BoundedBTreeMap', 'BoundedVec', 'Box', 'BTreeMap', 'Cow', 'Result', 'Option', 'WeakBoundedVec']; +const WRAPPERS = ['BoundedBTreeMap', 'BoundedVec', 'Box', 'BTreeMap', 'Cow', 'Result', 'Option', 'WeakBoundedVec', 'WrapperOpaque']; // These are reserved and/or conflicts with built-in Codec definitions const RESERVED = ['call', 'entries', 'hash', 'keys', 'new', 'size']; @@ -415,6 +415,11 @@ export class GenericPortableRegistry extends Struct { ...this.#createSiDef(type) })) }); + } else if (specialVariant === 'WrapperOpaque') { + return withTypeString(this.registry, { + info: TypeDefInfo.WrapperOpaque, + sub: this.#createSiDef(params[0].type.unwrap()) + }); } return SETS.some((p) => matchParts(p, path))