Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/library-samevalue-idempotency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@layerzerolabs/ua-devtools": patch
---

Fix library wire skip checks with chain-aware address equality

`configureSendLibraries` / `configureReceiveLibraries` (and read-library equivalents)
previously compared addresses with `toLowerCase()` or strict `===`. Checksum or
padding mismatches caused already-pinned libraries to be re-proposed, which reverts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add some simple tests for hex padding and Solana base58 format?

on-chain with `LZ_SameValue`. Comparisons now use
`areBytes32Equal(normalizePeer(…, localEid), …)` so EVM checksum/padding and Solana
base58 both compare correctly. Bare `areBytes32Equal` on raw library strings is not
enough — it throws on base58. Keeps the existing "lock defaults" behavior when the
OApp is still on the endpoint default.
162 changes: 81 additions & 81 deletions examples/lzapp-migration/pnpm-lock.yaml

Large diffs are not rendered by default.

110 changes: 46 additions & 64 deletions examples/mint-burn-oft-adapter/pnpm-lock.yaml

Large diffs are not rendered by default.

86 changes: 43 additions & 43 deletions examples/oapp-aptos-move/pnpm-lock.yaml

Large diffs are not rendered by default.

220 changes: 110 additions & 110 deletions examples/oapp-solana/pnpm-lock.yaml

Large diffs are not rendered by default.

159 changes: 51 additions & 108 deletions examples/oapp/pnpm-lock.yaml

Large diffs are not rendered by default.

86 changes: 43 additions & 43 deletions examples/oft-adapter-aptos-move/pnpm-lock.yaml

Large diffs are not rendered by default.

86 changes: 43 additions & 43 deletions examples/oft-adapter-initia/pnpm-lock.yaml

Large diffs are not rendered by default.

110 changes: 46 additions & 64 deletions examples/oft-adapter/pnpm-lock.yaml

Large diffs are not rendered by default.

86 changes: 43 additions & 43 deletions examples/oft-aptos-move/pnpm-lock.yaml

Large diffs are not rendered by default.

98 changes: 49 additions & 49 deletions examples/oft-hyperliquid/pnpm-lock.yaml

Large diffs are not rendered by default.

478 changes: 358 additions & 120 deletions examples/oft-initia/pnpm-lock.yaml

Large diffs are not rendered by default.

803 changes: 644 additions & 159 deletions examples/oft-solana/pnpm-lock.yaml

Large diffs are not rendered by default.

114 changes: 48 additions & 66 deletions examples/oft-upgradeable/pnpm-lock.yaml

Large diffs are not rendered by default.

118 changes: 50 additions & 68 deletions examples/oft/pnpm-lock.yaml

Large diffs are not rendered by default.

159 changes: 51 additions & 108 deletions examples/onft721/pnpm-lock.yaml

Large diffs are not rendered by default.

