@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
package/dist/deploy/warp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MailboxClient__factory, ProxyAdmin__factory, } from '@hyperlane-xyz/core';
|
|
1
|
+
import { DelayedFlowRouterHookIsm__factory, MailboxClient__factory, Mailbox__factory, ProxyAdmin__factory, } from '@hyperlane-xyz/core';
|
|
2
2
|
import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js';
|
|
3
3
|
import { createHookWriter, createIsmWriter, createWarpTokenWriter, validateIsmConfig, } from '@hyperlane-xyz/deploy-sdk';
|
|
4
4
|
import { ProtocolType } from '@hyperlane-xyz/provider-sdk';
|
|
@@ -6,23 +6,27 @@ import { ArtifactState } from '@hyperlane-xyz/provider-sdk/artifact';
|
|
|
6
6
|
import { hookConfigToArtifact, } from '@hyperlane-xyz/provider-sdk/hook';
|
|
7
7
|
import { ismConfigToArtifact, } from '@hyperlane-xyz/provider-sdk/ism';
|
|
8
8
|
import { DEFAULT_CROSS_COLLATERAL_FEE_ROUTER_KEY, TokenType as ProviderTokenType, warpConfigToArtifact, } from '@hyperlane-xyz/provider-sdk/warp';
|
|
9
|
-
import { addressToBytes32, assert, isEVMLike, isNullish, isObjEmpty, mapAllSettled, mustGet, objFilter, objKeys, objMap, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils';
|
|
9
|
+
import { addressToBytes32, assert, eqAddress, isEVMLike, isNullish, isObjEmpty, isZeroishAddress, mapAllSettled, mustGet, objFilter, objKeys, objMap, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils';
|
|
10
10
|
import { ExplorerLicenseType } from '../block-explorer/etherscan.js';
|
|
11
11
|
import { CCIPContractCache } from '../ccip/utils.js';
|
|
12
12
|
import { tokenFeeInputToFeeConfig } from '../fee/feeConfigMapping.js';
|
|
13
13
|
import { EvmHookModule } from '../hook/EvmHookModule.js';
|
|
14
|
-
import { hookTreeContainsRateLimited } from '../hook/utils.js';
|
|
14
|
+
import { hookTreeContainsRateLimited, mapHybridHookNodes, } from '../hook/utils.js';
|
|
15
15
|
import { EvmIsmModule } from '../ism/EvmIsmModule.js';
|
|
16
|
+
import { EvmIsmReader } from '../ism/EvmIsmReader.js';
|
|
16
17
|
import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js';
|
|
18
|
+
import { IsmType, } from '../ism/types.js';
|
|
17
19
|
import { altVmChainLookup } from '../metadata/ChainMetadataManager.js';
|
|
18
20
|
import { resolveRouterMapConfig, } from '../router/types.js';
|
|
19
21
|
import { EvmWarpModule } from '../token/EvmWarpModule.js';
|
|
20
22
|
import { MAX_GAS_OVERHEAD, TokenType, gasOverhead } from '../token/config.js';
|
|
21
23
|
import { hypERC20factories } from '../token/contracts.js';
|
|
22
24
|
import { HypERC20Deployer, HypERC721Deployer } from '../token/deploy.js';
|
|
23
|
-
import { assertTimelockConfigHasNoProxyAdminOwnerOverride, } from '../token/types.js';
|
|
24
|
-
import {
|
|
25
|
+
import { assertTimelockConfigHasNoProxyAdminOwnerOverride, WarpRouteDeployConfigMailboxRequiredSchema, } from '../token/types.js';
|
|
26
|
+
import { throwIfNotMissingSelector } from '../utils/contract.js';
|
|
27
|
+
import { canonicalizeRemoteIsms, collectHybridIsmNodes, extractIsmAndHookFactoryAddresses, ismTreeContainsRateLimited, mapHybridIsmNodes, resolveDelayedFlowRemoteIsms, setRateLimitedIsmRecipient, } from '../utils/ism.js';
|
|
25
28
|
import { HyperlaneProxyFactoryDeployer } from './HyperlaneProxyFactoryDeployer.js';
|
|
29
|
+
import { hybridLeafDeployConfig, planWarpRouteHybrids, resolveHybridTrees, } from './warpHybridPlan.js';
|
|
26
30
|
import { ContractVerifier } from './verify/ContractVerifier.js';
|
|
27
31
|
const SUPPORTED_ALTVM_TOKEN_TYPES = new Set([
|
|
28
32
|
TokenType.synthetic,
|
|
@@ -168,39 +172,121 @@ class RateLimitedHookERC20Deployer extends HypERC20Deployer {
|
|
|
168
172
|
await this.preTransferFn(objMap(contractsMap, (_, contracts) => getRouter(contracts).address));
|
|
169
173
|
}
|
|
170
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Deploys a WHOLE warp route: its routers, plus the ISM and hook trees they
|
|
177
|
+
* install.
|
|
178
|
+
*
|
|
179
|
+
* Everything decidable from the config is decided before any gas is spent —
|
|
180
|
+
* the full `IsmConfigSchema`/`HookConfigSchema` refinement over both trees, the
|
|
181
|
+
* hybrid hook/ISM pairing (planWarpRouteHybrids), and the route-scoped
|
|
182
|
+
* delayed-flow invariants. The route-scoped ones read the config's chain set as
|
|
183
|
+
* "the route", so they belong to this entry point alone: shrinking the config
|
|
184
|
+
* does not narrow them, it enlarges what counts as outside the route. Use
|
|
185
|
+
* `executeWarpRouteExtensionDeploy` for the chains an extension adds.
|
|
186
|
+
*/
|
|
171
187
|
export async function executeWarpDeploy(warpDeployConfig, multiProvider, altVmSigners, registryAddresses, apiKeys) {
|
|
172
188
|
assertWarpConfigTimelocksSupportedByProtocols({
|
|
173
189
|
multiProvider,
|
|
174
190
|
warpDeployConfig,
|
|
175
191
|
});
|
|
192
|
+
WarpRouteDeployConfigMailboxRequiredSchema.parse(warpDeployConfig);
|
|
193
|
+
const args = {
|
|
194
|
+
warpDeployConfig,
|
|
195
|
+
multiProvider,
|
|
196
|
+
altVmSigners,
|
|
197
|
+
registryAddresses,
|
|
198
|
+
apiKeys,
|
|
199
|
+
};
|
|
200
|
+
const hybridPlan = planWarpRouteHybrids({ multiProvider, warpDeployConfig });
|
|
201
|
+
assertDelayedFlowRouteCoverage({ multiProvider, warpDeployConfig });
|
|
202
|
+
await assertDelayedFlowMailboxNonces(multiProvider, warpDeployConfig);
|
|
203
|
+
return deployWarpRouteChains(args, hybridPlan);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Deploys the chains an extension adds to an EXISTING route.
|
|
207
|
+
*
|
|
208
|
+
* Same engine as `executeWarpDeploy`, minus the invariants that measure the
|
|
209
|
+
* route by the config's own chain set — the caller holds the full route config
|
|
210
|
+
* and has already checked those (runWarpRouteApply). Everything per chain still
|
|
211
|
+
* runs here, so an extension cannot deploy a chain whose composition can never
|
|
212
|
+
* work.
|
|
213
|
+
*
|
|
214
|
+
* A separate entry point rather than a flag: which validation applies is a
|
|
215
|
+
* property of what the caller is deploying, not something to infer from the
|
|
216
|
+
* shape of the config it happens to pass.
|
|
217
|
+
*/
|
|
218
|
+
export async function executeWarpRouteExtensionDeploy(warpDeployConfig, multiProvider, altVmSigners, registryAddresses, apiKeys) {
|
|
219
|
+
assertWarpConfigTimelocksSupportedByProtocols({
|
|
220
|
+
multiProvider,
|
|
221
|
+
warpDeployConfig,
|
|
222
|
+
});
|
|
223
|
+
WarpRouteDeployConfigMailboxRequiredSchema.parse(warpDeployConfig);
|
|
224
|
+
const args = {
|
|
225
|
+
warpDeployConfig,
|
|
226
|
+
multiProvider,
|
|
227
|
+
altVmSigners,
|
|
228
|
+
registryAddresses,
|
|
229
|
+
apiKeys,
|
|
230
|
+
};
|
|
231
|
+
const hybridPlan = planWarpRouteHybrids({ multiProvider, warpDeployConfig });
|
|
232
|
+
assertDelayedFlowLegCoverage(warpDeployConfig);
|
|
233
|
+
await assertDelayedFlowMailboxNonces(multiProvider, warpDeployConfig);
|
|
234
|
+
return deployWarpRouteChains(args, hybridPlan);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The staged deploy engine shared by both entry points. Called only after the
|
|
238
|
+
* whole config has been parsed and validated, so nothing here re-decides
|
|
239
|
+
* whether the route is deployable — it just deploys it in an order that never
|
|
240
|
+
* leaves an unsafe route reachable:
|
|
241
|
+
*
|
|
242
|
+
* 1. routers are deployed BARE on every chain that installs a hybrid: the
|
|
243
|
+
* hybrid constructors read the live router's `token()` / `mailbox()`, so the
|
|
244
|
+
* router has to exist first, and it must not be usable in the meantime;
|
|
245
|
+
* 2. the hybrid leaf is deployed exactly once per chain and bound to that
|
|
246
|
+
* router;
|
|
247
|
+
* 3. DELAYED_FLOW_ROUTER counterparts are enrolled once every chain's instance
|
|
248
|
+
* address is known, while the deployer still owns the fresh instances;
|
|
249
|
+
* 4. the leaf address replaces the hybrid node in BOTH trees, and the remaining
|
|
250
|
+
* parents are deployed through the ordinary modules;
|
|
251
|
+
* 5. hooks are installed route-wide, then read back, and only then are the ISMs
|
|
252
|
+
* installed — a router whose hybrid ISM is live before its hook is one that
|
|
253
|
+
* rejects every delivery, because nothing sent the preverification.
|
|
254
|
+
*
|
|
255
|
+
* Router enrollment and ownership transfer happen after this returns
|
|
256
|
+
* (enrollCrossChainRouters), so no transfer can be dispatched through a
|
|
257
|
+
* half-wired router.
|
|
258
|
+
*/
|
|
259
|
+
async function deployWarpRouteChains({ warpDeployConfig, multiProvider, altVmSigners, registryAddresses, apiKeys, }, hybridPlan) {
|
|
176
260
|
const contractVerifier = new ContractVerifier(multiProvider, apiKeys, coreBuildArtifact, ExplorerLicenseType.MIT);
|
|
177
261
|
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider, contractVerifier);
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
// transferred so that setInterchainSecurityModule succeeds regardless of config.owner.
|
|
184
|
-
const rateLimitedSnapshot = {};
|
|
262
|
+
// RATE_LIMITED ISM trees are deployed after the router exists: the
|
|
263
|
+
// constructor takes the token as `recipient`. Unlike the hybrids they are
|
|
264
|
+
// only an ISM, so TokenDeployer wires them itself, before it hands ownership
|
|
265
|
+
// over.
|
|
266
|
+
const rateLimitedIsmSnapshot = {};
|
|
185
267
|
for (const [chain, config] of Object.entries(warpDeployConfig)) {
|
|
268
|
+
// Hybrid chains deploy the whole resolved parent tree in the staged pass;
|
|
269
|
+
// passing the same tree to TokenDeployer would deploy it a second time.
|
|
270
|
+
if (hybridPlan[chain])
|
|
271
|
+
continue;
|
|
186
272
|
if (typeof config.interchainSecurityModule !== 'object')
|
|
187
273
|
continue;
|
|
188
274
|
const ism = config.interchainSecurityModule;
|
|
189
275
|
if (!ismTreeContainsRateLimited(ism))
|
|
190
276
|
continue;
|
|
191
277
|
const protocol = multiProvider.getProtocol(chain);
|
|
192
|
-
assert(protocol === ProtocolType.Ethereum || protocol === ProtocolType.Tron,
|
|
193
|
-
|
|
194
|
-
// uniformly in setRateLimitedIsms via setRateLimitedIsmRecipient.
|
|
195
|
-
rateLimitedSnapshot[chain] = ism;
|
|
278
|
+
assert(protocol === ProtocolType.Ethereum || protocol === ProtocolType.Tron, `${IsmType.RATE_LIMITED} is only supported on Ethereum and Tron chains, but chain ${chain} has protocol ${protocol}`);
|
|
279
|
+
rateLimitedIsmSnapshot[chain] = ism;
|
|
196
280
|
}
|
|
197
281
|
// Hooks containing RATE_LIMITED need the token router address as sender, so they are deferred
|
|
198
282
|
// until after token deployment. resolveWarpIsmAndHook populates this map (EVM/Tron only) and
|
|
199
283
|
// returns undefined for those hooks, causing them to be set later via setHook().
|
|
200
284
|
const rateLimitedHookSnapshots = {};
|
|
201
285
|
// For each chain in WarpRouteConfig, deploy each Ism Factory, if it's not in the registry
|
|
202
|
-
// Then return a modified config with the ism and/or hook address as a string
|
|
203
|
-
|
|
286
|
+
// Then return a modified config with the ism and/or hook address as a string.
|
|
287
|
+
// Chains that install a hybrid get NEITHER surface here: their router is
|
|
288
|
+
// deployed bare and wired in the staged pass below.
|
|
289
|
+
const modifiedConfig = await resolveWarpIsmAndHook(warpDeployConfig, multiProvider, altVmSigners, registryAddresses, ismFactoryDeployer, contractVerifier, rateLimitedHookSnapshots, hybridPlan);
|
|
204
290
|
// Initialize with unsupported chains so that they are enrolled
|
|
205
291
|
let deployedContracts = objMap(objFilter(warpDeployConfig, (_chain, config) => !!config.foreignDeployment), (chain, config) => {
|
|
206
292
|
assert(config.foreignDeployment, `Expected foreignDeployment field to be defined on ${chain} after filtering`);
|
|
@@ -229,13 +315,14 @@ export async function executeWarpDeploy(warpDeployConfig, multiProvider, altVmSi
|
|
|
229
315
|
const chainSnapshots = objFilter(rateLimitedHookSnapshots, (chain, _v) => chain in deployedTokens);
|
|
230
316
|
if (isObjEmpty(chainSnapshots))
|
|
231
317
|
return;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
318
|
+
// The deployed address is deliberately NOT written back into
|
|
319
|
+
// warpDeployConfig: the caller persists that object to the
|
|
320
|
+
// registry, which stores declarative config, not the runtime
|
|
321
|
+
// addresses of this run.
|
|
322
|
+
await deployAndWireRateLimitedHooks(chainSnapshots, deployedTokens, multiProvider, contractVerifier);
|
|
236
323
|
});
|
|
237
324
|
const chainSet = new Set(Object.keys(protocolSpecificConfig));
|
|
238
|
-
const
|
|
325
|
+
const deferredForBatch = objFilter(rateLimitedIsmSnapshot, (_chain, _ismConfig) => chainSet.has(_chain));
|
|
239
326
|
// Deploy as the deployer signer (intermediate owner), mirroring the
|
|
240
327
|
// AltVM branch below. Cross-chain router enrollment runs after deploy
|
|
241
328
|
// in enrollCrossChainRouters, submitted by the deployer key, and hands
|
|
@@ -253,7 +340,12 @@ export async function executeWarpDeploy(warpDeployConfig, multiProvider, altVmSi
|
|
|
253
340
|
? config.owner
|
|
254
341
|
: await multiProvider.getSigner(chain).getAddress(),
|
|
255
342
|
})));
|
|
256
|
-
const
|
|
343
|
+
const deferRouterEnrollment = Object.keys(protocolSpecificConfig).some((chain) => chain in hybridPlan);
|
|
344
|
+
const evmContracts = warpDeployConfig.isNft
|
|
345
|
+
? await deployer.deploy(intermediateOwnerConfig)
|
|
346
|
+
: await deployer.deploy(intermediateOwnerConfig, deferredForBatch, {
|
|
347
|
+
deferRouterEnrollment,
|
|
348
|
+
});
|
|
257
349
|
deployedContracts = {
|
|
258
350
|
...deployedContracts,
|
|
259
351
|
...objMap(evmContracts, (_, contracts) => getRouter(contracts).address),
|
|
@@ -290,8 +382,185 @@ export async function executeWarpDeploy(warpDeployConfig, multiProvider, altVmSi
|
|
|
290
382
|
}
|
|
291
383
|
}
|
|
292
384
|
}
|
|
385
|
+
await wireHybridHookIsms({
|
|
386
|
+
hybridPlan,
|
|
387
|
+
multiProvider,
|
|
388
|
+
registryAddresses,
|
|
389
|
+
contractVerifier,
|
|
390
|
+
warpDeployConfig,
|
|
391
|
+
deployedContracts,
|
|
392
|
+
});
|
|
293
393
|
return deployedContracts;
|
|
294
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Stages 2 to 5 of the deploy: turn bare routers into fully wired hybrid legs.
|
|
397
|
+
*
|
|
398
|
+
* Split out of the protocol loop above because every stage is a route-wide
|
|
399
|
+
* barrier — stage N must have completed on EVERY chain before stage N+1 starts
|
|
400
|
+
* on ANY of them. Enrollment needs all instance addresses; the hook install
|
|
401
|
+
* needs every counterpart enrolled; the ISM install needs every hook live.
|
|
402
|
+
*/
|
|
403
|
+
async function wireHybridHookIsms({ hybridPlan, multiProvider, registryAddresses, contractVerifier, warpDeployConfig, deployedContracts, }) {
|
|
404
|
+
if (isObjEmpty(hybridPlan))
|
|
405
|
+
return;
|
|
406
|
+
const ismFactory = HyperlaneIsmFactory.fromAddressesMap(registryAddresses, multiProvider, undefined, contractVerifier);
|
|
407
|
+
// Stage 2 — one instance per chain, bound to the router deployed above.
|
|
408
|
+
// deployInternal rather than deploy: the leaf on its own is exactly the shape
|
|
409
|
+
// IsmConfigSchema rejects (a hybrid outside its mandatory aggregation), and
|
|
410
|
+
// the composition it would re-check was already validated over the whole tree
|
|
411
|
+
// by planWarpRouteHybrids.
|
|
412
|
+
const hybridAddresses = await promiseObjAll(objMap(hybridPlan, async (chain, entry) => {
|
|
413
|
+
const router = mustGet(deployedContracts, chain);
|
|
414
|
+
const chainAddresses = mustGet(registryAddresses, chain);
|
|
415
|
+
const leaf = hybridLeafDeployConfig({
|
|
416
|
+
entry,
|
|
417
|
+
warpRouter: router,
|
|
418
|
+
deployerAddress: await multiProvider.getSigner(chain).getAddress(),
|
|
419
|
+
});
|
|
420
|
+
rootLogger.info(`Deploying ${leaf.type} on ${chain} for router ${router}`);
|
|
421
|
+
const deployed = await ismFactory.deployInternal({
|
|
422
|
+
destination: chain,
|
|
423
|
+
config: leaf,
|
|
424
|
+
mailbox: chainAddresses.mailbox,
|
|
425
|
+
});
|
|
426
|
+
return deployed.address;
|
|
427
|
+
}));
|
|
428
|
+
// Stage 3 — pair the DELAYED_FLOW_ROUTER instances with each other while the
|
|
429
|
+
// deployer still owns them. Ownership moves to the configured owner in the
|
|
430
|
+
// route's final pass (enrollCrossChainRouters), which is also what re-runs
|
|
431
|
+
// this reconciliation for a route that was only partly enrolled.
|
|
432
|
+
await enrollDelayedFlowInstances({
|
|
433
|
+
hybridPlan,
|
|
434
|
+
multiProvider,
|
|
435
|
+
hybridAddresses,
|
|
436
|
+
});
|
|
437
|
+
// Stage 4 — substitute the shared address into both trees and deploy whatever
|
|
438
|
+
// parents remain through the ordinary modules.
|
|
439
|
+
const wiring = await promiseObjAll(objMap(hybridPlan, async (chain, entry) => {
|
|
440
|
+
const hybrid = mustGet(hybridAddresses, chain);
|
|
441
|
+
const router = mustGet(deployedContracts, chain);
|
|
442
|
+
const chainAddresses = mustGet(registryAddresses, chain);
|
|
443
|
+
const resolved = resolveHybridTrees(entry, hybrid);
|
|
444
|
+
const resolvedIsm = setRateLimitedIsmRecipient(resolved.ism, router, mustGet(warpDeployConfig, chain).owner);
|
|
445
|
+
const ccipContractCache = new CCIPContractCache(registryAddresses);
|
|
446
|
+
const proxyFactoryFactories = extractIsmAndHookFactoryAddresses(chainAddresses);
|
|
447
|
+
const ism = typeof resolvedIsm === 'string'
|
|
448
|
+
? resolvedIsm
|
|
449
|
+
: await deployIsmTree({
|
|
450
|
+
chain,
|
|
451
|
+
multiProvider,
|
|
452
|
+
chainAddresses,
|
|
453
|
+
config: resolvedIsm,
|
|
454
|
+
ccipContractCache,
|
|
455
|
+
contractVerifier,
|
|
456
|
+
proxyFactoryFactories,
|
|
457
|
+
});
|
|
458
|
+
const hook = typeof resolved.hook === 'string'
|
|
459
|
+
? resolved.hook
|
|
460
|
+
: await deployHookTree({
|
|
461
|
+
chain,
|
|
462
|
+
multiProvider,
|
|
463
|
+
chainAddresses,
|
|
464
|
+
config: resolved.hook,
|
|
465
|
+
ccipContractCache,
|
|
466
|
+
contractVerifier,
|
|
467
|
+
proxyFactoryFactories,
|
|
468
|
+
proxyAdminAddress: mustGet(warpDeployConfig, chain).proxyAdmin
|
|
469
|
+
?.address,
|
|
470
|
+
});
|
|
471
|
+
return { router, hybrid, ism, hook };
|
|
472
|
+
}));
|
|
473
|
+
// Stage 5 — hooks route-wide, barrier, then ISMs route-wide.
|
|
474
|
+
await installHybridSurfaces({ multiProvider, wiring });
|
|
475
|
+
}
|
|
476
|
+
async function deployIsmTree({ chain, multiProvider, chainAddresses, config, ccipContractCache, contractVerifier, proxyFactoryFactories, }) {
|
|
477
|
+
const module = await EvmIsmModule.create({
|
|
478
|
+
chain,
|
|
479
|
+
mailbox: chainAddresses.mailbox,
|
|
480
|
+
multiProvider,
|
|
481
|
+
proxyFactoryFactories,
|
|
482
|
+
config,
|
|
483
|
+
ccipContractCache,
|
|
484
|
+
contractVerifier,
|
|
485
|
+
});
|
|
486
|
+
const { deployedIsm } = module.serialize();
|
|
487
|
+
assert(deployedIsm, `Failed to deploy the ISM tree on ${chain}`);
|
|
488
|
+
return deployedIsm;
|
|
489
|
+
}
|
|
490
|
+
async function deployHookTree({ chain, multiProvider, chainAddresses, config, ccipContractCache, contractVerifier, proxyFactoryFactories, proxyAdminAddress, }) {
|
|
491
|
+
const resolvedProxyAdmin = proxyAdminAddress ??
|
|
492
|
+
(await multiProvider.handleDeploy(chain, new ProxyAdmin__factory(), []))
|
|
493
|
+
.address;
|
|
494
|
+
const module = await EvmHookModule.create({
|
|
495
|
+
chain,
|
|
496
|
+
multiProvider,
|
|
497
|
+
coreAddresses: {
|
|
498
|
+
mailbox: chainAddresses.mailbox,
|
|
499
|
+
proxyAdmin: resolvedProxyAdmin,
|
|
500
|
+
},
|
|
501
|
+
config,
|
|
502
|
+
ccipContractCache,
|
|
503
|
+
contractVerifier,
|
|
504
|
+
proxyFactoryFactories,
|
|
505
|
+
});
|
|
506
|
+
const { deployedHook } = module.serialize();
|
|
507
|
+
assert(deployedHook, `Failed to deploy the hook tree on ${chain}`);
|
|
508
|
+
return deployedHook;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Enrolls every DELAYED_FLOW_ROUTER instance of the route with every other one.
|
|
512
|
+
*
|
|
513
|
+
* Runs once all instance addresses exist and while the deployer still owns them
|
|
514
|
+
* (`hybridLeafDeployConfig` keeps DFR ownership with the deployer for exactly
|
|
515
|
+
* this reason), so a fresh route needs no owner-gated batch. Route-derived
|
|
516
|
+
* peers override configured entries for the same chains; configured external
|
|
517
|
+
* peers remain available for unusual topologies.
|
|
518
|
+
*/
|
|
519
|
+
async function enrollDelayedFlowInstances({ hybridPlan, multiProvider, hybridAddresses, }) {
|
|
520
|
+
const delayedChains = Object.keys(hybridPlan).filter((chain) => hybridPlan[chain].node.type === IsmType.DELAYED_FLOW_ROUTER);
|
|
521
|
+
if (delayedChains.length === 0)
|
|
522
|
+
return;
|
|
523
|
+
await Promise.all(delayedChains.map(async (chain) => {
|
|
524
|
+
const node = hybridPlan[chain].node;
|
|
525
|
+
assert(node.type === IsmType.DELAYED_FLOW_ROUTER, `Unreachable: ${chain} was filtered as a delayed-flow chain`);
|
|
526
|
+
const ismAddress = mustGet(hybridAddresses, chain);
|
|
527
|
+
const derived = Object.fromEntries(delayedChains
|
|
528
|
+
.filter((otherChain) => otherChain !== chain)
|
|
529
|
+
.map((otherChain) => [
|
|
530
|
+
otherChain,
|
|
531
|
+
addressToBytes32(mustGet(hybridAddresses, otherChain)),
|
|
532
|
+
]));
|
|
533
|
+
const remoteIsms = resolveDelayedFlowRemoteIsms(node.remoteIsms, derived, `${IsmType.DELAYED_FLOW_ROUTER} ${ismAddress} on ${chain}`, multiProvider);
|
|
534
|
+
if (!remoteIsms || isObjEmpty(remoteIsms))
|
|
535
|
+
return;
|
|
536
|
+
const domains = [];
|
|
537
|
+
const routers = [];
|
|
538
|
+
for (const [remoteChain, router] of Object.entries(remoteIsms)) {
|
|
539
|
+
domains.push(multiProvider.getDomainId(remoteChain));
|
|
540
|
+
routers.push(router);
|
|
541
|
+
}
|
|
542
|
+
rootLogger.info(`Enrolling ${domains.length} counterpart(s) on ${IsmType.DELAYED_FLOW_ROUTER} ${ismAddress} (${chain})`);
|
|
543
|
+
const instance = DelayedFlowRouterHookIsm__factory.connect(ismAddress, multiProvider.getSigner(chain));
|
|
544
|
+
await multiProvider.handleTx(chain, instance.enrollRemoteRouters(domains, routers, multiProvider.getTransactionOverrides(chain)));
|
|
545
|
+
}));
|
|
546
|
+
}
|
|
547
|
+
/** Installs hooks route-wide before installing the corresponding ISMs. */
|
|
548
|
+
async function installHybridSurfaces({ multiProvider, wiring, }) {
|
|
549
|
+
await promiseObjAll(objMap(wiring, async (chain, { router, hook }) => {
|
|
550
|
+
rootLogger.info(`Installing hook ${hook} on router ${router} (${chain})`);
|
|
551
|
+
await multiProvider.sendTransaction(chain, {
|
|
552
|
+
to: router,
|
|
553
|
+
data: MailboxClient__factory.createInterface().encodeFunctionData('setHook', [hook]),
|
|
554
|
+
});
|
|
555
|
+
}));
|
|
556
|
+
await promiseObjAll(objMap(wiring, async (chain, { router, ism }) => {
|
|
557
|
+
rootLogger.info(`Installing ISM ${ism} on router ${router} (${chain})`);
|
|
558
|
+
await multiProvider.sendTransaction(chain, {
|
|
559
|
+
to: router,
|
|
560
|
+
data: MailboxClient__factory.createInterface().encodeFunctionData('setInterchainSecurityModule', [ism]),
|
|
561
|
+
});
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
295
564
|
async function deployAndWireRateLimitedHooks(snapshots, deployedTokens, multiProvider, contractVerifier) {
|
|
296
565
|
return promiseObjAll(objMap(snapshots, async (chain, { hookConfig, chainAddresses, ccipContractCache, proxyAdminAddress }) => {
|
|
297
566
|
const tokenAddress = mustGet(deployedTokens, chain);
|
|
@@ -321,13 +590,26 @@ async function deployAndWireRateLimitedHooks(snapshots, deployedTokens, multiPro
|
|
|
321
590
|
return deployedHook;
|
|
322
591
|
}));
|
|
323
592
|
}
|
|
324
|
-
async function resolveWarpIsmAndHook(warpConfig, multiProvider, altVmSigners, registryAddresses, ismFactoryDeployer, contractVerifier, rateLimitedHookSnapshots) {
|
|
593
|
+
async function resolveWarpIsmAndHook(warpConfig, multiProvider, altVmSigners, registryAddresses, ismFactoryDeployer, contractVerifier, rateLimitedHookSnapshots, hybridPlan) {
|
|
325
594
|
return promiseObjAll(objMap(warpConfig, async (chain, config) => {
|
|
326
595
|
const ccipContractCache = new CCIPContractCache(registryAddresses);
|
|
327
596
|
const chainAddresses = registryAddresses[chain];
|
|
328
597
|
if (!chainAddresses) {
|
|
329
598
|
throw new Error(`Registry factory addresses not found for ${chain}.`);
|
|
330
599
|
}
|
|
600
|
+
// A chain whose composition includes a hybrid deploys its router BARE.
|
|
601
|
+
// Both surfaces reference one instance whose constructor reads the live
|
|
602
|
+
// router, so neither tree can be built yet — and leaving the router
|
|
603
|
+
// without an ISM or hook until both are ready is what keeps a
|
|
604
|
+
// half-wired route from being usable.
|
|
605
|
+
if (hybridPlan[chain]) {
|
|
606
|
+
rootLogger.info(`Deploying ${chain} router bare: its ${hybridPlan[chain].node.type} is wired after the router exists`);
|
|
607
|
+
return {
|
|
608
|
+
...config,
|
|
609
|
+
interchainSecurityModule: undefined,
|
|
610
|
+
hook: undefined,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
331
613
|
const ism = await createWarpIsm({
|
|
332
614
|
ccipContractCache,
|
|
333
615
|
chain,
|
|
@@ -371,13 +653,14 @@ async function createWarpIsm({ ccipContractCache, chain, chainAddresses, multiPr
|
|
|
371
653
|
rootLogger.info(`Config Ism is ${!interchainSecurityModule ? 'empty' : interchainSecurityModule}, skipping deployment.`);
|
|
372
654
|
return interchainSecurityModule;
|
|
373
655
|
}
|
|
374
|
-
//
|
|
375
|
-
// token
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
656
|
+
// A RATE_LIMITED ISM has a chicken-and-egg problem: its constructor requires
|
|
657
|
+
// the token address as `recipient`, but ISMs are deployed here — before the
|
|
658
|
+
// token exists. Skip any tree containing one; TokenDeployer.setRateLimitedIsms
|
|
659
|
+
// deploys it after the router and wires it with setInterchainSecurityModule()
|
|
660
|
+
// while the deployer still owns the router. (Hybrid hook/ISM trees never
|
|
661
|
+
// reach this function — resolveWarpIsmAndHook returns their chains bare.)
|
|
379
662
|
if (ismTreeContainsRateLimited(interchainSecurityModule)) {
|
|
380
|
-
rootLogger.info(`Skipping ISM deployment for ${chain} (contains
|
|
663
|
+
rootLogger.info(`Skipping ISM deployment for ${chain} (contains a ${IsmType.RATE_LIMITED} ISM), will deploy after token.`);
|
|
381
664
|
return undefined;
|
|
382
665
|
}
|
|
383
666
|
rootLogger.info(`Loading registry factory addresses for ${chain}...`);
|
|
@@ -475,12 +758,361 @@ async function createWarpHook({ ccipContractCache, chain, chainAddresses, multiP
|
|
|
475
758
|
}
|
|
476
759
|
}
|
|
477
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* The DELAYED_FLOW_ROUTER nodes a chain's warp config installs.
|
|
763
|
+
*
|
|
764
|
+
* An ISM configured as a bare address cannot be inspected, so it reads as "no
|
|
765
|
+
* instance"; assertDelayedFlowLegCoverage turns that into an explicit error
|
|
766
|
+
* instead of a silent pass.
|
|
767
|
+
*/
|
|
768
|
+
function delayedFlowIsmNodes(config) {
|
|
769
|
+
if (typeof config.interchainSecurityModule !== 'object')
|
|
770
|
+
return [];
|
|
771
|
+
return collectHybridIsmNodes(config.interchainSecurityModule).filter((node) => node.type === IsmType.DELAYED_FLOW_ROUTER);
|
|
772
|
+
}
|
|
773
|
+
/** True when a chain's warp config installs a DELAYED_FLOW_ROUTER instance. */
|
|
774
|
+
function configInstallsDelayedFlowIsm(config) {
|
|
775
|
+
return delayedFlowIsmNodes(config).length > 0;
|
|
776
|
+
}
|
|
777
|
+
/** The chains of a config whose ISM tree installs a DELAYED_FLOW_ROUTER. */
|
|
778
|
+
function delayedFlowChains(warpDeployConfig) {
|
|
779
|
+
return Object.keys(warpDeployConfig).filter((chain) => configInstallsDelayedFlowIsm(warpDeployConfig[chain]));
|
|
780
|
+
}
|
|
781
|
+
/** Domain id of every chain the route deploys, asserted resolvable. */
|
|
782
|
+
function resolveRouteDomains(multiProvider, warpDeployConfig) {
|
|
783
|
+
const routeDomains = new Map();
|
|
784
|
+
for (const chain of Object.keys(warpDeployConfig)) {
|
|
785
|
+
const domain = multiProvider.tryGetDomainId(chain);
|
|
786
|
+
assert(domain !== null, `No chain metadata for ${chain}, which this ${IsmType.DELAYED_FLOW_ROUTER} route deploys — add it to the registry`);
|
|
787
|
+
routeDomains.set(chain, domain);
|
|
788
|
+
}
|
|
789
|
+
return routeDomains;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* The domains a chain's DELAYED_FLOW_ROUTER nodes enroll through `remoteIsms`.
|
|
793
|
+
*
|
|
794
|
+
* Resolved through the same canonicalization the deploy, update and check
|
|
795
|
+
* paths use (canonicalizeRemoteIsms), so this preflight accepts exactly the
|
|
796
|
+
* keys they accept: a key it cannot resolve, or two keys naming one chain,
|
|
797
|
+
* throw here — before any contract is deployed — instead of passing the
|
|
798
|
+
* preflight and failing mid-deploy.
|
|
799
|
+
*/
|
|
800
|
+
function configuredRemoteIsmDomains(multiProvider, chain, config) {
|
|
801
|
+
const domains = new Set();
|
|
802
|
+
for (const node of delayedFlowIsmNodes(config)) {
|
|
803
|
+
const canonical = canonicalizeRemoteIsms(node.remoteIsms ?? {}, multiProvider, `${IsmType.DELAYED_FLOW_ROUTER} on ${chain}`);
|
|
804
|
+
for (const chainName of Object.keys(canonical)) {
|
|
805
|
+
domains.add(multiProvider.getDomainId(chainName));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return domains;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Rejects a `remoteRouters` entry naming a chain outside the deploy config
|
|
812
|
+
* unless the chain's DelayedFlowRouterHookIsm enrolls it through `remoteIsms`.
|
|
813
|
+
*
|
|
814
|
+
* `remoteRouters` legs are as connected as the deployed ones:
|
|
815
|
+
* `TimelockRouter.postDispatch` dispatches the preverification to the
|
|
816
|
+
* transfer's destination domain, and `Router._mustHaveRemoteRouter` reverts
|
|
817
|
+
* when the instance has no counterpart enrolled there. The automatic pairing
|
|
818
|
+
* only covers the chains of the deploy config (deriveDelayedFlowEnrollmentTargets),
|
|
819
|
+
* so an out-of-config leg strands transfers exactly like an uncovered one.
|
|
820
|
+
*
|
|
821
|
+
* `remoteIsms` is the escape hatch for pairings the route cannot derive. The
|
|
822
|
+
* route-derived peers are merged into it separately.
|
|
823
|
+
*/
|
|
824
|
+
function assertDelayedFlowRemoteRouterCoverage(multiProvider, warpDeployConfig, routeDomains) {
|
|
825
|
+
const deployedDomains = new Set(routeDomains.values());
|
|
826
|
+
for (const [chain, config] of Object.entries(warpDeployConfig)) {
|
|
827
|
+
const enrolledDomains = configuredRemoteIsmDomains(multiProvider, chain, config);
|
|
828
|
+
for (const key of Object.keys(config.remoteRouters ?? {})) {
|
|
829
|
+
// Resolved through the registry for BOTH key forms (chain name and
|
|
830
|
+
// domain id): the only remedy this check can suggest for an unenrolled
|
|
831
|
+
// domain is listing it under 'remoteIsms', and canonicalizeRemoteIsms
|
|
832
|
+
// rejects a key it cannot resolve to a registered chain. Taking a
|
|
833
|
+
// numeric key at face value here would send the operator round in a
|
|
834
|
+
// circle between the two checks.
|
|
835
|
+
const domain = multiProvider.tryGetDomainId(key);
|
|
836
|
+
assert(domain !== null, `No chain metadata for ${key}, which ${chain} names in 'remoteRouters' — this route's ${IsmType.DELAYED_FLOW_ROUTER} legs are resolved through the registry, so add the chain to it or remove the entry`);
|
|
837
|
+
const remoteName = multiProvider.getChainName(domain);
|
|
838
|
+
assert(deployedDomains.has(domain) || enrolledDomains.has(domain), `${chain} names ${remoteName} (domain ${domain}) in 'remoteRouters', but that chain is not part of this route and ${chain}'s ${IsmType.DELAYED_FLOW_ROUTER} does not enroll it. Transfers to it dispatch a preverification through that instance, which reverts because no counterpart is enrolled for the domain. Add ${remoteName} to the route, drop it from 'remoteRouters', or list its ${IsmType.DELAYED_FLOW_ROUTER} instance under 'remoteIsms' on ${chain}.`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Rejects a config that carries a DelayedFlowRouterHookIsm on only some of its
|
|
844
|
+
* legs, before any on-chain work happens.
|
|
845
|
+
*
|
|
846
|
+
* The instances are peers: `TimelockRouter.postDispatch` dispatches a
|
|
847
|
+
* preverification message to the transfer's destination domain, and
|
|
848
|
+
* `Router._mustHaveRemoteRouter` reverts when that domain has no counterpart
|
|
849
|
+
* enrolled. So a DFR origin paired with a plain destination reverts at
|
|
850
|
+
* quote/dispatch time, and a plain origin paired with a DFR destination never
|
|
851
|
+
* sends the preverification the destination waits for — the transfer is
|
|
852
|
+
* stranded either way. A foreignDeployment leg is rejected outright: its
|
|
853
|
+
* instance is outside this deployment's control, so it can never be enrolled.
|
|
854
|
+
*
|
|
855
|
+
* Safe on any subset of a route: dropping a chain drops it from both sides of
|
|
856
|
+
* every comparison here, so a topology accepted as a whole is still accepted
|
|
857
|
+
* chain by chain. The checks whose verdict depends on WHICH chains the config
|
|
858
|
+
* contains live in assertDelayedFlowRouteCoverage.
|
|
859
|
+
*
|
|
860
|
+
* Checked against the config rather than mid-apply on purpose: an extension
|
|
861
|
+
* applies chain by chain, so a mid-apply check would fire on a route that is
|
|
862
|
+
* merely part-deployed.
|
|
863
|
+
*/
|
|
864
|
+
function assertDelayedFlowLegCoverage(warpDeployConfig) {
|
|
865
|
+
const chains = Object.keys(warpDeployConfig);
|
|
866
|
+
const covered = delayedFlowChains(warpDeployConfig);
|
|
867
|
+
if (covered.length === 0)
|
|
868
|
+
return;
|
|
869
|
+
const foreignDeployments = chains.filter((chain) => !!warpDeployConfig[chain].foreignDeployment);
|
|
870
|
+
assert(foreignDeployments.length === 0, `${IsmType.DELAYED_FLOW_ROUTER} is configured on ${covered.join(', ')}, but ${foreignDeployments.join(', ')} is a foreignDeployment whose instance cannot be deployed or enrolled by this route. Every leg of a delayed-flow route must be deployed by it.`);
|
|
871
|
+
const uncovered = chains.filter((chain) => !configInstallsDelayedFlowIsm(warpDeployConfig[chain]));
|
|
872
|
+
assert(uncovered.length === 0, `${IsmType.DELAYED_FLOW_ROUTER} is configured on ${covered.join(', ')} but not on ${uncovered.join(', ')}. Transfers between a delayed-flow leg and a plain leg revert on both sides, so every chain of the route must configure one (an ISM given as an address cannot be inspected — declare it inline).`);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Whole-route coverage: every leg carries an instance
|
|
876
|
+
* (assertDelayedFlowLegCoverage), and a `remoteRouters` entry naming a chain
|
|
877
|
+
* outside the route has to enroll that counterpart explicitly
|
|
878
|
+
* (assertDelayedFlowRemoteRouterCoverage).
|
|
879
|
+
*
|
|
880
|
+
* Correct only against the FULL route config. Shrinking the config enlarges
|
|
881
|
+
* what counts as outside the route, so running this on a subset — the single
|
|
882
|
+
* chain `warp apply` deploys per extension, say — rejects `remoteRouters`
|
|
883
|
+
* entries naming the very legs that chain is joining.
|
|
884
|
+
*/
|
|
885
|
+
export function assertDelayedFlowRouteCoverage({ multiProvider, warpDeployConfig, }) {
|
|
886
|
+
assertDelayedFlowLegCoverage(warpDeployConfig);
|
|
887
|
+
if (delayedFlowChains(warpDeployConfig).length === 0)
|
|
888
|
+
return;
|
|
889
|
+
const routeDomains = resolveRouteDomains(multiProvider, warpDeployConfig);
|
|
890
|
+
assertDelayedFlowRemoteRouterCoverage(multiProvider, warpDeployConfig, routeDomains);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Rejects a delayed-flow leg whose mailbox has never dispatched a message.
|
|
894
|
+
*
|
|
895
|
+
* DelayedFlowRouterHookIsm initialises `lastCreditedNonce` to 0 and rejects
|
|
896
|
+
* `nonce <= lastCreditedNonce`, so a dispatch carrying mailbox nonce 0 can
|
|
897
|
+
* never be credited: the first transfer out of a brand-new mailbox reverts
|
|
898
|
+
* inside postDispatch and the route strands it. Deploying onto such a chain is
|
|
899
|
+
* rejected instead of producing a route whose first transfer is guaranteed to
|
|
900
|
+
* fail. Per chain, so it holds on any subset of a route.
|
|
901
|
+
*/
|
|
902
|
+
async function assertDelayedFlowMailboxNonces(multiProvider, warpDeployConfig) {
|
|
903
|
+
const mailboxNonces = await Promise.all(delayedFlowChains(warpDeployConfig).map(async (chain) => {
|
|
904
|
+
const protocol = multiProvider.getProtocol(chain);
|
|
905
|
+
assert(protocol === ProtocolType.Ethereum || protocol === ProtocolType.Tron, `${IsmType.DELAYED_FLOW_ROUTER} is configured on ${chain}, whose protocol is ${protocol} — the contract only exists on Ethereum and Tron chains`);
|
|
906
|
+
const { mailbox } = warpDeployConfig[chain];
|
|
907
|
+
assert(mailbox, `Missing mailbox address for ${chain}`);
|
|
908
|
+
const nonce = await Mailbox__factory.connect(mailbox, multiProvider.getProvider(chain)).nonce();
|
|
909
|
+
return { chain, mailbox, nonce };
|
|
910
|
+
}));
|
|
911
|
+
for (const { chain, mailbox, nonce } of mailboxNonces) {
|
|
912
|
+
assert(nonce > 0, `Mailbox ${mailbox} on ${chain} has never dispatched a message, and ${IsmType.DELAYED_FLOW_ROUTER} cannot credit a dispatch carrying mailbox nonce 0 (its lastCreditedNonce starts at 0 and it rejects nonce <= lastCreditedNonce), so the route's first transfer out of ${chain} would revert. Dispatch any other message through that mailbox first.`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* The full delayed-flow preconditions for a whole route, checked before any
|
|
917
|
+
* on-chain work: assertDelayedFlowRouteCoverage — which adds the route-scoped
|
|
918
|
+
* external `remoteRouters` check to the leg coverage above — and the
|
|
919
|
+
* mailbox nonce guard.
|
|
920
|
+
*
|
|
921
|
+
* Call this with every chain of the route. The CLI entry points do
|
|
922
|
+
* (runWarpRouteDeploy, runWarpRouteApply); executeWarpDeploy cannot, because
|
|
923
|
+
* an extension reaches it one chain at a time.
|
|
924
|
+
*/
|
|
925
|
+
export async function assertDelayedFlowRoutePreconditions({ multiProvider, warpDeployConfig, }) {
|
|
926
|
+
assertDelayedFlowRouteCoverage({ multiProvider, warpDeployConfig });
|
|
927
|
+
await assertDelayedFlowMailboxNonces(multiProvider, warpDeployConfig);
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* The hybrid hook/ISM instance nested inside an ISM tree deployed on chain, or
|
|
931
|
+
* undefined when the tree holds none.
|
|
932
|
+
*
|
|
933
|
+
* The ISM surface is where the instance is always reachable: the hook surface
|
|
934
|
+
* may install an aggregation the instance merely sits inside, whereas the ISM
|
|
935
|
+
* tree is what the schema constrains and what `EvmIsmReader` walks, and derived
|
|
936
|
+
* nodes carry their address.
|
|
937
|
+
*/
|
|
938
|
+
async function readHybridIsmInTree(multiProvider, chain, ismTreeAddress) {
|
|
939
|
+
if (isZeroishAddress(ismTreeAddress))
|
|
940
|
+
return undefined;
|
|
941
|
+
const derived = await new EvmIsmReader(multiProvider, chain).deriveIsmConfig(ismTreeAddress);
|
|
942
|
+
const nodes = collectHybridIsmNodes(derived);
|
|
943
|
+
if (nodes.length === 0)
|
|
944
|
+
return undefined;
|
|
945
|
+
assert(nodes.length === 1, `Expected at most one hybrid hook/ISM instance in the ISM tree at ${ismTreeAddress} on ${chain}, found ${nodes.length}`);
|
|
946
|
+
const node = nodes[0];
|
|
947
|
+
assert('address' in node && typeof node.address === 'string', `Derived hybrid hook/ISM node on ${chain} is missing its address`);
|
|
948
|
+
return node.address;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Resolves the shared hybrid instance behind each chain's ISM tree.
|
|
952
|
+
*
|
|
953
|
+
* Callers pass whichever tree is authoritative for them: the address a run has
|
|
954
|
+
* just deployed but not yet installed, or the tree already on the router.
|
|
955
|
+
* Nothing here guesses between the two — a caller that knows both states knows
|
|
956
|
+
* which one it means.
|
|
957
|
+
*/
|
|
958
|
+
export async function resolveHybridIsmAddresses(multiProvider, ismTreeAddresses) {
|
|
959
|
+
const resolved = {};
|
|
960
|
+
await Promise.all(Object.entries(ismTreeAddresses).map(async ([chain, treeAddress]) => {
|
|
961
|
+
const hybrid = await readHybridIsmInTree(multiProvider, chain, treeAddress);
|
|
962
|
+
if (hybrid)
|
|
963
|
+
resolved[chain] = hybrid;
|
|
964
|
+
}));
|
|
965
|
+
return resolved;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Resolves the hybrid instance installed on each router, by reading the ISM the
|
|
969
|
+
* router currently points at. Used by callers holding only routers, such as
|
|
970
|
+
* `warp apply` on chains it is not re-deploying.
|
|
971
|
+
*/
|
|
972
|
+
export async function readInstalledHybridIsmAddresses(multiProvider, routers) {
|
|
973
|
+
const ismTrees = {};
|
|
974
|
+
await Promise.all(Object.entries(routers).map(async ([chain, router]) => {
|
|
975
|
+
const protocol = multiProvider.getProtocol(chain);
|
|
976
|
+
if (protocol !== ProtocolType.Ethereum &&
|
|
977
|
+
protocol !== ProtocolType.Tron) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
ismTrees[chain] = await MailboxClient__factory.connect(router, multiProvider.getProvider(chain)).interchainSecurityModule();
|
|
981
|
+
}));
|
|
982
|
+
return resolveHybridIsmAddresses(multiProvider, ismTrees);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Pairs the route's DelayedFlowRouterHookIsm instances so each one's enrollment
|
|
986
|
+
* transactions can name every other one.
|
|
987
|
+
*
|
|
988
|
+
* `hybridIsmAddresses` is supplied by the caller (resolveHybridIsmAddresses /
|
|
989
|
+
* readInstalledHybridIsmAddresses) rather than re-derived here: which tree is
|
|
990
|
+
* authoritative differs per caller, and a chain whose instance the caller could
|
|
991
|
+
* not resolve is skipped rather than silently paired against a stale one. The
|
|
992
|
+
* config node is carried through as `userNode` so configured external
|
|
993
|
+
* `remoteIsms` are merged with the route-derived pairing.
|
|
994
|
+
*/
|
|
995
|
+
export async function deriveDelayedFlowEnrollmentTargets(multiProvider,
|
|
996
|
+
// Mailbox-independent: the pairing is read from the instances themselves, so
|
|
997
|
+
// callers that only hold a registry deploy config can use it too.
|
|
998
|
+
warpDeployConfig, deployedContracts, hybridIsmAddresses) {
|
|
999
|
+
const resolvedHybridAddresses = hybridIsmAddresses ??
|
|
1000
|
+
(await readInstalledHybridIsmAddresses(multiProvider, deployedContracts));
|
|
1001
|
+
const targets = {};
|
|
1002
|
+
for (const [chain, config] of Object.entries(warpDeployConfig)) {
|
|
1003
|
+
if (config.foreignDeployment)
|
|
1004
|
+
continue;
|
|
1005
|
+
if (typeof config.interchainSecurityModule !== 'object')
|
|
1006
|
+
continue;
|
|
1007
|
+
const protocol = multiProvider.getProtocol(chain);
|
|
1008
|
+
if (protocol !== ProtocolType.Ethereum && protocol !== ProtocolType.Tron) {
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
const delayedNodes = collectHybridIsmNodes(config.interchainSecurityModule).filter((node) => node.type === IsmType.DELAYED_FLOW_ROUTER);
|
|
1012
|
+
if (delayedNodes.length === 0)
|
|
1013
|
+
continue;
|
|
1014
|
+
assert(delayedNodes.length === 1, `Expected exactly one DELAYED_FLOW_ROUTER node in the ISM tree on ${chain}, found ${delayedNodes.length}`);
|
|
1015
|
+
const tokenAddress = deployedContracts[chain];
|
|
1016
|
+
assert(tokenAddress, `No deployed token found for ${chain}`);
|
|
1017
|
+
const provider = multiProvider.getProvider(chain);
|
|
1018
|
+
const ismAddress = resolvedHybridAddresses[chain];
|
|
1019
|
+
assert(ismAddress, `${chain} declares a ${IsmType.DELAYED_FLOW_ROUTER} but no instance was resolved for it — the instance must be deployed and reachable through the router's ISM tree before its remote counterparts can be enrolled`);
|
|
1020
|
+
const instance = DelayedFlowRouterHookIsm__factory.connect(ismAddress, provider);
|
|
1021
|
+
// maxDelay() is what identifies the contract: NetFlowRateLimitedHookIsm
|
|
1022
|
+
// exposes warpRouter() too, so probing that alone would return a NetFlow
|
|
1023
|
+
// instance as a DELAYED_FLOW_ROUTER enrollment target and pair the wrong
|
|
1024
|
+
// contract. Only DelayedFlowRouterHookIsm declares maxDelay (a uint48
|
|
1025
|
+
// immutable), the same discriminator EvmIsmReader and EvmHookReader use,
|
|
1026
|
+
// and it returns a value, so a contract missing the selector reverts
|
|
1027
|
+
// instead of decoding empty returndata as a match.
|
|
1028
|
+
//
|
|
1029
|
+
// The router's current hook is only a DelayedFlowRouterHookIsm once the
|
|
1030
|
+
// hybrid has been wired; on an older route it can be another hook or the
|
|
1031
|
+
// zero address, and these getters would then revert inside the provider
|
|
1032
|
+
// with a raw call exception instead of reaching the asserts below.
|
|
1033
|
+
let onChainMaxDelay;
|
|
1034
|
+
try {
|
|
1035
|
+
onChainMaxDelay = await instance.maxDelay();
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
throwIfNotMissingSelector(error);
|
|
1039
|
+
}
|
|
1040
|
+
assert(onChainMaxDelay !== undefined, `Contract ${ismAddress} on ${chain} does not expose maxDelay(), so it is not a DelayedFlowRouterHookIsm — resolve the instance from the ISM tree the router actually installs`);
|
|
1041
|
+
let onChainWarpRouter;
|
|
1042
|
+
try {
|
|
1043
|
+
onChainWarpRouter = await instance.warpRouter();
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
throwIfNotMissingSelector(error);
|
|
1047
|
+
}
|
|
1048
|
+
assert(onChainWarpRouter !== undefined, `Contract ${ismAddress} on ${chain} does not expose warpRouter(), so it is not the token's DelayedFlowRouterHookIsm — resolve the instance from the ISM tree the router actually installs`);
|
|
1049
|
+
assert(eqAddress(onChainWarpRouter, tokenAddress), `Contract ${ismAddress} on ${chain} is not the token's DelayedFlowRouterHookIsm (warpRouter mismatch)`);
|
|
1050
|
+
targets[chain] = { ismAddress, userNode: delayedNodes[0] };
|
|
1051
|
+
}
|
|
1052
|
+
return targets;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Builds the enrollment transactions for one chain's
|
|
1056
|
+
* DelayedFlowRouterHookIsm: enrolls every other chain's instance as a remote
|
|
1057
|
+
* counterpart, plus configured peers outside the route, and
|
|
1058
|
+
* optionally transfers ownership to the configured owner LAST (the enrollment
|
|
1059
|
+
* calls are owner-gated). Converges to zero transactions once the on-chain
|
|
1060
|
+
* state already matches.
|
|
1061
|
+
*
|
|
1062
|
+
* Shared by the deploy flow (deployer-owned instances) and `warp apply`
|
|
1063
|
+
* (instances that may be owned by a Safe/ICA — the returned transactions carry
|
|
1064
|
+
* no signing assumptions and ride whichever submitter the caller uses).
|
|
1065
|
+
*/
|
|
1066
|
+
export async function buildDelayedFlowEnrollmentTxs({ chain, multiProvider, registryAddresses, warpRouter, target, allTargets, reconcileOwnership = true, }) {
|
|
1067
|
+
const { ismAddress, userNode } = target;
|
|
1068
|
+
const derivedRemoteIsms = Object.fromEntries(Object.entries(allTargets)
|
|
1069
|
+
.filter(([otherChain]) => otherChain !== chain)
|
|
1070
|
+
.map(([otherChain, otherTarget]) => [
|
|
1071
|
+
otherChain,
|
|
1072
|
+
addressToBytes32(otherTarget.ismAddress).toLowerCase(),
|
|
1073
|
+
]));
|
|
1074
|
+
// Same resolution expandWarpDeployConfig applies to the expected config, so
|
|
1075
|
+
// `warp check` compares exactly the enrollment installed here.
|
|
1076
|
+
const remoteIsms = resolveDelayedFlowRemoteIsms(userNode.remoteIsms, derivedRemoteIsms, `DelayedFlowRouterHookIsm ${ismAddress} on ${chain}`, multiProvider);
|
|
1077
|
+
const delayedFlowIsmModule = new EvmIsmModule(multiProvider, {
|
|
1078
|
+
chain,
|
|
1079
|
+
config: userNode,
|
|
1080
|
+
addresses: {
|
|
1081
|
+
...extractIsmAndHookFactoryAddresses(registryAddresses[chain]),
|
|
1082
|
+
mailbox: registryAddresses[chain].mailbox,
|
|
1083
|
+
deployedIsm: ismAddress,
|
|
1084
|
+
},
|
|
1085
|
+
});
|
|
1086
|
+
const current = await delayedFlowIsmModule.read();
|
|
1087
|
+
assert(typeof current === 'object' && current.type === IsmType.DELAYED_FLOW_ROUTER, `Expected ${ismAddress} on ${chain} to be a ${IsmType.DELAYED_FLOW_ROUTER}`);
|
|
1088
|
+
// Single-instance reconciliation: the tree's composition was validated when
|
|
1089
|
+
// it was deployed. Warp apply preserves ownership here because its existing
|
|
1090
|
+
// ownership phase handles the shared instance alongside the router.
|
|
1091
|
+
return delayedFlowIsmModule.updateDeployedInstance({
|
|
1092
|
+
type: IsmType.DELAYED_FLOW_ROUTER,
|
|
1093
|
+
warpRouter,
|
|
1094
|
+
thresholdBps: userNode.thresholdBps,
|
|
1095
|
+
maxDelay: userNode.maxDelay,
|
|
1096
|
+
duration: userNode.duration,
|
|
1097
|
+
owner: reconcileOwnership ? userNode.owner : current.owner,
|
|
1098
|
+
remoteIsms,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
478
1101
|
export async function enrollCrossChainRouters({ multiProvider, altVmSigners, registryAddresses, warpDeployConfig, }, deployedContracts) {
|
|
479
1102
|
rootLogger.info(`Start enrolling cross chain routers`);
|
|
480
1103
|
assertWarpConfigTimelocksSupportedByProtocols({
|
|
481
1104
|
multiProvider,
|
|
482
1105
|
warpDeployConfig,
|
|
483
1106
|
});
|
|
1107
|
+
// Resolve every delayed-flow counterpart before building per-chain batches.
|
|
1108
|
+
// Initial deployment already enrolled these pairs; this final pass repairs
|
|
1109
|
+
// any drift while the deployer still owns the instances.
|
|
1110
|
+
const hasDelayedFlow = Object.values(warpDeployConfig).some((config) => typeof config.interchainSecurityModule === 'object' &&
|
|
1111
|
+
config.interchainSecurityModule !== null &&
|
|
1112
|
+
collectHybridIsmNodes(config.interchainSecurityModule).some((node) => node.type === IsmType.DELAYED_FLOW_ROUTER));
|
|
1113
|
+
const delayedFlowTargets = hasDelayedFlow
|
|
1114
|
+
? await deriveDelayedFlowEnrollmentTargets(multiProvider, warpDeployConfig, deployedContracts, await readInstalledHybridIsmAddresses(multiProvider, deployedContracts))
|
|
1115
|
+
: {};
|
|
484
1116
|
const resolvedConfigMap = objMap(warpDeployConfig, (_, config) => ({
|
|
485
1117
|
gas: gasOverhead(config.type),
|
|
486
1118
|
...config,
|
|
@@ -545,9 +1177,24 @@ export async function enrollCrossChainRouters({ multiProvider, altVmSigners, reg
|
|
|
545
1177
|
},
|
|
546
1178
|
});
|
|
547
1179
|
const actualConfig = await evmWarpModule.read();
|
|
1180
|
+
const targetOwner = resolvedConfigMap[currentChain].owner;
|
|
548
1181
|
const expectedConfig = {
|
|
549
1182
|
...actualConfig,
|
|
550
|
-
owner:
|
|
1183
|
+
owner: targetOwner,
|
|
1184
|
+
// Deployment leaves the router and its shared hybrid instance with
|
|
1185
|
+
// the deployer until this final pass. Describe their common target
|
|
1186
|
+
// owner on both config surfaces so EvmWarpModule can transfer them
|
|
1187
|
+
// together after enrollment.
|
|
1188
|
+
interchainSecurityModule: typeof actualConfig.interchainSecurityModule === 'object' &&
|
|
1189
|
+
actualConfig.interchainSecurityModule
|
|
1190
|
+
? mapHybridIsmNodes(actualConfig.interchainSecurityModule, (node) => ({ ...node, owner: targetOwner }))
|
|
1191
|
+
: actualConfig.interchainSecurityModule,
|
|
1192
|
+
hook: typeof actualConfig.hook === 'object' && actualConfig.hook
|
|
1193
|
+
? mapHybridHookNodes(actualConfig.hook, (node) => ({
|
|
1194
|
+
...node,
|
|
1195
|
+
owner: targetOwner,
|
|
1196
|
+
}))
|
|
1197
|
+
: actualConfig.hook,
|
|
551
1198
|
// Deploy set the ProxyAdmin owner to the intermediate deployer owner
|
|
552
1199
|
// so post-deploy enrollment could be self-signed. actualConfig reads
|
|
553
1200
|
// that live (deployer) owner, so carry the configured owner through
|
|
@@ -572,9 +1219,25 @@ export async function enrollCrossChainRouters({ multiProvider, altVmSigners, reg
|
|
|
572
1219
|
tokenFee: resolvedConfigMap[currentChain].tokenFee,
|
|
573
1220
|
}),
|
|
574
1221
|
};
|
|
575
|
-
|
|
1222
|
+
const { txs, feeTxs, ownershipTxs } = await evmWarpModule.updateSplit(expectedConfig, {
|
|
576
1223
|
routingDestinations: Object.keys(remoteRouters).map((domain) => parseInt(domain, 10)),
|
|
577
1224
|
});
|
|
1225
|
+
transactions = [...txs, ...feeTxs];
|
|
1226
|
+
const delayedFlowTarget = delayedFlowTargets[currentChain];
|
|
1227
|
+
if (delayedFlowTarget) {
|
|
1228
|
+
transactions.push(...(await buildDelayedFlowEnrollmentTxs({
|
|
1229
|
+
chain: currentChain,
|
|
1230
|
+
multiProvider,
|
|
1231
|
+
registryAddresses,
|
|
1232
|
+
warpRouter: deployedContracts[currentChain],
|
|
1233
|
+
target: delayedFlowTarget,
|
|
1234
|
+
allTargets: delayedFlowTargets,
|
|
1235
|
+
reconcileOwnership: false,
|
|
1236
|
+
})));
|
|
1237
|
+
}
|
|
1238
|
+
// The router, ProxyAdmin, and shared hybrid move together only after
|
|
1239
|
+
// router and delayed-flow enrollment have converged.
|
|
1240
|
+
transactions.push(...ownershipTxs);
|
|
578
1241
|
break;
|
|
579
1242
|
}
|
|
580
1243
|
default: {
|