Existing deployment support in Router tooling, small ISM factory fixes (#2392)

### Description

To accommodate warp routes that exist on EVM chains *and* other
execution environments, we need the ability to enroll remote routers on
EVM-based routers that weren't deployed by the router deployer. This
adds a new optional `existingDeployment` property to the `RouterConfig`,
which means that deployment will not be attempted to this chain, but it
will be enrolled as a remote router on any contracts that are deployed
to. The general flow looks something like:
* Deploy the warp route contracts on all EVM chains, enrolling each
other as routers
* Deploy the warp route contracts on the alt execution environment
chains, also configuring the existing EVM contract deployments as
`existingDeployment`s so that they are enrolled as routers on the alt
execution environment contracts
* Now equipped with *all* the router addresses, any new non-EVM router
addresses are configured as `existingDeployment`s, which now enrolls
these routers on the EVM chains

Note there's still a lot left to be desired even with this change --
e.g. much of the tooling expects the MultiProvider to include metadata
(& even providers) for each and every chain. Because the MultiProvider
is of course EVM only, things get a bit weird. For now, I've been able
to deploy warp routes with configs relating to a sealevel chain
successfully by just running a local Anvil instance and setting the RPC
URL of the sealevel config to that anvil instance. We'll want to
readdress this at some point, but for now I'd prefer to have a flow that
just works and we can be more intentional about bigger deploy / tooling
changes to support alt execution environments in the future

This also includes some small changes to the ISM factory - because we
werent actually waiting for the `enrollValidators` tx to succeed before
trying `setThreshold`, the gas estimation of `setThreshold` would fail
because the threshold of 1 exceeded the current on-chain validator set
size of 0. It also sometimes resulted in nonce contention

I was able to get everything in hyperlane-deploy working locally by
having dependencies be local filepaths

The order of operations for things iiuc looks like:
1. Merge this
2. Ship new SDK version
3. Update hyperlane-token to use this new SDK version
4. Ship new hyperlane-token version
5. Update hyperlane-deploy to use the new SDK version and the new
hyperlane-token

### Drive-by changes

none

### Related issues

- partially
https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2366

### Backward compatibility

_Are these changes backward compatible?_

Yes

_Are there any infrastructure implications, e.g. changes that would
prohibit deploying older commits using this infra tooling?_

None


### Testing

_What kind of testing have these changes undergone?_

unit tests, ran deploy tooling
pull/2400/head
Trevor Porter 1 year ago committed by GitHub
parent feb6d2b4e9
commit 186fcdab36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      typescript/sdk/src/ism/HyperlaneIsmFactory.ts
  2. 7
      typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts
  3. 4
      typescript/sdk/src/router/GasRouterDeployer.ts
  4. 4
      typescript/sdk/src/router/HyperlaneRouterChecker.ts
  5. 57
      typescript/sdk/src/router/HyperlaneRouterDeployer.ts
  6. 8
      typescript/sdk/src/router/types.ts

@ -113,8 +113,14 @@ export class HyperlaneIsmFactory extends HyperlaneApp<IsmFactoryFactories> {
.deploy(); .deploy();
await this.multiProvider.handleTx(chain, multisig.deployTransaction); await this.multiProvider.handleTx(chain, multisig.deployTransaction);
const originDomain = this.multiProvider.getDomainId(origin!); const originDomain = this.multiProvider.getDomainId(origin!);
await multisig.enrollValidators([originDomain], [config.validators]); await this.multiProvider.handleTx(
await multisig.setThreshold(originDomain, config.threshold); chain,
multisig.enrollValidators([originDomain], [config.validators]),
);
await this.multiProvider.handleTx(
chain,
multisig.setThreshold(originDomain, config.threshold),
);
address = multisig.address; address = multisig.address;
} else { } else {
const multisigIsmFactory = const multisigIsmFactory =
@ -163,7 +169,10 @@ export class HyperlaneIsmFactory extends HyperlaneApp<IsmFactoryFactories> {
moduleAddress, moduleAddress,
this.multiProvider.getSigner(chain), this.multiProvider.getSigner(chain),
); );
await routingIsm.transferOwnership(config.owner); await this.multiProvider.handleTx(
chain,
await routingIsm.transferOwnership(config.owner),
);
const address = dispatchLogs[0].args['module']; const address = dispatchLogs[0].args['module'];
return IRoutingIsm__factory.connect(address, signer); return IRoutingIsm__factory.connect(address, signer);
} }