104 changes: 52 additions & 52 deletions examples/ovault-evm/pnpm-lock.yaml

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions packages/ua-devtools/src/oapp-read/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
type Bytes,
areBytes32Equal,
type OmniTransaction,
formatOmniPoint,
createConfigureMultiple,
createConfigureNodes,
normalizePeer,
} from '@layerzerolabs/devtools'
import type { OAppReadConfigurator, OAppReadEnforcedOption } from './types'
import { createModuleLogger, createWithAsyncLogger, printBoolean } from '@layerzerolabs/io-devtools'
Expand Down Expand Up @@ -106,7 +108,15 @@ export const configureReadLibraries: OAppReadConfigurator = withOAppReadLogger(
const isDefaultSendLibrary = await endpointSdk.isDefaultSendLibrary(point.address, channelId)
const currentSendLibrary = await endpointSdk.getSendLibrary(point.address, channelId)

if (!isDefaultSendLibrary && currentSendLibrary === readLibrary) {
// Keep !isDefault* so we still lock a matching resolved default into OApp storage.
// Compare via normalizePeer(local eid) so EVM checksum/padding and Solana base58 both work.
if (
!isDefaultSendLibrary &&
areBytes32Equal(
normalizePeer(currentSendLibrary, point.eid),
normalizePeer(readLibrary, point.eid)
)
) {
logger.verbose(
`Current sendlibrary of channelId ${channelId} is already set to ${readLibrary} for ${label}, skipping`
)
Expand All @@ -120,7 +130,13 @@ export const configureReadLibraries: OAppReadConfigurator = withOAppReadLogger(
channelId
)

if (!isDefaultReceiveLibrary && currentReceiveLibrary === readLibrary) {
if (
!isDefaultReceiveLibrary &&
areBytes32Equal(
normalizePeer(currentReceiveLibrary, point.eid),
normalizePeer(readLibrary, point.eid)
)
) {
logger.verbose(
`Current receiveLibrary of channelId ${channelId} is already set to ${readLibrary} for ${label}, skipping`
)
Expand Down
22 changes: 20 additions & 2 deletions packages/ua-devtools/src/oapp/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type Bytes,
areBytes32Equal,
formatOmniVector,
isDeepEqual,
type OmniAddress,
Expand All @@ -9,6 +10,7 @@ import {
createConfigureMultiple,
createConfigureNodes,
createConfigureEdges,
normalizePeer,
} from '@layerzerolabs/devtools'
import type { OAppConfigurator, OAppEnforcedOption, OAppEnforcedOptionParam, OAppFactory } from './types'
import { createModuleLogger, createWithAsyncLogger, printBoolean } from '@layerzerolabs/io-devtools'
Expand Down Expand Up @@ -137,7 +139,15 @@ export const configureSendLibraries: OAppConfigurator = withOAppLogger(
)
}

if (!isDefaultLibrary && currentSendLibrary?.toLowerCase() === config.sendLibrary.toLowerCase()) {
// Keep !isDefaultLibrary so we still "lock" a matching resolved default into OApp storage.
// Compare via normalizePeer(local eid) so EVM checksum/padding and Solana base58 both work.
if (
!isDefaultLibrary &&
areBytes32Equal(
normalizePeer(currentSendLibrary, from.eid),
normalizePeer(config.sendLibrary, from.eid)
)
) {
logger.verbose(
`Current sendLibrary is not default library and is already set to ${config.sendLibrary} for ${formatOmniVector({ from, to })}, skipping`
)
Expand Down Expand Up @@ -212,7 +222,15 @@ export const configureReceiveLibraries: OAppConfigurator = withOAppLogger(
)
}

if (!isDefaultLibrary && currentReceiveLibrary === config.receiveLibraryConfig.receiveLibrary) {
// Keep !isDefaultLibrary so we still "lock" a matching resolved default into OApp storage.
// Compare via normalizePeer(local eid) so EVM checksum/padding and Solana base58 both work.
if (
!isDefaultLibrary &&
areBytes32Equal(
normalizePeer(currentReceiveLibrary, from.eid),
normalizePeer(config.receiveLibraryConfig.receiveLibrary, from.eid)
)
) {
logger.verbose(
`Current recieveLibrary is not default and is already set to ${config.receiveLibraryConfig.receiveLibrary} for ${formatOmniVector({ from, to })}, skipping`
)
Expand Down
59 changes: 58 additions & 1 deletion tests/ua-devtools-evm-hardhat-test/test/oapp-read/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {
OmniContractFactoryHardhat,
} from '@layerzerolabs/devtools-evm-hardhat'
import { createOAppReadFactory } from '@layerzerolabs/ua-devtools-evm'
import { configureOAppRead, IOAppRead, OAppReadFactory, OAppReadOmniGraph } from '@layerzerolabs/ua-devtools'
import {
configureOAppRead,
configureReadLibraries,
IOAppRead,
OAppReadFactory,
OAppReadOmniGraph,
} from '@layerzerolabs/ua-devtools'
import { OmniContract, omniContractToPoint } from '@layerzerolabs/devtools-evm'
import {
setupDefaultEndpointV2,
Expand Down Expand Up @@ -392,6 +398,57 @@ describe('oapp-read/config', () => {
})
})

describe('configureReadLibraries address equality idempotency', () => {
it('should skip when already pinned even if config uses lowercase addresses', async () => {
const ethReadLibrary = await getLibraryAddress(ethReadLib_Opt2)
const avaxReadLibrary = await getLibraryAddress(avaxReadLib_Opt2)
const [registerErrors] = await signAndSend([
await ethEndpointV2Sdk.registerLibrary(ethReadLibrary),
await avaxEndpointV2Sdk.registerLibrary(avaxReadLibrary),
])
expect(registerErrors).toEqual([])

const channelId = ChannelId.READ_CHANNEL_1.valueOf()
const [setErrors] = await signAndSend([
await ethEndpointV2Sdk.setSendLibrary(ethPoint.address, channelId, ethReadLibrary),
await ethEndpointV2Sdk.setReceiveLibrary(ethPoint.address, channelId, ethReadLibrary, BigInt(0)),
await avaxEndpointV2Sdk.setSendLibrary(avaxPoint.address, channelId, avaxReadLibrary),
await avaxEndpointV2Sdk.setReceiveLibrary(avaxPoint.address, channelId, avaxReadLibrary, BigInt(0)),
])
expect(setErrors).toEqual([])

const graph: OAppReadOmniGraph = {
contracts: [
{
point: ethPoint,
config: {
readChannelConfigs: [
{
channelId: ChannelId.READ_CHANNEL_1,
readLibrary: ethReadLibrary.toLowerCase(),
},
],
},
},
{
point: avaxPoint,
config: {
readChannelConfigs: [
{
channelId: ChannelId.READ_CHANNEL_1,
readLibrary: avaxReadLibrary.toLowerCase(),
},
],
},
},
],
connections: [],
}

expect(await configureReadLibraries(graph, oappSdkFactory)).toEqual([])
})
})

describe('configureConfig configureReadConfig', () => {
beforeEach(async () => {
// Before we configure the OApp, we'll set some peers
Expand Down
90 changes: 90 additions & 0 deletions tests/ua-devtools-evm-hardhat-test/test/oapp/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
configureOApp,
configureOAppDelegates,
configureCallerBpsCap,
configureReceiveLibraries,
configureSendLibraries,
IOApp,
OAppFactory,
OAppOmniGraph,
Expand Down Expand Up @@ -416,6 +418,40 @@ describe('oapp/config', () => {
})
})

describe('configureSendLibraries address equality idempotency', () => {
it('should skip when already pinned even if config uses lowercase addresses', async () => {
const ethSendLibrary = await getLibraryAddress(ethSendUln2_Opt2)
const avaxSendLibrary = await getLibraryAddress(avaxSendUln2_Opt2)
const [registerErrors] = await signAndSend([
await ethEndpointV2Sdk.registerLibrary(ethSendLibrary),
await avaxEndpointV2Sdk.registerLibrary(avaxSendLibrary),
])
expect(registerErrors).toEqual([])

const [setErrors] = await signAndSend([
await ethEndpointV2Sdk.setSendLibrary(ethPoint.address, avaxPoint.eid, ethSendLibrary),
await avaxEndpointV2Sdk.setSendLibrary(avaxPoint.address, ethPoint.eid, avaxSendLibrary),
])
expect(setErrors).toEqual([])

const graph: OAppOmniGraph = {
contracts: [{ point: ethPoint }, { point: avaxPoint }],
connections: [
{
vector: { from: ethPoint, to: avaxPoint },
config: { sendLibrary: ethSendLibrary.toLowerCase() },
},
{
vector: { from: avaxPoint, to: ethPoint },
config: { sendLibrary: avaxSendLibrary.toLowerCase() },
},
],
}

expect(await configureSendLibraries(graph, oappSdkFactory)).toEqual([])
})
})

describe('configureReceiveLibraries lock defaults', () => {
let ethReceiveLibrary: string, avaxReceiveLibrary: string, graph: OAppOmniGraph
beforeEach(async () => {
Expand Down Expand Up @@ -576,6 +612,60 @@ describe('oapp/config', () => {
})
})

describe('configureReceiveLibraries address equality idempotency', () => {
it('should skip when already pinned even if config uses lowercase addresses', async () => {
const ethReceiveLibrary = await getLibraryAddress(ethReceiveUln2_Opt2)
const avaxReceiveLibrary = await getLibraryAddress(avaxReceiveUln2_Opt2)
const [registerErrors] = await signAndSend([
await ethEndpointV2Sdk.registerLibrary(ethReceiveLibrary),
await avaxEndpointV2Sdk.registerLibrary(avaxReceiveLibrary),
])
expect(registerErrors).toEqual([])

const [setErrors] = await signAndSend([
await ethEndpointV2Sdk.setReceiveLibrary(
ethPoint.address,
avaxPoint.eid,
ethReceiveLibrary,
BigInt(0)
),
await avaxEndpointV2Sdk.setReceiveLibrary(
avaxPoint.address,
ethPoint.eid,
avaxReceiveLibrary,
BigInt(0)
),
])
expect(setErrors).toEqual([])

const graph: OAppOmniGraph = {
contracts: [{ point: ethPoint }, { point: avaxPoint }],
connections: [
{
vector: { from: ethPoint, to: avaxPoint },
config: {
receiveLibraryConfig: {
receiveLibrary: ethReceiveLibrary.toLowerCase(),
gracePeriod: BigInt(0),
},
},
},
{
vector: { from: avaxPoint, to: ethPoint },
config: {
receiveLibraryConfig: {
receiveLibrary: avaxReceiveLibrary.toLowerCase(),
gracePeriod: BigInt(0),
},
},
},
],
}

expect(await configureReceiveLibraries(graph, oappSdkFactory)).toEqual([])
})
})

describe('configureReceiveLibraryTimeouts', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we also want to run the normalized equality checks on configureReceiveLibraryTimeouts (packages/ua-devtools/src/oapp/config.ts:287).

let ethDefaultReceiveLibrary: string,
ethReceiveLibrary_Opt2: string,
Expand Down
Loading