diff --git a/.eslintignore b/.eslintignore index 6e1b5f88..34800870 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,9 @@ +*.config.js +*.config.ts +package*.json +.eslintrc.cjs +bundle*.js + packages/web3-utils packages/massa-web3/examples packages/massa-web3/code-snippets @@ -6,10 +12,7 @@ packages/massa-web3/src/utils packages/massa-web3/src/web3 packages/massa-web3/src/index.ts packages/massa-web3/test + **/generated/** **/dist/** -**/docs/** -*.config.js -*.config.ts -package*.json -.eslintrc.cjs \ No newline at end of file +**/docs/** \ No newline at end of file diff --git a/packages/massa-web3/src/experimental/basicElements/index.ts b/packages/massa-web3/src/experimental/basicElements/index.ts index 8afd1964..c1ca5487 100644 --- a/packages/massa-web3/src/experimental/basicElements/index.ts +++ b/packages/massa-web3/src/experimental/basicElements/index.ts @@ -5,3 +5,4 @@ export * from './operationManager' export * from './operation' export * from './storage' export * from './args' +export * as Mas from './mas' diff --git a/packages/massa-web3/src/experimental/basicElements/mas.ts b/packages/massa-web3/src/experimental/basicElements/mas.ts new file mode 100644 index 00000000..1040c8cf --- /dev/null +++ b/packages/massa-web3/src/experimental/basicElements/mas.ts @@ -0,0 +1,131 @@ +import { FIRST, ONE } from '../utils' + +const NB_DECIMALS = 9 +const POWER_TEN = 10 +const SIZE_U256_BIT = 256 + +export const ERROR_NOT_SAFE_INTEGER = 'value is not a safe integer.' +export const ERROR_VALUE_TOO_LARGE = 'value is too large.' + +export type Mas = bigint + +/** + * Converts an integer value to the smallest unit of the Massa currency. + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger#description + * + * @param value - The integer value. + * @returns The value in the smallest unit of the Massa currency. + * + * @throws An error if the value is not a safe integer. + */ +export function fromMas(value: number): Mas { + if (!Number.isSafeInteger(value)) { + throw new Error(ERROR_NOT_SAFE_INTEGER) + } + + return BigInt(value * POWER_TEN ** NB_DECIMALS) +} + +/** + * Converts an integer value in milli Massa to the smallest unit of the Massa currency. + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger#description + * + * @param value - The value in milli Massa. + * @returns The value in the smallest unit of the Massa currency. + * + * @throws An error if the value is not a safe integer. + */ +export function fromMilliMas(value: number): Mas { + if (!Number.isSafeInteger(value)) { + throw new Error(ERROR_NOT_SAFE_INTEGER) + } + const milli = 3 + return BigInt(value * POWER_TEN ** (NB_DECIMALS - milli)) +} + +/** + * Converts an integer value in micro Massa to the smallest unit of the Massa currency. + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger#description + * + * @param value - The value in micro Massa. + * @returns The value in the smallest unit of the Massa currency. + * + * @throws An error if the value is not a safe integer. + */ +export function fromMicroMas(value: number): Mas { + if (!Number.isSafeInteger(value)) { + throw new Error(ERROR_NOT_SAFE_INTEGER) + } + const micro = 6 + return BigInt(value * POWER_TEN ** (NB_DECIMALS - micro)) +} + +/** + * Converts an integer value in nano Massa to the smallest unit of the Massa currency. + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger#description + * + * @param value - The value in nano Massa. + * @returns The value in the smallest unit of the Massa currency. + * + * @throws An error if the value is not a safe integer. + */ +export function fromNanoMas(value: number): Mas { + if (!Number.isSafeInteger(value)) { + throw new Error(ERROR_NOT_SAFE_INTEGER) + } + + const nano = 9 + return BigInt(value * POWER_TEN ** (NB_DECIMALS - nano)) +} + +/** + * Converts a decimal value to the smallest unit of the Massa currency. + * + * @param value - The decimal value. + * @returns The value in the smallest unit of the Massa currency. + * + * @throws An error if the format is not a decimal. + * @throws An error if the value is too large to be represented by an U256 or has too many decimals. + */ +export function fromString(value: string): Mas { + const parts = value.split('.') + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + if (parts.length > 2) { + throw new Error('invalid format') + } + + const integerPart = parts[FIRST] + const decimalPart = parts[ONE] || '' + if (decimalPart.length > NB_DECIMALS) { + throw new Error(ERROR_VALUE_TOO_LARGE) + } + + const mas = BigInt(integerPart + decimalPart.padEnd(NB_DECIMALS, '0')) + if (mas >= BigInt(ONE) << BigInt(SIZE_U256_BIT)) { + throw new Error(ERROR_VALUE_TOO_LARGE) + } + + return mas +} + +/** + * Converts a value in the smallest unit of the Massa currency to a decimal value. + * + * @param value - The value in the smallest unit of the Massa currency. + * @returns The decimal value. + * + * @throws An error if the value is too large to be represented by an U256. + */ +export function toString(value: Mas): string { + if (value >= BigInt(ONE) << BigInt(SIZE_U256_BIT)) { + throw new Error(ERROR_VALUE_TOO_LARGE) + } + const valueString = value.toString() + const integerPart = valueString.slice(FIRST, -NB_DECIMALS) || '0' + const decimalPart = valueString.slice(-NB_DECIMALS).replace(/0+$/, '') + return `${integerPart}${decimalPart.length ? '.' + decimalPart : ''}` +} diff --git a/packages/massa-web3/test/experimental/unit/mas.spec.ts b/packages/massa-web3/test/experimental/unit/mas.spec.ts new file mode 100644 index 00000000..62cf7f1f --- /dev/null +++ b/packages/massa-web3/test/experimental/unit/mas.spec.ts @@ -0,0 +1,40 @@ +import { Mas } from '../../../src/experimental/basicElements' + +describe('amount conversion', () => { + it('converts from integer', () => { + expect(Mas.fromMas(1)).toStrictEqual(1_000_000_000n) + expect(Mas.fromMas(1234)).toStrictEqual(1_234_000_000_000n) + expect(() => Mas.fromMas(1.1)).toThrow(Mas.ERROR_NOT_SAFE_INTEGER) + + expect(Mas.fromMilliMas(1)).toStrictEqual(1_000_000n) + expect(Mas.fromMilliMas(1234)).toStrictEqual(1_234_000_000n) + expect(() => Mas.fromMilliMas(1.1)).toThrow(Mas.ERROR_NOT_SAFE_INTEGER) + + expect(Mas.fromMicroMas(1)).toStrictEqual(1_000n) + expect(Mas.fromMicroMas(1234)).toStrictEqual(1_234_000n) + expect(() => Mas.fromMicroMas(1.1)).toThrow(Mas.ERROR_NOT_SAFE_INTEGER) + + expect(Mas.fromNanoMas(1)).toStrictEqual(1n) + expect(Mas.fromNanoMas(1234)).toStrictEqual(1_234n) + expect(() => Mas.fromNanoMas(1.1)).toThrow(Mas.ERROR_NOT_SAFE_INTEGER) + }) + + it('converts from string', () => { + expect(Mas.fromString('1')).toStrictEqual(1_000_000_000n) + expect(Mas.fromString('1.1')).toStrictEqual(1_100_000_000n) + expect(Mas.fromString('01234.56789')).toStrictEqual(1_234_567_890_000n) + expect(Mas.fromString('01234.567890000')).toStrictEqual(1_234_567_890_000n) + expect(() => Mas.fromString('1.1.1')).toThrow() + expect(() => Mas.fromString('0.1234567890')).toThrow() + expect(() => + Mas.fromString((BigInt(1) << BigInt(256)).toString()) + ).toThrow() + }) + + it('converts to string', () => { + expect(Mas.toString(1_000_000_000n)).toStrictEqual('1') + expect(Mas.toString(1_100_000_000n)).toStrictEqual('1.1') + expect(Mas.toString(1_234_567_890_000n)).toStrictEqual('1234.56789') + expect(() => Mas.toString(BigInt(1) << BigInt(256))).toThrow() + }) +})