@hyperlane-xyz/sdk 41.3.1 → 42.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ccip/consts.d.ts.map +1 -1
- package/dist/ccip/consts.js +20 -0
- package/dist/ccip/consts.js.map +1 -1
- package/dist/consts/multisigIsm.d.ts.map +1 -1
- package/dist/consts/multisigIsm.js +20 -0
- package/dist/consts/multisigIsm.js.map +1 -1
- package/dist/contracts/contracts.d.ts.map +1 -1
- package/dist/contracts/contracts.js +15 -0
- package/dist/contracts/contracts.js.map +1 -1
- package/dist/contracts/contracts.test.d.ts +2 -0
- package/dist/contracts/contracts.test.d.ts.map +1 -0
- package/dist/contracts/contracts.test.js +88 -0
- package/dist/contracts/contracts.test.js.map +1 -0
- package/dist/core/types.d.ts +80 -2602
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +19 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/types.test.js +58 -0
- package/dist/core/types.test.js.map +1 -1
- package/dist/deploy/warp.d.ts +119 -3
- package/dist/deploy/warp.d.ts.map +1 -1
- package/dist/deploy/warp.js +696 -33
- package/dist/deploy/warp.js.map +1 -1
- package/dist/deploy/warp.test.d.ts +2 -0
- package/dist/deploy/warp.test.d.ts.map +1 -0
- package/dist/deploy/warp.test.js +358 -0
- package/dist/deploy/warp.test.js.map +1 -0
- package/dist/deploy/warpHybridPlan.d.ts +104 -0
- package/dist/deploy/warpHybridPlan.d.ts.map +1 -0
- package/dist/deploy/warpHybridPlan.js +178 -0
- package/dist/deploy/warpHybridPlan.js.map +1 -0
- package/dist/deploy/warpHybridPlan.test.d.ts +2 -0
- package/dist/deploy/warpHybridPlan.test.d.ts.map +1 -0
- package/dist/deploy/warpHybridPlan.test.js +141 -0
- package/dist/deploy/warpHybridPlan.test.js.map +1 -0
- package/dist/hook/EvmHookModule.d.ts +1 -1
- package/dist/hook/EvmHookModule.d.ts.map +1 -1
- package/dist/hook/EvmHookModule.js +16 -3
- package/dist/hook/EvmHookModule.js.map +1 -1
- package/dist/hook/EvmHookReader.d.ts +10 -0
- package/dist/hook/EvmHookReader.d.ts.map +1 -1
- package/dist/hook/EvmHookReader.js +92 -11
- package/dist/hook/EvmHookReader.js.map +1 -1
- package/dist/hook/EvmHookReader.test.js +155 -1
- package/dist/hook/EvmHookReader.test.js.map +1 -1
- package/dist/hook/types.d.ts +107 -1983
- package/dist/hook/types.d.ts.map +1 -1
- package/dist/hook/types.js +49 -0
- package/dist/hook/types.js.map +1 -1
- package/dist/hook/types.test.js +21 -1
- package/dist/hook/types.test.js.map +1 -1
- package/dist/hook/updates.d.ts +1 -0
- package/dist/hook/updates.d.ts.map +1 -1
- package/dist/hook/updates.js +2 -2
- package/dist/hook/updates.js.map +1 -1
- package/dist/hook/utils.d.ts +28 -1
- package/dist/hook/utils.d.ts.map +1 -1
- package/dist/hook/utils.js +89 -0
- package/dist/hook/utils.js.map +1 -1
- package/dist/ica/types.d.ts +4 -674
- package/dist/ica/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/ism/EvmIsmModule.d.ts +28 -2
- package/dist/ism/EvmIsmModule.d.ts.map +1 -1
- package/dist/ism/EvmIsmModule.hardhat-test.js +478 -3
- package/dist/ism/EvmIsmModule.hardhat-test.js.map +1 -1
- package/dist/ism/EvmIsmModule.js +224 -10
- package/dist/ism/EvmIsmModule.js.map +1 -1
- package/dist/ism/EvmIsmReader.d.ts.map +1 -1
- package/dist/ism/EvmIsmReader.js +77 -1
- package/dist/ism/EvmIsmReader.js.map +1 -1
- package/dist/ism/EvmIsmReader.test.js +245 -4
- package/dist/ism/EvmIsmReader.test.js.map +1 -1
- package/dist/ism/HyperlaneIsmFactory.d.ts +14 -4
- package/dist/ism/HyperlaneIsmFactory.d.ts.map +1 -1
- package/dist/ism/HyperlaneIsmFactory.hardhat-test.js +372 -3
- package/dist/ism/HyperlaneIsmFactory.hardhat-test.js.map +1 -1
- package/dist/ism/HyperlaneIsmFactory.js +80 -6
- package/dist/ism/HyperlaneIsmFactory.js.map +1 -1
- package/dist/ism/delayedFlow.d.ts +31 -0
- package/dist/ism/delayedFlow.d.ts.map +1 -0
- package/dist/ism/delayedFlow.js +33 -0
- package/dist/ism/delayedFlow.js.map +1 -0
- package/dist/ism/types.d.ts +135 -4
- package/dist/ism/types.d.ts.map +1 -1
- package/dist/ism/types.js +263 -11
- package/dist/ism/types.js.map +1 -1
- package/dist/ism/types.test.js +433 -1
- package/dist/ism/types.test.js.map +1 -1
- package/dist/ism/utils.d.ts.map +1 -1
- package/dist/ism/utils.js +113 -3
- package/dist/ism/utils.js.map +1 -1
- package/dist/ism/utils.test.js +44 -0
- package/dist/ism/utils.test.js.map +1 -1
- package/dist/providers/MultiProviderAdapter.d.ts +3 -3
- package/dist/providers/MultiProviderAdapter.d.ts.map +1 -1
- package/dist/providers/MultiProviderAdapter.js +2 -1
- package/dist/providers/MultiProviderAdapter.js.map +1 -1
- package/dist/providers/SmartProvider/HyperlaneJsonRpcProvider.d.ts.map +1 -1
- package/dist/providers/SmartProvider/HyperlaneJsonRpcProvider.js +4 -0
- package/dist/providers/SmartProvider/HyperlaneJsonRpcProvider.js.map +1 -1
- package/dist/providers/transactionFeeEstimators.d.ts +16 -9
- package/dist/providers/transactionFeeEstimators.d.ts.map +1 -1
- package/dist/providers/transactionFeeEstimators.js +66 -17
- package/dist/providers/transactionFeeEstimators.js.map +1 -1
- package/dist/providers/transactionFeeEstimators.test.js +153 -1
- package/dist/providers/transactionFeeEstimators.test.js.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.d.ts +14 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js +46 -5
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.d.ts +2 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.d.ts.map +1 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.js +125 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.test.js.map +1 -0
- package/dist/router/HyperlaneRouterDeployer.d.ts +3 -1
- package/dist/router/HyperlaneRouterDeployer.d.ts.map +1 -1
- package/dist/router/HyperlaneRouterDeployer.js +4 -2
- package/dist/router/HyperlaneRouterDeployer.js.map +1 -1
- package/dist/router/types.d.ts +6 -1011
- package/dist/router/types.d.ts.map +1 -1
- package/dist/test/testUtils.d.ts +1 -1
- package/dist/test/testUtils.d.ts.map +1 -1
- package/dist/test/testUtils.js +17 -0
- package/dist/test/testUtils.js.map +1 -1
- package/dist/test/testUtils.test.d.ts +2 -0
- package/dist/test/testUtils.test.d.ts.map +1 -0
- package/dist/test/testUtils.test.js +28 -0
- package/dist/test/testUtils.test.js.map +1 -0
- package/dist/token/EvmWarpModule.d.ts +44 -8
- package/dist/token/EvmWarpModule.d.ts.map +1 -1
- package/dist/token/EvmWarpModule.hardhat-test.js +574 -4
- package/dist/token/EvmWarpModule.hardhat-test.js.map +1 -1
- package/dist/token/EvmWarpModule.js +237 -25
- package/dist/token/EvmWarpModule.js.map +1 -1
- package/dist/token/configUtils.d.ts +17 -7
- package/dist/token/configUtils.d.ts.map +1 -1
- package/dist/token/configUtils.js +108 -0
- package/dist/token/configUtils.js.map +1 -1
- package/dist/token/configUtils.test.js +155 -1
- package/dist/token/configUtils.test.js.map +1 -1
- package/dist/token/deploy.d.ts +3 -1
- package/dist/token/deploy.d.ts.map +1 -1
- package/dist/token/deploy.hardhat-test.js +454 -3
- package/dist/token/deploy.hardhat-test.js.map +1 -1
- package/dist/token/deploy.js +25 -6
- package/dist/token/deploy.js.map +1 -1
- package/dist/token/types.d.ts +169 -2931
- package/dist/token/types.d.ts.map +1 -1
- package/dist/token/types.test.js +48 -0
- package/dist/token/types.test.js.map +1 -1
- package/dist/token/warpCheck.d.ts.map +1 -1
- package/dist/token/warpCheck.js +36 -1
- package/dist/token/warpCheck.js.map +1 -1
- package/dist/utils/ism.d.ts +167 -1
- package/dist/utils/ism.d.ts.map +1 -1
- package/dist/utils/ism.js +412 -2
- package/dist/utils/ism.js.map +1 -1
- package/dist/utils/ism.test.d.ts +2 -0
- package/dist/utils/ism.test.d.ts.map +1 -0
- package/dist/utils/ism.test.js +372 -0
- package/dist/utils/ism.test.js.map +1 -0
- package/package.json +10 -10
|
@@ -13,16 +13,20 @@ import { EvmTokenFeeModule } from '../fee/EvmTokenFeeModule.js';
|
|
|
13
13
|
import { mergeCrossCollateralRouters } from '../fee/crossCollateralUtils.js';
|
|
14
14
|
import { TokenFeeType } from '../fee/types.js';
|
|
15
15
|
import { getEvmHookUpdateTransactions } from '../hook/updates.js';
|
|
16
|
-
import { stripPredicateSubHook } from '../hook/utils.js';
|
|
17
|
-
import { OnchainHookType } from '../hook/types.js';
|
|
16
|
+
import { collectHybridHookNodes, resolveHybridHookNodesToAddress, stripPredicateSubHook, } from '../hook/utils.js';
|
|
17
|
+
import { HookType, OnchainHookType, } from '../hook/types.js';
|
|
18
18
|
import { EvmIsmModule } from '../ism/EvmIsmModule.js';
|
|
19
|
+
import { EvmIsmReader } from '../ism/EvmIsmReader.js';
|
|
20
|
+
import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js';
|
|
21
|
+
import { moduleMatchesConfig } from '../ism/utils.js';
|
|
19
22
|
import { PredicateWrapperDeployer } from '../predicate/PredicateDeployer.js';
|
|
20
23
|
import { resolveRouterMapConfig } from '../router/types.js';
|
|
21
24
|
import { CANCELLER_ROLE, EXECUTOR_ROLE, PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE, } from '../timelock/evm/constants.js';
|
|
22
25
|
import { isDeterministicTimelockReadError } from '../timelock/evm/errors.js';
|
|
26
|
+
import { planChainHybrid, } from '../deploy/warpHybridPlan.js';
|
|
23
27
|
import { scalesEqual } from '../utils/decimals.js';
|
|
24
28
|
import { IsmType } from '../ism/types.js';
|
|
25
|
-
import { extractIsmAndHookFactoryAddresses, ismTreeContainsRateLimited, setRateLimitedIsmRecipient, } from '../utils/ism.js';
|
|
29
|
+
import { DelayedFlowRemoteIsmsSourceType, collectHybridIsmNodes, completeHybridIsmNodes, extractIsmAndHookFactoryAddresses, ismTreeContainsHybridHookIsm, ismTreeContainsRateLimited, normalizeConfig, resolveHybridIsmNodesToAddress, setRateLimitedIsmRecipient, } from '../utils/ism.js';
|
|
26
30
|
import { CCTP_PPM_STORAGE_VERSION, EvmWarpRouteReader, } from './EvmWarpRouteReader.js';
|
|
27
31
|
import { EvmXERC20Module } from './EvmXERC20Module.js';
|
|
28
32
|
import { TokenType } from './config.js';
|
|
@@ -198,7 +202,7 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
198
202
|
return deployedTimelockByConfig;
|
|
199
203
|
}
|
|
200
204
|
/**
|
|
201
|
-
*
|
|
205
|
+
* Plans a Warp Route update as per-chain transaction groups.
|
|
202
206
|
*
|
|
203
207
|
* IMPORTANT — irreversible side effects when expectedConfig includes `predicateWrapper`:
|
|
204
208
|
* The PredicateRouterWrapper contract is deployed on-chain during planning (before this
|
|
@@ -213,11 +217,21 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
213
217
|
* transaction plan can still leave an untracked timelock deployment.
|
|
214
218
|
*
|
|
215
219
|
* @param expectedConfig - The configuration for the token router to be updated.
|
|
216
|
-
*
|
|
220
|
+
* Used by warp apply to assemble one ordered batch for this chain.
|
|
217
221
|
*/
|
|
218
|
-
async
|
|
222
|
+
async updatePhases(expectedConfig, tokenReaderParams) {
|
|
219
223
|
HypTokenRouterConfigSchema.parse(expectedConfig);
|
|
220
224
|
const actualConfig = await this.read();
|
|
225
|
+
const hybridPlan = this.planHybridUpdate(actualConfig, expectedConfig);
|
|
226
|
+
const actualHasDelayedFlowRouter = (typeof actualConfig.hook === 'object' &&
|
|
227
|
+
collectHybridHookNodes(actualConfig.hook).some(({ type }) => type === HookType.DELAYED_FLOW_ROUTER)) ||
|
|
228
|
+
(typeof actualConfig.interchainSecurityModule === 'object' &&
|
|
229
|
+
collectHybridIsmNodes(actualConfig.interchainSecurityModule).some(({ type }) => type === IsmType.DELAYED_FLOW_ROUTER));
|
|
230
|
+
const targetHasDelayedFlowRouter = (typeof expectedConfig.hook === 'object' &&
|
|
231
|
+
collectHybridHookNodes(expectedConfig.hook).some(({ type }) => type === HookType.DELAYED_FLOW_ROUTER)) ||
|
|
232
|
+
(typeof expectedConfig.interchainSecurityModule === 'object' &&
|
|
233
|
+
collectHybridIsmNodes(expectedConfig.interchainSecurityModule).some(({ type }) => type === IsmType.DELAYED_FLOW_ROUTER));
|
|
234
|
+
const removesDelayedFlowRouter = actualHasDelayedFlowRouter && !targetHasDelayedFlowRouter;
|
|
221
235
|
expectedConfig = await this.configWithTimelockProxyAdminOwner(actualConfig, expectedConfig);
|
|
222
236
|
const transactions = [];
|
|
223
237
|
let xerc20Txs = [];
|
|
@@ -243,9 +257,35 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
243
257
|
* because GasRouter requires routers to be enrolled before setting destination gas
|
|
244
258
|
* 3. createHookAndPredicateUpdateTxs() handles hook + predicate wrapper together so the
|
|
245
259
|
* pending new hook address is threaded through without leaking into other method signatures
|
|
260
|
+
* 4. The hook transactions are pushed BEFORE the ISM transactions, whatever the order they
|
|
261
|
+
* are built in — see the comment at the push
|
|
246
262
|
*/
|
|
247
263
|
const upgradeTxs = await this.upgradeWarpRouteImplementationTx(actualConfig, expectedConfig);
|
|
248
|
-
|
|
264
|
+
// Pure hybrid validation already ran immediately after the read. Resolve
|
|
265
|
+
// (and, when needed, deploy) the shared instance for both config trees.
|
|
266
|
+
const hybrid = await this.resolveHybridInstance(actualConfig, expectedConfig, hybridPlan);
|
|
267
|
+
// With the shared address known, both trees reference it as an ordinary
|
|
268
|
+
// address child and the existing ISM/hook modules deploy whatever parents
|
|
269
|
+
// remain. Nothing downstream needs to know a hybrid is involved.
|
|
270
|
+
const resolvedConfig = hybrid
|
|
271
|
+
? {
|
|
272
|
+
...expectedConfig,
|
|
273
|
+
interchainSecurityModule: resolveHybridIsmNodesToAddress(hybrid.ismTree, hybrid.address),
|
|
274
|
+
hook: resolveHybridHookNodesToAddress(hybrid.hookTree, hybrid.address),
|
|
275
|
+
}
|
|
276
|
+
: expectedConfig;
|
|
277
|
+
const ismTxs = await this.createIsmUpdateTxs(actualConfig, resolvedConfig, hybrid?.address);
|
|
278
|
+
// Built after the ISM step, which may deploy the tree, but ORDERED BEFORE
|
|
279
|
+
// it: a batch executes sequentially with no rollback, so a failure between
|
|
280
|
+
// the two installs is a state the route can be left in. Hook first leaves
|
|
281
|
+
// the previous ISM verifying inbound messages. ISM first can leave a hybrid
|
|
282
|
+
// hook/ISM gating delivery while nothing drives its postDispatch — no
|
|
283
|
+
// preverification is ever sent for the messages dispatched in that window,
|
|
284
|
+
// and none can be sent afterwards, so they are permanently undeliverable.
|
|
285
|
+
// Returned in their own buckets so callers can choose the safe order within
|
|
286
|
+
// this chain's batch, including reversing it when removing delayed flow.
|
|
287
|
+
const hookTxs = await this.createHookAndPredicateUpdateTxs(actualConfig, resolvedConfig, hybrid?.address);
|
|
288
|
+
transactions.push(...this.createFeeHookUpdateTxs(actualConfig, expectedConfig), ...this.createUnenrollRemoteRoutersUpdateTxs(actualConfig, expectedConfig), ...this.createEnrollRemoteRoutersUpdateTxs(actualConfig, expectedConfig),
|
|
249
289
|
// MC unenroll before enroll for consistency with remote routers.
|
|
250
290
|
// MC enrollment must come before gas setting so that MC-only domains
|
|
251
291
|
...this.createUnenrollCrossCollateralRoutersTxs(actualConfig, expectedConfig), ...this.createEnrollCrossCollateralRoutersTxs(actualConfig, expectedConfig), ...this.createSetDestinationGasUpdateTxs(actualConfig, expectedConfig), ...this.createAddRebalancersUpdateTxs(actualConfig, expectedConfig), ...this.createRemoveRebalancersUpdateTxs(actualConfig, expectedConfig), ...(await this.createAddAllowedBridgesUpdateTxs(actualConfig, expectedConfig)), ...this.createRemoveBridgesTxs(actualConfig, expectedConfig), ...this.createAddRebalanceTargetsUpdateTxs(actualConfig, expectedConfig), ...this.createRemoveRebalanceTargetsTxs(actualConfig, expectedConfig), ...this.createSetRecipientsUpdateTxs(actualConfig, expectedConfig), ...this.createRemoveRecipientsTxs(actualConfig, expectedConfig), ...(await this.createRevokeStaleBridgeAllowancesTxs(actualConfig, expectedConfig, upgradeTxs.length > 0)), ...this.createAddRemoteOutputAssetsTxs(actualConfig, expectedConfig), ...this.createRemoveRemoteOutputAssetsTxs(actualConfig, expectedConfig), ...this.createUpdateEverclearFeeParamsTxs(actualConfig, expectedConfig), ...this.createRemoveEverclearFeeParamsTxs(actualConfig, expectedConfig), ...this.createSetMaxFeePpmTxs(actualConfig, expectedConfig), ...xerc20Txs,
|
|
@@ -256,21 +296,173 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
256
296
|
// Ownership/proxyAdmin must always execute last; returned separately so callers
|
|
257
297
|
// can place feeTxs between main txs and ownership (see update() below).
|
|
258
298
|
const ownershipTxs = [
|
|
299
|
+
...(hybrid?.ownershipTxs ?? []),
|
|
259
300
|
...this.createOwnershipUpdateTxs(actualConfig, expectedConfig),
|
|
260
301
|
...proxyAdminUpdateTxs(this.chainId, this.args.addresses.deployedTokenRoute, actualConfig, expectedConfig),
|
|
261
302
|
];
|
|
262
|
-
return {
|
|
303
|
+
return {
|
|
304
|
+
upgradeTxs,
|
|
305
|
+
instanceTxs: hybrid?.instanceTxs ?? [],
|
|
306
|
+
hookTxs,
|
|
307
|
+
ismTxs,
|
|
308
|
+
txs: transactions,
|
|
309
|
+
feeTxs,
|
|
310
|
+
ownershipTxs,
|
|
311
|
+
hybridIsm: hybrid?.address,
|
|
312
|
+
removesDelayedFlowRouter,
|
|
313
|
+
};
|
|
263
314
|
}
|
|
264
315
|
/**
|
|
265
|
-
*
|
|
266
|
-
* transaction array suitable for a single submitter.
|
|
316
|
+
* Main-compatible split: router-owner work, fee-owner work, then ownership.
|
|
267
317
|
*/
|
|
318
|
+
async updateSplit(expectedConfig, tokenReaderParams) {
|
|
319
|
+
const { upgradeTxs, instanceTxs, hookTxs, ismTxs, txs, feeTxs, ownershipTxs, removesDelayedFlowRouter, } = await this.updatePhases(expectedConfig, tokenReaderParams);
|
|
320
|
+
return {
|
|
321
|
+
txs: removesDelayedFlowRouter
|
|
322
|
+
? [...upgradeTxs, ...instanceTxs, ...ismTxs, ...hookTxs, ...txs]
|
|
323
|
+
: [...upgradeTxs, ...instanceTxs, ...hookTxs, ...ismTxs, ...txs],
|
|
324
|
+
feeTxs,
|
|
325
|
+
ownershipTxs,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/** Returns a flat, ordered transaction array suitable for one submitter. */
|
|
268
329
|
async update(expectedConfig, tokenReaderParams) {
|
|
269
330
|
const { txs, feeTxs, ownershipTxs } = await this.updateSplit(expectedConfig, tokenReaderParams);
|
|
270
331
|
// feeTxs (fee-contract-owner calls) must come before ownershipTxs so they
|
|
271
332
|
// execute before the router owner changes.
|
|
272
333
|
return [...txs, ...feeTxs, ...ownershipTxs];
|
|
273
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Validates hybrid composition and fee-hook transitions before update
|
|
337
|
+
* planning reaches any operation that may deploy a contract.
|
|
338
|
+
*/
|
|
339
|
+
planHybridUpdate(actualConfig, expectedConfig) {
|
|
340
|
+
// An omitted feeHook is intentionally left unchanged by warp apply. Plan
|
|
341
|
+
// against the effective value so an already-installed ERC20 fee hook
|
|
342
|
+
// cannot bypass hybrid hook composition validation.
|
|
343
|
+
const effectiveFeeHook = expectedConfig.feeHook ?? actualConfig.feeHook;
|
|
344
|
+
const plan = planChainHybrid({
|
|
345
|
+
multiProvider: this.multiProvider,
|
|
346
|
+
chain: this.chainName,
|
|
347
|
+
config: { ...expectedConfig, feeHook: effectiveFeeHook },
|
|
348
|
+
});
|
|
349
|
+
if (!plan)
|
|
350
|
+
return undefined;
|
|
351
|
+
if (plan.node.type === IsmType.DELAYED_FLOW_ROUTER &&
|
|
352
|
+
actualConfig.feeHook &&
|
|
353
|
+
!isZeroishAddress(actualConfig.feeHook) &&
|
|
354
|
+
expectedConfig.feeHook &&
|
|
355
|
+
isZeroishAddress(expectedConfig.feeHook)) {
|
|
356
|
+
const actualHook = actualConfig.hook;
|
|
357
|
+
const expectedHook = expectedConfig.hook;
|
|
358
|
+
const installedHybrid = typeof actualHook === 'object'
|
|
359
|
+
? collectHybridHookNodes(actualHook)[0]
|
|
360
|
+
: undefined;
|
|
361
|
+
const delayedFlowHookStaysInstalled = installedHybrid?.type === HookType.DELAYED_FLOW_ROUTER &&
|
|
362
|
+
installedHybrid.duration === plan.node.duration &&
|
|
363
|
+
installedHybrid.thresholdBps === plan.node.thresholdBps &&
|
|
364
|
+
installedHybrid.maxDelay === plan.node.maxDelay &&
|
|
365
|
+
(plan.node.warpRouter === undefined ||
|
|
366
|
+
(!!installedHybrid.warpRouter &&
|
|
367
|
+
eqAddress(installedHybrid.warpRouter, plan.node.warpRouter))) &&
|
|
368
|
+
typeof expectedHook === 'object' &&
|
|
369
|
+
deepEquals(normalizeConfig(resolveHybridHookNodesToAddress(actualHook, constants.AddressZero)), normalizeConfig(resolveHybridHookNodesToAddress(expectedHook, constants.AddressZero)));
|
|
370
|
+
assert(delayedFlowHookStaysInstalled, `Cannot introduce, replace, or recompose ${IsmType.DELAYED_FLOW_ROUTER} on ${this.chainName} while clearing existing feeHook ${actualConfig.feeHook} in the same warp apply. Hook installation runs before router updates, leaving the delayed-flow hook active while ERC20 fee metadata still prevents its native control dispatch. Apply feeHook address(0) without changing the hook first, execute that update, then apply the ${IsmType.DELAYED_FLOW_ROUTER} config.`);
|
|
371
|
+
}
|
|
372
|
+
return plan;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Reuses or deploys the one hybrid leaf declared on both config surfaces.
|
|
376
|
+
* Parent trees only ever receive the returned address, preventing duplicate
|
|
377
|
+
* bucket state. Instance mutations are returned separately so counterpart
|
|
378
|
+
* enrollment can precede either router-surface change in this chain's batch.
|
|
379
|
+
*/
|
|
380
|
+
async resolveHybridInstance(actualConfig, expectedConfig, plan) {
|
|
381
|
+
if (!plan)
|
|
382
|
+
return undefined;
|
|
383
|
+
const completed = completeHybridIsmNodes(plan.node, this.args.addresses.deployedTokenRoute, { type: DelayedFlowRemoteIsmsSourceType.Deferred }, actualConfig.owner, this.multiProvider);
|
|
384
|
+
assert(typeof completed === 'object' &&
|
|
385
|
+
(completed.type === IsmType.NET_FLOW_RATE_LIMITED ||
|
|
386
|
+
completed.type === IsmType.DELAYED_FLOW_ROUTER), `Failed to complete hybrid hook/ISM config on ${this.chainName}`);
|
|
387
|
+
// Every owner-gated mutation is signed by the router's current owner.
|
|
388
|
+
// Ownership moves to the target owner only in the final ownership phase.
|
|
389
|
+
const desired = {
|
|
390
|
+
...completed,
|
|
391
|
+
owner: actualConfig.owner,
|
|
392
|
+
};
|
|
393
|
+
const actualHook = actualConfig.hook;
|
|
394
|
+
const hookNodes = typeof actualHook === 'object' ? collectHybridHookNodes(actualHook) : [];
|
|
395
|
+
const actualIsm = actualConfig.interchainSecurityModule;
|
|
396
|
+
const ismNodes = typeof actualIsm === 'object' ? collectHybridIsmNodes(actualIsm) : [];
|
|
397
|
+
assert(hookNodes.length <= 1 && ismNodes.length <= 1, `Expected at most one installed hybrid hook/ISM on each surface of ${this.chainName}, found ${hookNodes.length} under hook and ${ismNodes.length} under interchainSecurityModule`);
|
|
398
|
+
// Prefer the hook: submission can be interrupted after its update but before
|
|
399
|
+
// the ISM update, leaving the new leaf installed while the ISM still has the
|
|
400
|
+
// old tree. A later apply must reuse that new leaf.
|
|
401
|
+
const candidate = hookNodes[0] ?? ismNodes[0];
|
|
402
|
+
const currentAddress = candidate &&
|
|
403
|
+
'address' in candidate &&
|
|
404
|
+
typeof candidate.address === 'string'
|
|
405
|
+
? candidate.address
|
|
406
|
+
: undefined;
|
|
407
|
+
if (currentAddress) {
|
|
408
|
+
const current = await new EvmIsmReader(this.multiProvider, this.chainName).deriveIsmConfig(currentAddress);
|
|
409
|
+
assert(typeof current === 'object' &&
|
|
410
|
+
(current.type === IsmType.NET_FLOW_RATE_LIMITED ||
|
|
411
|
+
current.type === IsmType.DELAYED_FLOW_ROUTER), `Expected ${currentAddress} on ${this.chainName} to be a hybrid hook/ISM`);
|
|
412
|
+
const ownedByRouter = current.owner && eqAddress(current.owner, actualConfig.owner);
|
|
413
|
+
const alreadyTransferred = !ownedByRouter &&
|
|
414
|
+
current.owner &&
|
|
415
|
+
expectedConfig.owner &&
|
|
416
|
+
eqAddress(current.owner, expectedConfig.owner);
|
|
417
|
+
assert(ownedByRouter || alreadyTransferred, `Hybrid hook/ISM ${currentAddress} on ${this.chainName} is owned by ${current.owner}, but its warp router is owned by ${actualConfig.owner}. Warp apply uses the router's submitter for every non-fee mutation, so align the owners before applying this config.`);
|
|
418
|
+
if (alreadyTransferred) {
|
|
419
|
+
const factory = HyperlaneIsmFactory.fromAddressesMap({ [this.chainName]: this.args.addresses }, this.multiProvider, this.ccipContractCache, this.contractVerifier);
|
|
420
|
+
const matchesFinalConfig = await moduleMatchesConfig(this.chainName, currentAddress, { ...completed, owner: expectedConfig.owner }, this.multiProvider, factory.getContracts(this.chainName), actualConfig.mailbox);
|
|
421
|
+
assert(matchesFinalConfig, `Hybrid hook/ISM ${currentAddress} on ${this.chainName} was already transferred to the target owner ${expectedConfig.owner}, but its config does not match the target. Complete or revert the interrupted ownership transfer before changing its config.`);
|
|
422
|
+
return {
|
|
423
|
+
address: currentAddress,
|
|
424
|
+
ismTree: plan.ismTree,
|
|
425
|
+
hookTree: plan.hookTree,
|
|
426
|
+
instanceTxs: [],
|
|
427
|
+
ownershipTxs: [],
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const module = new EvmIsmModule(this.multiProvider, {
|
|
431
|
+
chain: this.chainName,
|
|
432
|
+
config: current,
|
|
433
|
+
addresses: {
|
|
434
|
+
...this.args.addresses,
|
|
435
|
+
mailbox: actualConfig.mailbox,
|
|
436
|
+
deployedIsm: currentAddress,
|
|
437
|
+
},
|
|
438
|
+
}, this.ccipContractCache, this.contractVerifier);
|
|
439
|
+
const txs = await module.updateDeployedInstance(desired);
|
|
440
|
+
const { deployedIsm } = module.serialize();
|
|
441
|
+
assert(deployedIsm, `Failed to resolve hybrid hook/ISM on ${this.chainName}`);
|
|
442
|
+
const ownershipTxs = transferOwnershipTransactions(this.chainId, deployedIsm, { owner: actualConfig.owner }, { owner: expectedConfig.owner }, `hybrid hook/ISM on ${this.chainName}`);
|
|
443
|
+
return {
|
|
444
|
+
address: deployedIsm,
|
|
445
|
+
ismTree: plan.ismTree,
|
|
446
|
+
hookTree: plan.hookTree,
|
|
447
|
+
instanceTxs: txs,
|
|
448
|
+
ownershipTxs,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const factory = HyperlaneIsmFactory.fromAddressesMap({ [this.chainName]: this.args.addresses }, this.multiProvider, this.ccipContractCache, this.contractVerifier);
|
|
452
|
+
const deployed = await factory.deployInternal({
|
|
453
|
+
destination: this.chainName,
|
|
454
|
+
config: desired,
|
|
455
|
+
mailbox: actualConfig.mailbox,
|
|
456
|
+
});
|
|
457
|
+
const ownershipTxs = transferOwnershipTransactions(this.chainId, deployed.address, { owner: actualConfig.owner }, { owner: expectedConfig.owner }, `hybrid hook/ISM on ${this.chainName}`);
|
|
458
|
+
return {
|
|
459
|
+
address: deployed.address,
|
|
460
|
+
ismTree: plan.ismTree,
|
|
461
|
+
hookTree: plan.hookTree,
|
|
462
|
+
instanceTxs: [],
|
|
463
|
+
ownershipTxs,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
274
466
|
/**
|
|
275
467
|
* Create a transaction to update the remote routers for the Warp Route contract.
|
|
276
468
|
*
|
|
@@ -907,14 +1099,14 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
907
1099
|
* @param expectedConfig - The expected token router configuration, including the ISM configuration.
|
|
908
1100
|
* @returns Ethereum transaction that need to be executed to update the ISM configuration.
|
|
909
1101
|
*/
|
|
910
|
-
async createIsmUpdateTxs(actualConfig, expectedConfig) {
|
|
1102
|
+
async createIsmUpdateTxs(actualConfig, expectedConfig, opaqueHybridAddress) {
|
|
911
1103
|
const updateTransactions = [];
|
|
912
1104
|
if (!expectedConfig.interchainSecurityModule) {
|
|
913
1105
|
return [];
|
|
914
1106
|
}
|
|
915
1107
|
const actualDeployedIsm = derivedIsmAddress(actualConfig);
|
|
916
1108
|
// Try to update (may also deploy) Ism with the expected config
|
|
917
|
-
const { deployedIsm: expectedDeployedIsm, updateTransactions: ismUpdateTransactions, } = await this.deployOrUpdateIsm(actualConfig, expectedConfig);
|
|
1109
|
+
const { deployedIsm: expectedDeployedIsm, updateTransactions: ismUpdateTransactions, } = await this.deployOrUpdateIsm(actualConfig, expectedConfig, opaqueHybridAddress);
|
|
918
1110
|
// If an ISM is updated in-place, push the update txs
|
|
919
1111
|
updateTransactions.push(...ismUpdateTransactions);
|
|
920
1112
|
// If a new ISM is deployed, push the setInterchainSecurityModule tx
|
|
@@ -936,7 +1128,7 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
936
1128
|
* Deploys hook updates and predicate wrapper together so the post-update hook address
|
|
937
1129
|
* is available to deployAndConfigure without a stale on-chain read.
|
|
938
1130
|
*/
|
|
939
|
-
async createHookAndPredicateUpdateTxs(actualConfig, expectedConfig) {
|
|
1131
|
+
async createHookAndPredicateUpdateTxs(actualConfig, expectedConfig, opaqueHybridAddress) {
|
|
940
1132
|
let hookTransactions = [];
|
|
941
1133
|
let newHookAddress;
|
|
942
1134
|
// Explicit type annotation narrows away the undefined that TypeScript infers
|
|
@@ -957,6 +1149,10 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
957
1149
|
? expectedConfig.predicateWrapper
|
|
958
1150
|
: undefined;
|
|
959
1151
|
const needsPredicateRemoval = actualPredicateWrapper != null && !expectedPredicateWrapper;
|
|
1152
|
+
const needsDelayedFlowRemoval = typeof expectedConfig.hook === 'string' &&
|
|
1153
|
+
isZeroishAddress(expectedConfig.hook) &&
|
|
1154
|
+
typeof actualHook === 'object' &&
|
|
1155
|
+
collectHybridHookNodes(actualHook).some(({ type }) => type === HookType.DELAYED_FLOW_ROUTER);
|
|
960
1156
|
// Treat a zero-address hook the same as "no explicit hook": expandWarpDeployConfig
|
|
961
1157
|
// sets hook: zeroAddress as a default when the user config omits the hook field.
|
|
962
1158
|
// EvmHookModule.update(zeroAddress) returns [] early without updating deployedHook,
|
|
@@ -995,20 +1191,24 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
995
1191
|
multiProvider: this.multiProvider,
|
|
996
1192
|
proxyAdminAddress,
|
|
997
1193
|
rateLimitedSender: this.args.addresses.deployedTokenRoute,
|
|
1194
|
+
opaqueHybridAddresses: opaqueHybridAddress
|
|
1195
|
+
? [opaqueHybridAddress]
|
|
1196
|
+
: undefined,
|
|
998
1197
|
});
|
|
999
1198
|
hookTransactions = result.transactions;
|
|
1000
1199
|
newHookAddress = result.newHookAddress;
|
|
1001
1200
|
}
|
|
1002
|
-
// Predicate
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1006
|
-
if (needsPredicateRemoval && !newHookAddress) {
|
|
1201
|
+
// Predicate and delayed-flow removals clear the custom hook when no
|
|
1202
|
+
// replacement was deployed. Delayed flow requires an explicit zero target;
|
|
1203
|
+
// omission retains the existing hook like every ordinary update.
|
|
1204
|
+
if ((needsPredicateRemoval || needsDelayedFlowRemoval) && !newHookAddress) {
|
|
1007
1205
|
const currentAddress = typeof actualHook === 'string' ? actualHook : actualHook.address;
|
|
1008
1206
|
if (!isZeroishAddress(currentAddress)) {
|
|
1009
|
-
this.logger.debug({ chain: this.chainName }, 'Removing
|
|
1207
|
+
this.logger.debug({ chain: this.chainName }, 'Removing custom hook: generating setHook(zero)');
|
|
1010
1208
|
hookTransactions.push({
|
|
1011
|
-
annotation:
|
|
1209
|
+
annotation: needsDelayedFlowRemoval
|
|
1210
|
+
? 'Remove delayed-flow hook (router will use mailbox default)'
|
|
1211
|
+
: 'Remove predicate wrapper: clear custom hook (router will use mailbox default)',
|
|
1012
1212
|
chainId: this.chainId,
|
|
1013
1213
|
to: this.args.addresses.deployedTokenRoute,
|
|
1014
1214
|
data: MailboxClient__factory.createInterface().encodeFunctionData('setHook', [constants.AddressZero]),
|
|
@@ -1329,7 +1529,7 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
1329
1529
|
*
|
|
1330
1530
|
* @returns Object with deployedIsm address, and update Transactions
|
|
1331
1531
|
*/
|
|
1332
|
-
async deployOrUpdateIsm(actualConfig, expectedConfig) {
|
|
1532
|
+
async deployOrUpdateIsm(actualConfig, expectedConfig, opaqueHybridAddress) {
|
|
1333
1533
|
assert(expectedConfig.interchainSecurityModule, 'Ism derived incorrectly');
|
|
1334
1534
|
if (typeof expectedConfig.interchainSecurityModule === 'string' &&
|
|
1335
1535
|
isZeroishAddress(expectedConfig.interchainSecurityModule)) {
|
|
@@ -1348,15 +1548,27 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
1348
1548
|
// Primary default: the warp-route owner (matches deploy path in token/deploy.ts).
|
|
1349
1549
|
// Fallback: the current on-chain ISM owner (same-type, same-owner in-place update).
|
|
1350
1550
|
let expectedIsm = expectedConfig.interchainSecurityModule;
|
|
1351
|
-
if (typeof expectedIsm === 'object'
|
|
1352
|
-
ismTreeContainsRateLimited(expectedIsm)) {
|
|
1551
|
+
if (typeof expectedIsm === 'object') {
|
|
1353
1552
|
const actualIsm = actualConfig.interchainSecurityModule;
|
|
1354
1553
|
const onChainOwner = typeof actualIsm === 'object' && 'owner' in actualIsm
|
|
1355
1554
|
? actualIsm.owner
|
|
1356
1555
|
: undefined;
|
|
1357
1556
|
const defaultOwner = expectedConfig.owner ??
|
|
1358
1557
|
(typeof onChainOwner === 'string' ? onChainOwner : undefined);
|
|
1359
|
-
|
|
1558
|
+
if (ismTreeContainsRateLimited(expectedIsm)) {
|
|
1559
|
+
expectedIsm = setRateLimitedIsmRecipient(expectedIsm, this.args.addresses.deployedTokenRoute, defaultOwner);
|
|
1560
|
+
}
|
|
1561
|
+
// Hybrid hook/ISMs need the paired warp router, which in warp-route
|
|
1562
|
+
// context is always this token — configs routinely omit it (it is only
|
|
1563
|
+
// required for standalone ISM deploys). Injected independently of the
|
|
1564
|
+
// owner: an unresolvable owner would otherwise leave `warpRouter`
|
|
1565
|
+
// missing and the update would plan against an incomplete config.
|
|
1566
|
+
// `remoteIsms` is deferred: cross-chain enrollment needs every chain's
|
|
1567
|
+
// instance address, so it is reconciled by a separate pass that must
|
|
1568
|
+
// stay its only writer.
|
|
1569
|
+
if (ismTreeContainsHybridHookIsm(expectedIsm)) {
|
|
1570
|
+
expectedIsm = completeHybridIsmNodes(expectedIsm, this.args.addresses.deployedTokenRoute, { type: DelayedFlowRemoteIsmsSourceType.Deferred }, defaultOwner, this.multiProvider);
|
|
1571
|
+
}
|
|
1360
1572
|
}
|
|
1361
1573
|
const ismModule = new EvmIsmModule(this.multiProvider, {
|
|
1362
1574
|
chain: this.args.chain,
|
|
@@ -1368,7 +1580,7 @@ export class EvmWarpModule extends HyperlaneModule {
|
|
|
1368
1580
|
},
|
|
1369
1581
|
}, this.ccipContractCache, this.contractVerifier);
|
|
1370
1582
|
this.logger.info(`Comparing target ISM config with ${this.args.chain} chain`);
|
|
1371
|
-
const updateTransactions = await ismModule.update(expectedIsm);
|
|
1583
|
+
const updateTransactions = await ismModule.update(expectedIsm, opaqueHybridAddress ? [opaqueHybridAddress] : []);
|
|
1372
1584
|
const { deployedIsm } = ismModule.serialize();
|
|
1373
1585
|
return { deployedIsm, updateTransactions };
|
|
1374
1586
|
}
|