@ -5,7 +5,7 @@ import {
LiquidityLayerRouter, LiquidityLayerRouter,
PortalAdapter, PortalAdapter,
} from '@hyperlane-xyz/core'; } from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils'; import { types, utils } from '@hyperlane-xyz/utils';
import { HyperlaneContracts, HyperlaneContractsMap } from '../../contracts'; import { HyperlaneContracts, HyperlaneContractsMap } from '../../contracts';
import { MultiProvider } from '../../providers/MultiProvider'; import { MultiProvider } from '../../providers/MultiProvider';
@ -86,9 +86,10 @@ export class LiquidityLayerDeployer extends ProxiedRouterDeployer<
async enrollRemoteRouters( async enrollRemoteRouters(
contractsMap: HyperlaneContractsMap<LiquidityLayerFactories>, contractsMap: HyperlaneContractsMap<LiquidityLayerFactories>,
configMap: ChainMap<LiquidityLayerConfig>, configMap: ChainMap<LiquidityLayerConfig>,
foreignRouters: ChainMap<types.Address>,
): Promise<void> { ): Promise<void> {
this.logger(`Enroll LiquidityLayerRouters with each other`); this.logger(`Enroll LiquidityLayerRouters with each other`);
await super.enrollRemoteRouters(contractsMap, configMap); await super.enrollRemoteRouters(contractsMap, configMap, foreignRouters);
this.logger(`Enroll CircleBridgeAdapters with each other`); this.logger(`Enroll CircleBridgeAdapters with each other`);
// Hack to allow use of super.enrollRemoteRouters // Hack to allow use of super.enrollRemoteRouters
@ -104,6 +105,7 @@ export class LiquidityLayerDeployer extends ProxiedRouterDeployer<
}), }),
) as unknown as HyperlaneContractsMap<LiquidityLayerFactories>, ) as unknown as HyperlaneContractsMap<LiquidityLayerFactories>,
configMap, configMap,
foreignRouters,
); );
this.logger(`Enroll PortalAdapters with each other`); this.logger(`Enroll PortalAdapters with each other`);
@ -120,6 +122,7 @@ export class LiquidityLayerDeployer extends ProxiedRouterDeployer<
}), }),
) as unknown as HyperlaneContractsMap<LiquidityLayerFactories>, ) as unknown as HyperlaneContractsMap<LiquidityLayerFactories>,
configMap, configMap,
foreignRouters,
); );
} }

