From 804b9c70cb12fe7af8ccb193a2cfc04c2935ddb5 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 10:38:04 +0100 Subject: [PATCH 01/10] feat: 1110 remove Quantity class and add HexInt class --- docs/diagrams/architecture/vcdm.md | 8 +- packages/core/src/utils/hex/_Hex.ts | 4 +- packages/core/src/vcdm/Hex.ts | 168 +++++++++++++----- packages/core/src/vcdm/HexInt.ts | 53 ++++++ packages/core/src/vcdm/Quantity.ts | 65 ------- packages/core/src/vcdm/ThorId.ts | 2 +- packages/core/src/vcdm/index.ts | 2 +- packages/core/tests/vcdm/Hex.unit.test.ts | 70 +++++++- .../vechain-provider/vechain-provider.ts | 4 +- .../utils/formatter/blocks/formatter.ts | 12 +- .../utils/formatter/logs/formatter.ts | 4 +- .../utils/formatter/transactions/formatter.ts | 22 +-- .../methods/eth_syncing/eth_syncing.ts | 4 +- .../tests/provider/providers/fixture.ts | 10 +- .../eth_blockNumber.testnet.test.ts | 4 +- .../methods/eth_getBalance/fixture.ts | 10 +- .../methods/eth_getBlockByNumber/fixture.ts | 10 +- .../methods/eth_getBlockReceipts/fixture.ts | 4 +- 18 files changed, 286 insertions(+), 170 deletions(-) create mode 100644 packages/core/src/vcdm/HexInt.ts delete mode 100644 packages/core/src/vcdm/Quantity.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 90e5c5fa6..531b6d57a 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -18,11 +18,11 @@ classDiagram +Hex of(bigint|number|string|Uint8Array exp)$ +Hex random(number bytes)$ } + class HexInt { + +HexInt of(bigint|number|string|Uint8Array|Hex exp)$ + } class Keccak256 class Keystore - class Quantity { - +Quantity of(bigint|number|string|Uint8Array|Hex exp)$ - } class Revision class Sha256 class String @@ -50,8 +50,8 @@ classDiagram Hex <|-- BloomFilter Hex <|-- HDNode Hex <|-- Hash + Hex <|-- HexInt Hex <|-- Keystore - Hex <|-- Quantity Hex <|-- Revision Hex <|-- ThorId String <|-- Hex diff --git a/packages/core/src/utils/hex/_Hex.ts b/packages/core/src/utils/hex/_Hex.ts index 01a8789e4..4bd1dff89 100644 --- a/packages/core/src/utils/hex/_Hex.ts +++ b/packages/core/src/utils/hex/_Hex.ts @@ -1,6 +1,6 @@ import * as n_utils from '@noble/curves/abstract/utils'; import { InvalidDataType } from '@vechain/sdk-errors'; -import { Quantity } from '../../vcdm'; +import { HexInt } from '../../vcdm'; import { randomBytes } from '@noble/hashes/utils'; import { type HexString } from './types'; @@ -436,7 +436,7 @@ const _Quantity = { */ of(n: HexRepresentable): string { // return `${PREFIX}${trim(_Hex.of(n))}`; - return Quantity.of(n).toString(); + return HexInt.of(n).toString(); } }; diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index 662d7f21f..e9ef46445 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -4,10 +4,31 @@ import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; import { type VeChainDataModel } from './VeChainDataModel'; /** - * Represents a hexadecimal value. + * Represents a hexadecimal value expressed as + * * `-` sign if the value is negative, + * * `0x` hexadecimal notation tag, + * * a not empty string of hexadecimal digits from `0` to `9` and from `a` to `f`. * @implements {VeChainDataModel} + * @description This hexadecimal notation is coherent with the decimal notation: + * * the sign is only expressed for negative values, and it is always the first symbol, + * * the `0x` tags the string as an hexaecimal expression, + * * hexadecimal digits follow. */ class Hex extends String implements VeChainDataModel { + /** + * Negative multiplier of the {@link hex} absolute value. + * + * @type {number} + */ + protected static readonly NEGATIVE: number = -1; + + /** + * Positive multiplier of the {@link hex} absolute value. + * + * @type {number} + */ + protected static readonly POSITIVE: number = 1; + /** * The radix used for representing numbers base 16 in a positional numeral notation system. * @@ -15,39 +36,49 @@ class Hex extends String implements VeChainDataModel { */ protected static readonly RADIX: number = 16; - private static readonly REGEX_HEX: RegExp = /^(0x)?[0-9a-f]*$/i; + /** + * Regular expression for matching hexadecimal strings. + * + * @type {RegExp} + */ + private static readonly REGEX_HEX: RegExp = /^-?(0x)?[0-9a-f]*$/i; - protected static readonly REGEX_PREFIX: RegExp = /^0x/i; + /** + * Regular expression pattern to match a prefix indicating hexadecimal number. + * + * @type {RegExp} + */ + protected static readonly REGEX_PREFIX: RegExp = /^-?0x/i; + /** + * Returns the hexadecimal digits expressing this absolute value, sign and `0x` prefix omitted. + */ public readonly hex: string; /** - * Creates a new instance of this class representing the `exp` hexadecimal expression. - * - * @param {string} exp - The hexadecimal expression. - * @param {function} normalize - A function used to normalize the hexadecimal expression. Defaults to converting it to lowercase. + * Represents the sign multiplier of a given number: + * * {@link NEGATIVE} `-1` if negative, + * * {@link POSITIVE} `1` if positive. + */ + public readonly sign; + + /** + * Creates a new instance of this class to represent the value + * built multiplying `sign` for the absolute value expressed by the hexadecimal `digits`. * - * @throws {InvalidDataType} - Thrown when the exp parameter is not a valid hexadecimal expression. + * @param {number} sign - The sign of the value. + * @param {string} digits - The digits of the absolute value in hexadecimal base. + * @param {function} [normalize] - The function used to normalize the digits. Defaults to converting digits to lowercase. */ protected constructor( - exp: string, - normalize: (exp: string) => string = (exp) => exp.toLowerCase() + sign: number, + digits: string, + normalize: (digits: string) => string = (digits) => digits.toLowerCase() ) { - let value = exp; - if (Hex.REGEX_PREFIX.test(value)) { - value = value.slice(2); - } - if (Hex.isValid(value)) { - value = normalize(value); - super('0x' + value); - this.hex = value; - } else { - throw new InvalidDataType( - 'Hex.constructor', - 'not an hexadecimal expression', - { value } - ); - } + const normalizedDigits = normalize(digits); + super((sign < 0 ? '-0x' : '0x') + normalizedDigits); + this.hex = normalizedDigits; + this.sign = sign; } /** @@ -56,7 +87,7 @@ class Hex extends String implements VeChainDataModel { * @returns {bigint} The value of `bi` as a `BigInt`. */ get bi(): bigint { - return nc_utils.hexToNumber(this.hex); + return BigInt(this.sign) * nc_utils.hexToNumber(this.hex); } /** @@ -72,11 +103,13 @@ class Hex extends String implements VeChainDataModel { * Retrieves the value of n. * * @return {number} The value of n. + * * @throws {InvalidCastType} Throws an error if this instance doesn't represent * an [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). */ get n(): number { if (this.isNumber()) { + // The sign is part of the IEEE 754 representation hence no need to consider `this.sign` property. return new DataView(this.bytes.buffer).getFloat64(0); } throw new InvalidCastType( @@ -92,27 +125,33 @@ class Hex extends String implements VeChainDataModel { * @returns {Hex} - The aligned hexadecimal string. */ public alignToBytes(): Hex { - return this.hex.length % 2 === 0 ? this : new Hex('0' + this.hex); + return this.hex.length % 2 === 0 + ? this + : new Hex(this.sign, '0' + this.hex); } /** * Compares the current Hex object with another Hex object. * * @param {Hex} that - The Hex object to compare with. + * * @return {number} - Returns a negative number if the current Hex object is less than the given Hex object, * zero if they are equal, or a positive number if the current Hex object is greater than the given Hex object. */ compareTo(that: Hex): number { - const digits = Math.max(this.hex.length, that.hex.length); - const thisBytes = this.fit(digits).bytes; - const thatBytes = that.fit(digits).bytes; - let i = 0; - let compareByte = 0; - while (compareByte === 0 && i < thisBytes.length) { - compareByte = thisBytes[i] - thatBytes[i]; - i++; + if (this.sign === that.sign) { + const digits = Math.max(this.hex.length, that.hex.length); + const thisBytes = this.fit(digits).bytes; + const thatBytes = that.fit(digits).bytes; + let i = 0; + let compareByte = 0; + while (compareByte === 0 && i < thisBytes.length) { + compareByte = thisBytes[i] - thatBytes[i]; + i++; + } + return compareByte; } - return compareByte; + return this.sign - that.sign; } /** @@ -132,7 +171,7 @@ class Hex extends String implements VeChainDataModel { cue++; } if (this.hex.length - cue === digits) { - return new Hex(this.hex.slice(cue)); + return new Hex(this.sign, this.hex.slice(cue)); } throw new InvalidDataType( 'Hex.fit', @@ -142,7 +181,10 @@ class Hex extends String implements VeChainDataModel { } if (digits > this.hex.length) { // Pad. - return new Hex('0'.repeat(digits - this.hex.length) + this.hex); + return new Hex( + this.sign, + '0'.repeat(digits - this.hex.length) + this.hex + ); } return this; } @@ -195,29 +237,61 @@ class Hex extends String implements VeChainDataModel { * Create a Hex instance from a bigint, number, string, or Uint8Array. * * @param {bigint | number | string | Uint8Array} exp - The input value to convert to a Hex instance: - * * bigint encoded as hexadecimal expression of the bytes representing its absolute value; - * * number encoded as [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format); - * * string parsed as a hexadecimal expression, optionally prefixed with `0x`; - * * Uint8Array encoded as hexadecimal expression of the bytes represented in the provided expression; + * * bigint, converted to a signed hexadecimal expression of its absolute value; + * * number, encoded as [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format); + * * string, parsed as a hexadecimal expression, optionally signed `-`, optionally tagged with `0x`; + * * Uint8Array, encoded as hexadecimal expression of the bytes represented in the provided expression; + * * @returns {Hex} - A Hex instance representing the input value. + * + * @throws {InvalidDataType} if the given `exp` can't be represented as a hexadecimal expression. */ public static of(exp: bigint | number | string | Uint8Array): Hex { try { if (exp instanceof Uint8Array) { - return new Hex(nc_utils.bytesToHex(exp)); + return new Hex(this.POSITIVE, nc_utils.bytesToHex(exp)); } else if (typeof exp === 'bigint') { - return new Hex(nc_utils.numberToHexUnpadded(exp)); + if (exp < 0n) { + return new Hex( + this.NEGATIVE, + nc_utils.numberToHexUnpadded(-1n * exp) + ); + } + return new Hex( + this.POSITIVE, + nc_utils.numberToHexUnpadded(exp) + ); } else if (typeof exp === 'number') { const dataView = new DataView(new ArrayBuffer(16)); dataView.setFloat64(0, exp); - return Hex.of(new Uint8Array(dataView.buffer)); + return new Hex( + exp < 0 ? this.NEGATIVE : this.POSITIVE, + nc_utils.bytesToHex(new Uint8Array(dataView.buffer)) + ); + } + if (this.isValid(exp)) { + if (exp.startsWith('-')) { + return new Hex( + this.NEGATIVE, + this.REGEX_PREFIX.test(exp) + ? exp.slice(3) + : exp.slice(1) + ); + } + return new Hex( + this.POSITIVE, + this.REGEX_PREFIX.test(exp) ? exp.slice(2) : exp + ); } - return new Hex(exp); + throw new InvalidDataType('Hex.of', 'not an hexadecimal string', { + exp + }); } catch (e) { throw new InvalidDataType( 'Hex.of', 'not an hexadecimal expression', - { exp }, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + { exp: `${exp}` }, // Needed to serialize bigint values. e ); } diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts new file mode 100644 index 000000000..ce835dd2c --- /dev/null +++ b/packages/core/src/vcdm/HexInt.ts @@ -0,0 +1,53 @@ +import { Hex } from './Hex'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +class HexInt extends Hex { + protected constructor(hex: Hex) { + let cue = 0; + while (cue < hex.hex.length && hex.hex.at(cue) === '0') { + cue++; + } + super( + hex.sign, + cue === hex.hex.length ? '0' : hex.hex.slice(cue, hex.hex.length) + ); + } + + public override get n(): number { + const bi = this.bi; + if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_VALUE) { + return Number(bi); + } + throw new InvalidDataType('HexInt.of', 'not in the safe number range', { + bi: `${bi}`, + hex: this.toString() + }); + } + + public static of(exp: bigint | number | string | Uint8Array | Hex): HexInt { + try { + if (exp instanceof Hex) { + return new HexInt(exp); + } + if (typeof exp === 'number') { + if (Number.isInteger(exp)) { + return new HexInt(Hex.of(BigInt(exp))); + } + throw new InvalidDataType('HexInt.of', 'not an integer', { + exp + }); + } + return new HexInt(Hex.of(exp)); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new InvalidDataType( + 'HexInt.of', + 'not an hexadecimal integer expression', + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + { exp: `${exp}`, e } // Needed to serialize bigint values. + ); + } + } +} + +export { HexInt }; diff --git a/packages/core/src/vcdm/Quantity.ts b/packages/core/src/vcdm/Quantity.ts deleted file mode 100644 index 987bf9cc9..000000000 --- a/packages/core/src/vcdm/Quantity.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as nc_utils from '@noble/curves/abstract/utils'; -import { Hex } from './Hex'; -import { InvalidDataType } from '@vechain/sdk-errors'; - -/** - * A class representing a quantity in hexadecimal format. - * @extends Hex - * @experimental - */ -class Quantity extends Hex { - /** - * Constructs a new Quantity object with the given hexadecimal expression. - * - * @param {string} exp - The hexadecimal expression to use. - * @protected - */ - protected constructor(exp: string) { - let value = exp; - if (Hex.REGEX_PREFIX.test(value)) { - value = value.slice(2); - } - if (Hex.isValid(value)) { - let cue = 0; - while (cue < value.length && value.at(cue) === '0') { - cue++; - } - if (cue === value.length) { - super('0'); - } else { - super(value.slice(cue)); - } - } else { - throw new InvalidDataType( - 'Quantity.constructor', - 'not an hexadecimal expression', - { value } - ); - } - } - - /** - * Creates a new Quantity object from the given input. - * - * @param {bigint | number | string | Hex | Uint8Array} exp - The input value to create Quantity from. - * @returns {Quantity} A new Quantity object. - * @experimental - */ - public static of( - exp: bigint | number | string | Hex | Uint8Array - ): Quantity { - if (exp instanceof Hex) { - return new Quantity(exp.hex); - } - if (exp instanceof Uint8Array) { - return new Quantity(nc_utils.bytesToHex(exp)); - } else if (typeof exp === 'bigint') { - return new Quantity(nc_utils.numberToHexUnpadded(exp)); - } else if (typeof exp === 'number') { - return new Quantity(nc_utils.numberToHexUnpadded(exp)); - } - return new Hex(exp); - } -} - -export { Quantity }; diff --git a/packages/core/src/vcdm/ThorId.ts b/packages/core/src/vcdm/ThorId.ts index 61a5f33cd..a7c919761 100644 --- a/packages/core/src/vcdm/ThorId.ts +++ b/packages/core/src/vcdm/ThorId.ts @@ -23,7 +23,7 @@ class ThorId extends Hex { */ protected constructor(hex: Hex) { if (ThorId.isValid(hex.hex)) { - super(hex.hex); + super(Hex.POSITIVE, hex.hex); } else { throw new InvalidDataType( 'ThorId.constructor', diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index 9b0f79121..87b10bf02 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -1,5 +1,5 @@ export * from './Hex'; -export * from './Quantity'; +export * from './HexInt'; export * from './ThorId'; export * from './Txt'; export * from './VeChainDataModel'; diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 55bd5c2a9..858761adf 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -8,8 +8,14 @@ import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; */ describe('Hex class tests', () => { describe('VeChain Data Model tests', () => { - test('Return a bi value from a bi expression', () => { - const exp = BigInt(12357); + test('Return a bi value from a negative bi expression', () => { + const exp = -12357n; // From literal. + const hex = Hex.of(exp); + expect(hex.bi).toEqual(exp); + }); + + test('Return a bi value from a positive bi expression', () => { + const exp = BigInt(12357); // From constructor. const hex = Hex.of(exp); expect(hex.bi).toEqual(exp); }); @@ -18,6 +24,7 @@ describe('Hex class tests', () => { const exp = Txt.of('👋🌐'); const hex = Hex.of(exp.bytes); expect(hex.bytes).toEqual(exp.bytes); + expect(hex.sign).toBeGreaterThan(0); expect(Txt.of(hex.bytes).isEqual(exp)).toBeTruthy(); }); @@ -39,7 +46,7 @@ describe('Hex class tests', () => { expect(() => hex.n).toThrow(InvalidCastType); }); - test('compareTo metthod tests', () => { + test('compareTo method tests - same signs', () => { const hex1 = Hex.of('0x123'); const hex2 = Hex.of('0x456'); expect(hex1.compareTo(hex2)).toBeLessThan(0); @@ -47,6 +54,13 @@ describe('Hex class tests', () => { expect(hex1.compareTo(hex1)).toEqual(0); }); + test('compareTo method tests - opposite signs', () => { + const hex1 = Hex.of('-0x123'); + const hex2 = Hex.of('0x123'); + expect(hex1.compareTo(hex2)).toBeLessThan(0); + expect(hex2.compareTo(hex1)).toBeGreaterThan(0); + }); + test('isEqual method tests', () => { const coffe = Hex.of('0xc0ffe'); const COFFE = Hex.of('C0FFE'); @@ -57,12 +71,18 @@ describe('Hex class tests', () => { }); describe('Construction tests', () => { - test('Return an Hex instance if the passed argument is negative bigint', () => {}); + test('Return an Hex instance if the passed argument is negative bigint', () => { + const exp = -12357n; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.bi).toEqual(exp); + }); test('Return an Hex instance if the passed argument is positive bigint', () => { const exp = BigInt(12357); const hex = Hex.of(exp); expect(hex).toBeInstanceOf(Hex); + expect(hex.bi).toEqual(exp); }); test('Return an Hex instance if the passed argument is negative number', () => { @@ -93,6 +113,20 @@ describe('Hex class tests', () => { expect(hex.hex).toEqual(exp.toLowerCase()); // Normalized from is lower case. }); + test('Return an Hex instance if the passed argument is string - negative value with 0x prefix', () => { + const exp = '-0xc0fFeE'; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.toString()).toEqual(exp.toLowerCase()); // Normalized from is lower case. + }); + + test('Return an Hex instance if the passed argument is string - negative value without 0x prefix', () => { + const exp = '-C0c0a'; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect('-' + hex.hex).toEqual(exp.toLowerCase()); // Normalized from is lower case. + }); + test('Return an Hex instance if the passed argument is UInt8Array', () => { const exp = Uint8Array.of(0xc0, 0xff, 0xee); const hex = Hex.of(exp); @@ -169,12 +203,22 @@ describe('Hex class tests', () => { expect(Hex.isValid(exp)).toBeFalsy(); }); - test('Return true for valid hex with 0x prefix', () => { + test('Return true for valid negative hex with 0x prefix', () => { + const exp = '-0xBadBabe'; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + + test('Return true for valid negative hex without 0x prefix', () => { + const exp = '-BadBabe'; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + + test('Return true for valid positive hex with 0x prefix', () => { const exp = '0xBadBabe'; expect(Hex.isValid(exp)).toBeTruthy(); }); - test('Return true for valid hex without 0x prefix', () => { + test('Return true for valid positive hex without 0x prefix', () => { const exp = 'BadBabe'; expect(Hex.isValid(exp)).toBeTruthy(); }); @@ -186,12 +230,22 @@ describe('Hex class tests', () => { expect(Hex.isValid0x(exp)).toBeFalsy(); }); - test('Return false for valid hex without 0x prefix', () => { + test('Return false for valid negative hex without 0x prefix', () => { + const exp = '-BadBabe'; + expect(Hex.isValid0x(exp)).toBeFalsy(); + }); + + test('Return true for valid positive hex with 0x prefix', () => { + const exp = '-0xBadBabe'; + expect(Hex.isValid0x(exp)).toBeTruthy(); + }); + + test('Return false for valid positive hex without 0x prefix', () => { const exp = 'BadBabe'; expect(Hex.isValid0x(exp)).toBeFalsy(); }); - test('Return true for valid hex with 0x prefix', () => { + test('Return true for valid positive hex with 0x prefix', () => { const exp = '0xBadBabe'; expect(Hex.isValid0x(exp)).toBeTruthy(); }); diff --git a/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts b/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts index ba0f596ce..5fce1f572 100644 --- a/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts +++ b/packages/network/src/provider/providers/vechain-provider/vechain-provider.ts @@ -18,7 +18,7 @@ import { type SubscriptionEvent, type SubscriptionManager } from './types'; -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { type EventPoll, Poll, vnsUtils } from '../../../utils'; import { type CompressedBlockDetail, @@ -220,7 +220,7 @@ class VeChainProvider extends EventEmitter implements EIP1193ProviderMessage { const promises = Array.from( this.subscriptionManager.logSubscriptions.entries() ).map(async ([subscriptionId, subscriptionDetails]) => { - const currentBlock = Quantity.of( + const currentBlock = HexInt.of( this.subscriptionManager.currentBlockNumber ).toString(); // Construct filter options for the Ethereum logs query based on the subscription details diff --git a/packages/network/src/provider/utils/formatter/blocks/formatter.ts b/packages/network/src/provider/utils/formatter/blocks/formatter.ts index a79193721..22d15126a 100644 --- a/packages/network/src/provider/utils/formatter/blocks/formatter.ts +++ b/packages/network/src/provider/utils/formatter/blocks/formatter.ts @@ -1,5 +1,5 @@ import { type BlocksRPC } from './types'; -import { Quantity, Hex, ZERO_BYTES } from '@vechain/sdk-core'; +import { Hex, HexInt, ZERO_BYTES } from '@vechain/sdk-core'; import { transactionsFormatter } from '../transactions'; import { type CompressedBlockDetail, @@ -35,14 +35,14 @@ const formatToRPCStandard = ( // Supported fields converted to RPC standard hash: block.id, parentHash: block.parentID, - number: Quantity.of(block.number).toString(), - size: Quantity.of(block.size).toString(), + number: HexInt.of(block.number).toString(), + size: HexInt.of(block.size).toString(), stateRoot: block.stateRoot, receiptsRoot: block.receiptsRoot, transactionsRoot: block.txsRoot, - timestamp: Quantity.of(block.timestamp).toString(), - gasLimit: Quantity.of(block.gasLimit).toString(), - gasUsed: Quantity.of(block.gasUsed).toString(), + timestamp: HexInt.of(block.timestamp).toString(), + gasLimit: HexInt.of(block.gasLimit).toString(), + gasUsed: HexInt.of(block.gasUsed).toString(), transactions, miner: block.beneficiary, diff --git a/packages/network/src/provider/utils/formatter/logs/formatter.ts b/packages/network/src/provider/utils/formatter/logs/formatter.ts index 2ee96517e..f40843956 100644 --- a/packages/network/src/provider/utils/formatter/logs/formatter.ts +++ b/packages/network/src/provider/utils/formatter/logs/formatter.ts @@ -1,4 +1,4 @@ -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { type EventCriteria, type EventLogs } from '../../../../thor-client'; import { type LogsRPC } from './types'; @@ -14,7 +14,7 @@ const formatToLogsRPC = (eventLogs: EventLogs[]): LogsRPC[] => { return { address: eventLog.address, blockHash: eventLog.meta.blockID, - blockNumber: Quantity.of(eventLog.meta.blockNumber).toString(), + blockNumber: HexInt.of(eventLog.meta.blockNumber).toString(), data: eventLog.data, logIndex: '0x0', // Always false for now diff --git a/packages/network/src/provider/utils/formatter/transactions/formatter.ts b/packages/network/src/provider/utils/formatter/transactions/formatter.ts index 52d9f31e7..549489d12 100644 --- a/packages/network/src/provider/utils/formatter/transactions/formatter.ts +++ b/packages/network/src/provider/utils/formatter/transactions/formatter.ts @@ -3,7 +3,7 @@ import { type TransactionReceiptRPC, type TransactionRPC } from './types'; -import { Hex, Quantity, ZERO_BYTES } from '@vechain/sdk-core'; +import { Hex, HexInt, ZERO_BYTES } from '@vechain/sdk-core'; import { getNumberOfLogsAheadOfTransactionIntoBlockExpanded, getTransactionIndexIntoBlock @@ -37,13 +37,13 @@ const _formatTransactionToRPC = ( return { // Supported fields blockHash, - blockNumber: Quantity.of(blockNumber).toString(), + blockNumber: HexInt.of(blockNumber).toString(), from: tx.origin, - gas: Quantity.of(tx.gas).toString(), + gas: HexInt.of(tx.gas).toString(), chainId, hash: tx.id, nonce: tx.nonce as string, - transactionIndex: Quantity.of(txIndex).toString(), + transactionIndex: HexInt.of(txIndex).toString(), /** * `input`, `to`, `value` are being referred to the first clause. @@ -54,7 +54,7 @@ const _formatTransactionToRPC = ( to: tx.clauses[0]?.to !== undefined ? tx.clauses[0].to : null, value: tx.clauses[0]?.value !== undefined - ? Quantity.of(tx.clauses[0].value).toString() + ? HexInt.of(tx.clauses[0].value).toString() : '', // Unsupported fields @@ -156,14 +156,14 @@ function formatTransactionReceiptToRPCStandard( output.events.forEach((event) => { logs.push({ blockHash: receipt.meta.blockID, - blockNumber: Quantity.of(receipt.meta.blockNumber).toString(), + blockNumber: HexInt.of(receipt.meta.blockNumber).toString(), transactionHash: receipt.meta.txID as string, address: event.address, topics: event.topics.map((topic) => topic), data: event.data, removed: false, - transactionIndex: Quantity.of(transactionIndex).toString(), - logIndex: Quantity.of(logIndex).toString() + transactionIndex: HexInt.of(transactionIndex).toString(), + logIndex: HexInt.of(logIndex).toString() }); logIndex++; }); @@ -171,18 +171,18 @@ function formatTransactionReceiptToRPCStandard( return { blockHash: receipt.meta.blockID, - blockNumber: Quantity.of(receipt.meta.blockNumber).toString(), + blockNumber: HexInt.of(receipt.meta.blockNumber).toString(), contractAddress: receipt.outputs.length > 0 ? receipt.outputs[0].contractAddress : null, from: transaction.origin, - gasUsed: Quantity.of(receipt.gasUsed).toString(), + gasUsed: HexInt.of(receipt.gasUsed).toString(), logs, status: receipt.reverted ? '0x0' : '0x1', to: transaction.clauses[0].to, transactionHash: receipt.meta.txID as string, - transactionIndex: Quantity.of(transactionIndex).toString(), + transactionIndex: HexInt.of(transactionIndex).toString(), // Incompatible fields logsBloom: Hex.of(ZERO_BYTES(256)).toString(), diff --git a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts index 2e53e10d4..0e224bdb8 100644 --- a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts +++ b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_syncing/eth_syncing.ts @@ -1,5 +1,5 @@ import { JSONRPCInternalError, stringifyData } from '@vechain/sdk-errors'; -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { type CompressedBlockDetail, type ThorClient @@ -40,7 +40,7 @@ const ethSyncing = async ( // Get the highest block number const highestBlockNumber: string | null = genesisBlock !== null - ? Quantity.of( + ? HexInt.of( Math.floor((Date.now() - genesisBlock.timestamp) / 10000) ).toString() : null; diff --git a/packages/network/tests/provider/providers/fixture.ts b/packages/network/tests/provider/providers/fixture.ts index aee36f219..1ba5d0c49 100644 --- a/packages/network/tests/provider/providers/fixture.ts +++ b/packages/network/tests/provider/providers/fixture.ts @@ -1,4 +1,4 @@ -import { Quantity, unitsUtils } from '@vechain/sdk-core'; +import { HexInt, unitsUtils } from '@vechain/sdk-core'; import { zeroBlock } from '../rpc-mapper/methods/eth_getBlockByNumber/fixture'; import { validTransactionDetailTestnet, @@ -18,7 +18,7 @@ const providerMethodsTestCasesTestnet = [ description: "Should be able to call eth_getBlockByNumber with '0x0' as the block number", method: 'eth_getBlockByNumber', - params: [Quantity.of(0).toString(), false], + params: [HexInt.of(0).toString(), false], expected: zeroBlock }, { @@ -44,7 +44,7 @@ const providerMethodsTestCasesSolo = [ 'Should be able to call eth_getBalance of an address with balance more than 0 VET', method: 'eth_getBalance', params: [THOR_SOLO_ACCOUNTS[0].address, 'latest'], - expected: Quantity.of(unitsUtils.parseVET('500000000')).toString() + expected: HexInt.of(unitsUtils.parseVET('500000000')).toString() }, { description: 'Should be able to call eth_getCode of a smart contract', @@ -59,8 +59,8 @@ const logsInput = { '0x0000000000000000000000000000456e65726779', '0x0000000000000000000000000000456e65726779' ], - fromBlock: Quantity.of(0).toString(), - toBlock: Quantity.of(100000).toString(), + fromBlock: HexInt.of(0).toString(), + toBlock: HexInt.of(100000).toString(), topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x0000000000000000000000005034aa590125b64023a0262112b98d72e3c8e40e', diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts index 926a48b77..8d68a3b1c 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_blockNumber/eth_blockNumber.testnet.test.ts @@ -5,7 +5,7 @@ import { TESTNET_URL, ThorClient } from '../../../../../src'; -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; /** * RPC Mapper integration tests for 'eth_blockNumber' method on Testnet Network @@ -50,7 +50,7 @@ describe('RPC Mapper - eth_blockNumber method tests', () => { expect(rpcCallUpdatedLatestBlockNumber).not.toBe('0x0'); expect(rpcCallUpdatedLatestBlockNumber).toBe( - Quantity.of(Number(rpcCallLatestBlockNumber) + 1).toString() + HexInt.of(Number(rpcCallLatestBlockNumber) + 1).toString() ); }, 20000); }); diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts index 5c297172c..826e2643b 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_getBalance/fixture.ts @@ -1,4 +1,4 @@ -import { Hex, Quantity, ZERO_BYTES, unitsUtils } from '@vechain/sdk-core'; +import { Hex, HexInt, ZERO_BYTES, unitsUtils } from '@vechain/sdk-core'; import { JSONRPCInternalError, JSONRPCInvalidParams @@ -12,19 +12,19 @@ const ethGetBalanceTestCases = [ { description: 'Should return correct balance of the test account', params: [THOR_SOLO_ACCOUNTS[0].address, 'latest'], - expected: Quantity.of(unitsUtils.parseVET('500000000')).toString() + expected: HexInt.of(unitsUtils.parseVET('500000000')).toString() }, { description: 'Should return correct balance of the test account', params: [THOR_SOLO_ACCOUNTS[0].address, 'best'], - expected: Quantity.of(unitsUtils.parseVET('500000000')).toString() + expected: HexInt.of(unitsUtils.parseVET('500000000')).toString() }, { description: 'Should return correct balance of the test account before seeding', params: [ THOR_SOLO_ACCOUNTS[0].address, - Quantity.of(0).toString() // 0 is the genesis block + HexInt.of(0).toString() // 0 is the genesis block ], expected: '0x0' // Expected balance is 0 }, @@ -34,7 +34,7 @@ const ethGetBalanceTestCases = [ params: [ // Zero address Hex.of(ZERO_BYTES(20)).toString(), - Quantity.of(1).toString() + HexInt.of(1).toString() ], expected: '0x0' }, diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts index 261a3d84e..1aa2a31a9 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockByNumber/fixture.ts @@ -1,4 +1,4 @@ -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; import { blockWithTransactionsExpanded, blockWithTransactionsNotExpanded @@ -46,24 +46,24 @@ const ethGetBlockByNumberTestCases = [ // NOTE: hydrate true or false is the same, Because genesis block doesn't have any transactions on testnet { description: "Should get block by number '0x0'", - params: [Quantity.of(0).toString(), false], + params: [HexInt.of(0).toString(), false], expected: zeroBlock }, { description: "Should get block by number '0x0' with transaction details", - params: [Quantity.of(0).toString(), true], + params: [HexInt.of(0).toString(), true], expected: zeroBlock }, { description: 'Should get block which has transactions with details', - params: [Quantity.of(17529453).toString(), true], + params: [HexInt.of(17529453).toString(), true], expected: blockWithTransactionsExpanded }, { description: 'Should get block which has transactions with transaction NOT expanded', - params: [Quantity.of(17529453).toString(), false], + params: [HexInt.of(17529453).toString(), false], expected: blockWithTransactionsNotExpanded } ]; diff --git a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts index a127c80f1..9efc39b4c 100644 --- a/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts +++ b/packages/network/tests/provider/rpc-mapper/methods/eth_getBlockReceipts/fixture.ts @@ -1,4 +1,4 @@ -import { Quantity } from '@vechain/sdk-core'; +import { HexInt } from '@vechain/sdk-core'; /** * Fixtures for the `eth_getBlockReceipts` RPC method. @@ -50,7 +50,7 @@ const blockReceiptsFixture = [ ] }, { - blockNumber: Quantity.of(17529453).toString(), + blockNumber: HexInt.of(17529453).toString(), expected: [ { blockHash: From 9424bc126d33d2c8daafb8a2ee8c7c13f63a4664 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 15:59:25 +0100 Subject: [PATCH 02/10] feat: 1110 remove Quantity class and add HexInt class --- docs/diagrams/architecture/vcdm.md | 18 ++++-- packages/core/src/hash/blake2b256.ts | 3 +- packages/core/src/vcdm/Hex.ts | 4 +- packages/core/tests/bloom/bloom.unit.test.ts | 10 +-- .../core/tests/hash/blake2b256.unit.test.ts | 13 ++-- .../core/tests/keystore/keystore.unit.test.ts | 6 +- packages/core/tests/mnemonic/fixture.ts | 8 +-- .../core/tests/mnemonic/mnemonic.unit.test.ts | 4 +- packages/core/tests/rlp/helpers.unit.test.ts | 2 +- packages/core/tests/rlp/rlp.fixture.ts | 64 +++++++++---------- packages/core/tests/rlp/rlp.unit.test.ts | 32 +++++----- packages/core/tests/transaction/fixture.ts | 24 +++---- .../transaction-handler.unit.test.ts | 29 +++++---- .../transaction/transaction.unit.test.ts | 20 +++--- packages/core/tests/vcdm/Hex.unit.test.ts | 12 +++- 15 files changed, 133 insertions(+), 116 deletions(-) diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 531b6d57a..133d01f9a 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -21,6 +21,9 @@ classDiagram class HexInt { +HexInt of(bigint|number|string|Uint8Array|Hex exp)$ } + class HexUInt { + +HexUInt of(bigint|number|string|Uint8Array|HexInt exp)$ + } class Keccak256 class Keystore class Revision @@ -47,16 +50,17 @@ classDiagram Hash <|-- Blake2b256 Hash <|-- Keccak256 Hash <|-- Sha256 - Hex <|-- BloomFilter - Hex <|-- HDNode - Hex <|-- Hash Hex <|-- HexInt - Hex <|-- Keystore - Hex <|-- Revision - Hex <|-- ThorId + HexUInt <|-- Address + HexUInt <|-- BloomFilter + HexUInt <|-- HDNode + HexInt <|-- HexUInt + HexUInt <|-- Hash + HexUInt <|-- ThorId + HexUInt <|-- Keystore + HexUInt <|-- Revision String <|-- Hex String <|-- Txt - ThorId <|-- Address VeChainDataModel <|.. Hex VeChainDataModel <|.. Txt ``` diff --git a/packages/core/src/hash/blake2b256.ts b/packages/core/src/hash/blake2b256.ts index d0084e05f..54ea08af6 100644 --- a/packages/core/src/hash/blake2b256.ts +++ b/packages/core/src/hash/blake2b256.ts @@ -1,7 +1,6 @@ import { Hex } from '../vcdm/Hex'; import { Txt } from '../vcdm'; import { blake2b } from '@noble/hashes/blake2b'; -import { hexToBytes } from '@noble/hashes/utils'; import { type ReturnType } from './types'; import { InvalidDataType } from '@vechain/sdk-errors'; @@ -133,7 +132,7 @@ function blake2b256OfHex( } try { - const hash = blake2b256OfArray(hexToBytes(Hex.of(hex).hex)); + const hash = blake2b256OfArray(Hex.of(hex).bytes); return returnType === 'hex' ? Hex.of(hash).toString() : hash; } catch (e) { throw new InvalidDataType( diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index e9ef46445..db21887c8 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -11,8 +11,9 @@ import { type VeChainDataModel } from './VeChainDataModel'; * @implements {VeChainDataModel} * @description This hexadecimal notation is coherent with the decimal notation: * * the sign is only expressed for negative values, and it is always the first symbol, - * * the `0x` tags the string as an hexaecimal expression, + * * the `0x` tags the string as a hexadecimal expression, * * hexadecimal digits follow. + * * An empty content results is no digits. */ class Hex extends String implements VeChainDataModel { /** @@ -38,6 +39,7 @@ class Hex extends String implements VeChainDataModel { /** * Regular expression for matching hexadecimal strings. + * An empty input is represented as a empty digits. * * @type {RegExp} */ diff --git a/packages/core/tests/bloom/bloom.unit.test.ts b/packages/core/tests/bloom/bloom.unit.test.ts index 3ca5316e4..2eb2bd129 100644 --- a/packages/core/tests/bloom/bloom.unit.test.ts +++ b/packages/core/tests/bloom/bloom.unit.test.ts @@ -220,7 +220,7 @@ describe('Bloom Filter', () => { // Generate the bloom filter const filter = generator.generate(bitsPerKey, k); - expect(Hex.of(filter.bits).hex).toBe('96b298d634155950'); + expect(Hex.of(filter.bits).toString()).toBe('0x96b298d634155950'); expect(filter.bits.byteLength).toBe(8); // 64 bits expect(filter.k).toBe(k); @@ -248,7 +248,7 @@ describe('Bloom Filter', () => { expect(filter.bits.byteLength).toBe(8); expect(filter.k).toBe(k); - expect(Hex.of(filter.bits).hex).toBe('0000000000000000'); // 16 groups of 4 bits, all 0 -> 64 bits + expect(Hex.of(filter.bits).toString()).toBe('0x0000000000000000'); // 16 groups of 4 bits, all 0 -> 64 bits }); /** @@ -273,8 +273,8 @@ describe('Bloom Filter', () => { // Generate the bloom filter const filter = generator.generate(bitsPerKey, k); - expect(Hex.of(filter.bits).hex).toBe( - 'a4d641159d68d829345f86f40d50676cf042f6265072075a94' + expect(Hex.of(filter.bits).toString()).toBe( + '0xa4d641159d68d829345f86f40d50676cf042f6265072075a94' ); expect(filter.bits.byteLength).toBe(25); expect(filter.k).toBe(k); @@ -313,7 +313,7 @@ describe('Bloom Filter', () => { const filter = generator.generate(bitsPerKey, k); - expect(Hex.of(filter.bits).hex).toBe('1190199325088200'); + expect(Hex.of(filter.bits).toString()).toBe('0x1190199325088200'); // Validate the generated filter keys.forEach((key) => { diff --git a/packages/core/tests/hash/blake2b256.unit.test.ts b/packages/core/tests/hash/blake2b256.unit.test.ts index 7414b41fa..59f1f9a55 100644 --- a/packages/core/tests/hash/blake2b256.unit.test.ts +++ b/packages/core/tests/hash/blake2b256.unit.test.ts @@ -2,7 +2,6 @@ // import * as ThorDevKit from 'thor-devkit'; import { Txt, blake2b256, blake2b256OfHex, type ReturnType } from '../../src'; import { describe, expect, test } from '@jest/globals'; -import { hexToBytes } from '@noble/hashes/utils'; import { BLAKE2B256_CONTENT, BLAKE2B256_CONTENT_HASH, @@ -30,7 +29,7 @@ describe('blake2b256', () => { test('blake2b256 - valid content - string', () => { const content = Txt.of(BLAKE2B256_CONTENT).bytes; - const expected = hexToBytes(Hex.of(BLAKE2B256_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(content); expect(actual).toEqual(expected); @@ -41,7 +40,7 @@ describe('blake2b256', () => { }); test('blake2b256 - valid content - Uint8Array', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(BLAKE2B256_CONTENT); expect(actual).toEqual(expected); @@ -52,7 +51,7 @@ describe('blake2b256', () => { }); test('blake2b256 - valid no content - Uint8Array', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_NO_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_NO_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(BLAKE2B256_NO_CONTENT); expect(actual).toEqual(expected); @@ -63,7 +62,7 @@ describe('blake2b256', () => { }); test('blake2b256 - valid zero content - string', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_NO_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_NO_CONTENT_HASH).bytes; let actual: string | Uint8Array; actual = blake2b256(''); expect(actual).toEqual(expected); @@ -105,7 +104,7 @@ describe('blake2b256', () => { }); test('blake2b256OfHex - valid - content', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_CONTENT_HASH).bytes; let actual = blake2b256OfHex(content); expect(actual).toEqual(expected); actual = blake2b256OfHex(content, 'hex'); @@ -113,7 +112,7 @@ describe('blake2b256', () => { }); test('blake2b256OfHex - valid - no content', () => { - const expected = hexToBytes(Hex.of(BLAKE2B256_NO_CONTENT_HASH).hex); + const expected = Hex.of(BLAKE2B256_NO_CONTENT_HASH).bytes; let actual = blake2b256OfHex(zeroContent); expect(actual).toEqual(expected); actual = blake2b256OfHex(zeroContent, 'hex'); diff --git a/packages/core/tests/keystore/keystore.unit.test.ts b/packages/core/tests/keystore/keystore.unit.test.ts index 4044b5e8e..31b252527 100644 --- a/packages/core/tests/keystore/keystore.unit.test.ts +++ b/packages/core/tests/keystore/keystore.unit.test.ts @@ -78,9 +78,9 @@ import { encryptionPassword ); - // Verify private key (slice(2) is used to remove 0x prefix) - expect(decryptedKeystore.privateKey.slice(2)).toEqual( - Hex.of(privateKey).hex + // Verify private key + expect(decryptedKeystore.privateKey).toEqual( + Hex.of(privateKey).toString() ); }); diff --git a/packages/core/tests/mnemonic/fixture.ts b/packages/core/tests/mnemonic/fixture.ts index af15f2582..7e297b190 100644 --- a/packages/core/tests/mnemonic/fixture.ts +++ b/packages/core/tests/mnemonic/fixture.ts @@ -27,28 +27,28 @@ const derivationPaths = [ testName: 'Derive private key', derivationPath: undefined, resultingPrivateKey: - '27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', + '0x27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', resultingAddress: '0x339Fb3C438606519E2C75bbf531fb43a0F449A70' }, { testName: 'Derive private key with standard derivation path', derivationPath: 'm/0', resultingPrivateKey: - '27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', + '0x27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425', resultingAddress: '0x339Fb3C438606519E2C75bbf531fb43a0F449A70' }, { testName: 'Derive private key with custom derivation path', derivationPath: 'm/0/1', resultingPrivateKey: - 'fbbd4e92d4ee4ca2e985648599abb4e95b0886b4e0390b7bfc365283a7befc86', + '0xfbbd4e92d4ee4ca2e985648599abb4e95b0886b4e0390b7bfc365283a7befc86', resultingAddress: '0x43e60f60C89333121236226B7ADC884DC2a8847a' }, { testName: 'Derive private key with custom deep derivation path', derivationPath: 'm/0/1/4/2/4/3', resultingPrivateKey: - '66962cecff67bea483935c87fd33c6b6a524f06cc46430fa9591350bbd9f4999', + '0x66962cecff67bea483935c87fd33c6b6a524f06cc46430fa9591350bbd9f4999', resultingAddress: '0x0b41c56e19c5151122568873a039fEa090937Fe2' } ]; diff --git a/packages/core/tests/mnemonic/mnemonic.unit.test.ts b/packages/core/tests/mnemonic/mnemonic.unit.test.ts index 1f14b268c..9495bc511 100644 --- a/packages/core/tests/mnemonic/mnemonic.unit.test.ts +++ b/packages/core/tests/mnemonic/mnemonic.unit.test.ts @@ -70,7 +70,7 @@ describe('mnemonic', () => { words, path.derivationPath ) - ).hex + ).toString() ).toEqual(path.resultingPrivateKey); }); }); @@ -89,7 +89,7 @@ describe('mnemonic', () => { words, path.derivationPath ) - ).hex + ).toString() ).toEqual(path.resultingPrivateKey); }); }); diff --git a/packages/core/tests/rlp/helpers.unit.test.ts b/packages/core/tests/rlp/helpers.unit.test.ts index 51855689b..06734a33d 100644 --- a/packages/core/tests/rlp/helpers.unit.test.ts +++ b/packages/core/tests/rlp/helpers.unit.test.ts @@ -41,7 +41,7 @@ describe('encodeBigIntToBuffer', () => { test('encodeBigIntToBuffer', () => { const bi = 123456789012345678901n; // or any BigInt you want to test with const buffer = encodeBigIntToBuffer(bi, 9, 'encodeBigIntToBuffer'); - expect(Hex.of(buffer).hex).toBe('06b14e9f812f366c35'); + expect(Hex.of(buffer).toString()).toBe('0x06b14e9f812f366c35'); }); }); diff --git a/packages/core/tests/rlp/rlp.fixture.ts b/packages/core/tests/rlp/rlp.fixture.ts index 66b9658d0..e20937090 100644 --- a/packages/core/tests/rlp/rlp.fixture.ts +++ b/packages/core/tests/rlp/rlp.fixture.ts @@ -4,21 +4,21 @@ import { RLP_CODER, type RLPProfile, type RLPValidObject } from '../../src'; const encodeTestCases = [ { input: [1, 2, 3, [4, 5]], - expected: 'c6010203c20405', + expected: '0xc6010203c20405', description: 'array with multiple and nested items' }, - { input: 42, expected: '2a', description: 'single value' }, - { input: [], expected: 'c0', description: 'empty array' } + { input: 42, expected: '0x2a', description: 'single value' }, + { input: [], expected: '0xc0', description: 'empty array' } ]; /* Simple RLP_CODER decode */ const decodeTestCases = [ { - input: '2a', + input: '0x2a', expected: Buffer.from([42]), description: 'single value' }, - { input: 'c0', expected: [], description: 'empty array' } + { input: '0xc0', expected: [], description: 'empty array' } ]; /* NumericKind encode */ @@ -26,55 +26,55 @@ const numericKindEncodeTestCases = [ { kind: new RLP_CODER.NumericKind(8), data: '0x0', - expected: '', + expected: '0x', description: 'zero hex string' }, { kind: new RLP_CODER.NumericKind(8), data: '0x123', - expected: '0123', + expected: '0x0123', description: 'hex string' }, { kind: new RLP_CODER.NumericKind(8), data: '0', - expected: '', + expected: '0x', description: 'zero number string' }, { kind: new RLP_CODER.NumericKind(8), data: '100', - expected: '64', + expected: '0x64', description: 'number string' }, { kind: new RLP_CODER.NumericKind(8), data: 0, - expected: '', + expected: '0x', description: 'zero number' }, { kind: new RLP_CODER.NumericKind(8), data: 0x123, - expected: '0123', + expected: '0x0123', description: 'number in hex format' }, { kind: new RLP_CODER.NumericKind(8), data: 12524, - expected: '30ec', + expected: '0x30ec', description: 'number non hex format' }, { kind: new RLP_CODER.NumericKind(1), data: '5', - expected: '05', + expected: '0x05', description: 'number in hex format' }, { kind: new RLP_CODER.NumericKind(1), data: 255, - expected: 'ff', + expected: '0xff', description: 'number non hex format' } ]; @@ -171,7 +171,7 @@ const hexBlobKindEncodeTestCases = [ { kind: new RLP_CODER.HexBlobKind(), data: '0x010203', - expected: '010203', + expected: '0x010203', description: 'hex string' } ]; @@ -199,7 +199,7 @@ const fixedHexBlobKindEncodeTestCases = [ { kind: new RLP_CODER.FixedHexBlobKind(1), data: '0x01', - expected: '01', + expected: '0x01', description: 'hex string' } ]; @@ -296,13 +296,13 @@ const compactFixedHexBlobKindEncodeTestCases = [ kind: new RLP_CODER.CompactFixedHexBlobKind(4), data: '0x00112233', description: 'buffer with data', - expected: '112233' + expected: '0x112233' }, { kind: new RLP_CODER.CompactFixedHexBlobKind(1), data: '0x00', description: 'buffer with data', - expected: '' + expected: '0x' } ]; @@ -648,19 +648,19 @@ const encodeNumericProfileTestCases = [ { profile: numericProfile, data: numericData, - expected: 'd10102cec60304c3c20708c60506c3c2800a', + expected: '0xd10102cec60304c3c20708c60506c3c2800a', description: 'encode numeric profile' }, { profile: numericProfileWithMaxBytes, data: numericDataWithMaxBytes, - expected: 'c90102c6c20304c20506', + expected: '0xc90102c6c20304c20506', description: 'encode numeric profile with max bytes' }, { profile: numericProfileWithMaxBytes, data: numericDataWithString, - expected: 'c90102c6c20304c20506', + expected: '0xc90102c6c20304c20506', description: 'encode numeric profile with string' } ]; @@ -669,7 +669,7 @@ const encodeBufferProfileTestCases = [ { profile: bufferProfile, data: bufferData, - expected: 'c88301020383040506', + expected: '0xc88301020383040506', description: 'encode buffer profile' } ]; @@ -678,7 +678,7 @@ const encodeHexBlobProfileTestCases = [ { profile: hexBlobProfile, data: hexBlobData, - expected: 'c88301020383040506', + expected: '0xc88301020383040506', description: 'encode hex blob profile' } ]; @@ -687,7 +687,7 @@ const encodeFixedHexBlobProfileTestCases = [ { profile: fixedHexBlobProfile, data: fixedHexBlobData, - expected: 'c50183010203', + expected: '0xc50183010203', description: 'encode fixed hex blob profile' } ]; @@ -696,31 +696,31 @@ const encodeOptionalFixedHexBlobProfileTestCases = [ { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobData, - expected: 'c50183010203', + expected: '0xc50183010203', description: 'encode nullable fixed hex blob profile' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataNull, - expected: 'c28080', + expected: '0xc28080', description: 'encode nullable fixed hex blob profile with null' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataMixed, - expected: 'c20180', + expected: '0xc20180', description: 'encode nullable fixed hex blob profile with mixed data' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataMixed2, - expected: 'c58083010203', + expected: '0xc58083010203', description: 'encode nullable fixed hex blob profile with mixed data 2' }, { profile: optionalFixedHexBlobProfile, data: optionalFixedHexBlobDataMixed3, - expected: 'c28080', + expected: '0xc28080', description: 'encode nullable fixed hex blob profile with mixed data 3' } ]; @@ -729,7 +729,7 @@ const encodeCompactFixedHexBlobProfileTestCases = [ { profile: compactFixedHexBlobProfile, data: compactFixedHexBlobData, - expected: 'c50183010203', + expected: '0xc50183010203', description: 'encode compact fixed hex blob profile' } ]; @@ -738,14 +738,14 @@ const encodeMixedKindProfileTestCases = [ { profile: mixedKindProfile1, data: mixedKindData1, - expected: 'd17b8412345678cac4118204d2c41282162e', + expected: '0xd17b8412345678cac4118204d2c41282162e', description: 'encode mixed kind profile' }, { profile: mixedKindProfile2, data: mixedKindData2, expected: - 'f8530184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614e', + '0xf8530184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614e', description: 'encode mixed kind profile with transaction like data' } ]; diff --git a/packages/core/tests/rlp/rlp.unit.test.ts b/packages/core/tests/rlp/rlp.unit.test.ts index f71a1abfc..700ed3c19 100644 --- a/packages/core/tests/rlp/rlp.unit.test.ts +++ b/packages/core/tests/rlp/rlp.unit.test.ts @@ -45,7 +45,9 @@ describe('RLP', () => { describe('encode', () => { encodeTestCases.forEach(({ input, expected, description }) => { test(description, () => { - expect(Hex.of(RLP_CODER.encode(input)).hex).toEqual(expected); + expect(Hex.of(RLP_CODER.encode(input)).toString()).toEqual( + expected + ); }); }); }); @@ -54,9 +56,9 @@ describe('RLP', () => { describe('decode', () => { decodeTestCases.forEach(({ input, expected, description }) => { test(description, () => { - expect(RLP_CODER.decode(Buffer.from(input, 'hex'))).toEqual( - expected - ); + expect( + RLP_CODER.decode(Buffer.from(Hex.of(input).bytes)) + ).toEqual(expected); }); }); }); @@ -89,7 +91,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -140,7 +142,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -192,7 +194,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -245,7 +247,7 @@ describe('RLP', () => { ({ kind, data, expected, description }) => { test(description, () => { expect( - Hex.of(kind.data(data, '').encode()).hex + Hex.of(kind.data(data, '').encode()).toString() ).toEqual(expected); }); } @@ -279,7 +281,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -294,7 +296,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -309,7 +311,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -323,7 +325,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -338,7 +340,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -353,7 +355,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); @@ -368,7 +370,7 @@ describe('RLP', () => { const encoded = rlp.encodeObject(data); - expect(Hex.of(encoded).hex).toBe(expected); + expect(Hex.of(encoded).toString()).toBe(expected); }); } ); diff --git a/packages/core/tests/transaction/fixture.ts b/packages/core/tests/transaction/fixture.ts index a2b099d9f..129ca0cdd 100644 --- a/packages/core/tests/transaction/fixture.ts +++ b/packages/core/tests/transaction/fixture.ts @@ -1,4 +1,4 @@ -import { addressUtils, secp256k1, type TransactionBody } from '../../src'; +import { addressUtils, Hex, secp256k1, type TransactionBody } from '../../src'; /** * Simple correct transaction body fixture @@ -33,7 +33,7 @@ const transactions = { { body: _correctTransactionBody, signatureHashExpected: - '2a1c25ce0d66f45276a5f308b99bf410e2fc7d5b6ea37a49f2ab9f1da9446478', + '0x2a1c25ce0d66f45276a5f308b99bf410e2fc7d5b6ea37a49f2ab9f1da9446478', signedTransactionIdExpected: '0xda90eaea52980bc4bb8d40cb2ff84d78433b3b4a6e7d50b75736c5e3e77b71ec', encodedUnsignedExpected: Buffer.from( @@ -57,7 +57,7 @@ const transactions = { } }, signatureHashExpected: - '005fb0b47dfd16b7f2f61bb17df791242bc37ed1fffe9b05fa55fb0fe069f9a3', + '0x005fb0b47dfd16b7f2f61bb17df791242bc37ed1fffe9b05fa55fb0fe069f9a3', encodedUnsignedExpected: Buffer.from( 'f8550184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614ec101', 'hex' @@ -79,7 +79,7 @@ const transactions = { } }, signatureHashExpected: - 'd6e8f162e3e08585ee8fcf81868e5bd57a59966fef218528339766ee2587726c', + '0xd6e8f162e3e08585ee8fcf81868e5bd57a59966fef218528339766ee2587726c', encodedUnsignedExpected: Buffer.from( 'f8610184aabbccdd20f840df947567d83b7b8d80addcb281a71d54fc7b3364ffed82271086000000606060df947567d83b7b8d80addcb281a71d54fc7b3364ffed824e208600000060606081808252088083bc614ecd01853078303030853078303030', 'hex' @@ -97,28 +97,24 @@ const transactions = { /** * Simple private key fixture */ -const _signerPrivateKey = Buffer.from( - '7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a', - 'hex' -); +const _signerPrivateKey = + '7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a'; const signer = { privateKey: _signerPrivateKey, address: addressUtils.fromPublicKey( - Buffer.from(secp256k1.derivePublicKey(_signerPrivateKey)) + secp256k1.derivePublicKey(Hex.of(_signerPrivateKey).bytes) ) }; /** * Simple private key of transaction delegator fixture */ -const _delegatorPrivateKey = Buffer.from( - '40de805e918403683fb9a6081c3fba072cdc5c88232c62a9509165122488dab7', - 'hex' -); +const _delegatorPrivateKey = + '40de805e918403683fb9a6081c3fba072cdc5c88232c62a9509165122488dab7'; const delegator = { privateKey: _delegatorPrivateKey, address: addressUtils.fromPublicKey( - Buffer.from(secp256k1.derivePublicKey(_delegatorPrivateKey)) + secp256k1.derivePublicKey(Hex.of(_delegatorPrivateKey).bytes) ) }; diff --git a/packages/core/tests/transaction/transaction-handler.unit.test.ts b/packages/core/tests/transaction/transaction-handler.unit.test.ts index b2a269ef9..4d223c376 100644 --- a/packages/core/tests/transaction/transaction-handler.unit.test.ts +++ b/packages/core/tests/transaction/transaction-handler.unit.test.ts @@ -30,15 +30,15 @@ describe('Transaction handler', () => { transactions.undelegated.forEach((transaction) => { const signedTransaction = TransactionHandler.sign( transaction.body, - signer.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes) ); // Sign a non-delegated transaction with delegator expect(() => TransactionHandler.signWithDelegator( transaction.body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ) ).toThrowError(NotDelegatedTransaction); @@ -68,19 +68,22 @@ describe('Transaction handler', () => { transactions.delegated.forEach((transaction) => { const signedTransaction = TransactionHandler.signWithDelegator( transaction.body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ); // Sign normally a delegated transaction expect(() => - TransactionHandler.sign(transaction.body, signer.privateKey) + TransactionHandler.sign( + transaction.body, + Buffer.from(Hex.of(signer.privateKey).bytes) + ) ).toThrowError(InvalidTransactionField); expect(() => TransactionHandler.sign( transaction.body, - delegator.privateKey + Buffer.from(Hex.of(delegator.privateKey).bytes) ) ).toThrowError(InvalidTransactionField); @@ -118,14 +121,14 @@ describe('Transaction handler', () => { TransactionHandler.signWithDelegator( transactions.delegated[0].body, Buffer.from('INVALID', 'hex'), - delegator.privateKey + Buffer.from(Hex.of(delegator.privateKey).bytes) ); }).toThrowError(InvalidSecp256k1PrivateKey); expect(() => { TransactionHandler.signWithDelegator( transactions.delegated[0].body, - signer.privateKey, + Buffer.from(Hex.of(signer.privateKey).bytes), Buffer.from('INVALID', 'hex') ); }).toThrowError(InvalidSecp256k1PrivateKey); @@ -151,7 +154,7 @@ describe('Transaction handler', () => { transactions.undelegated.forEach((transaction) => { // Unsigned transaction const decodedUnsigned = TransactionHandler.decode( - transaction.encodedUnsignedExpected, + Buffer.from(transaction.encodedUnsignedExpected), false ); @@ -182,7 +185,7 @@ describe('Transaction handler', () => { // Signed transaction const decodedSigned = TransactionHandler.decode( - transaction.encodedSignedExpected, + Buffer.from(transaction.encodedSignedExpected), true ); @@ -242,8 +245,8 @@ describe('Transaction handler', () => { const encodedSignedDelegated = TransactionHandler.signWithDelegator( transactions.delegated[0].body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ); // Signed transaction diff --git a/packages/core/tests/transaction/transaction.unit.test.ts b/packages/core/tests/transaction/transaction.unit.test.ts index c6d32b92b..9e124ccf5 100644 --- a/packages/core/tests/transaction/transaction.unit.test.ts +++ b/packages/core/tests/transaction/transaction.unit.test.ts @@ -31,7 +31,7 @@ describe('Transaction', () => { expect(unsignedTransaction.isSigned).toEqual(false); expect(unsignedTransaction.isDelegated).toEqual(false); expect( - Hex.of(unsignedTransaction.getSignatureHash()).hex + Hex.of(unsignedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Get id from unsigned transaction (should throw error) @@ -51,7 +51,9 @@ describe('Transaction', () => { // Encoding expect(unsignedTransaction.encoded).toEqual( - transaction.encodedUnsignedExpected + Buffer.from( + Hex.of(transaction.encodedUnsignedExpected).bytes + ) ); // Intrinsic gas @@ -72,7 +74,7 @@ describe('Transaction', () => { // Init unsigned transaction from body const signedTransaction = TransactionHandler.sign( transaction.body, - signer.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes) ); // Checks on signature @@ -80,7 +82,7 @@ describe('Transaction', () => { expect(signedTransaction.isSigned).toEqual(true); expect(signedTransaction.isDelegated).toEqual(false); expect( - Hex.of(signedTransaction.getSignatureHash()).hex + Hex.of(signedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Checks on origin, id and delegator @@ -96,7 +98,7 @@ describe('Transaction', () => { // Encoding expect(signedTransaction.encoded).toEqual( - transaction.encodedSignedExpected + Buffer.from(Hex.of(transaction.encodedSignedExpected).bytes) ); }); }); @@ -119,7 +121,7 @@ describe('Transaction', () => { expect(unsignedTransaction.isSigned).toEqual(false); expect(unsignedTransaction.isDelegated).toEqual(true); expect( - Hex.of(unsignedTransaction.getSignatureHash()).hex + Hex.of(unsignedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Get id from unsigned transaction (should throw error) @@ -154,8 +156,8 @@ describe('Transaction', () => { transactions.delegated.forEach((transaction) => { const signedTransaction = TransactionHandler.signWithDelegator( transaction.body, - signer.privateKey, - delegator.privateKey + Buffer.from(Hex.of(signer.privateKey).bytes), + Buffer.from(Hex.of(delegator.privateKey).bytes) ); // Checks on signature @@ -163,7 +165,7 @@ describe('Transaction', () => { expect(signedTransaction.isSigned).toEqual(true); expect(signedTransaction.isDelegated).toEqual(true); expect( - Hex.of(signedTransaction.getSignatureHash()).hex + Hex.of(signedTransaction.getSignatureHash()).toString() ).toEqual(transaction.signatureHashExpected); // Checks on origin, id and delegator diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 858761adf..dd48300d1 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, test } from '@jest/globals'; import { Hex, Txt } from '../../src'; +import { describe, expect, test } from '@jest/globals'; import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; /** @@ -198,6 +198,11 @@ describe('Hex class tests', () => { }); describe('isValid method tests', () => { + test('Return true for empty input', () => { + const exp = ''; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + test('Return false for invalid hex', () => { const exp = 'G00dB0y'; expect(Hex.isValid(exp)).toBeFalsy(); @@ -225,6 +230,11 @@ describe('Hex class tests', () => { }); describe('isValid0x method tests', () => { + test('Return true for empty input', () => { + const exp = '0x'; + expect(Hex.isValid(exp)).toBeTruthy(); + }); + test('Return false for invalid hex', () => { const exp = '0xG00dB0y'; expect(Hex.isValid0x(exp)).toBeFalsy(); From 52b6358396332250298a1f713046796809f71cac Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 17:49:20 +0100 Subject: [PATCH 03/10] feat: 1110 HexInt class documented --- packages/core/src/vcdm/Hex.ts | 6 ++- packages/core/src/vcdm/HexInt.ts | 46 ++++++++++++++++++- .../methods/eth_subscribe/eth_subscribe.ts | 1 - .../vechain-private-key-signer.ts | 16 +++---- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index db21887c8..c13ed26ba 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -8,12 +8,14 @@ import { type VeChainDataModel } from './VeChainDataModel'; * * `-` sign if the value is negative, * * `0x` hexadecimal notation tag, * * a not empty string of hexadecimal digits from `0` to `9` and from `a` to `f`. - * @implements {VeChainDataModel} + * * @description This hexadecimal notation is coherent with the decimal notation: * * the sign is only expressed for negative values, and it is always the first symbol, * * the `0x` tags the string as a hexadecimal expression, * * hexadecimal digits follow. * * An empty content results is no digits. + * + * @implements {VeChainDataModel} */ class Hex extends String implements VeChainDataModel { /** @@ -238,7 +240,7 @@ class Hex extends String implements VeChainDataModel { /** * Create a Hex instance from a bigint, number, string, or Uint8Array. * - * @param {bigint | number | string | Uint8Array} exp - The input value to convert to a Hex instance: + * @param {bigint | number | string | Uint8Array} exp - The value to convert to a Hex instance: * * bigint, converted to a signed hexadecimal expression of its absolute value; * * number, encoded as [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format); * * string, parsed as a hexadecimal expression, optionally signed `-`, optionally tagged with `0x`; diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts index ce835dd2c..3f6e24191 100644 --- a/packages/core/src/vcdm/HexInt.ts +++ b/packages/core/src/vcdm/HexInt.ts @@ -1,7 +1,23 @@ import { Hex } from './Hex'; import { InvalidDataType } from '@vechain/sdk-errors'; +/** + * Represents a hexadecimal signed integer value. + * + * @description + * Any non-meaningful zero digits are removed from the hexadecimal expression of this instance. + * + * @remark This class makes equal instances created from the same value as number or as bigint. + * + * @extends {Hex} + */ class HexInt extends Hex { + /** + * Creates a new instance of this class to represent the `hex` value, + * not meaningful zero digits are remved from the hexadecimal representation. + * + * @param {Hex} hex - The Hex object to be used for constructing a new instance. + */ protected constructor(hex: Hex) { let cue = 0; while (cue < hex.hex.length && hex.hex.at(cue) === '0') { @@ -13,6 +29,16 @@ class HexInt extends Hex { ); } + /** + * Retrieves the value of n cast from this instance interpreted as the hexadecimal expression of a bigint value. + * + * @return {number} The value of n. + * + * @throws {InvalidDataType} If n is not within the safe number range, if the number representation of this + * instance results approximated. + * + * @remark This class makes equal instances created from the same value as number or as bigint. + */ public override get n(): number { const bi = this.bi; if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_VALUE) { @@ -24,6 +50,25 @@ class HexInt extends Hex { }); } + /** + * Create a HexInt instance from a bigint, number, string, Uint8Array, or {@link Hex}. + * + * @param {bigint | number | string | Uint8Array | Hex} exp - The value to convert to a HexInt instance: + * * bigint is converted to its hexadecimal expression, + * * number is cast to a bigint then converted to its hexadecimal expression, + * it throws {@link InvalidDataType} if not an integer value, + * * string is parsed as a bigint hexadecimal expression, + * * Uint8Array is interpreted as the sequence of bytes expressing a bigint value, + * then concerted to its hexadecimal expression, + * * {@link Hex} is interpreted as expressing a bigint value. + * + * @returns {HexInt} - The converted HexInt object. + * + * @throws {InvalidDataType} - If the value is not a valid hexadecimal integer expression, + * if `exp` is a not integer number. + * + * @remark This class makes equal instances created from the same value as number or as bigint. + */ public static of(exp: bigint | number | string | Uint8Array | Hex): HexInt { try { if (exp instanceof Hex) { @@ -39,7 +84,6 @@ class HexInt extends Hex { } return new HexInt(Hex.of(exp)); } catch (e) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new InvalidDataType( 'HexInt.of', 'not an hexadecimal integer expression', diff --git a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts index 911fbc111..49919cbc0 100644 --- a/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts +++ b/packages/network/src/provider/utils/rpc-mapper/methods-map/methods/eth_subscribe/eth_subscribe.ts @@ -102,7 +102,6 @@ const ethSubscribe = async ( provider.startSubscriptionsPolling(); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access const subscriptionId: string = Hex.random(16).hex; if (params.includes(SUBSCRIPTION_TYPE.NEW_HEADS)) { diff --git a/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts b/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts index dd2f27611..d343daac6 100644 --- a/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts +++ b/packages/network/src/signer/signers/vechain-private-key-signer/vechain-private-key-signer.ts @@ -184,15 +184,13 @@ class VeChainPrivateKeySigner extends VeChainAbstractSigner { ): Promise { return await new Promise((resolve, reject) => { try { - const hash = n_utils.hexToBytes( - Hex.of( - vechain_sdk_core_ethers.TypedDataEncoder.hash( - domain, - types, - value - ) - ).hex - ); + const hash = Hex.of( + vechain_sdk_core_ethers.TypedDataEncoder.hash( + domain, + types, + value + ) + ).bytes; const sign = secp256k1.sign( hash, new Uint8Array(this.privateKey) From 0825dddf570610dcfe63da93d2f0c45f6320dc9a Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Thu, 8 Aug 2024 19:07:05 +0100 Subject: [PATCH 04/10] feat: 1110 HexInt class tested --- packages/core/src/vcdm/Hex.ts | 3 + packages/core/src/vcdm/HexInt.ts | 3 +- packages/core/tests/vcdm/Hex.unit.test.ts | 16 ++++ packages/core/tests/vcdm/HexInt.unit.test.ts | 77 ++++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 packages/core/tests/vcdm/HexInt.unit.test.ts diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index c13ed26ba..b5a66d20b 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -56,6 +56,8 @@ class Hex extends String implements VeChainDataModel { /** * Returns the hexadecimal digits expressing this absolute value, sign and `0x` prefix omitted. + + * @remark An empty content results in an empty string returned. */ public readonly hex: string; @@ -287,6 +289,7 @@ class Hex extends String implements VeChainDataModel { this.REGEX_PREFIX.test(exp) ? exp.slice(2) : exp ); } + // noinspection ExceptionCaughtLocallyJS throw new InvalidDataType('Hex.of', 'not an hexadecimal string', { exp }); diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts index 3f6e24191..29f1edf6c 100644 --- a/packages/core/src/vcdm/HexInt.ts +++ b/packages/core/src/vcdm/HexInt.ts @@ -41,7 +41,7 @@ class HexInt extends Hex { */ public override get n(): number { const bi = this.bi; - if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_VALUE) { + if (Number.MIN_SAFE_INTEGER <= bi && bi <= Number.MAX_SAFE_INTEGER) { return Number(bi); } throw new InvalidDataType('HexInt.of', 'not in the safe number range', { @@ -78,6 +78,7 @@ class HexInt extends Hex { if (Number.isInteger(exp)) { return new HexInt(Hex.of(BigInt(exp))); } + // noinspection ExceptionCaughtLocallyJS throw new InvalidDataType('HexInt.of', 'not an integer', { exp }); diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index dd48300d1..903e8c810 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -127,6 +127,22 @@ describe('Hex class tests', () => { expect('-' + hex.hex).toEqual(exp.toLowerCase()); // Normalized from is lower case. }); + test('Return an Hex instance if the passed argument is an empty string with 0x prefix', () => { + const exp = '0x'; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.toString()).toEqual(exp); + expect(hex.hex.length).toEqual(0); + }); + + test('Return an Hex instance if the passed argument is an empty string without 0x prefix', () => { + const exp = ''; + const hex = Hex.of(exp); + expect(hex).toBeInstanceOf(Hex); + expect(hex.toString()).toEqual('0x'); + expect(hex.hex.length).toEqual(0); + }); + test('Return an Hex instance if the passed argument is UInt8Array', () => { const exp = Uint8Array.of(0xc0, 0xff, 0xee); const hex = Hex.of(exp); diff --git a/packages/core/tests/vcdm/HexInt.unit.test.ts b/packages/core/tests/vcdm/HexInt.unit.test.ts new file mode 100644 index 000000000..623493cac --- /dev/null +++ b/packages/core/tests/vcdm/HexInt.unit.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, test } from '@jest/globals'; +import { Hex, HexInt } from '../../src'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +/** + * Test Txt class. + * @group unit/vcdm + */ +describe('HexInt class tests', () => { + describe('VeChain Data Model tests', () => { + test('Return equals values for bi and n properties from bigint value', () => { + const exp = -789514n; // Safe integer + const hex = HexInt.of(exp); + expect(hex.bi).toEqual(exp); + expect(hex.n).toEqual(Number(exp)); + expect(hex.toString()).toEqual('-0xc0c0a'); + }); + + test('Return equals values for bi and n properties from number value', () => { + const exp = 12648430; // Safe integer. + const hex = HexInt.of(exp); + expect(hex.n).toEqual(exp); + expect(hex.bi).toEqual(BigInt(exp)); + expect(hex.toString()).toEqual('0xc0ffee'); + }); + + test('Throw an exception if this integer is beyond safe integer range - underflow', () => { + const largeBigInt = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(10); + const hexIntInstance = HexInt.of(largeBigInt); + expect(() => hexIntInstance.n).toThrow(InvalidDataType); + }); + + test('Throw an exception if this integer is beyond safe integer range - overflow', () => { + const largeBigInt = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(10); + const hexIntInstance = HexInt.of(largeBigInt); + expect(() => hexIntInstance.n).toThrow(InvalidDataType); + }); + }); + + describe('Construction tests', () => { + test('Return an HexInt instance if the passed argument is a bigint', () => { + const exp = 12357n; + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + }); + + test('Return an HexInt instance if the passed argument is an integer number', () => { + const exp = 12357; + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + }); + + test('Return an HexInt instance if the passed argument is an string hexadecimal expression', () => { + const exp = '-C0c0a'; // Safe integer -789514. + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + }); + + test('Return an HexInt instance if the passed argument is an Hex', () => { + const exp = Hex.of(-789514n); + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(HexInt); + expect(hex.isEqual(exp)).toBeTruthy(); + }); + + test('Return an HexInt instance if the passed argument is Uint8Array', () => { + const exp = Uint8Array.of(0xc0, 0xff, 0xee); // Safe integer 12648430. + const hex = HexInt.of(exp); + expect(hex).toBeInstanceOf(Hex); + }); + + test('Throw an exception if the passed argument is not an integer number', () => { + const exp = 123.57; + expect(() => HexInt.of(exp)).toThrow(InvalidDataType); + }); + }); +}); From bfe0202d7e26bbc09f8ce4a3042dbf63fdbd7455 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 9 Aug 2024 08:58:33 +0100 Subject: [PATCH 05/10] feat: 1110 HexUInt class provided --- docs/diagrams/architecture/vcdm.md | 1 + packages/core/src/vcdm/Hex.ts | 9 ++++ packages/core/src/vcdm/HexUInt.ts | 53 +++++++++++++++++++ packages/core/src/vcdm/index.ts | 1 + packages/core/tests/vcdm/Hex.unit.test.ts | 14 +++++ packages/core/tests/vcdm/HexUInt.unit.test.ts | 22 ++++++++ 6 files changed, 100 insertions(+) create mode 100644 packages/core/src/vcdm/HexUInt.ts create mode 100644 packages/core/tests/vcdm/HexUInt.unit.test.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 133d01f9a..48ed5c6e8 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -10,6 +10,7 @@ classDiagram <> } class Hex { + +Hex abs +number sign +Hex alignToBytes() +Hex fit(number digits) diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index b5a66d20b..b74f353fb 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -87,6 +87,15 @@ class Hex extends String implements VeChainDataModel { this.sign = sign; } + /** + * Returns the absolute value of this Hex object. + * + * @return {Hex} A new Hex object representing the absolute value of this Hex. + */ + public get abs(): Hex { + return new Hex(Hex.POSITIVE, this.hex); + } + /** * Returns the value of `bi` as a `BigInt` type. * diff --git a/packages/core/src/vcdm/HexUInt.ts b/packages/core/src/vcdm/HexUInt.ts new file mode 100644 index 000000000..36323c365 --- /dev/null +++ b/packages/core/src/vcdm/HexUInt.ts @@ -0,0 +1,53 @@ +import { HexInt } from './HexInt'; +import { Hex } from './Hex'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +/** + * Represents a hexadecimal unsigned integer. + * + * @extends HexInt + */ +class HexUInt extends HexInt { + /** + * Creates a new instance of this class to represent the absolute `hi` value. + * + * @param {HexInt} hi - The HexInt object representing the hexadecimal value. + * + * @throws {InvalidDataType} Throws an error if the sign of hi is not positive. + */ + protected constructor(hi: HexInt) { + if (hi.sign >= Hex.POSITIVE) { + super(hi); + } else { + throw new InvalidDataType('HexUInt.constructor', 'not positive', { + hi + }); + } + } + + /** + * Create a HexUInt instance from the given expression interprete as an unsigned integer. + * + * @param exp The expression to convert. It can be of type bigint, number, string, Uint8Array, or HexInt. + * + * @returns {HexUInt} The converted hexadecimal unsigned integer. + * + * @throws {InvalidDataType} If the given expression is not a valid hexadecimal positive integer expression. + */ + public static of( + exp: bigint | number | string | Uint8Array | HexInt + ): HexUInt { + try { + return new HexUInt(HexInt.of(exp)); + } catch (e) { + throw new InvalidDataType( + 'HexUInt.of', + 'not a hexadecimal positive integer expression', + { exp }, + e + ); + } + } +} + +export { HexUInt }; diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index 87b10bf02..e477be9a7 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -1,5 +1,6 @@ export * from './Hex'; export * from './HexInt'; +export * from './HexUInt'; export * from './ThorId'; export * from './Txt'; export * from './VeChainDataModel'; diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 903e8c810..3d03b468b 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -156,6 +156,20 @@ describe('Hex class tests', () => { }); }); + describe('abs property test', () => { + test('Return a positive Hex instance for a negative argument', () => { + const exp = '-0xA01fe'; + const hex = Hex.of(exp); + expect(hex.abs.isEqual(Hex.of(exp.slice(1)))).toBeTruthy(); + }); + + test('Return a positive Hex instance for a positive argument', () => { + const exp = '0xA01fe'; + const hex = Hex.of(exp); + expect(hex.abs.isEqual(hex)).toBeTruthy(); + }); + }); + describe('alignToBytes method tests', () => { test('Return bytes aligned from aligned expression', () => { const hex = Hex.of('0xc0fee'); diff --git a/packages/core/tests/vcdm/HexUInt.unit.test.ts b/packages/core/tests/vcdm/HexUInt.unit.test.ts new file mode 100644 index 000000000..982f12fce --- /dev/null +++ b/packages/core/tests/vcdm/HexUInt.unit.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from '@jest/globals'; +import { HexUInt } from '../../src'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +/** + * Test Txt class. + * @group unit/vcdm + */ +describe('HexUInt class tests', () => { + describe('Construction tests', () => { + test('Return an HexUInt instance if the passed argument is positive', () => { + const exp = '0xcaffee'; + const hi = HexUInt.of(exp); + expect(hi).toBeInstanceOf(HexUInt); + }); + + test('Throw an error if the passed argument is negative', () => { + const exp = '-0xcaffee'; + expect(() => HexUInt.of(exp)).toThrow(InvalidDataType); + }); + }); +}); From b9c481e5aa98df07933946016372fc1a85313bf1 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 9 Aug 2024 11:59:14 +0100 Subject: [PATCH 06/10] feat: 1113 utils/hex removed --- packages/core/src/utils/hex/_Hex.ts | 443 ------------------ packages/core/src/utils/hex/index.ts | 2 - packages/core/src/utils/hex/types.d.ts | 1 - packages/core/src/utils/index.ts | 1 - .../core/tests/utils/hex/hex.unit.test.ts | 302 ------------ 5 files changed, 749 deletions(-) delete mode 100644 packages/core/src/utils/hex/_Hex.ts delete mode 100644 packages/core/src/utils/hex/index.ts delete mode 100644 packages/core/src/utils/hex/types.d.ts delete mode 100644 packages/core/tests/utils/hex/hex.unit.test.ts diff --git a/packages/core/src/utils/hex/_Hex.ts b/packages/core/src/utils/hex/_Hex.ts deleted file mode 100644 index 4bd1dff89..000000000 --- a/packages/core/src/utils/hex/_Hex.ts +++ /dev/null @@ -1,443 +0,0 @@ -import * as n_utils from '@noble/curves/abstract/utils'; -import { InvalidDataType } from '@vechain/sdk-errors'; -import { HexInt } from '../../vcdm'; -import { randomBytes } from '@noble/hashes/utils'; -import { type HexString } from './types'; - -/** - * The PREFIX constant represents the prefix string used in the code. - * The prefix is set to '0x', indicating that the following value is in hexadecimal format. - * - * @constant {string} - * @default '0x' - * @see {_Hex0x.of} - */ -const PREFIX: string = '0x'; - -/** - * The radix value used for hexadecimal numbers. - * - * @type {number} - */ -const RADIX: number = 16; - -/** - * Regular expression for matching a string in the format `/^0x[0-9a-f]*$/i;` - * - * @type {RegExp} - * @see _Hex0x.of - * @see HexString - */ -const REGEX_FOR_0X_PREFIX_HEX: RegExp = /^0x[0-9a-f]*$/i; - -/** - * Regular expression for matching a string in the format `/^(0x)?[0-9a-f]*$/i;` - * - * @type {RegExp} - * @see HexString - */ -const REGEX_FOR_OPTIONAL_0X_PREFIX_HEX: RegExp = /^(0x)?[0-9a-f]*$/i; - -/** - * Default length of thor id hex string. - * Thor id is a 64 characters long hexadecimal string. - * This is used to validate thor id strings (block ids, transaction ids, ...). - */ -const THOR_ID_LENGTH = 64; - -/** - * Represents the error messages used in the {@link _Hex} object. - * @enum {string} - */ -enum ErrorMessage { - /** - * Error message constant indicating that the provided arguments are not sufficient - * to fit the expected value. - * - * @type {string} - * @see _Hex.canon - */ - NOT_FIT = `Arg 'bytes' not enough to fit 'exp'.`, - - /** - * Error message constant for invalid hexadecimal expression - * not matching {@link REGEX_FOR_0X_PREFIX_HEX}. - * - * @type {string} - * @see _Hex.canon - */ - NOT_HEX = `Arg 'n' not an hexadecimal expression.`, - - /** - * String constant representing an error message when the argument 'n' is not an integer. - * - * @type {string} - * @see ofNumber - */ - NOT_INTEGER = `Arg 'n' not an integer.`, - - /** - * Variable representing an error message when the argument 'bytes' is not a valid length. - * - * @type {string} - * @see _Hex.canon - */ - NOT_LENGTH = `Arg 'bytes' not a length.`, - - /** - * String constant representing an error message when argument 'n' is not negative. - * - * @type {string} - * @see {ofBigInt} - * @see {ofNumber} - */ - NOT_POSITIVE = `Arg 'n' not negative.` -} - -/** - * Represents a value that can be represented in hexadecimal format. - * - * @typedef { bigint | Uint8Array | number | string } HexRepresentable - */ -type HexRepresentable = bigint | Uint8Array | number | string; - -/** - * Convert a bigint number to a padded hexadecimal representation long the specified number of bytes. - * - * @param {bigint} bi - The bigint number to be represented as hexadecimal string. - * @param {number} bytes - The number of bytes the resulting hexadecimal representation should be padded to. - * @returns {string} - The padded hexadecimal representation of the bigint number. - * @throws {InvalidDataType} - */ -function ofBigInt(bi: bigint, bytes: number): string { - if (bi < 0) { - throw new InvalidDataType( - 'Hex.ofBigInt()', - ErrorMessage.NOT_POSITIVE as string, - { - bi: bi.toString() - } - ); - } - - return pad(bi.toString(RADIX), bytes); -} - -/** - * Converts a hexadecimal string representing a number to a padded lowercase hexadecimal string. - * - * @param {HexString} n - The hexadecimal string representing the number. - * @param {number} [bytes=0] - The number of bytes the resulting hexadecimal string should be padded to. Defaults to 0. - * @returns {string} - The padded lowercase hexadecimal string. - * @throws {InvalidDataType} - */ -function ofHexString(n: HexString, bytes: number): string { - if (!_Hex0x.isValid(n)) - throw new InvalidDataType( - 'Hex.ofHexString()', - ErrorMessage.NOT_HEX as string, - { - n - } - ); - return pad(n.slice(2).toLowerCase(), bytes); -} - -/** - * Convert a number to a padded hexadecimal representation long the specified number of bytes. - * - * @param {number} n - The number to be represented as hexadecimal string. - * @param {number} bytes - The number of bytes the resulting hexadecimal representation should be padded to. - * @returns {string} The padded hexadecimal representation of the number. - * @throws {InvalidDataType} - */ -function ofNumber(n: number, bytes: number): string { - if (!Number.isInteger(n)) { - throw new InvalidDataType( - 'Hex.ofNumber()', - ErrorMessage.NOT_INTEGER as string, - { n } - ); - } - if (n < 0) { - throw new InvalidDataType( - 'Hex.ofNumber()', - ErrorMessage.NOT_POSITIVE as string, - { n } - ); - } - - return pad(n.toString(RADIX), bytes); -} - -/** - * Converts a string to its binary representation, - * then to its padded hexadecimal representation - * long the specified number of bytes. - * - * @param {string} txt - The input string to be converted. - * @param {number} bytes - The number of bytes the resulting hexadecimal representation should be padded to. - * @returns {string} - The padded hexadecimal representation of the number. - * - * @see {ofUint8Array} - */ -function ofString(txt: string, bytes: number): string { - return ofUint8Array(n_utils.utf8ToBytes(txt), bytes); -} - -/** - * Convert an Uint8Array to a padded hexadecimal representation long the specified number of bytes. - * - * Secure audit function. - * * [n_utils](https://github.com/paulmillr/noble-curves?tab=readme-ov-file#utils-useful-utilities) - * - * @param {Uint8Array} uint8Array - The Uint8Array to be represented as hexadecimal string. - * @param {number} [bytes=0] - The number of bytes the resulting hexadecimal representation should be padded to. - * @return {string} - The padded hexadecimal representation of the buffer. - */ -function ofUint8Array(uint8Array: Uint8Array, bytes: number): string { - return pad(n_utils.bytesToHex(uint8Array), bytes); -} - -/** - * Adds padding to a hexadecimal expression to ensure it represents the specified number of bytes. - * - * @param {string} exp - The hexadecimal expression to pad. - * @param {number} bytes - The number of bytes that the expression should occupy. - * @return {string} The padded hexadecimal expression. - */ -function pad(exp: string, bytes: number): string { - let result = exp; - if (result.length % 2 !== 0) { - result = '0' + result; - } - if (bytes > 0) { - const gap = bytes - result.length / 2; - if (gap > 0) { - return `${'00'.repeat(gap)}${result}`; - } - } - return result; -} - -/** - * Trims leading zeros from a string. - * - * @param {string} exp - The string to trim. - * @returns {string} - The trimmed string. - * @see _Quantity.of - */ -// function trim(exp: string): string { -// let i = 0; -// while (i < exp.length && exp.at(i) === '0') { -// i++; -// } -// return i === exp.length ? '0' : exp.slice(i); -// } - -/** - * Helper for encoding hexadecimal values prefixed with '0x'. - */ -const _Hex0x = { - /** - * Converts a given string expression to a canonical representation prefixed with `0x`, - * optionally specifying the number of bytes to include in the canonical form. - * - * @param {string} exp - The string expression to convert to canonical form. - * @param {number} [bytes] - The number of bytes to include in the canonical form. - * If not specified, all bytes will be included. - * @returns {string} The canonical representation of the given string expression. - * @throws {Error} if `exp` is not a valid hexadecimal expression, - * if `bytes` is not integer and greater or equal to zero. - */ - canon: function (exp: string, bytes?: number): string { - return `${PREFIX}${_Hex.canon(exp, bytes)}`; - }, - - /** - * Checks if the given expression is a valid Thor-based ID. - * Thor id is a 64 characters long hexadecimal string. - * It is used to identify a transaction id, a block id, etc. - * - * @param {string} exp - The expression to check. - * @param {boolean} is0xOptional - Do not check if `exp` is `0x` prefixed, `false` by default. - * @returns {boolean} - Returns true if the expression is a valid Thor ID, otherwise false. - */ - isThorId: function (exp: string, is0xOptional: boolean = false): boolean { - return ( - this.isValid(exp, is0xOptional) && - (is0xOptional - ? exp.length === THOR_ID_LENGTH - : exp.length === THOR_ID_LENGTH + 2) // +2 for '0x' - ); - }, - - /** - * Checks if the given expression is a valid hexadecimal expression - * - prefixed with `0x` (or optionally if `is0xOptional is `true`), - * - byte aligned if `isByteAligned` is `true`. - * - * @param {string} exp - The expression to be validated. - * @param {boolean} is0xOptional - Do not check if `exp` is `0x` prefixed, `false` by default. - * @param {boolean} isByteAliged - Check `exp` represents a full byte or an array of bytes, `false`, by default. - * @returns {boolean} - Whether the expression is valid or not. - */ - isValid: function ( - exp: string, - is0xOptional: boolean = false, - isByteAliged: boolean = false - ): boolean { - let predicate: boolean = is0xOptional - ? REGEX_FOR_OPTIONAL_0X_PREFIX_HEX.test(exp) - : REGEX_FOR_0X_PREFIX_HEX.test(exp); - if (isByteAliged && predicate) { - predicate = exp.length % 2 === 0; - } - return predicate; - }, - - /** - * Returns a hexadecimal representation from the given input data prefixed with `0x`. - * - * **Note:** this method calls {@link _Hex.of} to generate the hexadecimal representation of n, - * then it prefixes the result with `0x`. - * - * @param {HexRepresentable} n - The input data to be represented. - * @param {number} [bytes=0] - If not `0` by default, the hexadecimal representation encodes at least {number} bytes. - * @returns {Uint8Array} - The resulting hexadecimal representation, - * it is guaranteed to be even characters long. - * @see _Hex - * @see HexRepresentable - */ - of: function (n: HexRepresentable, bytes: number = 0): string { - return `${PREFIX}${_Hex.of(n, bytes)}`; - } -}; - -/** - * Helper for encoding hexadecimal values. - */ -const _Hex = { - /** - * Converts a given string expression to a canonical representation prefixed with `0x`, - * optionally specifying the number of bytes to include in the canonical form. - * - * @param {string} exp - The string expression to convert to canonical form. - * @param {number} [bytes] - The number of bytes to include in the canonical form. - * If not specified, all bytes will be included. - * @returns {string} The canonical representation of the given string expression. - * @throws {InvalidDataType} - */ - canon: function (exp: string, bytes?: number): string { - let result: string = ''; - if (REGEX_FOR_0X_PREFIX_HEX.test(exp)) { - result = exp.slice(2).toLowerCase(); - } else if (REGEX_FOR_OPTIONAL_0X_PREFIX_HEX.test(exp)) { - result = exp.toLowerCase(); - } else { - throw new InvalidDataType('Hex.canon()', ErrorMessage.NOT_HEX, { - exp - }); - } - if (typeof bytes !== 'undefined') { - if (!Number.isInteger(bytes) || bytes < 0) { - throw new InvalidDataType( - 'Hex.canon()', - ErrorMessage.NOT_LENGTH as string, - { - bytes - } - ); - } - - result = pad(result, bytes); - - if (result.length > bytes * 2) { - throw new InvalidDataType( - 'Hex.canon()', - ErrorMessage.NOT_FIT as string, - { - bytes - } - ); - } - } - return result; - }, - - /** - * Returns a hexadecimal representation from the given input data. - * This method calls - * * {@link ofBigInt} if `n` type is `bigint`; - * * {@link ofHexString} if `n` type is {@link HexString}`; - * * {@link ofNumber} if `n` type is `number`; - * * {@link ofString} if `n` type is `string`; - * * {@link ofUint8Array} if `n` is an instance of {@link Uint8Array}. - * - * **Note:** the returned string is not prefixed with `0x`, - * see {@link _Hex0x.of} to make a hexadecimal representation prefixed with `0x`. - * - * **Note:** [HexString](https://docs.ethers.org/v6/api/utils/#HexString) - * definition overlaps `string` TS completely as an alias. - * This function tests if the given input starts with `0x` - * and is positive to {@link _Hex0x.isValid} - * processing it as {@link HexString} type, - * else it considers the string as an array of bytes and - * returns its hexadecimal representation. - * To force a string to be considered an array of bytes despite it is - * a valid `0x` hexadecimal expression, convert it to {@link Uint8Array}. - * ``` - * Hex.of(buffer.toString('hex')) - * ``` - * - * @param {HexRepresentable} n - The input data to be represented. - * @param {number} [bytes=0] - If not `0` by default, the hexadecimal representation encodes at least {number} bytes. - * @returns {Uint8Array} - The resulting hexadecimal representation, - * it is guaranteed to be even characters long. - * @see HexRepresentable - */ - of: function (n: HexRepresentable, bytes: number = 0): string { - if (n instanceof Uint8Array) return ofUint8Array(n, bytes); - if (typeof n === 'bigint') return ofBigInt(n, bytes); - if (typeof n === `number`) return ofNumber(n, bytes); - if (_Hex0x.isValid(n)) return ofHexString(n, bytes); - return ofString(n, bytes); - }, - - /** - * Generates a random hexadecimal string of the specified number of bytes. - * The length of the string is twice the `bytes`. - * - * @param {number} bytes - The number of bytes for the random string. - * @return {string} - The generated random string. - */ - random: function (bytes: number): string { - return ofUint8Array(randomBytes(bytes), bytes); - } -}; - -/** - * Helper for encoding hexadecimal values as used to represent Ethereum quantities. - */ -const _Quantity = { - /** - * Returns a hexadecimal representation for the given input data - * - without any not meaningful `0` digit on the left side, - * - prefixed with `0x`, - * - hence returns `0x0` if `n` is zero. - * - * This function is a more efficient drop-in replacement of the function - * `toQuantity` in [math.ts](https://github.com/ethers-io/ethers.js/blob/main/src.ts/utils/maths.ts) - * of [The Ethers Project](https://github.com/ethers-io/ethers.js/tree/main) library. - * - * @param {HexRepresentable} n - The input data to be represented. - * @return The resulting hexadecimal representation, nibble aligned. - * @see HexRepresentable - */ - of(n: HexRepresentable): string { - // return `${PREFIX}${trim(_Hex.of(n))}`; - return HexInt.of(n).toString(); - } -}; - -export { _Hex, _Hex0x, _Quantity }; diff --git a/packages/core/src/utils/hex/index.ts b/packages/core/src/utils/hex/index.ts deleted file mode 100644 index c436800d7..000000000 --- a/packages/core/src/utils/hex/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './_Hex'; -export * from './types.d'; diff --git a/packages/core/src/utils/hex/types.d.ts b/packages/core/src/utils/hex/types.d.ts deleted file mode 100644 index 33ae05c17..000000000 --- a/packages/core/src/utils/hex/types.d.ts +++ /dev/null @@ -1 +0,0 @@ -export type HexString = string; diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 89163871f..a66953550 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -2,7 +2,6 @@ export * from './const'; export * from './bloom'; export * from './data'; export * from './hdnode'; -export * from './hex'; export * from './transaction'; export * from './units'; export * from './revision'; diff --git a/packages/core/tests/utils/hex/hex.unit.test.ts b/packages/core/tests/utils/hex/hex.unit.test.ts deleted file mode 100644 index dcb97efc9..000000000 --- a/packages/core/tests/utils/hex/hex.unit.test.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { describe, expect, test } from '@jest/globals'; -import { _Hex, _Hex0x, _Quantity } from '../../../src'; -import { - invalidThorIDs, - prefixedAndUnprefixedStrings, - validThorIDs -} from '../data/fixture'; -import { InvalidDataType } from '@vechain/sdk-errors'; - -/** - * Text Hex representation from TS types prefixed with `0x`. - * @group unit/utils/hex - */ -describe('Hex0x', () => { - test('canon - pad to 64 characters by default', () => { - const result = _Hex0x.canon('1a', 32); - expect(result).toHaveLength(66); // 64 chars + '0x' - expect(result).toBe( - '0x000000000000000000000000000000000000000000000000000000000000001a' - ); - }); - - test('canon - pad to custom length', () => { - const result = _Hex0x.canon('1a', 64); - expect(result).toHaveLength(130); // 128 chars + '0x' - expect(result).toBe('0x' + '0'.repeat(126) + '1a'); - }); - - test('canon - with 0x', () => { - const result = _Hex0x.canon('0x1a', 32); - expect(result).toBe( - '0x000000000000000000000000000000000000000000000000000000000000001a' - ); - }); - - test('canon - return unchanged if it is already the correct length', () => { - const hex = '0x' + '1'.repeat(64); - const result = _Hex0x.canon(hex, 32); - expect(result).toBe(hex); - }); - - test('canon - return a string of just zeros if the input is empty', () => { - const result = _Hex0x.canon('', 32); - expect(result).toBe('0x' + '0'.repeat(64)); - }); - - test('canon - not hex', () => { - expect(() => _Hex0x.canon('defg', 1)).toThrowError(InvalidDataType); - }); - - test('canon - not fit', () => { - expect(() => _Hex0x.canon('001a', 1)).toThrowError(InvalidDataType); - }); - - test('canon - non-integer length', () => { - expect(() => _Hex0x.canon('1a', 31.5)).toThrowError(InvalidDataType); - }); - - test('canon - not positive length', () => { - expect(() => _Hex0x.canon('1a', -32)).toThrowError(InvalidDataType); - }); - - test('of bigint', () => { - const output: string = _Hex0x.of(BigInt(10)); - expect(output).toBe('0x0a'); - }); - - test('of bigint - padded', () => { - const output: string = _Hex0x.of(BigInt(10), 2); - expect(output).toBe('0x000a'); - }); - - test('of Buffer', () => { - const buffer: Uint8Array = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex0x.of(buffer); - expect(output).toBe('0x0a'); - }); - - test('of Buffer - padded', () => { - const buffer: Uint8Array = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex0x.of(buffer, 2); - expect(output).toBe('0x000a'); - }); - - test('of Uint8Array', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex0x.of(uint8Array); - expect(output).toBe('0x0a'); - }); - - test('of Uint8Array - padded', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex0x.of(uint8Array, 2); - expect(output).toBe('0x000a'); - }); - - test('of number', () => { - const output: string = _Hex0x.of(10 as number); - expect(output).toBe('0x0a'); - }); - - test('of number', () => { - const output: string = _Hex0x.of(10 as number, 2); - expect(output).toBe('0x000a'); - }); - - test('of string', () => { - const output: string = _Hex0x.of('a' as string); - expect(output).toBe('0x61'); - }); - - test('of string', () => { - const output: string = _Hex0x.of('a' as string, 2); - expect(output).toBe('0x0061'); - }); - - test('isThorId - true', () => { - validThorIDs.forEach((id) => { - expect(_Hex0x.isThorId(id.value, !id.checkPrefix)).toBe(true); - }); - }); - - test('isThorId - false', () => { - invalidThorIDs.forEach((id) => { - expect(_Hex0x.isThorId(id.value, !id.checkPrefix)).toBe(false); - }); - }); - - test('isValid - true', () => { - const output: boolean = _Hex0x.isValid('0x12ef'); - expect(output).toBe(true); - }); - - test('isValid - true - optional 0x', () => { - expect(_Hex0x.isValid('12ef', true)).toBe(true); - expect(_Hex0x.isValid('0x12ef', true)).toBe(true); - }); - - test('isValid - false - no 0x', () => { - const output: boolean = _Hex0x.isValid('12ef'); - expect(output).toBe(false); - }); - - test('isValid - false - no hex', () => { - const output: boolean = _Hex0x.isValid('12fg'); - expect(output).toBe(false); - }); - - test('isValid - false - no hex', () => { - const output: boolean = _Hex0x.isValid('12fg'); - expect(output).toBe(false); - }); - - test('isValid - false - byte aligned', () => { - const output: boolean = _Hex0x.isValid('12e', true, true); - expect(output).toBe(false); - }); - - test('isValid - true - byte aligned', () => { - const output: boolean = _Hex0x.isValid('12ef', true, true); - expect(output).toBe(true); - }); -}); - -/** - * Text Hex representation from TS types. - * @group unit/utils/hex - */ -describe('Hex', () => { - test('canon', () => { - prefixedAndUnprefixedStrings.forEach((prefixAndUnprefix) => { - expect(_Hex.canon(prefixAndUnprefix.prefixed)).toBe( - prefixAndUnprefix.unprefixed - ); - }); - }); - - test('of bigint', () => { - const output: string = _Hex.of(BigInt(10)); - expect(output).toBe('0a'); - - expect(() => { - _Hex.of(BigInt(-10), 0); - }).toThrow("Arg 'n' not negative."); - }); - - test('of bigint - padded', () => { - const output: string = _Hex.of(BigInt(10), 2); - expect(output).toBe('000a'); - }); - - test('of Buffer', () => { - const buffer: Buffer = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex.of(buffer); - expect(output).toBe('0a'); - }); - - test('of Buffer - padded', () => { - const buffer: Buffer = Buffer.from(new Uint8Array(1)); - buffer[0] = 10; - const output: string = _Hex.of(buffer, 2); - expect(output).toBe('000a'); - }); - - test('of HexString', () => { - const output: string = _Hex.of('0x61' as string); - expect(output).toBe('61'); - }); - - test('of HexString - padded', () => { - const output: string = _Hex.of('0x61' as string, 2); - expect(output).toBe('0061'); - }); - - test('of number', () => { - const output: string = _Hex.of(10 as number); - expect(output).toBe('0a'); - - expect(() => { - _Hex.of(3.14 as number); - }).toThrow(`Arg 'n' not an integer.`); - - expect(() => { - _Hex.of(-10 as number); - }).toThrow("Arg 'n' not negative."); - }); - - test('of number - padded', () => { - const output: string = _Hex.of(10 as number, 2); - expect(output).toBe('000a'); - }); - - test('of string', () => { - const output: string = _Hex.of('a' as string); - expect(output).toBe('61'); - }); - - test('of string - padded', () => { - const output: string = _Hex.of('a' as string, 2); - expect(output).toBe('0061'); - }); - - test('of Uint8Array', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex.of(uint8Array); - expect(output).toBe('0a'); - }); - - test('of Uint8Array - padded', () => { - const uint8Array: Uint8Array = new Uint8Array(1); - uint8Array[0] = 10; - const output: string = _Hex.of(uint8Array); - expect(output).toBe('0a'); - }); - - test('random - should return a string of the correct length', () => { - const size = 8; - const hex = _Hex.random(size / 2); - expect(hex).toHaveLength(size); - }); - - test('random - should only contain hexadecimal characters', () => { - const size = 8; - const hex = _Hex.random(size / 2); - // This regex matches strings that only contain characters 0-9 and a-f - expect(hex).toMatch(/^[0-9a-f]+$/); - }); - - test('random - should return different values on subsequent calls', () => { - const size = 8; - const hex1 = _Hex.random(size / 2); - const hex2 = _Hex.random(size / 2); - expect(hex1).not.toEqual(hex2); - }); -}); - -/** - * Text hexadecimal representation of Ethereum quantities. - * @group unit/utils/hex - */ -describe('Quantity', () => { - test('of zero', () => { - const output: string = _Quantity.of(0); - expect(output).toBe('0x0'); - }); - - test('of odd long hex expression', () => { - const output: string = _Quantity.of(256); - expect(output).toBe('0x100'); - }); - - test('of even long hex expression', () => { - const output: string = _Quantity.of(255); - expect(output).toBe('0xff'); - }); -}); From 43422b1b19788b797b232b39c5c89b678d050d71 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Fri, 9 Aug 2024 12:26:34 +0100 Subject: [PATCH 07/10] feat: 1113 utils/hex removed --- packages/core/src/vcdm/Hex.ts | 1 - packages/core/src/vcdm/HexInt.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index b74f353fb..cab765fd6 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -306,7 +306,6 @@ class Hex extends String implements VeChainDataModel { throw new InvalidDataType( 'Hex.of', 'not an hexadecimal expression', - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions { exp: `${exp}` }, // Needed to serialize bigint values. e ); diff --git a/packages/core/src/vcdm/HexInt.ts b/packages/core/src/vcdm/HexInt.ts index 29f1edf6c..1f5670fd6 100644 --- a/packages/core/src/vcdm/HexInt.ts +++ b/packages/core/src/vcdm/HexInt.ts @@ -88,7 +88,6 @@ class HexInt extends Hex { throw new InvalidDataType( 'HexInt.of', 'not an hexadecimal integer expression', - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions { exp: `${exp}`, e } // Needed to serialize bigint values. ); } From 30441c3750aa819c0ded0c84eb2a8cf9602e47cf Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Sat, 10 Aug 2024 10:40:00 +0100 Subject: [PATCH 08/10] feat: 1115 Sha256.ts class implemented --- docs/diagrams/architecture/vcdm.md | 43 +-- packages/core/src/hash/Sha256.ts | 49 +++ packages/core/src/hash/index.ts | 2 +- packages/core/src/hash/sha256.ts | 71 ---- packages/core/src/hdnode/hdnode.ts | 6 +- packages/core/src/vcdm/Currency.ts | 21 ++ packages/core/src/vcdm/Hash.ts | 7 + packages/core/src/vcdm/Hex.ts | 24 +- packages/core/src/vcdm/HexUInt.ts | 2 +- packages/core/src/vcdm/Txt.ts | 10 +- packages/core/src/vcdm/VeChainDataModel.ts | 2 + packages/core/src/vcdm/index.ts | 2 + packages/core/tests/hash/Sha256.unit.test.ts | 32 ++ packages/core/tests/hash/fixture.ts | 98 +++--- packages/core/tests/hash/hash.unit.test.ts | 324 +++++++++--------- packages/core/tests/vcdm/Hex.unit.test.ts | 8 +- packages/core/tests/vcdm/HexInt.unit.test.ts | 2 +- packages/core/tests/vcdm/HexUInt.unit.test.ts | 2 +- packages/core/tests/vcdm/Txt.unit.test.ts | 4 +- .../errors/src/available-errors/vcdm/vcdm.ts | 7 +- .../tests/available-errors/vcdm.unit.test.ts | 8 +- 21 files changed, 376 insertions(+), 348 deletions(-) create mode 100644 packages/core/src/hash/Sha256.ts delete mode 100644 packages/core/src/hash/sha256.ts create mode 100644 packages/core/src/vcdm/Currency.ts create mode 100644 packages/core/src/vcdm/Hash.ts create mode 100644 packages/core/tests/hash/Sha256.unit.test.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 48ed5c6e8..d1576fb1b 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -1,13 +1,14 @@ ```mermaid classDiagram - class Account class Address - class Blake2b256 - class BloomFilter - class Contract - class HDNode - class Hash { + class Currency { <> + +bigint units + +bigint fraction + +string code + } + class Hash { + <> } class Hex { +Hex abs @@ -25,18 +26,12 @@ classDiagram class HexUInt { +HexUInt of(bigint|number|string|Uint8Array|HexInt exp)$ } - class Keccak256 - class Keystore - class Revision - class Sha256 - class String - class ThorId { - +ThorId of(bigint|number|string|Uint8Array|Hex exp)$ - } class Txt { +Txt of(bigint|number|string|Uint8Array exp)$ } - class String + class Sha256 { + +Sha256 of(bigint|number|string|Uint8Array|Hex exp)$ + } class VeChainDataModel{ <> +bigint bi @@ -46,22 +41,14 @@ classDiagram +boolean isEqual(~T~ that) +boolean isNumber() } - Address <|-- Account - Address <|-- Contract - Hash <|-- Blake2b256 - Hash <|-- Keccak256 + class Wei + Currency <|-- Wei Hash <|-- Sha256 Hex <|-- HexInt - HexUInt <|-- Address - HexUInt <|-- BloomFilter - HexUInt <|-- HDNode HexInt <|-- HexUInt - HexUInt <|-- Hash - HexUInt <|-- ThorId - HexUInt <|-- Keystore - HexUInt <|-- Revision - String <|-- Hex - String <|-- Txt + HexUInt <|-- Sha256 + HexUInt <|-- Address + VeChainDataModel <|.. Currency VeChainDataModel <|.. Hex VeChainDataModel <|.. Txt ``` diff --git a/packages/core/src/hash/Sha256.ts b/packages/core/src/hash/Sha256.ts new file mode 100644 index 000000000..f40b05a57 --- /dev/null +++ b/packages/core/src/hash/Sha256.ts @@ -0,0 +1,49 @@ +import { Hex, HexUInt, type Hash } from '../vcdm'; +import * as nh_sha256 from '@noble/hashes/sha256'; +import { InvalidOperation } from '@vechain/sdk-errors'; + +/** + * Represents the result of an [SHA256](https://en.wikipedia.org/wiki/SHA-2) hash operation. + * + * @implements Hash + */ +class Sha256 extends HexUInt implements Hash { + /** + * Creates a new instance of this class to represent the absolute `hui` hash result. + * + * @param hui The hash result. + * @protected + */ + protected constructor(hui: HexUInt) { + super(hui); + } + + /** + * Generates the [SHA256](https://en.wikipedia.org/wiki/SHA-2) hash of the given input. + * + * @param {bigint | number | string | Uint8Array | Hex} exp - The input value to hash. + * + * @returns {Sha256} - The [SHA256](https://en.wikipedia.org/wiki/SHA-2) hash of the input value. + * + * @throws {InvalidOperation} - If a hash error occurs. + */ + public static of(exp: bigint | number | string | Uint8Array | Hex): Sha256 { + try { + return new Sha256( + HexUInt.of( + nh_sha256.sha256( + (exp instanceof Hex ? exp : Hex.of(exp)).bytes + ) + ) + ); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + throw new InvalidOperation('Sha256.of', 'hash error', { + exp: `${exp}`, // Needed to serialize bigint values. + e + }); + } + } +} + +export { Sha256 }; diff --git a/packages/core/src/hash/index.ts b/packages/core/src/hash/index.ts index 188b89195..965bc21af 100644 --- a/packages/core/src/hash/index.ts +++ b/packages/core/src/hash/index.ts @@ -1,4 +1,4 @@ export * from './types.d'; export * from './blake2b256'; -export * from './sha256'; +export * from './Sha256'; export * from './keccak256'; diff --git a/packages/core/src/hash/sha256.ts b/packages/core/src/hash/sha256.ts deleted file mode 100644 index 2b24201ca..000000000 --- a/packages/core/src/hash/sha256.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Hex } from '../vcdm/Hex'; -import { InvalidDataType } from '@vechain/sdk-errors'; -import { sha256 as _sha256 } from '@noble/hashes/sha256'; -import { type ReturnType } from './types'; - -/* --- Overloaded functions start --- */ - -/** - * Calculates the SHA-256 hash of the given data. - * - * Secure audit function. - * * {@link sha256} - * - * @param {string | Uint8Array} data - The data to calculate the hash for. - * @return {Uint8Array} - The SHA-256 hash of the given data. - */ -function sha256(data: string | Uint8Array): Uint8Array; - -/** - * Calculates the SHA-256 hash of the given data. - * - * @param {string | Uint8Array} data - The data to calculate the SHA-256 hash for. - * @param {'buffer'} returnType - The return type for the hash. Currently only supports 'buffer'. - * - * @return {Uint8Array} - The SHA-256 hash as a Uint8Array. - */ -function sha256(data: string | Uint8Array, returnType: 'buffer'): Uint8Array; - -/** - * Calculates the SHA-256 hash of the given data. - * - * Secure audit function. - * * {@link sha256} - * - * @param {string | Uint8Array} data - The input data to be hashed. - * @param {'hex'} returnType - The desired return type of the hash. - * @return {string} The SHA-256 hash of the data in the specified return type. - */ -function sha256(data: string | Uint8Array, returnType: 'hex'): string; - -/* --- Overloaded functions end --- */ - -/** - * Computes the SHA-256 hash of the given data. - * - * Secure audit function. - * * [_sha256](https://github.com/paulmillr/noble-hashes?tab=readme-ov-file#sha2-sha256-sha384-sha512-sha512_256) - * - * @param {string | Uint8Array} data - The data to compute the hash for. - * @param {ReturnType} [returnType='buffer'] - The desired return type for the hash. Defaults to 'buffer'. - * @return {Uint8Array | string} - The computed SHA-256 hash. - * @throws {InvalidDataType} - */ -function sha256( - data: string | Uint8Array, - returnType: ReturnType = 'buffer' -): Uint8Array | string { - // Assert that the returnType is valid - if (!['hex', 'buffer'].includes(returnType)) { - throw new InvalidDataType( - 'sha256()', - "Validation error: Invalid return type. Return type in hash function must be 'buffer' or 'hex'.", - { returnType } - ); - } - - const hash = _sha256(data); - return returnType === 'buffer' ? hash : Hex.of(hash).toString(); -} - -export { sha256 }; diff --git a/packages/core/src/hdnode/hdnode.ts b/packages/core/src/hdnode/hdnode.ts index 27d8bb7ae..6ca3ec687 100644 --- a/packages/core/src/hdnode/hdnode.ts +++ b/packages/core/src/hdnode/hdnode.ts @@ -8,7 +8,7 @@ import { } from '@vechain/sdk-errors'; import { base58 } from '@scure/base'; import { secp256k1 } from '../secp256k1'; -import { sha256 } from '../hash'; +import { Sha256 } from '../hash'; import { VET_DERIVATION_PATH, X_PRIV_PREFIX, X_PUB_PREFIX } from '../utils'; /** @@ -82,7 +82,7 @@ function fromPrivateKey( privateKey ); privateKey.fill(0); // Clear the private key from memory. - const checksum = sha256(sha256(header)).subarray(0, 4); + const checksum = Sha256.of(Sha256.of(header)).bytes.subarray(0, 4); const expandedPrivateKey = n_utils.concatBytes(header, checksum); try { return n_bip32.HDKey.fromExtendedKey( @@ -125,7 +125,7 @@ function fromPublicKey( chainCode, secp256k1.compressPublicKey(publicKey) ); - const checksum = sha256(sha256(header)).subarray(0, 4); + const checksum = Sha256.of(Sha256.of(header)).bytes.subarray(0, 4); const expandedPublicKey = n_utils.concatBytes(header, checksum); try { return n_bip32.HDKey.fromExtendedKey( diff --git a/packages/core/src/vcdm/Currency.ts b/packages/core/src/vcdm/Currency.ts new file mode 100644 index 000000000..7cde71cab --- /dev/null +++ b/packages/core/src/vcdm/Currency.ts @@ -0,0 +1,21 @@ +import { type VeChainDataModel } from './VeChainDataModel'; + +export interface Currency extends VeChainDataModel { + get bi(): bigint; + + get code(): string; +} + +// class Wei implements Currency { +// } + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +enum Digits { + wei, + kwei = 3, + mwei = 6, + gwei = 9, + szabo = 12, + finney = 15, + ether = 18 +} diff --git a/packages/core/src/vcdm/Hash.ts b/packages/core/src/vcdm/Hash.ts new file mode 100644 index 000000000..faf62c5f0 --- /dev/null +++ b/packages/core/src/vcdm/Hash.ts @@ -0,0 +1,7 @@ +/** + * Marker interface for classes implementing hash functionalities. + * + * @interface + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Hash {} diff --git a/packages/core/src/vcdm/Hex.ts b/packages/core/src/vcdm/Hex.ts index cab765fd6..3a8a7a0e0 100644 --- a/packages/core/src/vcdm/Hex.ts +++ b/packages/core/src/vcdm/Hex.ts @@ -1,6 +1,6 @@ import * as nh_utils from '@noble/hashes/utils'; import * as nc_utils from '@noble/curves/abstract/utils'; -import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; +import { InvalidOperation, InvalidDataType } from '@vechain/sdk-errors'; import { type VeChainDataModel } from './VeChainDataModel'; /** @@ -17,7 +17,7 @@ import { type VeChainDataModel } from './VeChainDataModel'; * * @implements {VeChainDataModel} */ -class Hex extends String implements VeChainDataModel { +class Hex implements VeChainDataModel { /** * Negative multiplier of the {@link hex} absolute value. * @@ -82,7 +82,6 @@ class Hex extends String implements VeChainDataModel { normalize: (digits: string) => string = (digits) => digits.toLowerCase() ) { const normalizedDigits = normalize(digits); - super((sign < 0 ? '-0x' : '0x') + normalizedDigits); this.hex = normalizedDigits; this.sign = sign; } @@ -119,7 +118,7 @@ class Hex extends String implements VeChainDataModel { * * @return {number} The value of n. * - * @throws {InvalidCastType} Throws an error if this instance doesn't represent + * @throws {InvalidOperation} Throws an error if this instance doesn't represent * an [IEEE 754 double precision 64 bits floating point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). */ get n(): number { @@ -127,11 +126,9 @@ class Hex extends String implements VeChainDataModel { // The sign is part of the IEEE 754 representation hence no need to consider `this.sign` property. return new DataView(this.bytes.buffer).getFloat64(0); } - throw new InvalidCastType( - 'Hex.n', - 'not an IEEE 754 float 64 number', - this - ); + throw new InvalidOperation('Hex.n', 'not an IEEE 754 float 64 number', { + hex: this.toString() + }); } /** @@ -330,6 +327,15 @@ class Hex extends String implements VeChainDataModel { bytes }); } + + /** + * Returns a string representation of the object. + * + * @return {string} The string representation of the object. + */ + public toString(): string { + return (this.sign < 0 ? '-0x' : '0x') + this.hex; + } } export { Hex }; diff --git a/packages/core/src/vcdm/HexUInt.ts b/packages/core/src/vcdm/HexUInt.ts index 36323c365..4f29d6d58 100644 --- a/packages/core/src/vcdm/HexUInt.ts +++ b/packages/core/src/vcdm/HexUInt.ts @@ -43,7 +43,7 @@ class HexUInt extends HexInt { throw new InvalidDataType( 'HexUInt.of', 'not a hexadecimal positive integer expression', - { exp }, + { exp: `${exp}`, e }, // Needed to serialize bigint values. e ); } diff --git a/packages/core/src/vcdm/Txt.ts b/packages/core/src/vcdm/Txt.ts index a0446032d..ba0a24e0f 100644 --- a/packages/core/src/vcdm/Txt.ts +++ b/packages/core/src/vcdm/Txt.ts @@ -1,4 +1,4 @@ -import { InvalidCastType } from '@vechain/sdk-errors'; +import { InvalidOperation } from '@vechain/sdk-errors'; import { type VeChainDataModel } from './VeChainDataModel'; /** @@ -50,17 +50,17 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a BigInt. * * @returns {bigint} The BigInt representation of the Txt string. - * @throws {InvalidCastType} If the conversion to BigInt fails because this Txt string doesn't represent an integer. + * @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer. */ get bi(): bigint { try { return BigInt(this.toString()); } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call - throw new InvalidCastType( + throw new InvalidOperation( 'Txt.bi()', "Can't cast to big integer", - this, + { txt: this.toString() }, e ); } @@ -84,7 +84,7 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a number. * * @returns {number} The numeric value of the Txt string. - * @throws {InvalidCastType} If the conversion to number fails because this Txt string doesn't represent a decimal number. + * @throws {InvalidOperation} If the conversion to number fails because this Txt string doesn't represent a decimal number. */ get n(): number { return Number(this.toString()); diff --git a/packages/core/src/vcdm/VeChainDataModel.ts b/packages/core/src/vcdm/VeChainDataModel.ts index 7a2ce564e..994349949 100644 --- a/packages/core/src/vcdm/VeChainDataModel.ts +++ b/packages/core/src/vcdm/VeChainDataModel.ts @@ -1,6 +1,8 @@ /** * Root interface for all the classes part of the `VeChain Data Model` * to provide a coherent API to represent, encode, and cast data among data types. + * + * @interface */ export interface VeChainDataModel { // Properties. diff --git a/packages/core/src/vcdm/index.ts b/packages/core/src/vcdm/index.ts index e477be9a7..d29a89ca6 100644 --- a/packages/core/src/vcdm/index.ts +++ b/packages/core/src/vcdm/index.ts @@ -1,3 +1,5 @@ +export * from './Currency'; +export * from './Hash'; export * from './Hex'; export * from './HexInt'; export * from './HexUInt'; diff --git a/packages/core/tests/hash/Sha256.unit.test.ts b/packages/core/tests/hash/Sha256.unit.test.ts new file mode 100644 index 000000000..a095bd94f --- /dev/null +++ b/packages/core/tests/hash/Sha256.unit.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from '@jest/globals'; +import { CONTENT, NO_CONTENT } from './fixture'; +import { Hex, Sha256 } from '../../src'; +import { InvalidOperation } from '@vechain/sdk-errors'; + +const CONTENT_SHA256 = Hex.of( + '0xdb484f1fdd0c7ae9268a04a876ee4d1b1c40f801e80e56ff718b198aa2f1166f' +); +const NO_CONTENT_SHA256 = Hex.of( + '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' +); + +/** + * Test Sha256 class. + * @group unit/hash + */ +describe('Sha256 class tests', () => { + test('Return hash for content', () => { + const sha256 = Sha256.of(CONTENT); + expect(sha256.isEqual(CONTENT_SHA256)).toBe(true); + }); + + test('Return hash for no content', () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const sha256 = Sha256.of(NO_CONTENT); + expect(sha256.isEqual(NO_CONTENT_SHA256)).toBe(true); + }); + + test('Throw an exception for illegal content', () => { + expect(() => Sha256.of('0xfoe')).toThrow(InvalidOperation); + }); +}); diff --git a/packages/core/tests/hash/fixture.ts b/packages/core/tests/hash/fixture.ts index 085475835..cd7f9ec52 100644 --- a/packages/core/tests/hash/fixture.ts +++ b/packages/core/tests/hash/fixture.ts @@ -1,56 +1,47 @@ -import * as n_utils from '@noble/curves/abstract/utils'; -import { blake2b256, keccak256, sha256, ZERO_BYTES } from '../../src'; +// import * as n_utils from '@noble/curves/abstract/utils'; +import { Hex, Txt, ZERO_BYTES } from '../../src'; +// +// /** +// * Hash functions to test +// */ +// const hashFunctionsToTest = [ +// { +// hashFunction: keccak256, +// results: { +// HELLO_WORLD_HASH_VALUE_HEX: +// '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad', +// HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// '47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' +// ), +// ZERO_BUFFER_HASH_VALUE_HEX: +// '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', +// ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' +// ) +// } +// }, +// { +// hashFunction: blake2b256, +// results: { +// HELLO_WORLD_HASH_VALUE_HEX: +// '0x256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610', +// HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// '256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610' +// ), +// ZERO_BUFFER_HASH_VALUE_HEX: +// '0x0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8', +// ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( +// '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8' +// ) +// } +// }, +// ]; +// -/** - * Hash functions to test - */ -const hashFunctionsToTest = [ - { - hashFunction: keccak256, - results: { - HELLO_WORLD_HASH_VALUE_HEX: - '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad', - HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( - '47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' - ), - ZERO_BUFFER_HASH_VALUE_HEX: - '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', - ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( - 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' - ) - } - }, - { - hashFunction: blake2b256, - results: { - HELLO_WORLD_HASH_VALUE_HEX: - '0x256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610', - HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( - '256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610' - ), - ZERO_BUFFER_HASH_VALUE_HEX: - '0x0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8', - ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( - '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8' - ) - } - }, - { - hashFunction: sha256, - results: { - HELLO_WORLD_HASH_VALUE_HEX: - '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9', - HELLO_WORLD_HASH_VALUE_BUFFER: n_utils.hexToBytes( - 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9' - ), - ZERO_BUFFER_HASH_VALUE_HEX: - '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - ZERO_BUFFER_HASH_VALUE_BUFFER: n_utils.hexToBytes( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' - ) - } - } -]; +const CONTENT = Hex.of( + Txt.of('Hello world - Здравствуйте - こんにちは!').bytes +); +const NO_CONTENT = Hex.of('0x'); const BLAKE2B256_CONTENT = 'Hello world - Здравствуйте - こんにちは!'; const BLAKE2B256_CONTENT_HASH = @@ -65,5 +56,6 @@ export { BLAKE2B256_CONTENT_HASH, BLAKE2B256_NO_CONTENT, BLAKE2B256_NO_CONTENT_HASH, - hashFunctionsToTest + CONTENT, + NO_CONTENT }; diff --git a/packages/core/tests/hash/hash.unit.test.ts b/packages/core/tests/hash/hash.unit.test.ts index 68b8a181b..bcaa4b882 100644 --- a/packages/core/tests/hash/hash.unit.test.ts +++ b/packages/core/tests/hash/hash.unit.test.ts @@ -1,162 +1,162 @@ -import { Buffer } from 'buffer'; -import { Txt, ZERO_BYTES } from '../../src'; -import { bytesToHex } from '@noble/ciphers/utils'; -import { cert } from '../certificate/fixture'; -import { describe, expect, test } from '@jest/globals'; -import { ethers } from 'ethers'; -import { hashFunctionsToTest } from './fixture'; -import { InvalidDataType } from '@vechain/sdk-errors'; - -/** - * Test hash functions - * @group unit/hash - */ -describe('Hash', () => { - test('thordevkit', () => { - const json = JSON.stringify(cert); - console.log(json); - // const blake_dev_dir = ThorDevKit.blake2b256(json); - // console.log('dir', blake_dev_dir); - const buffer = Buffer.from(json, `utf8`); - console.log(bytesToHex(buffer)); - // const textEncoder = new TextEncoder(); - // form: "NFC" | "NFD" | "NFKC" | "NFKD"): string - const array = Txt.of(json).bytes; - console.log(bytesToHex(array)); - // const blake_dev_ind = ThorDevKit.blake2b256(buffer); - // console.log('ind', blake_dev_ind); - // const blake_sdk = blake2b256(buffer); - // console.log('sdk', blake_sdk); - }); - - /** - * Test each hash function - */ - hashFunctionsToTest.forEach((hashFunction) => { - describe(`Function ${hashFunction.hashFunction.name}`, () => { - // Correctness of hash function - test('Hash as hex is correct', () => { - // Hello world hash - expect(hashFunction.hashFunction('hello world', 'hex')).toBe( - hashFunction.results.HELLO_WORLD_HASH_VALUE_HEX - ); - - // Zero bytes hash - expect(hashFunction.hashFunction(ZERO_BYTES(0), 'hex')).toBe( - hashFunction.results.ZERO_BUFFER_HASH_VALUE_HEX - ); - }); - - test('Hash as buffer is correct', () => { - // Hello world hash - expect( - hashFunction.hashFunction('hello world', 'buffer') - ).toStrictEqual( - hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER - ); - - expect(hashFunction.hashFunction('hello world')).toStrictEqual( - hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER - ); - - // Zero bytes hash - expect( - hashFunction.hashFunction(ZERO_BYTES(0), 'buffer') - ).toStrictEqual( - hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER - ); - - expect(hashFunction.hashFunction(ZERO_BYTES(0))).toStrictEqual( - hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER - ); - }); - - // Different ways of giving input data take the same hash - test('Hash is the same for different input formats', () => { - // Different ways of giving input data take the same hash - const hashesToHex = [ - hashFunction.hashFunction('hello world', 'hex'), - hashFunction.hashFunction( - Buffer.from('hello world'), - 'hex' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes('hello world'), - 'hex' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes(['hello', ' world'].join('')), - 'hex' - ), - hashFunction.hashFunction( - Buffer.from('hello world', 'utf8'), - 'hex' - ) - ]; - - const hashesToBufferWithDefaultedParameter = [ - hashFunction.hashFunction('hello world'), - hashFunction.hashFunction(Buffer.from('hello world')), - hashFunction.hashFunction( - ethers.toUtf8Bytes('hello world') - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes(['hello', ' world'].join('')) - ), - hashFunction.hashFunction( - Buffer.from('hello world', 'utf8') - ) - ]; - - const hashesToBuffer = [ - hashFunction.hashFunction('hello world', 'buffer'), - hashFunction.hashFunction( - Buffer.from('hello world'), - 'buffer' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes('hello world'), - 'buffer' - ), - hashFunction.hashFunction( - ethers.toUtf8Bytes(['hello', ' world'].join('')), - 'buffer' - ), - hashFunction.hashFunction( - Buffer.from('hello world', 'utf8'), - 'buffer' - ) - ]; - - // All hashes are the same - for (let i = 0; i < hashesToHex.length; ++i) { - for (let j = i + 1; j < hashesToHex.length; ++j) { - if (i !== j) { - expect(hashesToHex[i]).toStrictEqual( - hashesToHex[j] - ); - expect( - hashesToBufferWithDefaultedParameter[i] - ).toStrictEqual( - hashesToBufferWithDefaultedParameter[j] - ); - expect(hashesToBuffer[i]).toStrictEqual( - hashesToBuffer[j] - ); - expect(hashesToBuffer[i]).toStrictEqual( - hashesToBufferWithDefaultedParameter[j] - ); - } - } - } - }); - - test('Invalid return type throws an error', () => { - expect(() => - // @ts-expect-error: Invalid return type - hashFunction.hashFunction('hello world', 'foo') - ).toThrowError(InvalidDataType); - }); - }); - }); -}); +// import { Buffer } from 'buffer'; +// import { Txt, ZERO_BYTES } from '../../src'; +// import { bytesToHex } from '@noble/ciphers/utils'; +// import { cert } from '../certificate/fixture'; +// import { describe, expect, test } from '@jest/globals'; +// import { ethers } from 'ethers'; +// import { hashFunctionsToTest } from './fixture'; +// import { InvalidDataType } from '@vechain/sdk-errors'; +// +// /** +// * Test hash functions +// * @group unit/hash +// */ +// describe('Hash', () => { +// test('thordevkit', () => { +// const json = JSON.stringify(cert); +// console.log(json); +// // const blake_dev_dir = ThorDevKit.blake2b256(json); +// // console.log('dir', blake_dev_dir); +// const buffer = Buffer.from(json, `utf8`); +// console.log(bytesToHex(buffer)); +// // const textEncoder = new TextEncoder(); +// // form: "NFC" | "NFD" | "NFKC" | "NFKD"): string +// const array = Txt.of(json).bytes; +// console.log(bytesToHex(array)); +// // const blake_dev_ind = ThorDevKit.blake2b256(buffer); +// // console.log('ind', blake_dev_ind); +// // const blake_sdk = blake2b256(buffer); +// // console.log('sdk', blake_sdk); +// }); +// +// /** +// * Test each hash function +// */ +// hashFunctionsToTest.forEach((hashFunction) => { +// describe(`Function ${hashFunction.hashFunction.name}`, () => { +// // Correctness of hash function +// test('Hash as hex is correct', () => { +// // Hello world hash +// expect(hashFunction.hashFunction('hello world', 'hex')).toBe( +// hashFunction.results.HELLO_WORLD_HASH_VALUE_HEX +// ); +// +// // Zero bytes hash +// expect(hashFunction.hashFunction(ZERO_BYTES(0), 'hex')).toBe( +// hashFunction.results.ZERO_BUFFER_HASH_VALUE_HEX +// ); +// }); +// +// test('Hash as buffer is correct', () => { +// // Hello world hash +// expect( +// hashFunction.hashFunction('hello world', 'buffer') +// ).toStrictEqual( +// hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER +// ); +// +// expect(hashFunction.hashFunction('hello world')).toStrictEqual( +// hashFunction.results.HELLO_WORLD_HASH_VALUE_BUFFER +// ); +// +// // Zero bytes hash +// expect( +// hashFunction.hashFunction(ZERO_BYTES(0), 'buffer') +// ).toStrictEqual( +// hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER +// ); +// +// expect(hashFunction.hashFunction(ZERO_BYTES(0))).toStrictEqual( +// hashFunction.results.ZERO_BUFFER_HASH_VALUE_BUFFER +// ); +// }); +// +// // Different ways of giving input data take the same hash +// test('Hash is the same for different input formats', () => { +// // Different ways of giving input data take the same hash +// const hashesToHex = [ +// hashFunction.hashFunction('hello world', 'hex'), +// hashFunction.hashFunction( +// Buffer.from('hello world'), +// 'hex' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes('hello world'), +// 'hex' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes(['hello', ' world'].join('')), +// 'hex' +// ), +// hashFunction.hashFunction( +// Buffer.from('hello world', 'utf8'), +// 'hex' +// ) +// ]; +// +// const hashesToBufferWithDefaultedParameter = [ +// hashFunction.hashFunction('hello world'), +// hashFunction.hashFunction(Buffer.from('hello world')), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes('hello world') +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes(['hello', ' world'].join('')) +// ), +// hashFunction.hashFunction( +// Buffer.from('hello world', 'utf8') +// ) +// ]; +// +// const hashesToBuffer = [ +// hashFunction.hashFunction('hello world', 'buffer'), +// hashFunction.hashFunction( +// Buffer.from('hello world'), +// 'buffer' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes('hello world'), +// 'buffer' +// ), +// hashFunction.hashFunction( +// ethers.toUtf8Bytes(['hello', ' world'].join('')), +// 'buffer' +// ), +// hashFunction.hashFunction( +// Buffer.from('hello world', 'utf8'), +// 'buffer' +// ) +// ]; +// +// // All hashes are the same +// for (let i = 0; i < hashesToHex.length; ++i) { +// for (let j = i + 1; j < hashesToHex.length; ++j) { +// if (i !== j) { +// expect(hashesToHex[i]).toStrictEqual( +// hashesToHex[j] +// ); +// expect( +// hashesToBufferWithDefaultedParameter[i] +// ).toStrictEqual( +// hashesToBufferWithDefaultedParameter[j] +// ); +// expect(hashesToBuffer[i]).toStrictEqual( +// hashesToBuffer[j] +// ); +// expect(hashesToBuffer[i]).toStrictEqual( +// hashesToBufferWithDefaultedParameter[j] +// ); +// } +// } +// } +// }); +// +// test('Invalid return type throws an error', () => { +// expect(() => +// // @ts-expect-error: Invalid return type +// hashFunction.hashFunction('hello world', 'foo') +// ).toThrowError(InvalidDataType); +// }); +// }); +// }); +// }); diff --git a/packages/core/tests/vcdm/Hex.unit.test.ts b/packages/core/tests/vcdm/Hex.unit.test.ts index 3d03b468b..52868e6c8 100644 --- a/packages/core/tests/vcdm/Hex.unit.test.ts +++ b/packages/core/tests/vcdm/Hex.unit.test.ts @@ -1,9 +1,9 @@ -import { Hex, Txt } from '../../src'; import { describe, expect, test } from '@jest/globals'; -import { InvalidCastType, InvalidDataType } from '@vechain/sdk-errors'; +import { Hex, Txt } from '../../src'; +import { InvalidDataType, InvalidOperation } from '@vechain/sdk-errors'; /** - * Test Txt class. + * Test Hex class. * @group unit/vcdm */ describe('Hex class tests', () => { @@ -43,7 +43,7 @@ describe('Hex class tests', () => { test('Throw an exception casting n from a not IEEE expression', () => { const exp = 12357n; const hex = Hex.of(exp); - expect(() => hex.n).toThrow(InvalidCastType); + expect(() => hex.n).toThrow(InvalidOperation); }); test('compareTo method tests - same signs', () => { diff --git a/packages/core/tests/vcdm/HexInt.unit.test.ts b/packages/core/tests/vcdm/HexInt.unit.test.ts index 623493cac..ded8a9513 100644 --- a/packages/core/tests/vcdm/HexInt.unit.test.ts +++ b/packages/core/tests/vcdm/HexInt.unit.test.ts @@ -3,7 +3,7 @@ import { Hex, HexInt } from '../../src'; import { InvalidDataType } from '@vechain/sdk-errors'; /** - * Test Txt class. + * Test HexInt class. * @group unit/vcdm */ describe('HexInt class tests', () => { diff --git a/packages/core/tests/vcdm/HexUInt.unit.test.ts b/packages/core/tests/vcdm/HexUInt.unit.test.ts index 982f12fce..17052360e 100644 --- a/packages/core/tests/vcdm/HexUInt.unit.test.ts +++ b/packages/core/tests/vcdm/HexUInt.unit.test.ts @@ -3,7 +3,7 @@ import { HexUInt } from '../../src'; import { InvalidDataType } from '@vechain/sdk-errors'; /** - * Test Txt class. + * Test HexUInt class. * @group unit/vcdm */ describe('HexUInt class tests', () => { diff --git a/packages/core/tests/vcdm/Txt.unit.test.ts b/packages/core/tests/vcdm/Txt.unit.test.ts index 082d0e355..0f83ade34 100644 --- a/packages/core/tests/vcdm/Txt.unit.test.ts +++ b/packages/core/tests/vcdm/Txt.unit.test.ts @@ -1,5 +1,5 @@ import { Txt } from '../../src'; -import { InvalidCastType } from '@vechain/sdk-errors'; +import { InvalidOperation } from '@vechain/sdk-errors'; import { TextEncoder } from 'util'; import { describe, expect, test } from '@jest/globals'; @@ -13,7 +13,7 @@ describe('Txt class tests', () => { describe('VeChain Data Model tests', () => { test('bi getter should throw an error if a decimal value is cast to big integer', () => { const txt = Txt.of('12.3'); - expect(() => txt.bi).toThrow(InvalidCastType); + expect(() => txt.bi).toThrow(InvalidOperation); }); test('bi getter should return a BigInt representation of the integer value', () => { diff --git a/packages/errors/src/available-errors/vcdm/vcdm.ts b/packages/errors/src/available-errors/vcdm/vcdm.ts index 0cf06aea9..7ede2c019 100644 --- a/packages/errors/src/available-errors/vcdm/vcdm.ts +++ b/packages/errors/src/available-errors/vcdm/vcdm.ts @@ -1,11 +1,12 @@ import { VechainSDKError } from '../sdk-error'; +import { type ObjectErrorData } from '../types'; /** * Invalid cast type error. * * WHEN TO USE: - * * Error will be thrown when a cast between types is invalid. + * * Error will be thrown when a method call or property read fails. */ -class InvalidCastType extends VechainSDKError {} +class InvalidOperation extends VechainSDKError {} -export { InvalidCastType }; +export { InvalidOperation }; diff --git a/packages/errors/tests/available-errors/vcdm.unit.test.ts b/packages/errors/tests/available-errors/vcdm.unit.test.ts index 56650bd48..5a327584e 100644 --- a/packages/errors/tests/available-errors/vcdm.unit.test.ts +++ b/packages/errors/tests/available-errors/vcdm.unit.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from '@jest/globals'; -import { InvalidCastType, VechainSDKError } from '../../src'; +import { InvalidOperation, VechainSDKError } from '../../src'; /** * Available errors test - VCDM @@ -7,13 +7,13 @@ import { InvalidCastType, VechainSDKError } from '../../src'; */ describe('Error package Available errors test - VCDM', () => { /** - * InvalidCastType + * InvalidOperation */ - test('InvalidCastType', () => { + test('InvalidOperation', () => { // Inner error [undefined, new Error('error')].forEach((innerError) => { expect(() => { - throw new InvalidCastType<{ data: string }>( + throw new InvalidOperation( 'method', 'message', { data: 'data' }, From 72f66f4818fa7887a6b88842b73ea67706253201 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 27 Aug 2024 13:54:26 +0100 Subject: [PATCH 09/10] feat: 1113 ThorId implemented --- docs/diagrams/architecture/vcdm.md | 6 + packages/core/src/vcdm/Mnemonic.ts | 27 +++-- packages/core/src/vcdm/ThorId.ts | 56 +++++---- packages/core/src/vcdm/Txt.ts | 9 +- packages/core/tests/vcdm/ThorId.unit.test.ts | 115 +++++++++++++++++++ 5 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 packages/core/tests/vcdm/ThorId.unit.test.ts diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 59722c8aa..09d97a13b 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -69,6 +69,11 @@ classDiagram class Txt { +Txt of(bigint|number|string|Uint8Array exp)$ } + class ThorId { + +boolean isValid(string exp) + +boolean isValid0x(string exp) + +ThorID of(bigint|number|string|Uint8Array|HexInt exp)$ + } class VeChainDataModel{ <> +bigint bi @@ -93,6 +98,7 @@ classDiagram HexUInt <|-- Keccak256 HexUInt <|-- Quantity HexUInt <|-- Sha256 + HexUInt <|-- ThorId String <|-- Txt Txt <|-- Revision Txt <|-- Mnemonic diff --git a/packages/core/src/vcdm/Mnemonic.ts b/packages/core/src/vcdm/Mnemonic.ts index 1be1cc15a..9572b4888 100644 --- a/packages/core/src/vcdm/Mnemonic.ts +++ b/packages/core/src/vcdm/Mnemonic.ts @@ -120,20 +120,19 @@ class Mnemonic extends Txt { * [BIP39 Mnemonic Words](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) * and a derivation path as in the examples. * - * Secure audit function. - * - {@link bip32.HDKey}(https://github.com/paulmillr/scure-bip32) - * - {@link HDNode} - * * @example `m/0` (default) * @example `m/0/2` * @example `m/0/2/4/6` * - * * @param {string[]} words - The set of words used for mnemonic generation. * @param {string} [path='m/0'] - The derivation path from the current node. + * * @returns {Uint8Array} - The derived private key as a Uint8Array. * * @throws {InvalidHDNode} + * + * @remarks Security auditable method, depends on + * * {@link HDNode}. */ public static toPrivateKey( words: string[], @@ -159,18 +158,18 @@ class Mnemonic extends Txt { * [BIP39 Mnemonic Words](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) * phrase using the specified wordlist size and random generator. * - * Secure audit function. - * - [bip39](https://github.com/paulmillr/scure-bip39) - * - `randomGenerator` - **Must provide a cryptographic secure source of entropy - * else any secure audit certification related with this software is invalid.** - * * @param {WordlistSizeType} wordlistSize The number of words to generate the mnemonic. * @param {function} [randomGenerator] The random generator function used to generate the entropy. * * @returns {Mnemonic} The generated mnemonic. * * @throws {InvalidDataType} If the number of words is not valid. - * @remarks This method is a wrapper around the `generateMnemonic` function from the `bip39` package. + * + * @remarks Security auditable method, depends on + * * [entropyToMnemonic](https://github.com/paulmillr/scure-bip39); + * * [generateMnemonic](https://github.com/paulmillr/scure-bip39); + * * `randomGenerator` - **Must provide a cryptographic secure source of entropy + * else any secure audit certification related with this software is invalid.** */ public static generate( wordlistSize: WordlistSizeType = 12, @@ -203,9 +202,13 @@ class Mnemonic extends Txt { /** * Check if the given mnemonic words are valid. + * * @param {string | string[]} words The mnemonic words to check. + * * @returns {boolean} true if the words are valid, false otherwise. - * @remarks This method is a wrapper around the `validateMnemonic` function from the `bip39` package. + * + * @remarks Security auditable method, depends on + * * [validateMnemonic](https://github.com/paulmillr/scure-bip39). */ public static isValid(words: string | string[]): boolean { const wordsToValidate = Array.isArray(words) ? words.join(' ') : words; diff --git a/packages/core/src/vcdm/ThorId.ts b/packages/core/src/vcdm/ThorId.ts index 262e14c6d..318fe2ba3 100644 --- a/packages/core/src/vcdm/ThorId.ts +++ b/packages/core/src/vcdm/ThorId.ts @@ -1,14 +1,18 @@ import { Hex } from './Hex'; +import { HexUInt } from './HexUInt'; import { InvalidDataType } from '@vechain/sdk-errors'; /** - * Represents a ThorId. - * @experiemntal + * The ThorId class represents a Thor ID value, which is a hexadecimal positive integer having 64 digits. + * + * @extends HexInt */ -class ThorId extends Hex { +class ThorId extends HexUInt { /** * Number of digits to represent a Thor ID value. * + * @remarks The `0x` prefix is excluded. + * * @type {number} */ private static readonly DIGITS = 64; @@ -16,19 +20,18 @@ class ThorId extends Hex { /** * Constructs a ThorId object with the provided hexadecimal value. * - * @param {Hex} hex - The hexadecimal value representing the ThorId. + * @param {HexUInt} huint - The hexadecimal value representing the ThorId. * * @throws {InvalidDataType} - If the provided value is not a valid ThorId expression. - * @experiemntal */ - protected constructor(hex: Hex) { - if (ThorId.isValid(hex.digits)) { - super(Hex.POSITIVE, hex.digits); + protected constructor(huint: HexUInt) { + if (ThorId.isValid(huint.digits)) { + super(Hex.POSITIVE, huint.digits); } else { throw new InvalidDataType( 'ThorId.constructor', 'not a ThorId expression', - { hex } + { hex: huint } ); } } @@ -37,11 +40,11 @@ class ThorId extends Hex { * Check if the given expression is a valid ThorId. * * @param {string} exp - The expression to be validated. + * * @return {boolean} Returns true if the expression is a valid ThorId, false otherwise. - * @experimental */ public static isValid(exp: string): boolean { - return Hex.isValid(exp) && Hex.REGEX_HEX_PREFIX.test(exp) + return Hex.isValid(exp) && HexUInt.REGEX_HEXUINT_PREFIX.test(exp) ? exp.length === ThorId.DIGITS + 2 : exp.length === ThorId.DIGITS; } @@ -50,11 +53,11 @@ class ThorId extends Hex { * Determines whether the given string is a valid hex number prefixed with '0x'. * * @param {string} exp - The hex number to be checked. - * @returns {boolean} - True if the hex number is valid, false otherwise. - * @experimental + * + * @returns {boolean} - True if the hex number is valid, false otherwise. */ public static isValid0x(exp: string): boolean { - return Hex.REGEX_HEX_PREFIX.test(exp) && ThorId.isValid(exp); + return HexUInt.REGEX_HEXUINT_PREFIX.test(exp) && ThorId.isValid(exp); } /** @@ -65,16 +68,29 @@ class ThorId extends Hex { * - bigint: A BigInteger value that represents the ThorId. * - number: A number value that represents the ThorId. * - string: A string value that represents the ThorId. - * - Hex: A Hex object that represents the ThorId. + * - HexUInt: A HexUInt object that represents the ThorId. * - Uint8Array: A Uint8Array object that represents the ThorId. + * * @returns {ThorId} - A new ThorId object created from the given expression. - * @experimntal + * + * @throws {InvalidDataType} If the given expression is not a valid hexadecimal positive integer expression. */ - public static of(exp: bigint | number | string | Hex | Uint8Array): ThorId { - if (exp instanceof Hex) { - return new ThorId(exp.fit(this.DIGITS)); + public static of( + exp: bigint | number | string | Uint8Array | HexUInt + ): ThorId { + try { + if (exp instanceof Hex) { + return new ThorId(exp.fit(this.DIGITS)); + } + return new ThorId(HexUInt.of(exp).fit(ThorId.DIGITS)); + } catch (e) { + throw new InvalidDataType( + 'ThorId.of', + 'not a ThorId expression', + { exp: `${exp}` }, // Needed to serialize bigint values. + e + ); } - return new ThorId(Hex.of(exp).fit(ThorId.DIGITS)); } } diff --git a/packages/core/src/vcdm/Txt.ts b/packages/core/src/vcdm/Txt.ts index ba0a24e0f..83a522da1 100644 --- a/packages/core/src/vcdm/Txt.ts +++ b/packages/core/src/vcdm/Txt.ts @@ -50,7 +50,8 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a BigInt. * * @returns {bigint} The BigInt representation of the Txt string. - * @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer. + * + * @throws {InvalidOperation} If the conversion to BigInt fails because this Txt string doesn't represent an integer. */ get bi(): bigint { try { @@ -84,6 +85,7 @@ class Txt extends String implements VeChainDataModel { * Converts the current Txt string to a number. * * @returns {number} The numeric value of the Txt string. + * * @throws {InvalidOperation} If the conversion to number fails because this Txt string doesn't represent a decimal number. */ get n(): number { @@ -94,6 +96,7 @@ class Txt extends String implements VeChainDataModel { * Compares the current instance to another instance of Txt. * * @param {Txt} that - The instance to compare with. + * * @return {number} - A negative number if the current instance is less than the specified instance, * zero if they are equal, or a positive number if the current instance is greater. */ @@ -105,7 +108,8 @@ class Txt extends String implements VeChainDataModel { * Checks if the current Txt object is equal to the given Txt object. * * @param {Txt} that - The Txt object to compare with. - * @return {boolean} - True if the objects are equal, false otherwise. + * + * @return {boolean} - True if the objects are equal, false otherwise. */ public isEqual(that: Txt): boolean { return this.compareTo(that) === 0; @@ -128,6 +132,7 @@ class Txt extends String implements VeChainDataModel { * * {@link number} is represented as a {@link NFC} encoded string expressing the value in base 10; * * {@link string} is encoded as {@link NFC} string; * * {@link Uint8Array} is {@link NFC} decoded to a string. + * * @returns {Txt} - A new Txt instance. */ public static of(exp: bigint | number | string | Uint8Array): Txt { diff --git a/packages/core/tests/vcdm/ThorId.unit.test.ts b/packages/core/tests/vcdm/ThorId.unit.test.ts new file mode 100644 index 000000000..07c23c9cd --- /dev/null +++ b/packages/core/tests/vcdm/ThorId.unit.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, test } from '@jest/globals'; +import { HexUInt, ThorId } from '../../src'; +import { InvalidDataType } from '@vechain/sdk-errors'; + +const ThorIdFixture = { + invalid: { + short: '0x271f7db20141001975f71deb8fca90d6b22b8d6610d', + noHex: '0xInvalidThorID' + }, + valid: { + bytes: '0x271f7db20141001975f71deb8fca90d6b22b8d6610dfb5a3e0bbeaf78b5a4891', + number: '0x00000000000000000000000000000000000000000000000000000000000000ff' // This safely casts to number. + } +}; + +/** + * Test ThorId class. + * @group unit/vcdm + */ +describe('ThorId class tests.', () => { + describe('Construction tests', () => { + test('Return a ThorId instance if the passed argument is an array of bytes', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.bytes); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a bigint', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.bi); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a number', () => { + const exp = HexUInt.of(ThorIdFixture.valid.number); + const tid = ThorId.of(exp.n); // This is a safe number cast. + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a `0x` prefixed string', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.toString()); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Return a ThorId instance if the passed argument is a not prefixed string', () => { + const exp = HexUInt.of(ThorIdFixture.valid.bytes); + const tid = ThorId.of(exp.digits); + expect(tid).toBeInstanceOf(ThorId); + expect(tid.isEqual(exp)).toBe(true); + }); + + test('Throw an error if the passed argument is a negative bigint', () => { + expect(() => ThorId.of(-1)).toThrow(InvalidDataType); + }); + + test('Throw an error if the passed argument is a negative number', () => { + expect(() => ThorId.of(-1n)).toThrow(InvalidDataType); + }); + }); + + describe('isValid method tests', () => { + test('Return false for no hex expression', () => { + expect(ThorId.isValid(ThorIdFixture.invalid.noHex)).toBe(false); + }); + + test('Return false for short expression', () => { + expect(ThorId.isValid(ThorIdFixture.invalid.short)).toBe(false); + }); + + test('Return true for valid `0x` prefixed expression', () => { + expect(ThorId.isValid(ThorIdFixture.valid.bytes)).toBe(true); + }); + + test('Return true for valid `not prefixed expression', () => { + expect( + ThorId.isValid(HexUInt.of(ThorIdFixture.valid.bytes).digits) + ).toBe(true); + }); + }); + + describe('isValid0x method tests', () => { + test('Return false for no hex expression', () => { + expect(ThorId.isValid0x(ThorIdFixture.invalid.noHex)).toBe(false); + }); + + test('Return false for short expression', () => { + expect(ThorId.isValid0x(ThorIdFixture.invalid.short)).toBe(false); + }); + + test('Return true for valid `0x` prefixed expression', () => { + expect(ThorId.isValid0x(ThorIdFixture.valid.bytes)).toBe(true); + }); + + test('Return false for valid `not prefixed expression', () => { + expect( + ThorId.isValid0x(HexUInt.of(ThorIdFixture.valid.bytes).digits) + ).toBe(false); + }); + }); + + test('digits property should return 64 characters', () => { + const tid = ThorId.of(0); + expect(tid.digits.length).toBe(64); + }); + + test('toString method should return 66 characters', () => { + const tid = ThorId.of(0); + expect(tid.toString().length).toBe(66); + }); +}); From fc0fbce28e2d9786cc864db1b4b993fc63a416f0 Mon Sep 17 00:00:00 2001 From: lucanicoladebiasi Date: Tue, 27 Aug 2024 13:59:39 +0100 Subject: [PATCH 10/10] feat: 1113 ThorId implemented --- docs/diagrams/architecture/vcdm.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/diagrams/architecture/vcdm.md b/docs/diagrams/architecture/vcdm.md index 09d97a13b..cf6737fcf 100644 --- a/docs/diagrams/architecture/vcdm.md +++ b/docs/diagrams/architecture/vcdm.md @@ -70,7 +70,6 @@ classDiagram +Txt of(bigint|number|string|Uint8Array exp)$ } class ThorId { - +boolean isValid(string exp) +boolean isValid0x(string exp) +ThorID of(bigint|number|string|Uint8Array|HexInt exp)$ }