Skip to content

Commit

Permalink
🪚 Move bytes helpers to devtools, add comparison utility (#300)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Jan 30, 2024
1 parent 0d24643 commit 8789236
Show file tree
Hide file tree
Showing 23 changed files with 399 additions and 268 deletions.
12 changes: 12 additions & 0 deletions .changeset/kind-rings-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@layerzerolabs/omnicounter-devtools-evm": patch
"@layerzerolabs/protocol-devtools-evm": patch
"@layerzerolabs/protocol-devtools": patch
"build-lz-options": patch
"@layerzerolabs/ua-devtools-evm": patch
"@layerzerolabs/devtools-evm": patch
"@layerzerolabs/ua-devtools": patch
"@layerzerolabs/devtools": patch
---

Move bytes utilities to devtools
1 change: 1 addition & 0 deletions packages/build-lz-options/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"yoga-layout-prebuilt": "^1.10.0"
},
"devDependencies": {
"@layerzerolabs/devtools": "~0.0.1",
"@layerzerolabs/devtools-evm": "~0.0.6",
"@layerzerolabs/io-devtools": "~0.0.5",
"@layerzerolabs/lz-v2-utilities": "~2.0.25",
Expand Down
2 changes: 1 addition & 1 deletion packages/build-lz-options/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeBytes32 } from "@layerzerolabs/devtools-evm";
import { makeBytes32 } from "@layerzerolabs/devtools";
import { optionsType1, optionsType2 } from "@layerzerolabs/lz-v2-utilities";
import React from "react";
import { render } from "ink";
Expand Down
2 changes: 1 addition & 1 deletion packages/build-lz-options/src/utilities/prompts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EXECUTOR_OPTION_TYPE, OPTION_TYPES, WORKER_TYPE } from '@/config'
import { OptionType1Summary, OptionType2Summary } from '@/types'
import { makeBytes32 } from '@layerzerolabs/devtools-evm'
import { makeBytes32 } from '@layerzerolabs/devtools'
import { ExecutorOptionType, Options, WorkerId } from '@layerzerolabs/lz-v2-utilities'
import prompts, { PromptObject } from 'prompts'
import { handlePromptState, promptToContinue } from '@layerzerolabs/io-devtools'
Expand Down
2 changes: 0 additions & 2 deletions packages/devtools-evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/constants": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
Expand All @@ -66,7 +65,6 @@
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/constants": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
Expand Down
49 changes: 1 addition & 48 deletions packages/devtools-evm/src/address.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,6 @@
import type { Address, Bytes32 } from '@layerzerolabs/devtools'
import { hexZeroPad } from '@ethersproject/bytes'
import type { Address } from '@layerzerolabs/devtools'
import { AddressZero } from '@ethersproject/constants'

/**
* Converts an address into Bytes32 by padding it with zeros.
*
* It will return zero bytes if passed `null`, `undefined` or an empty string.
*
* @param {Bytes32 | Address | null | undefined} address
* @returns {string}
*/
export const makeBytes32 = (address?: Bytes32 | Address | null | undefined): Bytes32 => hexZeroPad(address || '0x0', 32)

/**
* Compares two Bytes32-like values by value (i.e. ignores casing on strings
* and string length)
*
* @param {Bytes32 | Address | null | undefined} a
* @param {Bytes32 | Address | null | undefined} b
* @returns {boolean}
*/
export const areBytes32Equal = (
a: Bytes32 | Address | null | undefined,
b: Bytes32 | Address | null | undefined
): boolean => BigInt(makeBytes32(a)) === BigInt(makeBytes32(b))

/**
* Checks whether a value is a zero value.
*
* It will return true if passed `null`, `undefined`, empty bytes ('0x') or an empty string.
*
* It will throw an error if the value is not a valid numerical value.
*
* @param {Bytes32 | Address | null | undefined} value
* @returns {boolean}
*/
export const isZero = (value: Bytes32 | Address | null | undefined): boolean =>
value === '0x' || BigInt(value || 0) === BigInt(0)

/**
* Turns a potentially zero address into undefined
*
* @param {Bytes32 | Address | null | undefined} address
*
* @returns {string | undefined}
*/
export const ignoreZero = (value?: Bytes32 | Address | null | undefined): string | undefined =>
isZero(value) ? undefined : value ?? undefined