@ -1,4 +1,5 @@
import { GasRouter } from '@hyperlane-xyz/core'; import { GasRouter } from '@hyperlane-xyz/core';
import { types } from '@hyperlane-xyz/utils';
import { import {
HyperlaneContracts, HyperlaneContracts,
@ -19,8 +20,9 @@ export abstract class GasRouterDeployer<
async enrollRemoteRouters( async enrollRemoteRouters(
contractsMap: HyperlaneContractsMap<Factories>, contractsMap: HyperlaneContractsMap<Factories>,
configMap: ChainMap<Config>, configMap: ChainMap<Config>,
foreignRouters: ChainMap<types.Address> = {},
): Promise<void> { ): Promise<void> {
await super.enrollRemoteRouters(contractsMap, configMap); await super.enrollRemoteRouters(contractsMap, configMap, foreignRouters);
this.logger(`Setting enrolled router destination gas...`); this.logger(`Setting enrolled router destination gas...`);
for (const [chain, contracts] of Object.entries(contractsMap)) { for (const [chain, contracts] of Object.entries(contractsMap)) {

@ -8,8 +8,10 @@ import { ChainName } from '../types';
import { RouterApp } from './RouterApps'; import { RouterApp } from './RouterApps';
import { import {
ConnectionClientConfig,
ConnectionClientViolation, ConnectionClientViolation,
ConnectionClientViolationType, ConnectionClientViolationType,
OwnableConfig,
RouterConfig, RouterConfig,
} from './types'; } from './types';
@ -33,7 +35,7 @@ export class HyperlaneRouterChecker<
const router = this.app.router(this.app.getContracts(chain)); const router = this.app.router(this.app.getContracts(chain));
const checkConnectionClientProperty = async ( const checkConnectionClientProperty = async (
property: keyof RouterConfig, property: keyof (ConnectionClientConfig & OwnableConfig),
violationType: ConnectionClientViolationType, violationType: ConnectionClientViolationType,
) => { ) => {
const actual = await router[property](); const actual = await router[property]();

@ -3,7 +3,7 @@ import {
Mailbox__factory, Mailbox__factory,
Router, Router,
} from '@hyperlane-xyz/core'; } from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils'; import { types, utils } from '@hyperlane-xyz/utils';
import { import {
HyperlaneContracts, HyperlaneContracts,
@ -15,6 +15,7 @@ import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { moduleCanCertainlyVerify } from '../ism/HyperlaneIsmFactory'; import { moduleCanCertainlyVerify } from '../ism/HyperlaneIsmFactory';
import { RouterConfig } from '../router/types'; import { RouterConfig } from '../router/types';
import { ChainMap } from '../types'; import { ChainMap } from '../types';
import { objFilter, objMap, objMerge } from '../utils/objects';
export abstract class HyperlaneRouterDeployer< export abstract class HyperlaneRouterDeployer<
Config extends RouterConfig, Config extends RouterConfig,
@ -82,28 +83,35 @@ export abstract class HyperlaneRouterDeployer<
} }
async enrollRemoteRouters( async enrollRemoteRouters(
contractsMap: HyperlaneContractsMap<Factories>, deployedContractsMap: HyperlaneContractsMap<Factories>,
_: ChainMap<Config>, _: ChainMap<Config>,
foreignRouters: ChainMap<types.Address> = {},
): Promise<void> { ): Promise<void> {
this.logger( this.logger(
`Enrolling deployed routers with each other (if not already)...`, `Enrolling deployed routers with each other (if not already)...`,
); );
// Make all routers aware of each other. // Make all routers aware of each other.
const deployedChains = Object.keys(contractsMap);
for (const [chain, contracts] of Object.entries(contractsMap)) { // Routers that were deployed.
// only enroll chains which are deployed const deployedRouters: ChainMap<types.Address> = objMap(
const deployedRemoteChains = this.multiProvider deployedContractsMap,
(_, contracts) => this.router(contracts).address,
);
// All routers, including those that were deployed and those with existing deployments.
const allRouters = objMerge(deployedRouters, foreignRouters);
const allChains = Object.keys(allRouters);
for (const [chain, contracts] of Object.entries(deployedContractsMap)) {
const allRemoteChains = this.multiProvider
.getRemoteChains(chain) .getRemoteChains(chain)
.filter((c) => deployedChains.includes(c)); .filter((c) => allChains.includes(c));
const enrollEntries = await Promise.all( const enrollEntries = await Promise.all(
deployedRemoteChains.map(async (remote) => { allRemoteChains.map(async (remote) => {
const remoteDomain = this.multiProvider.getDomainId(remote); const remoteDomain = this.multiProvider.getDomainId(remote);
const current = await this.router(contracts).routers(remoteDomain); const current = await this.router(contracts).routers(remoteDomain);
const expected = utils.addressToBytes32( const expected = utils.addressToBytes32(allRouters[remote]);
this.router(contractsMap[remote]).address,
);
return current !== expected ? [remoteDomain, expected] : undefined; return current !== expected ? [remoteDomain, expected] : undefined;
}), }),
); );
@ -151,12 +159,29 @@ export abstract class HyperlaneRouterDeployer<
async deploy( async deploy(
configMap: ChainMap<Config>, configMap: ChainMap<Config>,
): Promise<HyperlaneContractsMap<Factories>> { ): Promise<HyperlaneContractsMap<Factories>> {
const contractsMap = await super.deploy(configMap); // Only deploy on chains that don't have foreign deployments.
const configMapToDeploy = objFilter(
configMap,
(_chainName, config): config is Config => !config.foreignDeployment,
);
// Create a map of chains that have foreign deployments.
const foreignDeployments: ChainMap<types.Address> = objFilter(
objMap(configMap, (_, config) => config.foreignDeployment),
(_chainName, foreignDeployment): foreignDeployment is string =>
foreignDeployment !== undefined,
);
await this.enrollRemoteRouters(contractsMap, configMap); const deployedContractsMap = await super.deploy(configMapToDeploy);
await this.initConnectionClients(contractsMap, configMap);
await this.transferOwnership(contractsMap, configMap); await this.enrollRemoteRouters(
deployedContractsMap,
configMap,
foreignDeployments,
);
await this.initConnectionClients(deployedContractsMap, configMap);
await this.transferOwnership(deployedContractsMap, configMap);
return contractsMap; return deployedContractsMap;
} }
} }

@ -11,7 +11,13 @@ export type OwnableConfig = {
owner: types.Address; owner: types.Address;
}; };
export type RouterConfig = ConnectionClientConfig & OwnableConfig; export type ForeignDeploymentConfig = {
foreignDeployment?: types.Address;
};
export type RouterConfig = ConnectionClientConfig &
OwnableConfig &
ForeignDeploymentConfig;
export type GasConfig = { export type GasConfig = {
gas: number; gas: number;

Loading…
Cancel
Save