Skip to content

Commit

Permalink
🪚 Add isDeepEqual utility (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Jan 8, 2024
1 parent 90e2245 commit b5991ca
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-drinks-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@layerzerolabs/devtools": patch
---

Add isDeepEqual utility
11 changes: 11 additions & 0 deletions packages/devtools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,14 @@ const to: OmniPoint = {

const omniVector: OmniVector = { from, to };
```

### Common utilities

### isDeepEqual(a, b)

Compares two objects by value, returning `true` if they match, `false` otherwise.

```typescript
isDeepEqual({ a: 1 }, { a: 1 }); // true
isDeepEqual({ a: 1 }, { a: "1" }); // false
```
21 changes: 21 additions & 0 deletions packages/devtools/src/common/assertion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { deepStrictEqual } from 'assert'

/**
* Compares two object by value, returning `true` if they match
*
* ```
* const theyMatch = isDeepEqual({ a: 1 }, { a: 1 }) // true
* const theyDontMatch = isDeepEqual({ a: 1 }, { a: '1' }) // false
* ```
*
* @param {T} a
* @param {unknown} b
* @returns {boolean}
*/
export const isDeepEqual = (a: unknown, b: unknown): boolean => {
try {
return deepStrictEqual(a, b), true
} catch {
return false
}
}
1 change: 1 addition & 0 deletions packages/devtools/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './assertion'
export * from './promise'
102 changes: 102 additions & 0 deletions packages/devtools/test/common/assertion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { isDeepEqual } from '@/common/assertion'
import fc from 'fast-check'

describe('common/assertion', () => {
describe('isDeepEqual', () => {
const arrayArbitrary = fc.array(fc.anything())
const entriesArbitrary = fc.array(fc.tuple(fc.anything(), fc.anything()))

it('should return true for identical values', () => {
fc.assert(
fc.property(fc.anything(), (value) => {
expect(isDeepEqual(value, value)).toBeTruthy()
})
)
})

it('should return true for arrays containing the same values', () => {
fc.assert(
fc.property(arrayArbitrary, (array) => {
expect(isDeepEqual(array, [...array])).toBeTruthy()
expect(isDeepEqual([...array], array)).toBeTruthy()
})
)
})

it('should return false for arrays containing different values', () => {
fc.assert(
fc.property(arrayArbitrary, arrayArbitrary, (arrayA, arrayB) => {
// We'll do a very simplified precondition - we'll only run tests when the first elements are different
fc.pre(!isDeepEqual(arrayA[0], arrayB[0]))

expect(isDeepEqual(arrayA, arrayB)).toBeFalsy()
expect(isDeepEqual(arrayB, arrayA)).toBeFalsy()
})
)
})

it('should return false for arrays containing more values', () => {
fc.assert(
fc.property(arrayArbitrary, fc.anything(), (array, extraValue) => {
expect(isDeepEqual(array, [...array, extraValue])).toBeFalsy()
expect(isDeepEqual([...array, extraValue], array)).toBeFalsy()
})
)
})

it('should return true for sets containing the same values', () => {
fc.assert(
fc.property(arrayArbitrary, (array) => {
const setA = new Set(array)
const setB = new Set(array)

expect(isDeepEqual(setA, setB)).toBeTruthy()
expect(isDeepEqual(setB, setA)).toBeTruthy()
})
)
})

it('should return true for maps containing the same values', () => {
fc.assert(
fc.property(entriesArbitrary, (entries) => {
const mapA = new Map(entries)
const mapB = new Map(entries)

expect(isDeepEqual(mapA, mapB)).toBeTruthy()
expect(isDeepEqual(mapB, mapA)).toBeTruthy()
})
)
})

it('should return true for objects containing the same values', () => {
fc.assert(
fc.property(
fc.record({
value: fc.anything(),
}),
(object) => {
expect(isDeepEqual(object, { ...object })).toBeTruthy()
expect(isDeepEqual({ ...object }, object)).toBeTruthy()
}
)
)
})

it('should return false for objects containing different values', () => {
fc.assert(
fc.property(
fc.record({
value: fc.anything(),
}),
fc.anything(),
(object, value) => {
fc.pre(!isDeepEqual(object.value, value))

expect(isDeepEqual(object, { value })).toBeFalsy()
expect(isDeepEqual({ value }, object)).toBeFalsy()
}
)
)
})
})
})
97 changes: 48 additions & 49 deletions packages/ua-devtools/src/oapp/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { flattenTransactions, type OmniTransaction } from '@layerzerolabs/devtools'
import type { OAppFactory, OAppOmniGraph } from './types'
import { createModuleLogger, printBoolean } from '@layerzerolabs/io-devtools'
import { formatOmniVector } from '@layerzerolabs/devtools'
import { formatOmniVector, isDeepEqual } from '@layerzerolabs/devtools'
import { Uln302ExecutorConfig, Uln302UlnConfig } from '@layerzerolabs/protocol-devtools'
import assert from 'assert'

Expand Down Expand Up @@ -81,22 +81,21 @@ export const configureReceiveLibraryTimeouts: OAppConfigurator = async (graph, c
flattenTransactions(
await Promise.all(
graph.connections.map(async ({ vector: { from, to }, config }): Promise<OmniTransaction[]> => {
if (!config?.receiveLibraryTimeoutConfig) return []
if (config?.receiveLibraryTimeoutConfig == null) return []

const { receiveLibraryTimeoutConfig } = config
const oappSdk = await createSdk(from)
const endpointSdk = await oappSdk.getEndpointSDK()
const timeout = await endpointSdk.getReceiveLibraryTimeout(from.address, to.eid)

if (
timeout.lib === config.receiveLibraryTimeoutConfig.lib &&
timeout.expiry === config.receiveLibraryTimeoutConfig.expiry
)
return []
if (isDeepEqual(timeout, receiveLibraryTimeoutConfig)) return []

return [
await endpointSdk.setReceiveLibraryTimeout(
from.address,
to.eid,
config.receiveLibraryTimeoutConfig.lib,
config.receiveLibraryTimeoutConfig.expiry
receiveLibraryTimeoutConfig.lib,
receiveLibraryTimeoutConfig.expiry
),
]
})
Expand All @@ -109,46 +108,45 @@ export const configureSendConfig: OAppConfigurator = async (graph, createSdk) =>
graph.connections.map(async ({ vector: { from, to }, config }): Promise<OmniTransaction[]> => {
const oappSdk = await createSdk(from)
const endpointSdk = await oappSdk.getEndpointSDK()

if (config?.sendConfig == null) return []

const transactions: OmniTransaction[] = []

if (config?.sendConfig) {
const currentSendLibrary =
config.sendLibrary ?? (await endpointSdk.getSendLibrary(from.address, to.eid))
assert(currentSendLibrary !== undefined, 'currentSendLibrary must be defined')
const sendExecutorConfig: Uln302ExecutorConfig = await endpointSdk.getExecutorConfig(
from.address,
currentSendLibrary,
to.eid
)
const currentSendLibrary =
config.sendLibrary ?? (await endpointSdk.getSendLibrary(from.address, to.eid))
assert(
currentSendLibrary !== undefined,
'sendLibrary has not been set in your config and no default value exists'
)

if (
sendExecutorConfig.maxMessageSize !== config.sendConfig.executorConfig.maxMessageSize ||
sendExecutorConfig.executor !== config.sendConfig.executorConfig.executor
) {
transactions.push(
await endpointSdk.setExecutorConfig(from.address, currentSendLibrary, [
{ eid: to.eid, executorConfig: config.sendConfig.executorConfig },
])
)
}
const sendExecutorConfig: Uln302ExecutorConfig = await endpointSdk.getExecutorConfig(
from.address,
currentSendLibrary,
to.eid
)

const sendUlnConfig = await endpointSdk.getUlnConfig(from.address, currentSendLibrary, to.eid)
// TODO Normalize the config values using a schema before comparing them
if (!isDeepEqual(sendExecutorConfig, config.sendConfig.executorConfig)) {
transactions.push(
await endpointSdk.setExecutorConfig(from.address, currentSendLibrary, [
{ eid: to.eid, executorConfig: config.sendConfig.executorConfig },
])
)
}

if (
sendUlnConfig.confirmations !== config.sendConfig.ulnConfig.confirmations ||
sendUlnConfig.optionalDVNThreshold !== config.sendConfig.ulnConfig.optionalDVNThreshold ||
sendUlnConfig.requiredDVNs !== config.sendConfig.ulnConfig.requiredDVNs ||
sendUlnConfig.optionalDVNs !== config.sendConfig.ulnConfig.optionalDVNs
) {
transactions.push(
await endpointSdk.setUlnConfig(from.address, currentSendLibrary, [
{ eid: to.eid, ulnConfig: config.sendConfig.ulnConfig },
])
)
}
const sendUlnConfig = await endpointSdk.getUlnConfig(from.address, currentSendLibrary, to.eid)

// TODO Normalize the config values using a schema before comparing them
if (!isDeepEqual(sendUlnConfig, config.sendConfig.ulnConfig)) {
transactions.push(
await endpointSdk.setUlnConfig(from.address, currentSendLibrary, [
{ eid: to.eid, ulnConfig: config.sendConfig.ulnConfig },
])
)
}

return [...transactions]
return transactions
})
)
)
Expand All @@ -165,16 +163,17 @@ export const configureReceiveConfig: OAppConfigurator = async (graph, createSdk)
const [currentReceiveLibrary] = config.receiveLibraryConfig?.receiveLibrary
? [config.receiveLibraryConfig?.receiveLibrary, false]
: await endpointSdk.getReceiveLibrary(from.address, to.eid)
assert(currentReceiveLibrary !== undefined, 'currentReceiveLibrary must be defined')
assert(
currentReceiveLibrary !== undefined,
'receiveLibrary has not been set in your config and no default value exists'
)

const receiveUlnConfig: Uln302UlnConfig = <Uln302UlnConfig>(
await endpointSdk.getUlnConfig(from.address, currentReceiveLibrary, to.eid)
)
if (
receiveUlnConfig.confirmations !== config.receiveConfig.ulnConfig.confirmations ||
receiveUlnConfig.optionalDVNThreshold !== config.receiveConfig.ulnConfig.optionalDVNThreshold ||
receiveUlnConfig.requiredDVNs !== config.receiveConfig.ulnConfig.requiredDVNs ||
receiveUlnConfig.optionalDVNs !== config.receiveConfig.ulnConfig.optionalDVNs
) {

// TODO Normalize the config values using a schema before comparing them
if (!isDeepEqual(receiveUlnConfig, config.receiveConfig.ulnConfig)) {
transactions.push(
await endpointSdk.setUlnConfig(from.address, currentReceiveLibrary, [
{ eid: to.eid, ulnConfig: config.receiveConfig.ulnConfig },
Expand Down

0 comments on commit b5991ca

Please sign in to comment.