diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d85791ae..19d1356e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changes: +- Allow for correct handling of `Option` types - Adjust for bundlers where `import.meta.url` is undefined diff --git a/packages/types-codec/src/base/Option.spec.ts b/packages/types-codec/src/base/Option.spec.ts index 667a76478f..32ece7fdb3 100644 --- a/packages/types-codec/src/base/Option.spec.ts +++ b/packages/types-codec/src/base/Option.spec.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { TypeRegistry } from '@polkadot/types'; -import { bool, Bytes, Option, Text, U32 } from '@polkadot/types-codec'; +import { bool, Bytes, Null, Option, Text, U32 } from '@polkadot/types-codec'; const registry = new TypeRegistry(); @@ -27,6 +27,20 @@ describe('Option', (): void => { expect(new Option(registry, Text, null).isNone).toBe(true); expect(new Option(registry, Text, 'test').isNone).toBe(false); expect(new Option(registry, Text, '0x').isNone).toBe(true); + expect(new Option(registry, '()', null).isNone).toBe(true); + }); + + it('can wrap an Option/Option<()>', (): void => { + [ + new Option(registry, Null, new Null(registry)), + new Option(registry, '()', new Null(registry)) + ].forEach((test): void => { + expect(test.isSome).toBe(true); + expect(test.isNone).toBe(false); + expect(test.isEmpty).toBe(false); + expect(test.toU8a()).toEqual(new Uint8Array([1])); + expect(test.unwrap().toHex()).toEqual('0x'); + }); }); it('correctly handles booleans', (): void => { diff --git a/packages/types-codec/src/base/Option.ts b/packages/types-codec/src/base/Option.ts index dcda725cd7..b3a28b0f3c 100644 --- a/packages/types-codec/src/base/Option.ts +++ b/packages/types-codec/src/base/Option.ts @@ -9,6 +9,15 @@ import { assert, isCodec, isNull, isU8a, isUndefined, u8aToHex } from '@polkadot import { typeToConstructor } from '../utils'; import { Null } from './Null'; +class None extends Null { + /** + * @description Returns the base runtime type name for this instance + */ + public override toRawType (): string { + return 'None'; + } +} + /** @internal */ function decodeOption (registry: Registry, Type: CodecClass, value?: unknown): Codec { // In the case of an option, unwrap the inner @@ -16,8 +25,8 @@ function decodeOption (registry: Registry, Type: CodecClass, value?: unknown): C value = value.value; } - if (isNull(value) || isUndefined(value) || value instanceof Null || value === '0x') { - return new Null(registry); + if (isNull(value) || isUndefined(value) || value === '0x') { + return new None(registry); } else if (value instanceof Type) { // don't re-create, use as it (which also caters for derived types) return value; @@ -25,7 +34,7 @@ function decodeOption (registry: Registry, Type: CodecClass, value?: unknown): C // the isU8a check happens last in the if-tree - since the wrapped value // may be an instance of it, so Type and Option checks go in first return !value.length || value[0] === 0 - ? new Null(registry) + ? new None(registry) : new Type(registry, value.subarray(1)); } @@ -55,7 +64,7 @@ export class Option implements IOption { const Type = typeToConstructor(registry, typeName); const decoded = isU8a(value) && value.length && !isCodec(value) ? value[0] === 0 - ? new Null(registry) + ? new None(registry) : new Type(registry, value.subarray(1)) : decodeOption(registry, Type, value); @@ -63,7 +72,7 @@ export class Option implements IOption { this.#Type = Type; this.#raw = decoded as T; - if (decoded.initialU8aLength) { + if (decoded && decoded.initialU8aLength) { this.#initialU8aLength = 1 + decoded.initialU8aLength; } } @@ -109,7 +118,7 @@ export class Option implements IOption { * @description Checks if the Option has no value */ public get isNone (): boolean { - return this.#raw instanceof Null; + return this.#raw instanceof None; } /**