/**
* Turns a nullish value (`null` or `undefined`) into a zero address
*
Expand Down
165 changes: 2 additions & 163 deletions packages/devtools-evm/test/address.test.ts
Original file line number Diff line number Diff line change
@@ -1,170 +1,9 @@
import fc from 'fast-check'
import { AddressZero } from '@ethersproject/constants'
import { evmAddressArbitrary, evmBytes32Arbitrary } from '@layerzerolabs/test-devtools'
import { areBytes32Equal, ignoreZero, isZero, makeBytes32, makeZeroAddress } from '@/address'
import { evmAddressArbitrary } from '@layerzerolabs/test-devtools'
import { makeZeroAddress } from '@/address'

describe('address', () => {
const ZERO_BYTES = '0x0000000000000000000000000000000000000000000000000000000000000000'

describe('makeBytes32', () => {
it('should return zero value with empty bytes32', () => {
expect(makeBytes32(ZERO_BYTES)).toBe(ZERO_BYTES)
})

it('should return zero value with empty string', () => {
expect(makeBytes32('')).toBe(ZERO_BYTES)
})

it('should return zero value with zero address', () => {
expect(makeBytes32(AddressZero)).toBe(ZERO_BYTES)
})

it('should return zero value with undefined', () => {
expect(makeBytes32(undefined)).toBe(ZERO_BYTES)
})

it('should return zero value with null', () => {
expect(makeBytes32(null)).toBe(ZERO_BYTES)
})

it('should return zero value with empty bytes', () => {
expect(makeBytes32('0x')).toBe(ZERO_BYTES)
})

it('should return padded values for address', () => {
fc.assert(
fc.property(evmAddressArbitrary, (address) => {
const bytes = makeBytes32(address)

expect(bytes.length).toBe(66)
expect(BigInt(bytes)).toBe(BigInt(address))
})
)
})

it('should return identity for bytes32', () => {
fc.assert(
fc.property(evmBytes32Arbitrary, (bytes) => {
expect(makeBytes32(bytes)).toBe(bytes)
})
)
})
})

describe('areBytes32Equal', () => {
const zeroishBytes32Arbitrary = fc.constantFrom(null, undefined, '0x', '0x0', makeZeroAddress(), ZERO_BYTES)

it('should return true for two nullish values', () => {
fc.assert(
fc.property(zeroishBytes32Arbitrary, zeroishBytes32Arbitrary, (a, b) => {
expect(areBytes32Equal(a, b)).toBe(true)
})
)
})

it('should return true for two identical values', () => {
fc.assert(
fc.property(evmBytes32Arbitrary, (a) => {
expect(areBytes32Equal(a, a)).toBe(true)
})
)
})

it('should return true for an address and bytes', () => {
fc.assert(
fc.property(evmAddressArbitrary, (address) => {
expect(areBytes32Equal(address, makeBytes32(address))).toBe(true)
})
)
})

it('should return false for a zeroish value and a non-zeroish address', () => {
fc.assert(
fc.property(zeroishBytes32Arbitrary, evmAddressArbitrary, (bytes, address) => {
fc.pre(!isZero(address))

expect(areBytes32Equal(bytes, address)).toBe(false)
})
)
})

it('should return false for a zeroish value and a non-zeroish bytes', () => {
fc.assert(
fc.property(zeroishBytes32Arbitrary, evmBytes32Arbitrary, (a, b) => {
fc.pre(!isZero(b))

expect(areBytes32Equal(a, b)).toBe(false)
})
)
})
})

describe('isZero', () => {
it('should return true with zero bytes32', () => {
expect(isZero(makeBytes32(AddressZero))).toBe(true)
})

it('should return true with zero bytes32 string', () => {
expect(isZero('0x')).toBe(true)
})

it('should return true with zero address', () => {
expect(isZero(AddressZero)).toBe(true)
})

it('should return true with undefined', () => {
expect(isZero(undefined)).toBe(true)
})

it('should return true with null', () => {
expect(isZero(null)).toBe(true)
})

it('should return false with non-zero address', () => {
fc.assert(
fc.property(evmAddressArbitrary, (address) => {
fc.pre(address !== AddressZero)

expect(isZero(address)).toBe(false)
})
)
})

it('should return false with non-zero bytes32', () => {
fc.assert(
fc.property(evmBytes32Arbitrary, (address) => {
fc.pre(address !== ZERO_BYTES)

expect(isZero(address)).toBe(false)
})
)
})
})

describe('ignoreZero', () => {
it('should return address with non-zero address', () => {
fc.assert(
fc.property(evmAddressArbitrary, (address) => {
fc.pre(address !== AddressZero)

expect(ignoreZero(address)).toBe(address)
})
)
})

it('should return undefined with zero address', () => {
expect(ignoreZero(AddressZero)).toBe(undefined)
})

it('should return undefined with undefined', () => {
expect(ignoreZero(undefined)).toBe(undefined)
})

it('should return undefined with null', () => {
expect(ignoreZero(null)).toBe(undefined)
})
})

describe('makeZeroAddress', () => {
it('should return address with non-zero address', () => {
fc.assert(
Expand Down
3 changes: 3 additions & 0 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"test": "jest --ci --forceExit"
},
"devDependencies": {
"@ethersproject/bytes": "~5.7.0",
"@ethersproject/constants": "~5.7.0",
"@layerzerolabs/io-devtools": "~0.0.5",
"@layerzerolabs/lz-definitions": "~2.0.25",
"@layerzerolabs/test-devtools": "~0.0.5",
Expand All @@ -46,6 +48,7 @@
"zod": "^3.22.4"
},
"peerDependencies": {
"@ethersproject/bytes": "~5.7.0",
"@layerzerolabs/io-devtools": "~0.0.4",
"@layerzerolabs/lz-definitions": "~2.0.25",
"zod": "^3.22.4"
Expand Down
66 changes: 66 additions & 0 deletions packages/devtools/src/common/bytes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { PossiblyBigInt, PossiblyBytes } from '@/types'
import { hexZeroPad } from '@ethersproject/bytes'

/**
* Converts an address into Bytes32 by padding it with zeros.
*
* It will return zero bytes if passed `null`, `undefined` or an empty string.
*
* @param {PossiblyBytes | null | undefined} address
* @returns {string}
*/
export const makeBytes32 = (address?: PossiblyBytes | null | undefined): PossiblyBytes =>
hexZeroPad(address || '0x0', 32)

