fix:enable `DefaultFallbackRoutingIsm` through non-factory deployment (#3009)

### Description

- `DefaultFallbackRoutingIsm` needs the mailbox address which meant
`DefaultFallbackRoutingIsmFactory` needed the mailbox address which is
not ideal. So, I switched to non-factory deployments for this specific
ISM type.
- CLI does the ISM deployment inside core deployment instead of ISM
first and core then.
- Using the coreDeployer's `cachedAddresses` to store the latest ie
toplevel ISM.

### Drive-by changes

- fixed bug in `ismFactory.fromAddressMap` to not use the multiprovider
filtered by contract map. This is undesirable as CLI user can provide
chain selection and artifacts which doesn't include all the chains in
the configs (and when we call `multiprovider.getDomainId` for the origin
which may now be missing from the filtered MP, we get an error).

### Related issues

- fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895

### Backward compatibility

Yes

### Testing

Manual through CLI
cli-figlet
Kunal Arora 12 months ago committed by GitHub
parent 8b16adee48
commit e06fe0b327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .changeset/large-guests-jump.md
  2. 20
      solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol
  3. 2
      solidity/test/isms/DomainRoutingIsm.t.sol
  4. 2
      typescript/cli/examples/ism-advanced.yaml
  5. 27
      typescript/cli/src/config/ism.ts
  6. 4
      typescript/cli/src/config/multisig.ts
  7. 10
      typescript/cli/src/config/warp.ts
  8. 29
      typescript/cli/src/deploy/core.ts
  9. 2
      typescript/cli/src/tests/ism.test.ts
  10. 2
      typescript/cli/src/tests/multisig.test.ts
  11. 8
      typescript/infra/src/govern/HyperlaneCoreGovernor.ts
  12. 2
      typescript/sdk/hardhat.config.ts
  13. 24
      typescript/sdk/src/core/HyperlaneCoreDeployer.ts
  14. 4
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  15. 4
      typescript/sdk/src/deploy/contracts.ts
  16. 10
      typescript/sdk/src/hook/HyperlaneHookDeployer.ts
  17. 1
      typescript/sdk/src/index.ts
  18. 72
      typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts
  19. 223
      typescript/sdk/src/ism/HyperlaneIsmFactory.ts
  20. 8
      typescript/sdk/src/ism/types.ts

@ -0,0 +1,8 @@
---
'@hyperlane-xyz/infra': patch
'@hyperlane-xyz/cli': patch
'@hyperlane-xyz/sdk': patch
'@hyperlane-xyz/core': patch
---
Supporting DefaultFallbackRoutingIsm through non-factory deployments

@ -18,10 +18,12 @@ abstract contract AbstractDomainRoutingIsmFactory {
/**
* @notice Deploys and initializes a DomainRoutingIsm using a minimal proxy
* @param _owner The owner to set on the ISM
* @param _domains The origin domains
* @param _modules The ISMs to use to verify messages
*/
function deploy(
address _owner,
uint32[] calldata _domains,
IInterchainSecurityModule[] calldata _modules
) external returns (DomainRoutingIsm) {
@ -29,7 +31,7 @@ abstract contract AbstractDomainRoutingIsmFactory {
MinimalProxy.create(implementation())
);
emit ModuleDeployed(_ism);
_ism.initialize(msg.sender, _domains, _modules);
_ism.initialize(_owner, _domains, _modules);
return _ism;
}
@ -51,19 +53,3 @@ contract DomainRoutingIsmFactory is AbstractDomainRoutingIsmFactory {
return _implementation;
}
}
/**
* @title DefaultFallbackRoutingIsmFactory
*/
contract DefaultFallbackRoutingIsmFactory is AbstractDomainRoutingIsmFactory {
// ============ Immutables ============
address internal immutable _implementation;
constructor(address mailbox) {
_implementation = address(new DefaultFallbackRoutingIsm(mailbox));
}
function implementation() public view override returns (address) {
return _implementation;
}
}

@ -5,7 +5,7 @@ import "forge-std/Test.sol";
import {DomainRoutingIsm} from "../../contracts/isms/routing/DomainRoutingIsm.sol";
import {DefaultFallbackRoutingIsm} from "../../contracts/isms/routing/DefaultFallbackRoutingIsm.sol";
import {DefaultFallbackRoutingIsmFactory, DomainRoutingIsmFactory} from "../../contracts/isms/routing/DomainRoutingIsmFactory.sol";
import {DomainRoutingIsmFactory} from "../../contracts/isms/routing/DomainRoutingIsmFactory.sol";
import {IInterchainSecurityModule} from "../../contracts/interfaces/IInterchainSecurityModule.sol";
import {MessageUtils, TestIsm} from "./IsmTestUtils.sol";
import {TestMailbox} from "../../contracts/test/TestMailbox.sol";

@ -1,5 +1,5 @@
anvil1:
type: domainRoutingIsm
type: defaultFallbackRoutingIsm
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
domains:
anvil2:

@ -1,7 +1,7 @@
import { confirm, input, select } from '@inquirer/prompts';
import { z } from 'zod';
import { ChainMap, ChainName, IsmType } from '@hyperlane-xyz/sdk';
import { ChainMap, ChainName, IsmType, ZHash } from '@hyperlane-xyz/sdk';
import { errorRed, log, logBlue, logGreen } from '../../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js';
@ -15,13 +15,16 @@ const MultisigIsmConfigSchema = z.object({
z.literal(IsmType.MESSAGE_ID_MULTISIG),
]),
threshold: z.number(),
validators: z.array(z.string()),
validators: z.array(ZHash),
});
const RoutingIsmConfigSchema: z.ZodSchema<any> = z.lazy(() =>
z.object({
type: z.literal(IsmType.ROUTING),
owner: z.string(),
type: z.union([
z.literal(IsmType.ROUTING),
z.literal(IsmType.FALLBACK_ROUTING),
]),
owner: ZHash,
domains: z.record(IsmConfigSchema),
}),
);
@ -160,6 +163,12 @@ export async function createIsmConfig(
description:
'Each origin chain can be verified by the specified ISM type via RoutingISM',
},
{
value: IsmType.FALLBACK_ROUTING,
name: IsmType.FALLBACK_ROUTING,
description:
"You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM",
},
{
value: IsmType.AGGREGATION,
name: IsmType.AGGREGATION,
@ -180,8 +189,11 @@ export async function createIsmConfig(
moduleType === IsmType.MERKLE_ROOT_MULTISIG
) {
lastConfig = await createMultisigConfig(moduleType);
} else if (moduleType === IsmType.ROUTING) {
lastConfig = await createRoutingConfig(remote, origins);
} else if (
moduleType === IsmType.ROUTING ||
moduleType === IsmType.FALLBACK_ROUTING
) {
lastConfig = await createRoutingConfig(moduleType, remote, origins);
} else if (moduleType === IsmType.AGGREGATION) {
lastConfig = await createAggregationConfig(remote, origins);
} else if (moduleType === IsmType.TEST_ISM) {
@ -241,6 +253,7 @@ export async function createAggregationConfig(
}
export async function createRoutingConfig(
type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING,
remote: ChainName,
chains: ChainName[],
): Promise<ZodIsmConfig> {
@ -259,7 +272,7 @@ export async function createRoutingConfig(
domainsMap[chain] = config;
}
return {
type: IsmType.ROUTING,
type,
owner: ownerAddress,
domains: domainsMap,
};

@ -1,7 +1,7 @@
import { confirm, input } from '@inquirer/prompts';
import { z } from 'zod';
import { ChainMap, MultisigConfig } from '@hyperlane-xyz/sdk';
import { ChainMap, MultisigConfig, ZHash } from '@hyperlane-xyz/sdk';
import {
Address,
isValidAddress,
@ -19,7 +19,7 @@ import { readChainConfigsIfExists } from './chain.js';
const MultisigConfigMapSchema = z.object({}).catchall(
z.object({
threshold: z.number(),
validators: z.array(z.string()),
validators: z.array(ZHash),
}),
);
export type MultisigConfigMap = z.infer<typeof MultisigConfigMapSchema>;

@ -2,7 +2,7 @@ import { confirm, input } from '@inquirer/prompts';
import { ethers } from 'ethers';
import { z } from 'zod';
import { TokenType } from '@hyperlane-xyz/sdk';
import { TokenType, ZHash } from '@hyperlane-xyz/sdk';
import { errorRed, logBlue, logGreen } from '../../logger.js';
import {
@ -14,9 +14,9 @@ import { FileFormat, readYamlOrJson, writeYamlOrJson } from '../utils/files.js';
import { readChainConfigsIfExists } from './chain.js';
const ConnectionConfigSchema = {
mailbox: z.string().optional(),
interchainGasPaymaster: z.string().optional(),
interchainSecurityModule: z.string().optional(),
mailbox: ZHash.optional(),
interchainGasPaymaster: ZHash.optional(),
interchainSecurityModule: ZHash.optional(),
foreignDeployment: z.string().optional(),
};
@ -24,7 +24,7 @@ export const WarpRouteConfigSchema = z.object({
base: z.object({
type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)),
chainName: z.string(),
address: z.string().optional(),
address: ZHash.optional(),
isNft: z.boolean().optional(),
name: z.string().optional(),
symbol: z.string().optional(),

@ -279,24 +279,15 @@ async function executeDeploy({
mergedContractAddrs,
multiProvider,
);
// 3. Deploy ISM contracts to remote deployable chains
logBlue('Deploying ISMs');
// 3. Construct ISM configs for all deployable chains
const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = {};
const defaultIsms: ChainMap<Address> = {};
const defaultIsms: ChainMap<IsmConfig> = {};
for (const ismOrigin of chains) {
logBlue(`Deploying ISM to ${ismOrigin}`);
const ismConfig =
defaultIsms[ismOrigin] =
ismConfigs[ismOrigin] ??
buildIsmConfig(owner, ismOrigin, chains, multisigConfigs);
ismContracts[ismOrigin] = {
interchainSecurityModule: await ismFactory.deploy(ismOrigin, ismConfig),
};
defaultIsms[ismOrigin] =
ismContracts[ismOrigin].interchainSecurityModule.address;
}
artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts);
logGreen('ISM contracts deployed');
// 4. Deploy core contracts to chains
logBlue(`Deploying core contracts to ${chains.join(', ')}`);
@ -310,6 +301,16 @@ async function executeDeploy({
multisigConfigs,
);
const coreContracts = await coreDeployer.deploy(coreConfigs);
// 4.5 recover the toplevel ISM address
const isms: HyperlaneAddressesMap<any> = {};
for (const chain of chains) {
isms[chain] = {
interchainSecurityModule:
coreDeployer.cachedAddresses[chain].interchainSecurityModule,
};
}
artifacts = objMerge(artifacts, isms);
artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts);
logGreen('Core contracts deployed');
@ -358,7 +359,7 @@ function buildIsmConfig(
function buildCoreConfigMap(
owner: Address,
chains: ChainName[],
defaultIsms: ChainMap<Address>,
defaultIsms: ChainMap<IsmConfig>,
hooksConfig: ChainMap<HooksConfig>,
multisigConfigs: ChainMap<MultisigConfig>,
): ChainMap<CoreConfig> {
@ -381,7 +382,7 @@ function buildCoreConfigMap(
}, {});
}
function buildTestRecipientConfigMap(
export function buildTestRecipientConfigMap(
chains: ChainName[],
addressesMap: HyperlaneAddressesMap<any>,
): ChainMap<TestRecipientConfig> {

@ -10,7 +10,7 @@ describe('readIsmConfig', () => {
const exampleIsmConfig: ChainMap<IsmConfig> = {
anvil1: {
type: IsmType.ROUTING,
type: IsmType.FALLBACK_ROUTING,
owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720',
domains: {
anvil2: {

@ -36,6 +36,6 @@ describe('readMultisigConfig', () => {
it('invalid address', () => {
expect(function () {
readMultisigConfig('src/tests/multisig/invalid-address-fail.yaml');
}).to.throw('Invalid address 0xa0ee7a142d267c1n36714e4a8f7561f20a79720');
}).to.throw('Invalid multisig config: anvil2,validators,0 => Invalid');
});
});

@ -29,10 +29,10 @@ export class HyperlaneCoreGovernor extends HyperlaneAppGovernor<
case MailboxViolationType.DefaultIsm: {
let ismAddress: string;
if (typeof violation.expected === 'object') {
const ism = await this.checker.ismFactory.deploy(
violation.chain,
violation.expected,
);
const ism = await this.checker.ismFactory.deploy({
destination: violation.chain,
config: violation.expected,
});
ismAddress = ism.address;
} else if (typeof violation.expected === 'string') {
ismAddress = violation.expected;

@ -7,7 +7,7 @@ import '@typechain/hardhat';
*/
module.exports = {
solidity: {
version: '0.7.6',
version: '0.8.19',
settings: {
optimizer: {
enabled: true,

@ -22,7 +22,6 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
CoreConfig,
CoreFactories
> {
startingBlockNumbers: ChainMap<number | undefined> = {};
hookDeployer: HyperlaneHookDeployer;
constructor(
@ -69,8 +68,13 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
);
if (!matches) {
this.logger('Deploying default ISM');
defaultIsm = await this.deployIsm(chain, config.defaultIsm);
defaultIsm = await this.deployIsm(
chain,
config.defaultIsm,
mailbox.address,
);
}
this.cachedAddresses[chain].interchainSecurityModule = defaultIsm;
const hookAddresses = { mailbox: mailbox.address, proxyAdmin };
@ -168,8 +172,16 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
return hooks[config.type].address;
}
async deployIsm(chain: ChainName, config: IsmConfig): Promise<Address> {
const ism = await this.ismFactory.deploy(chain, config);
async deployIsm(
chain: ChainName,
config: IsmConfig,
mailbox: Address,
): Promise<Address> {
const ism = await this.ismFactory.deploy({
destination: chain,
config,
mailbox,
});
this.addDeployedContracts(chain, this.ismFactory.deployedIsms[chain]);
return ism.address;
}
@ -187,10 +199,6 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
const mailbox = await this.deployMailbox(chain, config, proxyAdmin.address);
// TODO: remove once agents fetch deployedBlock from mailbox
const deployedBlock = await mailbox.deployedBlock();
this.startingBlockNumbers[chain] = deployedBlock.toNumber();
const validatorAnnounce = await this.deployValidatorAnnounce(
chain,
mailbox.address,

@ -220,7 +220,8 @@ export abstract class HyperlaneDeployer<
this.multiProvider,
ismFactory.getContracts(chain),
);
targetIsm = (await ismFactory.deploy(chain, config)).address;
targetIsm = (await ismFactory.deploy({ destination: chain, config }))
.address;
}
if (!matches) {
await this.runIfOwner(chain, contract, async () => {
@ -269,7 +270,6 @@ export abstract class HyperlaneDeployer<
config: MailboxClientConfig,
): Promise<void> {
this.logger(`Initializing mailbox client (if not already) on ${local}...`);
this.logger(`MailboxClient Config: ${JSON.stringify(config)}`);
if (config.hook) {
await this.configureHook(
local,

@ -13,9 +13,6 @@ export const proxyFactoryFactories = {
aggregationIsmFactory: new StaticAggregationIsmFactory__factory(),
aggregationHookFactory: new StaticAggregationHookFactory__factory(),
routingIsmFactory: new DomainRoutingIsmFactory__factory(),
// TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895
// defaultFallbackRoutingIsmFactory:
// new DefaultFallbackRoutingIsmFactory__factory(),
};
export type ProxyFactoryFactories = typeof proxyFactoryFactories;
@ -29,5 +26,4 @@ export const proxyFactoryImplementations: ProxyFactoryImplementations = {
aggregationIsmFactory: 'StaticAggregationIsm',
aggregationHookFactory: 'StaticAggregationHook',
routingIsmFactory: 'DomaingRoutingIsm',
// defaultFallbackRoutingIsmFactory: 'DefaultFallbackRoutingIsm',
};

@ -182,11 +182,11 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer<
origin: chain,
nativeBridge: l2Messenger,
};
const opstackIsm = (await this.ismFactory.deploy(
config.destinationChain,
ismConfig,
chain,
)) as OPStackIsm;
const opstackIsm = (await this.ismFactory.deploy({
destination: config.destinationChain,
config: ismConfig,
origin: chain,
})) as OPStackIsm;
// deploy opstack hook
const hook = await this.deployContract(chain, HookType.OP_STACK, [
mailbox,

@ -178,6 +178,7 @@ export {
getDomainId,
isValidChainMetadata,
} from './metadata/chainMetadataTypes';
export { ZHash } from './metadata/customZodTypes';
export {
HyperlaneDeploymentArtifacts,
HyperlaneDeploymentArtifactsSchema,

@ -4,6 +4,8 @@ import { ethers } from 'hardhat';
import { error } from '@hyperlane-xyz/utils';
import { TestChains } from '../consts/chains';
import { TestCoreApp } from '../core/TestCoreApp';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { randomAddress, randomInt } from '../test/testUtils';
@ -72,7 +74,9 @@ const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => {
};
describe('HyperlaneIsmFactory', async () => {
let factory: HyperlaneIsmFactory;
let ismFactory: HyperlaneIsmFactory;
let coreApp: TestCoreApp;
const chain = 'test1';
before(async () => {
@ -80,20 +84,24 @@ describe('HyperlaneIsmFactory', async () => {
const multiProvider = MultiProvider.createTestMultiProvider({ signer });
const deployer = new HyperlaneProxyFactoryDeployer(multiProvider);
const contracts = await deployer.deploy({ [chain]: {} });
factory = new HyperlaneIsmFactory(contracts, multiProvider);
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider);
ismFactory = new HyperlaneIsmFactory(
await ismFactoryDeployer.deploy(multiProvider.mapKnownChains(() => ({}))),
multiProvider,
);
const coreDeployer = new TestCoreDeployer(multiProvider, ismFactory);
coreApp = await coreDeployer.deployApp();
});
it('deploys a simple ism', async () => {
const config = randomMultisigIsmConfig(3, 5);
const ism = await factory.deploy(chain, config);
const ism = await ismFactory.deploy({ destination: chain, config });
const matches = await moduleMatchesConfig(
chain,
ism.address,
config,
factory.multiProvider,
factory.getContracts(chain),
ismFactory.multiProvider,
ismFactory.getContracts(chain),
);
expect(matches).to.be.true;
});
@ -103,7 +111,7 @@ describe('HyperlaneIsmFactory', async () => {
const config = randomIsmConfig();
let ismAddress: string;
try {
const ism = await factory.deploy(chain, config);
const ism = await ismFactory.deploy({ destination: chain, config });
ismAddress = ism.address;
} catch (e) {
error('Failed to deploy random ism config', e);
@ -116,8 +124,8 @@ describe('HyperlaneIsmFactory', async () => {
chain,
ismAddress,
config,
factory.multiProvider,
factory.getContracts(chain),
ismFactory.multiProvider,
ismFactory.getContracts(chain),
);
expect(matches).to.be.true;
} catch (e) {
@ -127,4 +135,48 @@ describe('HyperlaneIsmFactory', async () => {
}
});
}
it('deploys routingIsm with correct routes', async () => {
const config: RoutingIsmConfig = {
type: IsmType.ROUTING,
owner: randomAddress(),
domains: Object.fromEntries(
TestChains.map((c) => [c, randomIsmConfig()]),
),
};
const ism = await ismFactory.deploy({ destination: chain, config });
const matches = await moduleMatchesConfig(
chain,
ism.address,
config,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
);
expect(matches).to.be.true;
});
it('deploys defaultFallbackRoutingIsm with correct routes and fallback to mailbox', async () => {
const config: RoutingIsmConfig = {
type: IsmType.FALLBACK_ROUTING,
owner: randomAddress(),
domains: Object.fromEntries(
TestChains.map((c) => [c, randomIsmConfig()]),
),
};
const mailbox = await coreApp.getContracts(chain).mailbox;
const ism = await ismFactory.deploy({
destination: chain,
config,
mailbox: mailbox.address,
}); // not through an actual factory just for maintaining consistency in naming
const matches = await moduleMatchesConfig(
chain,
ism.address,
config,
ismFactory.multiProvider,
ismFactory.getContracts(chain),
mailbox.address,
);
expect(matches).to.be.true;
});
});

@ -2,11 +2,19 @@ import { debug } from 'debug';
import { ethers } from 'ethers';
import {
DefaultFallbackRoutingIsm,
DefaultFallbackRoutingIsm__factory,
DomainRoutingIsm,
DomainRoutingIsm__factory,
IAggregationIsm,
IAggregationIsm__factory,
IInterchainSecurityModule__factory,
IMultisigIsm,
IMultisigIsm__factory,
IRoutingIsm,
IRoutingIsm__factory,
MailboxClient__factory,
OPStackIsm,
OPStackIsm__factory,
StaticAddressSetFactory,
StaticAggregationIsm__factory,
@ -71,27 +79,29 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
);
return new HyperlaneIsmFactory(
helper.contractsMap,
helper.multiProvider,
multiProvider,
debug('hyperlane:IsmFactoryApp'),
);
}
async deploy<C extends IsmConfig>(
chain: ChainName,
config: C,
origin?: ChainName,
): Promise<DeployedIsm> {
async deploy<C extends IsmConfig>(params: {
destination: ChainName;
config: C;
origin?: ChainName;
mailbox?: Address;
}): Promise<DeployedIsm> {
const { destination, config, origin, mailbox } = params;
if (typeof config === 'string') {
// @ts-ignore
return IInterchainSecurityModule__factory.connect(
config,
this.multiProvider.getSignerOrProvider(chain),
this.multiProvider.getSignerOrProvider(destination),
);
}
const ismType = config.type;
this.logger(
`Deploying ${ismType} to ${chain} ${
`Deploying ${ismType} to ${destination} ${
origin ? `(for verifying ${origin})` : ''
}`,
);
@ -100,20 +110,31 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
switch (ismType) {
case IsmType.MESSAGE_ID_MULTISIG:
case IsmType.MERKLE_ROOT_MULTISIG:
contract = await this.deployMultisigIsm(chain, config);
contract = await this.deployMultisigIsm(destination, config);
break;
case IsmType.ROUTING:
contract = await this.deployRoutingIsm(chain, config);
case IsmType.FALLBACK_ROUTING:
contract = await this.deployRoutingIsm({
destination,
config,
origin,
mailbox,
});
break;
case IsmType.AGGREGATION:
contract = await this.deployAggregationIsm(chain, config, origin);
contract = await this.deployAggregationIsm({
destination,
config,
origin,
mailbox,
});
break;
case IsmType.OP_STACK:
contract = await this.deployOpStackIsm(chain, config);
contract = await this.deployOpStackIsm(destination, config);
break;
case IsmType.TEST_ISM:
contract = await this.multiProvider.handleDeploy(
chain,
destination,
new TestIsm__factory(),
[],
);
@ -122,33 +143,36 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
throw new Error(`Unsupported ISM type ${ismType}`);
}
if (!this.deployedIsms[chain]) {
this.deployedIsms[chain] = {};
if (!this.deployedIsms[destination]) {
this.deployedIsms[destination] = {};
}
if (origin) {
// if we're deploying network-specific contracts (e.g. ISMs), store them as sub-entry
// under that network's key (`origin`)
if (!this.deployedIsms[chain][origin]) {
this.deployedIsms[chain][origin] = {};
if (!this.deployedIsms[destination][origin]) {
this.deployedIsms[destination][origin] = {};
}
this.deployedIsms[chain][origin][ismType] = contract;
this.deployedIsms[destination][origin][ismType] = contract;
} else {
// otherwise store the entry directly
this.deployedIsms[chain][ismType] = contract;
this.deployedIsms[destination][ismType] = contract;
}
return contract;
}
private async deployMultisigIsm(chain: ChainName, config: MultisigIsmConfig) {
const signer = this.multiProvider.getSigner(chain);
protected async deployMultisigIsm(
destination: ChainName,
config: MultisigIsmConfig,
): Promise<IMultisigIsm> {
const signer = this.multiProvider.getSigner(destination);
const multisigIsmFactory =
config.type === IsmType.MERKLE_ROOT_MULTISIG
? this.getContracts(chain).merkleRootMultisigIsmFactory
: this.getContracts(chain).messageIdMultisigIsmFactory;
? this.getContracts(destination).merkleRootMultisigIsmFactory
: this.getContracts(destination).messageIdMultisigIsmFactory;
const address = await this.deployStaticAddressSet(
chain,
destination,
multisigIsmFactory,
config.validators,
config.threshold,
@ -157,71 +181,107 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
return IMultisigIsm__factory.connect(address, signer);
}
private async deployRoutingIsm(chain: ChainName, config: RoutingIsmConfig) {
const signer = this.multiProvider.getSigner(chain);
const routingIsmFactory = this.getContracts(chain).routingIsmFactory;
// TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895
// config.defaultFallback
// ? this.getContracts(chain).defaultFallbackRoutingIsmFactory
// : this.getContracts(chain).routingIsmFactory;
protected async deployRoutingIsm(params: {
destination: ChainName;
config: RoutingIsmConfig;
origin?: ChainName;
mailbox?: Address;
}): Promise<IRoutingIsm> {
const { destination, config, mailbox } = params;
const routingIsmFactory = this.getContracts(destination).routingIsmFactory;
const isms: ChainMap<Address> = {};
for (const origin of Object.keys(config.domains)) {
const ism = await this.deploy(chain, config.domains[origin], origin);
const ism = await this.deploy({
destination,
config: config.domains[origin],
origin,
mailbox,
});
isms[origin] = ism.address;
}
const domains = Object.keys(isms).map((chain) =>
this.multiProvider.getDomainId(chain),
);
const submoduleAddresses = Object.values(isms);
const overrides = this.multiProvider.getTransactionOverrides(chain);
const tx = await routingIsmFactory.deploy(
domains,
submoduleAddresses,
overrides,
);
const receipt = await this.multiProvider.handleTx(chain, tx);
// TODO: Break this out into a generalized function
const dispatchLogs = receipt.logs
.map((log) => {
try {
return routingIsmFactory.interface.parseLog(log);
} catch (e) {
return undefined;
}
})
.filter(
(log): log is ethers.utils.LogDescription =>
!!log && log.name === 'ModuleDeployed',
const overrides = this.multiProvider.getTransactionOverrides(destination);
let receipt: ethers.providers.TransactionReceipt;
let routingIsm: DomainRoutingIsm | DefaultFallbackRoutingIsm;
if (config.type === IsmType.FALLBACK_ROUTING) {
if (!mailbox) {
throw new Error(
'Mailbox address is required for deploying fallback routing ISM',
);
}
debug('Deploying fallback routing ISM ...');
routingIsm = await this.multiProvider.handleDeploy(
destination,
new DefaultFallbackRoutingIsm__factory(),
[mailbox],
);
const moduleAddress = dispatchLogs[0].args['module'];
const routingIsm = DomainRoutingIsm__factory.connect(
moduleAddress,
this.multiProvider.getSigner(chain),
);
this.logger(`Transferring ownership of routing ISM to ${config.owner}`);
await this.multiProvider.handleTx(
chain,
await routingIsm.transferOwnership(config.owner, overrides),
);
const address = dispatchLogs[0].args['module'];
return IRoutingIsm__factory.connect(address, signer);
debug('Initialising fallback routing ISM ...');
receipt = await this.multiProvider.handleTx(
destination,
routingIsm['initialize(address,uint32[],address[])'](
config.owner,
domains,
submoduleAddresses,
overrides,
),
);
} else {
const tx = await routingIsmFactory.deploy(
config.owner,
domains,
submoduleAddresses,
overrides,
);
receipt = await this.multiProvider.handleTx(destination, tx);
// TODO: Break this out into a generalized function
const dispatchLogs = receipt.logs
.map((log) => {
try {
return routingIsmFactory.interface.parseLog(log);
} catch (e) {
return undefined;
}
})
.filter(
(log): log is ethers.utils.LogDescription =>
!!log && log.name === 'ModuleDeployed',
);
const moduleAddress = dispatchLogs[0].args['module'];
routingIsm = DomainRoutingIsm__factory.connect(
moduleAddress,
this.multiProvider.getSigner(destination),
);
}
return routingIsm;
}
private async deployAggregationIsm(
chain: ChainName,
config: AggregationIsmConfig,
origin?: ChainName,
) {
const signer = this.multiProvider.getSigner(chain);
protected async deployAggregationIsm(params: {
destination: ChainName;
config: AggregationIsmConfig;
origin?: ChainName;
mailbox?: Address;
}): Promise<IAggregationIsm> {
const { destination, config, origin, mailbox } = params;
const signer = this.multiProvider.getSigner(destination);
const aggregationIsmFactory =
this.getContracts(chain).aggregationIsmFactory;
this.getContracts(destination).aggregationIsmFactory;
const addresses: Address[] = [];
for (const module of config.modules) {
const submodule = await this.deploy(chain, module, origin);
const submodule = await this.deploy({
destination,
config: module,
origin,
mailbox,
});
addresses.push(submodule.address);
}
const address = await this.deployStaticAddressSet(
chain,
destination,
aggregationIsmFactory,
addresses,
config.threshold,
@ -229,7 +289,10 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
return IAggregationIsm__factory.connect(address, signer);
}
private async deployOpStackIsm(chain: ChainName, config: OpStackIsmConfig) {
protected async deployOpStackIsm(
chain: ChainName,
config: OpStackIsmConfig,
): Promise<OPStackIsm> {
return await this.multiProvider.handleDeploy(
chain,
new OPStackIsm__factory(),
@ -397,7 +460,7 @@ export async function moduleMatchesConfig(
config: IsmConfig,
multiProvider: MultiProvider,
contracts: HyperlaneContracts<ProxyFactoryFactories>,
_origin?: ChainName,
mailbox?: Address,
): Promise<boolean> {
if (typeof config === 'string') {
return eqAddress(moduleAddress, config);
@ -438,6 +501,7 @@ export async function moduleMatchesConfig(
matches = eqAddress(expectedAddress, module.address);
break;
}
case IsmType.FALLBACK_ROUTING:
case IsmType.ROUTING: {
// A RoutingIsm matches if:
// 1. The set of domains in the config equals those on-chain
@ -450,6 +514,15 @@ export async function moduleMatchesConfig(
// Check that the RoutingISM owner matches the config
const owner = await routingIsm.owner();
matches = matches && eqAddress(owner, config.owner);
// check if the mailbox matches the config for fallback routing
if (config.type === IsmType.FALLBACK_ROUTING) {
const client = MailboxClient__factory.connect(moduleAddress, provider);
const mailboxAddress = await client.mailbox();
matches =
matches &&
mailbox !== undefined &&
eqAddress(mailboxAddress, mailbox);
}
// Recursively check that the submodule for each configured
// domain matches the submodule config.
for (const [origin, subConfig] of Object.entries(config.domains)) {
@ -462,7 +535,7 @@ export async function moduleMatchesConfig(
subConfig,
multiProvider,
contracts,
origin,
mailbox,
);
matches = matches && subModuleMatches;
}

@ -26,6 +26,7 @@ export enum ModuleType {
export enum IsmType {
OP_STACK = 'opStackIsm',
ROUTING = 'domainRoutingIsm',
FALLBACK_ROUTING = 'defaultFallbackRoutingIsm',
AGGREGATION = 'staticAggregationIsm',
MERKLE_ROOT_MULTISIG = 'merkleRootMultisigIsm',
MESSAGE_ID_MULTISIG = 'messageIdMultisigIsm',
@ -39,6 +40,8 @@ export function ismTypeToModuleType(ismType: IsmType): ModuleType {
return ModuleType.NULL;
case IsmType.ROUTING:
return ModuleType.ROUTING;
case IsmType.FALLBACK_ROUTING:
return ModuleType.ROUTING;
case IsmType.AGGREGATION:
return ModuleType.AGGREGATION;
case IsmType.MERKLE_ROOT_MULTISIG:
@ -64,11 +67,9 @@ export type TestIsmConfig = {
};
export type RoutingIsmConfig = {
type: IsmType.ROUTING;
type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING;
owner: Address;
domains: ChainMap<IsmConfig>;
// TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895
// defaultFallback?: boolean;
};
export type AggregationIsmConfig = {
@ -93,6 +94,7 @@ export type IsmConfig =
export type DeployedIsmType = {
[IsmType.ROUTING]: IRoutingIsm;
[IsmType.FALLBACK_ROUTING]: IRoutingIsm;
[IsmType.AGGREGATION]: IAggregationIsm;
[IsmType.MERKLE_ROOT_MULTISIG]: IMultisigIsm;
[IsmType.MESSAGE_ID_MULTISIG]: IMultisigIsm;

Loading…
Cancel
Save