diff --git a/.changeset/large-guests-jump.md b/.changeset/large-guests-jump.md new file mode 100644 index 0000000000..6276c4c088 --- /dev/null +++ b/.changeset/large-guests-jump.md @@ -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 diff --git a/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol b/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol index d0681723e3..fc8b203834 100644 --- a/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol +++ b/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol @@ -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; - } -} diff --git a/solidity/test/isms/DomainRoutingIsm.t.sol b/solidity/test/isms/DomainRoutingIsm.t.sol index 58166d0647..88785d3610 100644 --- a/solidity/test/isms/DomainRoutingIsm.t.sol +++ b/solidity/test/isms/DomainRoutingIsm.t.sol @@ -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"; diff --git a/typescript/cli/examples/ism-advanced.yaml b/typescript/cli/examples/ism-advanced.yaml index 8124102a50..5447811082 100644 --- a/typescript/cli/examples/ism-advanced.yaml +++ b/typescript/cli/examples/ism-advanced.yaml @@ -1,5 +1,5 @@ anvil1: - type: domainRoutingIsm + type: defaultFallbackRoutingIsm owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' domains: anvil2: diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 9f4846ce83..45a111ee26 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -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 = 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 { @@ -259,7 +272,7 @@ export async function createRoutingConfig( domainsMap[chain] = config; } return { - type: IsmType.ROUTING, + type, owner: ownerAddress, domains: domainsMap, }; diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index 19ef2ad5ac..ba59111abf 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -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; diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index f793959d9a..d187e9b9e7 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -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(), diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index f4b6701450..c49875ba26 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -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
= {}; + const defaultIsms: ChainMap = {}; 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 = {}; + 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
, + defaultIsms: ChainMap, hooksConfig: ChainMap, multisigConfigs: ChainMap, ): ChainMap { @@ -381,7 +382,7 @@ function buildCoreConfigMap( }, {}); } -function buildTestRecipientConfigMap( +export function buildTestRecipientConfigMap( chains: ChainName[], addressesMap: HyperlaneAddressesMap, ): ChainMap { diff --git a/typescript/cli/src/tests/ism.test.ts b/typescript/cli/src/tests/ism.test.ts index a0b16d5583..4942963cf1 100644 --- a/typescript/cli/src/tests/ism.test.ts +++ b/typescript/cli/src/tests/ism.test.ts @@ -10,7 +10,7 @@ describe('readIsmConfig', () => { const exampleIsmConfig: ChainMap = { anvil1: { - type: IsmType.ROUTING, + type: IsmType.FALLBACK_ROUTING, owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', domains: { anvil2: { diff --git a/typescript/cli/src/tests/multisig.test.ts b/typescript/cli/src/tests/multisig.test.ts index f54bfd5b24..f56b3e7bdd 100644 --- a/typescript/cli/src/tests/multisig.test.ts +++ b/typescript/cli/src/tests/multisig.test.ts @@ -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'); }); }); diff --git a/typescript/infra/src/govern/HyperlaneCoreGovernor.ts b/typescript/infra/src/govern/HyperlaneCoreGovernor.ts index 019c7321a2..25547381c8 100644 --- a/typescript/infra/src/govern/HyperlaneCoreGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneCoreGovernor.ts @@ -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; diff --git a/typescript/sdk/hardhat.config.ts b/typescript/sdk/hardhat.config.ts index fc40a39315..e65a4508ac 100644 --- a/typescript/sdk/hardhat.config.ts +++ b/typescript/sdk/hardhat.config.ts @@ -7,7 +7,7 @@ import '@typechain/hardhat'; */ module.exports = { solidity: { - version: '0.7.6', + version: '0.8.19', settings: { optimizer: { enabled: true, diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index a1ee914563..4432073a7b 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -22,7 +22,6 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< CoreConfig, CoreFactories > { - startingBlockNumbers: ChainMap = {}; 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
{ - const ism = await this.ismFactory.deploy(chain, config); + async deployIsm( + chain: ChainName, + config: IsmConfig, + mailbox: Address, + ): Promise
{ + 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, diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index fb3cc16e7f..0daf8998ec 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -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 { this.logger(`Initializing mailbox client (if not already) on ${local}...`); - this.logger(`MailboxClient Config: ${JSON.stringify(config)}`); if (config.hook) { await this.configureHook( local, diff --git a/typescript/sdk/src/deploy/contracts.ts b/typescript/sdk/src/deploy/contracts.ts index ad4e42c7c2..542c245c13 100644 --- a/typescript/sdk/src/deploy/contracts.ts +++ b/typescript/sdk/src/deploy/contracts.ts @@ -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', }; diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index 783bbe7ee7..5eb961cf9f 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -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, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index a5122765ce..2e76f69772 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -178,6 +178,7 @@ export { getDomainId, isValidChainMetadata, } from './metadata/chainMetadataTypes'; +export { ZHash } from './metadata/customZodTypes'; export { HyperlaneDeploymentArtifacts, HyperlaneDeploymentArtifactsSchema, diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index cdb0ba9edc..c5061e08d8 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -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; + }); }); diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 0f1dbba529..b7ada8b586 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -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 { ); return new HyperlaneIsmFactory( helper.contractsMap, - helper.multiProvider, + multiProvider, debug('hyperlane:IsmFactoryApp'), ); } - async deploy( - chain: ChainName, - config: C, - origin?: ChainName, - ): Promise { + async deploy(params: { + destination: ChainName; + config: C; + origin?: ChainName; + mailbox?: Address; + }): Promise { + 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 { 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 { 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 { + 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 { 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 { + const { destination, config, mailbox } = params; + const routingIsmFactory = this.getContracts(destination).routingIsmFactory; const isms: ChainMap
= {}; 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 { + 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 { return IAggregationIsm__factory.connect(address, signer); } - private async deployOpStackIsm(chain: ChainName, config: OpStackIsmConfig) { + protected async deployOpStackIsm( + chain: ChainName, + config: OpStackIsmConfig, + ): Promise { return await this.multiProvider.handleDeploy( chain, new OPStackIsm__factory(), @@ -397,7 +460,7 @@ export async function moduleMatchesConfig( config: IsmConfig, multiProvider: MultiProvider, contracts: HyperlaneContracts, - _origin?: ChainName, + mailbox?: Address, ): Promise { 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; } diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index 0f7e17cd8d..e9d222bfc1 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -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; - // 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;