Skip to content

Commit c6de4c9

Browse files
authored
fix(sdk): make warp check OFT-aware (#8614)
1 parent 31d5585 commit c6de4c9

4 files changed

Lines changed: 177 additions & 13 deletions

File tree

.changeset/swift-ducks-drift.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hyperlane-xyz/sdk': patch
3+
---
4+
5+
Updated warp check to validate OFT routes using OFT-specific sentinel router state and normalized empty extraOptions/domainMappings values.

typescript/sdk/src/token/configUtils.test.ts

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,31 @@ import {
1010
} from '../fee/types.js';
1111
import { HookType } from '../hook/types.js';
1212
import { IsmType } from '../ism/types.js';
13+
import { MultiProvider } from '../providers/MultiProvider.js';
14+
import { test1, test2 } from '../consts/testChains.js';
1315
import type { WarpCoreConfig } from '../warp/types.js';
1416

1517
import { TokenType } from './config.js';
1618
import {
1719
filterWarpCoreConfigMapByChains,
1820
getChainsFromWarpCoreConfig,
21+
normalizeWarpDeployConfigForCheck,
1922
resolveTokenFeeAddress,
2023
transformConfigToCheck,
2124
warpCoreConfigMatchesChains,
2225
} from './configUtils.js';
2326
import { TokenStandard } from './TokenStandard.js';
24-
import { HypTokenConfig } from './types.js';
27+
import {
28+
HypTokenConfig,
29+
WarpRouteDeployConfigMailboxRequired,
30+
} from './types.js';
31+
32+
function buildMultiProvider(): MultiProvider {
33+
return new MultiProvider({
34+
[test1.name]: test1,
35+
[test2.name]: test2,
36+
});
37+
}
2538

2639
describe('configUtils', () => {
2740
describe(transformConfigToCheck.name, () => {
@@ -556,13 +569,8 @@ describe('configUtils', () => {
556569
expect(result.token).to.equal(ROUTER_ADDRESS);
557570
expect(result.type).to.equal(TokenFeeType.RoutingFee);
558571

559-
const routingResult = result as ResolvedRoutingFeeConfigInput;
560-
expect(routingResult.feeContracts.ethereum.token).to.equal(
561-
ROUTER_ADDRESS,
562-
);
563-
expect(routingResult.feeContracts.arbitrum.token).to.equal(
564-
ROUTER_ADDRESS,
565-
);
572+
expect(result.feeContracts.ethereum.token).to.equal(ROUTER_ADDRESS);
573+
expect(result.feeContracts.arbitrum.token).to.equal(ROUTER_ADDRESS);
566574
});
567575

568576
it('should handle RoutingFee with empty feeContracts', () => {
@@ -619,6 +627,106 @@ describe('configUtils', () => {
619627
});
620628
});
621629

630+
describe(normalizeWarpDeployConfigForCheck.name, () => {
631+
const ADDRESS = '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359';
632+
const OTHER_ADDRESS = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
633+
634+
it('normalizes OFT configs to sentinel router state for checks', () => {
635+
const warpDeployConfig: WarpRouteDeployConfigMailboxRequired = {
636+
[test1.name]: {
637+
decimals: 6,
638+
destinationGas: { [test2.name]: '12345' },
639+
domainMappings: { [test2.name]: 30110 },
640+
extraOptions: '0x',
641+
hook: OTHER_ADDRESS,
642+
interchainSecurityModule: OTHER_ADDRESS,
643+
mailbox: ADDRESS,
644+
name: 'USDT',
645+
oft: OTHER_ADDRESS,
646+
owner: ADDRESS,
647+
remoteRouters: {
648+
[test2.name]: {
649+
address: OTHER_ADDRESS,
650+
},
651+
},
652+
symbol: 'USDT',
653+
token: ADDRESS,
654+
type: TokenType.collateralOft,
655+
},
656+
};
657+
658+
const normalized = normalizeWarpDeployConfigForCheck({
659+
multiProvider: buildMultiProvider(),
660+
warpDeployConfig,
661+
});
662+
663+
expect(normalized[test1.name]).to.deep.equal({
664+
decimals: 6,
665+
destinationGas: undefined,
666+
domainMappings: { [test2.domainId]: 30110 },
667+
extraOptions: undefined,
668+
hook: constants.AddressZero,
669+
interchainSecurityModule: constants.AddressZero,
670+
mailbox: constants.AddressZero,
671+
name: 'USDT',
672+
oft: OTHER_ADDRESS,
673+
owner: ADDRESS,
674+
remoteRouters: {},
675+
symbol: 'USDT',
676+
token: ADDRESS,
677+
type: TokenType.collateralOft,
678+
});
679+
});
680+
681+
it('preserves non-empty OFT extraOptions', () => {
682+
const warpDeployConfig: WarpRouteDeployConfigMailboxRequired = {
683+
[test1.name]: {
684+
decimals: 6,
685+
domainMappings: { [test2.name]: 30110 },
686+
extraOptions: '0xdeadbeef',
687+
hook: OTHER_ADDRESS,
688+
interchainSecurityModule: OTHER_ADDRESS,
689+
mailbox: ADDRESS,
690+
name: 'USDT',
691+
oft: OTHER_ADDRESS,
692+
owner: ADDRESS,
693+
symbol: 'USDT',
694+
token: ADDRESS,
695+
type: TokenType.collateralOft,
696+
},
697+
};
698+
699+
const normalized = normalizeWarpDeployConfigForCheck({
700+
multiProvider: buildMultiProvider(),
701+
warpDeployConfig,
702+
});
703+
704+
expect(normalized[test1.name]).to.deep.include({
705+
extraOptions: '0xdeadbeef',
706+
});
707+
});
708+
709+
it('leaves non-OFT configs unchanged', () => {
710+
const warpDeployConfig: WarpRouteDeployConfigMailboxRequired = {
711+
[test1.name]: {
712+
decimals: 18,
713+
mailbox: ADDRESS,
714+
name: 'TOKEN',
715+
owner: ADDRESS,
716+
symbol: 'TKN',
717+
type: TokenType.synthetic,
718+
},
719+
};
720+
721+
const normalized = normalizeWarpDeployConfigForCheck({
722+
multiProvider: buildMultiProvider(),
723+
warpDeployConfig,
724+
});
725+
726+
expect(normalized).to.deep.equal(warpDeployConfig);
727+
});
728+
});
729+
622730
const buildWarpCoreConfig = (chainNames: string[]): WarpCoreConfig => ({
623731
tokens: chainNames.map((chainName, index) => ({
624732
chainName,

typescript/sdk/src/token/configUtils.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ import {
2929
import { EvmHookReader } from '../hook/EvmHookReader.js';
3030
import { EvmIsmReader } from '../ism/EvmIsmReader.js';
3131
import { MultiProvider } from '../providers/MultiProvider.js';
32-
import { DestinationGas, RemoteRouters } from '../router/types.js';
32+
import {
33+
DestinationGas,
34+
RemoteRouters,
35+
resolveRouterMapConfig,
36+
} from '../router/types.js';
3337
import { ChainMap } from '../types.js';
3438
import { WarpCoreConfig } from '../warp/types.js';
3539

@@ -50,6 +54,7 @@ import {
5054
isCrossCollateralTokenConfig,
5155
isMovableCollateralTokenConfig,
5256
isNativeTokenConfig,
57+
isOftTokenConfig,
5358
isSyntheticRebaseTokenConfig,
5459
isSyntheticTokenConfig,
5560
} from './types.js';
@@ -410,6 +415,34 @@ export async function expandWarpDeployConfig(params: {
410415
);
411416
}
412417

418+
export function normalizeWarpDeployConfigForCheck(params: {
419+
multiProvider: MultiProvider;
420+
warpDeployConfig: WarpRouteDeployConfigMailboxRequired;
421+
}): WarpRouteDeployConfigMailboxRequired {
422+
const { multiProvider, warpDeployConfig } = params;
423+
424+
return objMap(warpDeployConfig, (_chain, config) => {
425+
if (!isOftTokenConfig(config)) {
426+
return config;
427+
}
428+
429+
return {
430+
...config,
431+
mailbox: constants.AddressZero,
432+
hook: constants.AddressZero,
433+
interchainSecurityModule: constants.AddressZero,
434+
remoteRouters: {},
435+
destinationGas: undefined,
436+
domainMappings: resolveRouterMapConfig(
437+
multiProvider,
438+
config.domainMappings,
439+
),
440+
extraOptions:
441+
config.extraOptions === '0x' ? undefined : config.extraOptions,
442+
};
443+
});
444+
}
445+
413446
/**
414447
* Resolves the fee token address based on the warp route token type.
415448
* - Native tokens: fee token is AddressZero

typescript/sdk/src/token/warpCheck.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
expandVirtualWarpDeployConfig,
3737
expandWarpDeployConfig,
3838
getRouterAddressesFromWarpCoreConfig,
39+
normalizeWarpDeployConfigForCheck,
3940
transformConfigToCheck,
4041
} from './configUtils.js';
4142
import {
@@ -155,8 +156,12 @@ export async function checkWarpRouteDeployConfig({
155156
expandedOnChainWarpConfig,
156157
validateScale: false,
157158
});
159+
const normalizedWarpDeployConfig = normalizeWarpDeployConfigForCheck({
160+
multiProvider,
161+
warpDeployConfig: expandedWarpDeployConfig,
162+
});
158163
const evmExpandedWarpDeployConfig = objFilter(
159-
expandedWarpDeployConfig,
164+
normalizedWarpDeployConfig,
160165
(chain, _config): _config is (typeof expandedWarpDeployConfig)[string] =>
161166
isEVMLike(multiProvider.getProtocol(chain)),
162167
);
@@ -176,7 +181,7 @@ export async function checkWarpRouteDeployConfig({
176181
const diffViolations = flattenWarpRouteCheckDiff(diff);
177182
const scaleViolations = await getScaleViolations({
178183
multiProvider,
179-
warpRouteConfig: expandedWarpDeployConfig,
184+
warpRouteConfig: normalizedWarpDeployConfig,
180185
});
181186

182187
return {
@@ -598,8 +603,21 @@ function stringifyViolationValue(value: unknown): string {
598603
return '';
599604
}
600605

601-
if (value === null || typeof value !== 'object') {
602-
return String(value);
606+
if (typeof value === 'string') {
607+
return value;
608+
}
609+
610+
if (value === null) {
611+
return 'null';
612+
}
613+
614+
if (
615+
typeof value === 'number' ||
616+
typeof value === 'boolean' ||
617+
typeof value === 'symbol' ||
618+
typeof value === 'function'
619+
) {
620+
return value.toString();
603621
}
604622

605623
if (Array.isArray(value)) {

0 commit comments

Comments
 (0)