diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a6f4ec912..c5174d9cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Changes: - Align `BeefyPayload` with Substrate master - Use `static get [Symbol.species] ()` for `Array` & `Uint8Array` types +- Add support for (optional, non-runtime recommended) `f32` & `f64` types ## 8.9.1 Jun 19, 2022 diff --git a/packages/api/src/checkTypes.manual.ts b/packages/api/src/checkTypes.manual.ts index f566fb0945..1890a1a140 100644 --- a/packages/api/src/checkTypes.manual.ts +++ b/packages/api/src/checkTypes.manual.ts @@ -213,12 +213,14 @@ function types (api: ApiPromise): void { const balance = registry.createType('Balance', 2); const gas = registry.createType('Gas', 2); const compact = registry.createType('Compact', 2); + const f32 = registry.createType('f32'); + const u32 = registry.createType('u32'); // const random = registry.createType('RandomType', 2); // This one should deliberately show a TS error const gasUnsafe = createTypeUnsafe(registry, 'Gas', [2]); const overriddenUnsafe = createTypeUnsafe
(registry, 'Gas', [2]); - console.log(balance, gas, compact, gasUnsafe, overriddenUnsafe, api.createType('AccountData')); + console.log(balance, gas, compact, gasUnsafe, overriddenUnsafe, u32.toNumber(), f32.toNumber(), api.createType('AccountData')); } async function tx (api: ApiPromise, pairs: TestKeyringMap): Promise { diff --git a/packages/types-codec/src/native/Float.spec.ts b/packages/types-codec/src/native/Float.spec.ts new file mode 100644 index 0000000000..836bf0abda --- /dev/null +++ b/packages/types-codec/src/native/Float.spec.ts @@ -0,0 +1,49 @@ +// Copyright 2017-2022 @polkadot/types-codec authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { TypeRegistry } from '@polkadot/types'; +import { F32, F64 } from '@polkadot/types-codec'; + +describe('Float', (): void => { + const registry = new TypeRegistry(); + + describe('F32', (): void => { + it('has a sane toRawType()', (): void => { + expect( + new F32(registry).toRawType() + ).toEqual('f32'); + }); + + it('constructs from Uint8Array', (): void => { + expect( + new F32(registry, new Uint8Array([0, 0, 0, 128])).toNumber() + ).toEqual(-0.0); + }); + + it('triggers isEmpty on 0', (): void => { + expect( + new F32(registry, 0).isEmpty + ).toEqual(true); + }); + + it('constructs from a float value', (): void => { + expect( + new F32(registry, 123.456).toString() + ).toEqual('123.456'); + }); + }); + + describe('F64', (): void => { + it('has a sane toRawType()', (): void => { + expect( + new F64(registry).toRawType() + ).toEqual('f64'); + }); + + it('constructs from hex', (): void => { + expect( + new F64(registry, '0x0000000000000080').toNumber() + ).toEqual(-0.0); + }); + }); +}); diff --git a/packages/types-codec/src/native/Float.ts b/packages/types-codec/src/native/Float.ts new file mode 100644 index 0000000000..88f8804e77 --- /dev/null +++ b/packages/types-codec/src/native/Float.ts @@ -0,0 +1,128 @@ +// Copyright 2017-2022 @polkadot/types-codec authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { HexString } from '@polkadot/util/types'; +import type { AnyFloat, CodecClass, IFloat, Inspect, IU8a, Registry } from '../types'; + +import { floatToU8a, isHex, isU8a, u8aToFloat, u8aToHex, u8aToU8a } from '@polkadot/util'; + +interface Options { + bitLength?: 32 | 64; +} + +/** + * @name Float + * @description + * A Codec wrapper for F32 & F64 values. You generally don't want to be using + * f32/f64 in your runtime, operations on fixed points numbers are preferable. This class + * was explicitly added since scale-codec has a flag that enables this and it is available + * in some eth_* RPCs + */ +export class Float extends Number implements IFloat { + readonly #bitLength: 32 | 64; + + public createdAtHash?: IU8a; + + readonly encodedLength: number; + + readonly initialU8aLength?: number; + + readonly registry: Registry; + + constructor (registry: Registry, value?: AnyFloat, { bitLength = 32 }: Options = {}) { + super( + isU8a(value) || isHex(value) + ? value.length === 0 + ? 0 + : u8aToFloat(u8aToU8a(value), { bitLength }) + : (value || 0) + ); + + this.#bitLength = bitLength; + this.encodedLength = bitLength / 8; + this.initialU8aLength = this.encodedLength; + this.registry = registry; + } + + public static with (bitLength: 32 | 64): CodecClass { + return class extends Float { + constructor (registry: Registry, value?: AnyFloat) { + super(registry, value, { bitLength }); + } + }; + } + + /** + * @description returns a hash of the contents + */ + public get hash (): IU8a { + return this.registry.hash(this.toU8a()); + } + + /** + * @description Returns true if the type wraps an empty/default all-0 value + */ + get isEmpty (): boolean { + return this.valueOf() === 0; + } + + /** + * @description Compares the value of the input to see if there is a match + */ + public eq (other?: unknown): boolean { + return this.valueOf() === Number(other); + } + + /** + * @description Returns a breakdown of the hex encoding for this Codec + */ + public inspect (): Inspect { + return { + outer: [this.toU8a()] + }; + } + + /** + * @description Returns a hex string representation of the value + */ + public toHex (): HexString { + return u8aToHex(this.toU8a()); + } + + /** + * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information + */ + public toHuman (): string { + return this.toString(); + } + + /** + * @description Converts the Object to JSON, typically used for RPC transfers + */ + public toJSON (): string { + // Not sure if this is actually a hex or a string value + // (would need to check against RPCs to see the result here) + return this.toHex(); + } + + /** + * @description Returns the number representation (Same as valueOf) + */ + public toNumber (): number { + return this.valueOf(); + } + + /** + * @description Returns the base runtime type name for this instance + */ + public toRawType (): string { + return `f${this.#bitLength}`; + } + + /** + * @description Encodes the value as a Uint8Array as per the SCALE specifications + */ + public toU8a (): Uint8Array { + return floatToU8a(this, { bitLength: this.#bitLength }); + } +} diff --git a/packages/types-codec/src/native/Json.ts b/packages/types-codec/src/native/Json.ts index 5bdf476a25..9350d72c7a 100644 --- a/packages/types-codec/src/native/Json.ts +++ b/packages/types-codec/src/native/Json.ts @@ -64,6 +64,9 @@ export class Json extends Map implements Codec { return compareMap(this, other); } + /** + * @description Returns a typed value from the internal map + */ public getT (key: string): T { return this.get(key) as unknown as T; } diff --git a/packages/types-codec/src/native/index.ts b/packages/types-codec/src/native/index.ts index e51c82d03c..cc31ea9d71 100644 --- a/packages/types-codec/src/native/index.ts +++ b/packages/types-codec/src/native/index.ts @@ -3,6 +3,7 @@ export { bool, bool as Bool } from './Bool'; export { CodecDate, CodecDate as Date } from './Date'; +export { Float } from './Float'; export { Json } from './Json'; export { Raw } from './Raw'; export { CodecSet, CodecSet as Set } from './Set'; diff --git a/packages/types-codec/src/primitive/F32.ts b/packages/types-codec/src/primitive/F32.ts new file mode 100644 index 0000000000..ba59b68daf --- /dev/null +++ b/packages/types-codec/src/primitive/F32.ts @@ -0,0 +1,14 @@ +// Copyright 2017-2022 @polkadot/types-codec authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { Float } from '../native/Float'; + +/** + * @name f32 + * @description + * A 32-bit float + */ +export class f32 extends Float.with(32) { + // NOTE without this, we cannot properly determine extensions + public readonly __FloatType = 'f32'; +} diff --git a/packages/types-codec/src/primitive/F64.ts b/packages/types-codec/src/primitive/F64.ts new file mode 100644 index 0000000000..47918554fd --- /dev/null +++ b/packages/types-codec/src/primitive/F64.ts @@ -0,0 +1,14 @@ +// Copyright 2017-2022 @polkadot/types-codec authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { Float } from '../native/Float'; + +/** + * @name f64 + * @description + * A 64-bit float + */ +export class f64 extends Float.with(64) { + // NOTE without this, we cannot properly determine extensions + public readonly __FloatType = 'f64'; +} diff --git a/packages/types-codec/src/primitive/index.ts b/packages/types-codec/src/primitive/index.ts index ff2c1a2171..7d4e6e6c2b 100644 --- a/packages/types-codec/src/primitive/index.ts +++ b/packages/types-codec/src/primitive/index.ts @@ -1,6 +1,8 @@ // Copyright 2017-2022 @polkadot/types-codec authors & contributors // SPDX-License-Identifier: Apache-2.0 +export { f32, f32 as F32 } from './F32'; +export { f64, f64 as F64 } from './F64'; export { i8, i8 as I8 } from './I8'; export { i16, i16 as I16 } from './I16'; export { i32, i32 as I32 } from './I32'; diff --git a/packages/types-codec/src/types/helpers.ts b/packages/types-codec/src/types/helpers.ts index 3c2d73e1c4..2df36e10b9 100644 --- a/packages/types-codec/src/types/helpers.ts +++ b/packages/types-codec/src/types/helpers.ts @@ -10,6 +10,9 @@ export type AnyFunction = (...args: any[]) => any; export type AnyNumber = BN | bigint | Uint8Array | number | string; +// eslint-disable-next-line @typescript-eslint/ban-types +export type AnyFloat = Number | number | Uint8Array | string; + // eslint-disable-next-line @typescript-eslint/ban-types export type AnyString = String | string; diff --git a/packages/types-codec/src/types/interfaces.ts b/packages/types-codec/src/types/interfaces.ts index 7346e1bf7f..cc0b30cb3c 100644 --- a/packages/types-codec/src/types/interfaces.ts +++ b/packages/types-codec/src/types/interfaces.ts @@ -33,6 +33,10 @@ export interface INumber extends Codec { toNumber (): number; } +export interface IFloat extends Codec { + toNumber (): number; +} + export interface IOption extends Codec { readonly isNone: boolean; readonly isSome: boolean; diff --git a/packages/types/src/types/registry.ts b/packages/types/src/types/registry.ts index f19dc231a3..4998700189 100644 --- a/packages/types/src/types/registry.ts +++ b/packages/types/src/types/registry.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { Observable } from 'rxjs'; -import type { BitVec, Bool, bool, Bytes, I8, i8, I16, i16, I32, i32, I64, i64, I128, i128, I256, i256, Json, Null, OptionBool, Raw, Text, Type, U8, u8, U16, u16, U32, u32, U64, u64, U128, u128, U256, u256, USize, usize } from '@polkadot/types-codec'; +import type { BitVec, Bool, bool, Bytes, F32, f32, F64, f64, I8, i8, I16, i16, I32, i32, I64, i64, I128, i128, I256, i256, Json, Null, OptionBool, Raw, Text, Type, U8, u8, U16, u16, U32, u32, U64, u64, U128, u128, U256, u256, USize, usize } from '@polkadot/types-codec'; import type { RegistryTypes } from '@polkadot/types-codec/types'; import type { BN } from '@polkadot/util'; import type { GenericExtrinsic, GenericExtrinsicEra, GenericExtrinsicPayload, GenericSignerPayload } from '../extrinsic'; @@ -18,7 +18,11 @@ export type { Registry, RegistryError, RegistryTypes } from '@polkadot/types-cod export interface InterfaceTypes { // base codec - BitVec: BitVec, Bool: Bool, Bytes: Bytes, I128: I128, I16: I16, I256: I256, I32: I32, I64: I64, I8: I8, Json: Json, Null: Null, OptionBool: OptionBool, Raw: Raw, Text: Text, Type: Type, U128: U128, U16: U16, U256: U256, U32: U32, U64: U64, U8: U8, USize: USize, bool: bool, i128: i128, i16: i16, i256: i256, i32: i32, i64: i64, i8: i8, u128: u128, u16: u16, u256: u256, u32: u32, u64: u64, u8: u8, usize: usize, + BitVec: BitVec, Bytes: Bytes, Json: Json, Null: Null, OptionBool: OptionBool, Raw: Raw, Text: Text, Type: Type, + // base codec - upper variants + Bool: Bool, F32: F32, F64: F64, I128: I128, I16: I16, I256: I256, I32: I32, I64: I64, I8: I8, U128: U128, U16: U16, U256: U256, U32: U32, U64: U64, U8: U8, USize: USize, + // base codec - lower variants + bool: bool, f32: f32, f64: f64, i128: i128, i16: i16, i256: i256, i32: i32, i64: i64, i8: i8, u128: u128, u16: u16, u256: u256, u32: u32, u64: u64, u8: u8, usize: usize, // extrinsic Extrinsic: GenericExtrinsic, ExtrinsicEra: GenericExtrinsicEra, ExtrinsicPayload: GenericExtrinsicPayload, SignerPayload: GenericSignerPayload, // generic