From 2dae16a5c7fc6c07186440267d8cf4c7cbc82243 Mon Sep 17 00:00:00 2001 From: peetzweg/ <839848+peetzweg@users.noreply.github.com> Date: Fri, 16 Feb 2024 11:30:30 +0100 Subject: [PATCH] feat: user friendly Weight scale (#544) --- src/lib/formatBalance.test.ts | 1 + src/lib/formatBalance.ts | 32 ++-------- src/lib/formatUInt.ts | 46 ++++++++++++++ src/lib/formatWeight.test.ts | 69 +++++++++++++++++++++ src/lib/formatWeight.ts | 67 ++++++++++++++++++++ src/ui/components/contract/DryRunResult.tsx | 17 ++--- src/ui/components/instantiate/DryRun.tsx | 33 +++++++--- 7 files changed, 220 insertions(+), 45 deletions(-) create mode 100644 src/lib/formatUInt.ts create mode 100644 src/lib/formatWeight.test.ts create mode 100644 src/lib/formatWeight.ts diff --git a/src/lib/formatBalance.test.ts b/src/lib/formatBalance.test.ts index 72ac53ad..67b2495d 100644 --- a/src/lib/formatBalance.test.ts +++ b/src/lib/formatBalance.test.ts @@ -4,6 +4,7 @@ import { TypeRegistry } from '@polkadot/types'; import { beforeAll, describe, expect, it } from 'vitest'; import { formatBalance } from './formatBalance'; + describe('formatBalance', () => { let registry: TypeRegistry; diff --git a/src/lib/formatBalance.ts b/src/lib/formatBalance.ts index 8b36ba10..b465ed17 100644 --- a/src/lib/formatBalance.ts +++ b/src/lib/formatBalance.ts @@ -1,39 +1,17 @@ // Copyright 2022-2024 @paritytech/contracts-ui authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { Balance } from 'types'; - -type FormattingOptions = { - decimals: number; - symbol: string | undefined; - fractionDigits: number; -}; +import { Balance } from '@polkadot/types/interfaces'; +import type { FormattingOptions } from './formatUInt'; +import { formatUInt } from './formatUInt'; const DEFAULT_OPTIONS: FormattingOptions = { decimals: 12, fractionDigits: 2, symbol: undefined, + digitGrouping: true, }; export const formatBalance = (balance: Balance, partialOptions?: Partial) => { - const options: FormattingOptions = { ...DEFAULT_OPTIONS, ...partialOptions }; - - if (options.decimals < 0) throw new Error('Decimals must be positive'); - if (options.fractionDigits < 0) throw new Error('Fraction digits must be positive'); - if (options.decimals < options.fractionDigits) - throw new Error('Decimals must be greater than fraction digits'); - - const balanceString = balance.toString(); - const integerDigits = balanceString.split(''); - const fractionDigits = integerDigits.splice(-options.decimals); - - const fractionalPart = fractionDigits.join('').padStart(options.decimals, '0'); - const integerPart = integerDigits.length ? integerDigits.join('') : '0'; - - return ( - Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(BigInt(integerPart)) + - '.' + - fractionalPart.toString().slice(0, options.fractionDigits).padEnd(options.fractionDigits, '0') + - (options.symbol ? ` ${options.symbol}` : '') - ); + return formatUInt(balance, { ...DEFAULT_OPTIONS, ...partialOptions }); }; diff --git a/src/lib/formatUInt.ts b/src/lib/formatUInt.ts new file mode 100644 index 00000000..63c08d47 --- /dev/null +++ b/src/lib/formatUInt.ts @@ -0,0 +1,46 @@ +// Copyright 2022-2024 @paritytech/contracts-ui authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Compact, UInt } from '@polkadot/types-codec'; + +export type FormattingOptions = { + decimals: number; + symbol: string | undefined; + fractionDigits: number; + digitGrouping: boolean; +}; + +export const formatUInt = (value: UInt | Compact, options: FormattingOptions) => { + if (options.decimals < 0) throw new Error('Decimals must be positive'); + if (options.fractionDigits < 0) throw new Error('Fraction digits must be positive'); + if (options.decimals < options.fractionDigits) + throw new Error('Decimals must be greater than fraction digits'); + + const valueString = value.toString(); + const integerDigits = valueString.split(''); + + let fractionalPart = ''.padStart(options.decimals, '0'); + if (options.decimals !== 0) { + const fractionDigits = integerDigits.splice(-options.decimals); + fractionalPart = fractionDigits.join('').padStart(options.decimals, '0'); + } + + let integerPart = integerDigits.length ? integerDigits.join('') : '0'; + + if (options.digitGrouping) { + integerPart = Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + BigInt(integerPart), + ); + } + + if (options.fractionDigits === 0) { + return integerPart + (options.symbol ? ` ${options.symbol}` : ''); + } else { + return ( + integerPart + + '.' + + fractionalPart.slice(0, options.fractionDigits) + + (options.symbol ? ` ${options.symbol}` : '') + ); + } +}; diff --git a/src/lib/formatWeight.test.ts b/src/lib/formatWeight.test.ts new file mode 100644 index 00000000..70c763f0 --- /dev/null +++ b/src/lib/formatWeight.test.ts @@ -0,0 +1,69 @@ +// Copyright 2022-2024 @paritytech/contracts-ui authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { TypeRegistry } from '@polkadot/types'; +import { beforeAll, describe, expect, it } from 'vitest'; +import { formatProofSize, formatRefTime } from './formatWeight'; + +describe('formatProofSize', () => { + let registry: TypeRegistry; + + beforeAll(() => { + registry = new TypeRegistry(); + }); + + it('should format edge cases correctly', () => { + [ + { value: 0n, expected: '0.00 MB' }, + { value: 1_000_000n, expected: '1.00 MB' }, + // u64::MAX value + { + value: 18_446_744_073_709_551_615n, + expected: '18446744073709.55 MB', + }, + ].forEach(({ value, expected }) => { + const proofSize = registry.createType('Compact', value); + expect(formatProofSize(proofSize, 'MB')).toBe(expected); + }); + }); + + it('should format edge cases correctly', () => { + [ + { value: 0n, expected: '0 bytes' }, + { value: 1_000_000n, expected: '1000000 bytes' }, + // u64::MAX value + { + value: 18_446_744_073_709_551_615n, + expected: '18446744073709551615 bytes', + }, + ].forEach(({ value, expected }) => { + const proofSize = registry.createType('Compact', value); + expect(formatProofSize(proofSize, 'bytes')).toBe(expected); + }); + }); +}); + +describe('formatRefTime', () => { + let registry: TypeRegistry; + + beforeAll(() => { + registry = new TypeRegistry(); + }); + + it('should format edge cases correctly', () => { + [ + { value: 0n, expected: '0.00 ms' }, + { value: 123n, expected: '0.00 ms' }, + { value: 123_000_000n, expected: '0.12 ms' }, + { value: 1_000_000_000n, expected: '1.00 ms' }, + // u64::MAX value + { + value: 18_446_744_073_709_551_615n, + expected: '18446744073.70 ms', + }, + ].forEach(({ value, expected }) => { + const refTime = registry.createType('Compact', value); + expect(formatRefTime(refTime, 'ms')).toBe(expected); + }); + }); +}); diff --git a/src/lib/formatWeight.ts b/src/lib/formatWeight.ts new file mode 100644 index 00000000..6fc551c1 --- /dev/null +++ b/src/lib/formatWeight.ts @@ -0,0 +1,67 @@ +// Copyright 2022-2024 @paritytech/contracts-ui authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { WeightV2 } from '@polkadot/types/interfaces'; +import { formatUInt } from './formatUInt'; + +/** + * Formats given Reference Time value from expected picoseconds to milliseconds. + * @param refTime + * @returns formatted refTime in milliseconds, with 2 decimal places + */ +export function formatRefTime(refTime: WeightV2['refTime'], unit: 'ms' | 'ps' = 'ps'): string { + switch (unit) { + case 'ps': + return formatUInt(refTime, { + decimals: 0, + digitGrouping: false, + fractionDigits: 0, + symbol: unit, + }); + case 'ms': + return formatUInt(refTime, { + decimals: 9, + digitGrouping: false, + fractionDigits: 2, + symbol: unit, + }); + default: + throw new Error('Unsupported unit'); + } +} + +/** + * Formats given Proof Size value from expected bytes to megabytes. + * @param refTime + * @returns formatted refTime in megabytes, with 2 decimal places + */ +export function formatProofSize( + proofSize: WeightV2['proofSize'], + unit: 'MB' | 'bytes' | 'kb' = 'bytes', +): string { + switch (unit) { + case 'bytes': + return formatUInt(proofSize, { + decimals: 0, + digitGrouping: false, + fractionDigits: 0, + symbol: unit, + }); + case 'kb': + return formatUInt(proofSize, { + decimals: 3, + digitGrouping: false, + fractionDigits: 2, + symbol: unit, + }); + case 'MB': + return formatUInt(proofSize, { + decimals: 6, + digitGrouping: false, + fractionDigits: 2, + symbol: unit, + }); + default: + throw new Error('Unsupported unit'); + } +} diff --git a/src/ui/components/contract/DryRunResult.tsx b/src/ui/components/contract/DryRunResult.tsx index 1711565a..0aea069e 100644 --- a/src/ui/components/contract/DryRunResult.tsx +++ b/src/ui/components/contract/DryRunResult.tsx @@ -9,6 +9,7 @@ import { ContractExecResult, Registry } from 'types'; import { useApi } from 'ui/contexts'; import { getDecodedOutput } from 'lib/output'; import { decodeStorageDeposit } from 'lib/callOptions'; +import { formatProofSize, formatRefTime } from '../../../lib/formatWeight'; interface Props { outcome: ContractExecResult; @@ -78,17 +79,17 @@ export function DryRunResult({
GasConsumed
-
+
-
+
GasRequired
-
+
-
+
GasConsumed
-
+
-
+
@@ -51,16 +58,22 @@ export function DryRun() { <> GasRequired
-
+
-
+