@ -21,7 +21,12 @@ import {
StaticThresholdAddressSetFactory ,
StaticThresholdAddressSetFactory ,
TestIsm__factory ,
TestIsm__factory ,
} from '@hyperlane-xyz/core' ;
} from '@hyperlane-xyz/core' ;
import { Address , eqAddress , formatMessage , warn } from '@hyperlane-xyz/utils' ;
import {
Address ,
eqAddress ,
formatMessage ,
normalizeAddress ,
} from '@hyperlane-xyz/utils' ;
import { HyperlaneApp } from '../app/HyperlaneApp' ;
import { HyperlaneApp } from '../app/HyperlaneApp' ;
import {
import {
@ -34,6 +39,7 @@ import {
ProxyFactoryFactories ,
ProxyFactoryFactories ,
proxyFactoryFactories ,
proxyFactoryFactories ,
} from '../deploy/contracts' ;
} from '../deploy/contracts' ;
import { logger } from '../logger' ;
import { MultiProvider } from '../providers/MultiProvider' ;
import { MultiProvider } from '../providers/MultiProvider' ;
import { ChainMap , ChainName } from '../types' ;
import { ChainMap , ChainName } from '../types' ;
@ -47,6 +53,7 @@ import {
MultisigIsmConfig ,
MultisigIsmConfig ,
OpStackIsmConfig ,
OpStackIsmConfig ,
RoutingIsmConfig ,
RoutingIsmConfig ,
RoutingIsmDelta ,
ismTypeToModuleType ,
ismTypeToModuleType ,
} from './types' ;
} from './types' ;
@ -89,8 +96,9 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
config : C ;
config : C ;
origin? : ChainName ;
origin? : ChainName ;
mailbox? : Address ;
mailbox? : Address ;
existingIsmAddress? : Address ;
} ) : Promise < DeployedIsm > {
} ) : Promise < DeployedIsm > {
const { destination , config , origin , mailbox } = params ;
const { destination , config , origin , mailbox , existingIsmAddress } = params ;
if ( typeof config === 'string' ) {
if ( typeof config === 'string' ) {
// @ts-ignore
// @ts-ignore
return IInterchainSecurityModule__factory . connect (
return IInterchainSecurityModule__factory . connect (
@ -119,6 +127,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
config ,
config ,
origin ,
origin ,
mailbox ,
mailbox ,
existingIsmAddress ,
} ) ;
} ) ;
break ;
break ;
case IsmType . AGGREGATION :
case IsmType . AGGREGATION :
@ -186,77 +195,151 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
config : RoutingIsmConfig ;
config : RoutingIsmConfig ;
origin? : ChainName ;
origin? : ChainName ;
mailbox? : Address ;
mailbox? : Address ;
existingIsmAddress? : Address ;
} ) : Promise < IRoutingIsm > {
} ) : Promise < IRoutingIsm > {
const { destination , config , mailbox } = params ;
const { destination , config , mailbox , existingIsmAddress } = params ;
const routingIsmFactory = this . getContracts ( destination ) . routingIsmFactory ;
const isms : ChainMap < Address > = { } ;
for ( const origin of Object . keys ( config . domains ) ) {
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 ( destination ) ;
const overrides = this . multiProvider . getTransactionOverrides ( destination ) ;
let receipt : ethers.providers.TransactionReceipt ;
const routingIsmFactory = this . getContracts ( destination ) . routingIsmFactory ;
let routingIsm : DomainRoutingIsm | DefaultFallbackRoutingIsm ;
let routingIsm : DomainRoutingIsm | DefaultFallbackRoutingIsm ;
if ( config . type === IsmType . FALLBACK_ROUTING ) {
const delta : RoutingIsmDelta = existingIsmAddress
if ( ! mailbox ) {
? await routingModuleDelta (
throw new Error (
destination ,
'Mailbox address is required for deploying fallback routing ISM' ,
existingIsmAddress ,
config ,
this . multiProvider ,
this . getContracts ( destination ) ,
mailbox ,
)
: {
domainsToUnenroll : [ ] ,
domainsToEnroll : Object.keys ( config . domains ) ,
} ;
const signer = this . multiProvider . getSigner ( destination ) ;
const provider = this . multiProvider . getProvider ( destination ) ;
let isOwner = false ;
if ( existingIsmAddress ) {
const owner = await DomainRoutingIsm__factory . connect (
existingIsmAddress ,
provider ,
) . owner ( ) ;
isOwner = eqAddress ( await signer . getAddress ( ) , owner ) ;
}
// reconfiguring existing routing ISM
if ( existingIsmAddress && isOwner && ! delta . mailbox ) {
const isms : ChainMap < Address > = { } ;
routingIsm = DomainRoutingIsm__factory . connect (
existingIsmAddress ,
this . multiProvider . getSigner ( destination ) ,
) ;
// deploying all the ISMs which have to be updated
for ( const origin of delta . domainsToEnroll ) {
logger (
` Reconfiguring preexisting routing ISM at for origin ${ origin } ... ` ,
) ;
) ;
const ism = await this . deploy ( {
destination ,
config : config.domains [ origin ] ,
origin ,
mailbox ,
} ) ;
isms [ origin ] = ism . address ;
const tx = await routingIsm . set (
this . multiProvider . getDomainId ( origin ) ,
isms [ origin ] ,
overrides ,
) ;
await this . multiProvider . handleTx ( destination , tx ) ;
}
}
debug ( 'Deploying fallback routing ISM ...' ) ;
// unenrolling domains if needed
routingIsm = await this . multiProvider . handleDeploy (
for ( const origin of delta . domainsToUnenroll ) {
destination ,
logger (
new DefaultFallbackRoutingIsm__factory ( ) ,
` Unenrolling origin ${ origin } from preexisting routing ISM at ${ existingIsmAddress } ... ` ,
[ mailbox ] ,
) ;
const tx = await routingIsm . remove (
this . multiProvider . getDomainId ( origin ) ,
overrides ,
) ;
await this . multiProvider . handleTx ( destination , tx ) ;
}
// transfer ownership if needed
if ( delta . owner ) {
logger ( ` Transferring ownership of routing ISM... ` ) ;
const tx = await routingIsm . transferOwnership ( delta . owner , overrides ) ;
await this . multiProvider . handleTx ( destination , tx ) ;
}
} else {
const isms : ChainMap < Address > = { } ;
for ( const origin of Object . keys ( config . domains ) ) {
const ism = await this . deploy ( {
destination ,
config : config.domains [ origin ] ,
origin ,
mailbox ,
} ) ;
isms [ origin ] = ism . address ;
}
const submoduleAddresses = Object . values ( isms ) ;
const domains = Object . keys ( isms ) . map ( ( chain ) = >
this . multiProvider . getDomainId ( chain ) ,
) ;
) ;
debug ( 'Initialising fallback routing ISM ...' ) ;
let receipt : ethers.providers.TransactionReceipt ;
receipt = await this . multiProvider . handleTx (
if ( config . type === IsmType . FALLBACK_ROUTING ) {
destination ,
// deploying new fallback routing ISM
routingIsm [ 'initialize(address,uint32[],address[])' ] (
if ( ! mailbox ) {
throw new Error (
'Mailbox address is required for deploying fallback routing ISM' ,
) ;
}
logger ( 'Deploying fallback routing ISM ...' ) ;
routingIsm = await this . multiProvider . handleDeploy (
destination ,
new DefaultFallbackRoutingIsm__factory ( ) ,
[ mailbox ] ,
) ;
logger ( 'Initialising fallback routing ISM ...' ) ;
receipt = await this . multiProvider . handleTx (
destination ,
routingIsm [ 'initialize(address,uint32[],address[])' ] (
config . owner ,
domains ,
submoduleAddresses ,
overrides ,
) ,
) ;
} else {
// deploying new domain routing ISM
const tx = await routingIsmFactory . deploy (
config . owner ,
config . owner ,
domains ,
domains ,
submoduleAddresses ,
submoduleAddresses ,
overrides ,
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 module Address = dispatchLogs [ 0 ] . args [ 'module' ] ;
receipt = await this . multiProvider . handleTx ( destination , tx ) ;
routingIsm = DomainRoutingIsm__factory . connect (
module Address ,
// TODO: Break this out into a generalized function
this . multiProvider . getSigner ( destination ) ,
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' ,
) ;
if ( dispatchLogs . length === 0 ) {
throw new Error ( 'No ModuleDeployed event found' ) ;
}
const module Address = dispatchLogs [ 0 ] . args [ 'module' ] ;
routingIsm = DomainRoutingIsm__factory . connect (
module Address ,
this . multiProvider . getSigner ( destination ) ,
) ;
}
}
}
return routingIsm ;
return routingIsm ;
}
}
@ -410,7 +493,7 @@ export async function moduleCanCertainlyVerify(
throw new Error ( ` Unsupported module type: ${ module Type } ` ) ;
throw new Error ( ` Unsupported module type: ${ module Type } ` ) ;
}
}
} catch ( e ) {
} catch ( e ) {
warn ( ` Error checking module ${ destModule } : ${ e } ` ) ;
logger ( ` Error checking module ${ destModule } : ${ e } ` ) ;
return false ;
return false ;
}
}
} else {
} else {
@ -523,22 +606,20 @@ export async function moduleMatchesConfig(
mailbox !== undefined &&
mailbox !== undefined &&
eqAddress ( mailboxAddress , mailbox ) ;
eqAddress ( mailboxAddress , mailbox ) ;
}
}
// Recursively check that the submodule for each configured
const delta = await routingModuleDelta (
// domain matches the submodule config.
chain ,
for ( const [ origin , subConfig ] of Object . entries ( config . domains ) ) {
module Address ,
const subModule = await routingIsm . module (
config ,
multiProvider . getDomainId ( origin ) ,
multiProvider ,
) ;
contracts ,
const subModuleMatches = await module MatchesConfig (
mailbox ,
chain ,
) ;
subModule ,
matches =
subConfig ,
matches &&
multiProvider ,
delta . domainsToEnroll . length === 0 &&
contracts ,
delta . domainsToUnenroll . length === 0 &&
mailbox ,
! delta . mailbox &&
) ;
! delta . owner ;
matches = matches && subModuleMatches ;
}
break ;
break ;
}
}
case IsmType . AGGREGATION : {
case IsmType . AGGREGATION : {
@ -596,6 +677,62 @@ export async function moduleMatchesConfig(
return matches ;
return matches ;
}
}
export async function routingModuleDelta (
destination : ChainName ,
module Address : Address ,
config : RoutingIsmConfig ,
multiProvider : MultiProvider ,
contracts : HyperlaneContracts < ProxyFactoryFactories > ,
mailbox? : Address ,
) : Promise < RoutingIsmDelta > {
const provider = multiProvider . getProvider ( destination ) ;
const routingIsm = DomainRoutingIsm__factory . connect ( module Address , provider ) ;
const owner = await routingIsm . owner ( ) ;
const deployedDomains = ( await routingIsm . domains ( ) ) . map ( ( domain ) = >
multiProvider . getChainName ( domain . toNumber ( ) ) ,
) ;
const delta : RoutingIsmDelta = {
domainsToUnenroll : [ ] ,
domainsToEnroll : [ ] ,
} ;
// if owners don't match, we need to transfer ownership
if ( ! eqAddress ( owner , normalizeAddress ( config . owner ) ) )
delta . owner = config . owner ;
if ( config . type === IsmType . FALLBACK_ROUTING ) {
const client = MailboxClient__factory . connect ( module Address , provider ) ;
const mailboxAddress = await client . mailbox ( ) ;
if ( mailbox && ! eqAddress ( mailboxAddress , mailbox ) ) delta . mailbox = mailbox ;
}
// check for exclusion of domains in the config
delta . domainsToUnenroll = deployedDomains . filter (
( domain ) = > ! Object . keys ( config . domains ) . includes ( domain ) ,
) ;
// check for inclusion of domains in the config
for ( const [ origin , subConfig ] of Object . entries ( config . domains ) ) {
if ( ! deployedDomains . includes ( origin ) ) {
delta . domainsToEnroll . push ( origin ) ;
} else {
const subModule = await routingIsm . module (
multiProvider . getDomainId ( origin ) ,
) ;
// Recursively check that the submodule for each configured
// domain matches the submodule config.
const subModuleMatches = await module MatchesConfig (
destination ,
subModule ,
subConfig ,
multiProvider ,
contracts ,
origin ,
) ;
if ( ! subModuleMatches ) delta . domainsToEnroll . push ( origin ) ;
}
}
return delta ;
}
export function collectValidators (
export function collectValidators (
origin : ChainName ,
origin : ChainName ,
config : IsmConfig ,
config : IsmConfig ,