From b5991ca02a8a996d0eaafb0b896f995386ff6a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1n=20Jakub=20Nani=C5=A1ta?= Date: Mon, 8 Jan 2024 14:45:33 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=9A=20Add=20isDeepEqual=20utility=20(#?= =?UTF-8?q?168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/silver-drinks-jam.md | 5 + packages/devtools/README.md | 11 ++ packages/devtools/src/common/assertion.ts | 21 ++++ packages/devtools/src/common/index.ts | 1 + .../devtools/test/common/assertion.test.ts | 102 ++++++++++++++++++ packages/ua-devtools/src/oapp/config.ts | 97 +++++++++-------- 6 files changed, 188 insertions(+), 49 deletions(-) create mode 100644 .changeset/silver-drinks-jam.md create mode 100644 packages/devtools/src/common/assertion.ts create mode 100644 packages/devtools/test/common/assertion.test.ts diff --git a/.changeset/silver-drinks-jam.md b/.changeset/silver-drinks-jam.md new file mode 100644 index 000000000..927150656 --- /dev/null +++ b/.changeset/silver-drinks-jam.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/devtools": patch +--- + +Add isDeepEqual utility diff --git a/packages/devtools/README.md b/packages/devtools/README.md index 3a405ea8f..793c6aae0 100644 --- a/packages/devtools/README.md +++ b/packages/devtools/README.md @@ -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 +``` diff --git a/packages/devtools/src/common/assertion.ts b/packages/devtools/src/common/assertion.ts new file mode 100644 index 000000000..9708b1b7a --- /dev/null +++ b/packages/devtools/src/common/assertion.ts @@ -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 + } +} diff --git a/packages/devtools/src/common/index.ts b/packages/devtools/src/common/index.ts index d4aeb6d16..27be9e943 100644 --- a/packages/devtools/src/common/index.ts +++ b/packages/devtools/src/common/index.ts @@ -1 +1,2 @@ +export * from './assertion' export * from './promise' diff --git a/packages/devtools/test/common/assertion.test.ts b/packages/devtools/test/common/assertion.test.ts new file mode 100644 index 000000000..f22f60784 --- /dev/null +++ b/packages/devtools/test/common/assertion.test.ts @@ -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() + } + ) + ) + }) + }) +}) diff --git a/packages/ua-devtools/src/oapp/config.ts b/packages/ua-devtools/src/oapp/config.ts index 3b3ef2ebb..983ec2f4c 100644 --- a/packages/ua-devtools/src/oapp/config.ts +++ b/packages/ua-devtools/src/oapp/config.ts @@ -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' @@ -81,22 +81,21 @@ export const configureReceiveLibraryTimeouts: OAppConfigurator = async (graph, c flattenTransactions( await Promise.all( graph.connections.map(async ({ vector: { from, to }, config }): Promise => { - 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 ), ] }) @@ -109,46 +108,45 @@ export const configureSendConfig: OAppConfigurator = async (graph, createSdk) => graph.connections.map(async ({ vector: { from, to }, config }): Promise => { 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 }) ) ) @@ -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 = ( 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 },