From 2216b6e707e7a076981a1b3abf7ac87a09badb61 Mon Sep 17 00:00:00 2001 From: Daniel Bate Date: Wed, 29 Nov 2023 20:47:04 +0000 Subject: [PATCH] feat: implement internal `arrayify` and `hexlify` functions (#1401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement BytesLike type * feat: implement hexlify and arrayify functions * chore: linting * chore: changeset * chore: fix docs Co-authored-by: Nedim Salkić * chore: fix assertion Co-authored-by: Nedim Salkić * chore: litning --------- Co-authored-by: Nedim Salkić Co-authored-by: Sérgio Torres <30977845+Torres-ssf@users.noreply.github.com> --- .changeset/soft-timers-sing.md | 7 ++++++ packages/fuels/src/index.ts | 3 --- packages/interfaces/src/index.ts | 2 ++ packages/utils/src/index.ts | 2 ++ packages/utils/src/utils/arrayify.test.ts | 19 ++++++++++++++++ packages/utils/src/utils/arrayify.ts | 27 +++++++++++++++++++++++ packages/utils/src/utils/hexlify.test.ts | 15 +++++++++++++ packages/utils/src/utils/hexlify.ts | 18 +++++++++++++++ 8 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 .changeset/soft-timers-sing.md create mode 100644 packages/utils/src/utils/arrayify.test.ts create mode 100644 packages/utils/src/utils/arrayify.ts create mode 100644 packages/utils/src/utils/hexlify.test.ts create mode 100644 packages/utils/src/utils/hexlify.ts diff --git a/.changeset/soft-timers-sing.md b/.changeset/soft-timers-sing.md new file mode 100644 index 00000000000..aa8cf2ee9f5 --- /dev/null +++ b/.changeset/soft-timers-sing.md @@ -0,0 +1,7 @@ +--- +"fuels": patch +"@fuel-ts/interfaces": patch +"@fuel-ts/utils": patch +--- + +Introduce internal hexlify and arrayify functions diff --git a/packages/fuels/src/index.ts b/packages/fuels/src/index.ts index 2fdb4ea5aaa..37a81955e90 100644 --- a/packages/fuels/src/index.ts +++ b/packages/fuels/src/index.ts @@ -1,6 +1,3 @@ -export { hexlify, getBytesCopy as arrayify } from 'ethers'; -export type { BytesLike } from 'ethers'; - export { Script } from '@fuel-ts/script'; export * from './cli/index'; export * from '@fuel-ts/abi-coder'; diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts index e1b9a721c95..07ed705d271 100644 --- a/packages/interfaces/src/index.ts +++ b/packages/interfaces/src/index.ts @@ -18,6 +18,8 @@ export type Bytes = Uint8Array | number[]; export type RawSlice = Uint8Array | number[]; +export type BytesLike = Uint8Array | string; + /** * @prop value - A 256 bit hash string with the first 12 bytes cleared */ diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 99cebf811b4..5278d7f3b09 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,4 +1,6 @@ export * from './utils/capitalizeString'; export * from './utils/chunkAndPadBytes'; export * from './utils/concat'; +export * from './utils/arrayify'; +export * from './utils/hexlify'; export * from './utils/normalizeString'; diff --git a/packages/utils/src/utils/arrayify.test.ts b/packages/utils/src/utils/arrayify.test.ts new file mode 100644 index 00000000000..1c5fa8d1c16 --- /dev/null +++ b/packages/utils/src/utils/arrayify.test.ts @@ -0,0 +1,19 @@ +import { arrayify } from './arrayify'; + +describe('arrayify', () => { + it('returns Uint8Array from Uint8Array', () => { + expect(arrayify(new Uint8Array([0, 1, 2, 3]))).toEqual(new Uint8Array([0, 1, 2, 3])); + }); + + it('returns Uint8Array from hex string', () => { + expect(arrayify('0x00010203')).toEqual(new Uint8Array([0, 1, 2, 3])); + }); + + it('returns Uint8Array from Buffer', () => { + expect(arrayify(Buffer.from('20'))).toEqual(new Uint8Array([50, 48])); + }); + + it('throws for invalid string', () => { + expect(() => arrayify('nope')).toThrow(); + }); +}); diff --git a/packages/utils/src/utils/arrayify.ts b/packages/utils/src/utils/arrayify.ts new file mode 100644 index 00000000000..4251ed8071d --- /dev/null +++ b/packages/utils/src/utils/arrayify.ts @@ -0,0 +1,27 @@ +import { FuelError, ErrorCode } from '@fuel-ts/errors'; +import type { BytesLike } from 'ethers'; + +/** + * Converts a bytes-like value to a `Uint8Array`. + * + * @param value - the value to convert to a Uint8Array + * @returns the Uint8Array + */ +export const arrayify = (value: BytesLike): Uint8Array => { + // Return buffers as a new byte array + if (value instanceof Uint8Array) { + return new Uint8Array(value); + } + + if (typeof value === 'string' && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) { + const result = new Uint8Array((value.length - 2) / 2); + let offset = 2; + for (let i = 0; i < result.length; i++) { + result[i] = parseInt(value.substring(offset, offset + 2), 16); + offset += 2; + } + return result; + } + + throw new FuelError(ErrorCode.PARSE_FAILED, 'invalid BytesLike value'); +}; diff --git a/packages/utils/src/utils/hexlify.test.ts b/packages/utils/src/utils/hexlify.test.ts new file mode 100644 index 00000000000..f54ebf1f009 --- /dev/null +++ b/packages/utils/src/utils/hexlify.test.ts @@ -0,0 +1,15 @@ +import { hexlify } from './hexlify'; + +describe('hexlify', () => { + it('returns hex from bytes', () => { + expect(hexlify(new Uint8Array([0, 1, 2, 3]))).toEqual('0x00010203'); + }); + + it('returns hex from string', () => { + expect(hexlify('0x01')).toEqual('0x01'); + }); + + it('throws for invalid string', () => { + expect(() => hexlify('nope')).toThrow(); + }); +}); diff --git a/packages/utils/src/utils/hexlify.ts b/packages/utils/src/utils/hexlify.ts new file mode 100644 index 00000000000..35014274081 --- /dev/null +++ b/packages/utils/src/utils/hexlify.ts @@ -0,0 +1,18 @@ +import type { BytesLike } from 'ethers'; +import { getBytes } from 'ethers'; + +const HexCharacters: string = '0123456789abcdef'; + +/** + * Returns a hex representation of the inputted bytes. + */ +export function hexlify(data: BytesLike): string { + const bytes = getBytes(data); + + let result = '0x'; + for (let i = 0; i < bytes.length; i++) { + const v = bytes[i]; + result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f]; + } + return result; +}