/**
* Compares two Bytes32-like values by value (i.e. ignores casing on strings
* and string length)
*
* @param {PossiblyBytes | null | undefined} a
* @param {PossiblyBytes | null | undefined} b
* @returns {boolean}
*/
export const areBytes32Equal = (a: PossiblyBytes | null | undefined, b: PossiblyBytes | null | undefined): boolean =>
BigInt(makeBytes32(a)) === BigInt(makeBytes32(b))

/**
* Checks whether a value is a zero value.
*
* It will return true if passed `null`, `undefined`, empty bytes ('0x') or an empty string.
*
* It will throw an error if the value is not a valid numerical value.
*
* @param {PossiblyBytes | PossiblyBigInt | null | undefined} value
* @returns {boolean}
*/
export const isZero = (value: PossiblyBytes | PossiblyBigInt | null | undefined): boolean =>
value === '0x' || BigInt(value || 0) === BigInt(0)

/**
* Turns a potentially zero address into undefined
*
* @param {PossiblyBytes | PossiblyBigInt | null | undefined} address
*
* @returns {string | undefined}
*/
export const ignoreZero = <T extends PossiblyBytes | PossiblyBigInt>(value?: T | null | undefined): T | undefined =>
isZero(value) ? undefined : value ?? undefined

/**
* Helper function to be used when sorting of addresses is necessary.
*
* This can be used to sort arrays of addresses in ascending manner:
*
* ```
* // The result will be ["0x000000000000000000636F6e736F6c652e6c6f67", "0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8"]
* ["0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8", "0x000000000000000000636F6e736F6c652e6c6f67"].sort(compareBytes32Ascending)
* ```
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare}
*
* @param {PossiblyBytes} a
* @param {PossiblyBytes} b
* @returns {number} `0` when the two are interchangeable, a negative value when `a` comes before `b` and a positive value when `a` comes after `b`
*/
export const compareBytes32Ascending = (a: PossiblyBytes, b: PossiblyBytes): number =>
Number(BigInt(makeBytes32(a)) - BigInt(makeBytes32(b)))
1 change: 1 addition & 0 deletions packages/devtools/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './assertion'
export * from './bytes'
export * from './promise'
8 changes: 8 additions & 0 deletions packages/devtools/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ export type Address = string

export type Bytes32 = string

export type Bytes = string

export type PossiblyBigInt = string | number | bigint

export type OmniAddress = Bytes32 | Address

export type PossiblyBytes = Bytes | Bytes32 | Address

/**
* Generic type for a hybrid (sync / async) factory
* that generates an instance of `TOutput` based on arguments of type `TInput`
Expand Down
Loading

0 comments on commit 8789236

Please sign in to comment.