Pausable ISM (#3141)

### Description

- Adds pausable hook and ism config support to deployers
- Configure testnet pausable hook and ISM
- Add pausable ism checking

### Drive-by changes

- Refactors ownable config

### Related issues

- https://github.com/hyperlane-xyz/issues/issues/706

### Backward compatibility

- Yes

### Testing

- Unit Tests and fork tests
pull/3163/head
Yorke Rhodes 10 months ago committed by GitHub
parent 3c298d0646
commit 0727a6178e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      solidity/contracts/isms/PausableIsm.sol
  2. 13
      solidity/test/isms/PausableIsm.t.sol
  3. 59
      typescript/infra/config/environments/mainnet3/core.ts
  4. 29
      typescript/infra/config/environments/testnet4/core.ts
  5. 4
      typescript/infra/src/deployment/deploy.ts
  6. 8
      typescript/sdk/src/core/types.ts
  7. 2
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  8. 10
      typescript/sdk/src/deploy/types.ts
  9. 7
      typescript/sdk/src/gas/types.ts
  10. 21
      typescript/sdk/src/hook/HyperlaneHookDeployer.ts
  11. 7
      typescript/sdk/src/hook/contracts.ts
  12. 15
      typescript/sdk/src/hook/types.ts
  13. 4
      typescript/sdk/src/index.ts
  14. 37
      typescript/sdk/src/ism/HyperlaneIsmFactory.ts
  15. 17
      typescript/sdk/src/ism/types.ts
  16. 3
      typescript/sdk/src/router/HyperlaneRouterChecker.ts
  17. 6
      typescript/sdk/src/router/types.ts

@ -11,6 +11,10 @@ import {IInterchainSecurityModule} from "../interfaces/IInterchainSecurityModule
contract PausableIsm is IInterchainSecurityModule, Ownable, Pausable { contract PausableIsm is IInterchainSecurityModule, Ownable, Pausable {
uint8 public constant override moduleType = uint8(Types.NULL); uint8 public constant override moduleType = uint8(Types.NULL);
constructor(address owner) Ownable() Pausable() {
_transferOwnership(owner);
}
/** /**
* @inheritdoc IInterchainSecurityModule * @inheritdoc IInterchainSecurityModule
* @dev Reverts when paused, otherwise returns `true`. * @dev Reverts when paused, otherwise returns `true`.

@ -8,24 +8,35 @@ import {PausableIsm} from "../../contracts/isms/PausableIsm.sol";
contract PausableIsmTest is Test { contract PausableIsmTest is Test {
PausableIsm ism; PausableIsm ism;
address owner;
function setUp() public { function setUp() public {
ism = new PausableIsm(); owner = msg.sender;
ism = new PausableIsm(owner);
} }
function test_verify() public { function test_verify() public {
assertTrue(ism.verify("", "")); assertTrue(ism.verify("", ""));
vm.prank(owner);
ism.pause(); ism.pause();
vm.expectRevert(bytes("Pausable: paused")); vm.expectRevert(bytes("Pausable: paused"));
ism.verify("", ""); ism.verify("", "");
} }
function test_pause() public { function test_pause() public {
vm.expectRevert(bytes("Ownable: caller is not the owner"));
ism.pause();
vm.prank(owner);
ism.pause(); ism.pause();
assertTrue(ism.paused()); assertTrue(ism.paused());
} }
function test_unpause() public { function test_unpause() public {
vm.prank(owner);
ism.pause(); ism.pause();
vm.expectRevert(bytes("Ownable: caller is not the owner"));
ism.unpause();
vm.prank(owner);
ism.unpause(); ism.unpause();
assertFalse(ism.paused()); assertFalse(ism.paused());
} }

@ -2,23 +2,67 @@ import { BigNumber, ethers } from 'ethers';
import { import {
AggregationHookConfig, AggregationHookConfig,
AggregationIsmConfig,
ChainMap, ChainMap,
CoreConfig, CoreConfig,
HookType, HookType,
IgpHookConfig, IgpHookConfig,
IsmType,
MerkleTreeHookConfig, MerkleTreeHookConfig,
MultisigConfig,
MultisigIsmConfig,
PausableHookConfig,
PausableIsmConfig,
ProtocolFeeHookConfig, ProtocolFeeHookConfig,
RoutingIsmConfig,
defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { objMap } from '@hyperlane-xyz/utils'; import { objMap } from '@hyperlane-xyz/utils';
import { Contexts } from '../../contexts'; import { supportedChainNames } from './chains';
import { routingIsm } from '../../routingIsm';
import { igp } from './igp'; import { igp } from './igp';
import { owners, safes } from './owners'; import { owners, safes } from './owners';
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => { export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
const defaultIsm = routingIsm('mainnet3', local, Contexts.Hyperlane); const originMultisigs: ChainMap<MultisigConfig> = Object.fromEntries(
supportedChainNames
.filter((chain) => chain !== local)
.map((origin) => [origin, defaultMultisigConfigs[origin]]),
);
const merkleRoot = (multisig: MultisigConfig): MultisigIsmConfig => ({
type: IsmType.MERKLE_ROOT_MULTISIG,
...multisig,
});
const messageIdIsm = (multisig: MultisigConfig): MultisigIsmConfig => ({
type: IsmType.MESSAGE_ID_MULTISIG,
...multisig,
});
const routingIsm: RoutingIsmConfig = {
type: IsmType.ROUTING,
domains: objMap(
originMultisigs,
(_, multisig): AggregationIsmConfig => ({
type: IsmType.AGGREGATION,
modules: [messageIdIsm(multisig), merkleRoot(multisig)],
threshold: 1,
}),
),
owner,
};
const pausableIsm: PausableIsmConfig = {
type: IsmType.PAUSABLE,
owner,
};
const defaultIsm: AggregationIsmConfig = {
type: IsmType.AGGREGATION,
modules: [routingIsm, pausableIsm],
threshold: 2,
};
const merkleHook: MerkleTreeHookConfig = { const merkleHook: MerkleTreeHookConfig = {
type: HookType.MERKLE_TREE, type: HookType.MERKLE_TREE,
@ -29,9 +73,14 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
...igp[local], ...igp[local],
}; };
const pausableHook: PausableHookConfig = {
type: HookType.PAUSABLE,
owner,
};
const defaultHook: AggregationHookConfig = { const defaultHook: AggregationHookConfig = {
type: HookType.AGGREGATION, type: HookType.AGGREGATION,
hooks: [merkleHook, igpHook], hooks: [pausableHook, merkleHook, igpHook],
}; };
const requiredHook: ProtocolFeeHookConfig = { const requiredHook: ProtocolFeeHookConfig = {

@ -5,17 +5,19 @@ import {
AggregationIsmConfig, AggregationIsmConfig,
ChainMap, ChainMap,
CoreConfig, CoreConfig,
FallbackRoutingHookConfig,
HookType, HookType,
IgpHookConfig, IgpHookConfig,
IsmType, IsmType,
MerkleTreeHookConfig, MerkleTreeHookConfig,
MultisigConfig, MultisigConfig,
MultisigIsmConfig, MultisigIsmConfig,
PausableHookConfig,
PausableIsmConfig,
ProtocolFeeHookConfig, ProtocolFeeHookConfig,
RoutingIsmConfig, RoutingIsmConfig,
defaultMultisigConfigs, defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { DomainRoutingHookConfig } from '@hyperlane-xyz/sdk/src/hook/types';
import { objMap } from '@hyperlane-xyz/utils'; import { objMap } from '@hyperlane-xyz/utils';
import { supportedChainNames } from './chains'; import { supportedChainNames } from './chains';
@ -39,7 +41,7 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
...multisig, ...multisig,
}); });
const defaultIsm: RoutingIsmConfig = { const routingIsm: RoutingIsmConfig = {
type: IsmType.ROUTING, type: IsmType.ROUTING,
domains: objMap( domains: objMap(
originMultisigs, originMultisigs,
@ -52,6 +54,17 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
owner, owner,
}; };
const pausableIsm: PausableIsmConfig = {
type: IsmType.PAUSABLE,
owner,
};
const defaultIsm: AggregationIsmConfig = {
type: IsmType.AGGREGATION,
modules: [routingIsm, pausableIsm],
threshold: 2,
};
const merkleHook: MerkleTreeHookConfig = { const merkleHook: MerkleTreeHookConfig = {
type: HookType.MERKLE_TREE, type: HookType.MERKLE_TREE,
}; };
@ -61,18 +74,22 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
...igp[local], ...igp[local],
}; };
const pausableHook: PausableHookConfig = {
type: HookType.PAUSABLE,
owner,
};
const aggregationHooks = objMap( const aggregationHooks = objMap(
originMultisigs, originMultisigs,
(_origin, _): AggregationHookConfig => ({ (_origin, _): AggregationHookConfig => ({
type: HookType.AGGREGATION, type: HookType.AGGREGATION,
hooks: [igpHook, merkleHook], hooks: [pausableHook, merkleHook, igpHook],
}), }),
); );
const defaultHook: FallbackRoutingHookConfig = { const defaultHook: DomainRoutingHookConfig = {
type: HookType.FALLBACK_ROUTING, type: HookType.ROUTING,
owner, owner,
fallback: merkleHook,
domains: aggregationHooks, domains: aggregationHooks,
}; };

@ -20,7 +20,7 @@ import {
writeMergedJSONAtPath, writeMergedJSONAtPath,
} from '../utils/utils'; } from '../utils/utils';
export async function deployWithArtifacts<Config>( export async function deployWithArtifacts<Config extends object>(
configMap: ChainMap<Config>, configMap: ChainMap<Config>,
deployer: HyperlaneDeployer<Config, any>, deployer: HyperlaneDeployer<Config, any>,
cache: { cache: {
@ -71,7 +71,7 @@ export async function deployWithArtifacts<Config>(
await postDeploy(deployer, cache, agentConfig); await postDeploy(deployer, cache, agentConfig);
} }
export async function postDeploy<Config>( export async function postDeploy<Config extends object>(
deployer: HyperlaneDeployer<Config, any>, deployer: HyperlaneDeployer<Config, any>,
cache: { cache: {
addresses: string; addresses: string;

@ -2,17 +2,17 @@ import type { Mailbox } from '@hyperlane-xyz/core';
import type { Address, ParsedMessage } from '@hyperlane-xyz/utils'; import type { Address, ParsedMessage } from '@hyperlane-xyz/utils';
import type { UpgradeConfig } from '../deploy/proxy'; import type { UpgradeConfig } from '../deploy/proxy';
import type { CheckerViolation } from '../deploy/types'; import type { CheckerViolation, OwnableConfig } from '../deploy/types';
import { HookConfig } from '../hook/types'; import { HookConfig } from '../hook/types';
import type { IsmConfig } from '../ism/types'; import type { IsmConfig } from '../ism/types';
import type { ChainName } from '../types'; import type { ChainName } from '../types';
export type CoreConfig = { import { CoreFactories } from './contracts';
export type CoreConfig = OwnableConfig<keyof CoreFactories> & {
defaultIsm: IsmConfig; defaultIsm: IsmConfig;
defaultHook: HookConfig; defaultHook: HookConfig;
requiredHook: HookConfig; requiredHook: HookConfig;
owner: Address;
ownerOverrides?: Record<string, string>;
remove?: boolean; remove?: boolean;
upgrade?: UpgradeConfig; upgrade?: UpgradeConfig;
}; };

@ -53,7 +53,7 @@ export interface DeployerOptions {
} }
export abstract class HyperlaneDeployer< export abstract class HyperlaneDeployer<
Config, Config extends object,
Factories extends HyperlaneFactories, Factories extends HyperlaneFactories,
> { > {
public verificationInputs: ChainMap<ContractVerificationInput[]> = {}; public verificationInputs: ChainMap<ContractVerificationInput[]> = {};

@ -5,9 +5,19 @@ import type {
Ownable, Ownable,
TimelockController, TimelockController,
} from '@hyperlane-xyz/core'; } from '@hyperlane-xyz/core';
import { Address } from '@hyperlane-xyz/utils';
import type { ChainName } from '../types'; import type { ChainName } from '../types';
export type OwnableConfig<Keys extends string = string> = {
owner: Address;
ownerOverrides?: Partial<Record<Keys, Address>>;
};
export function isOwnableConfig(config: object): config is OwnableConfig {
return 'owner' in config;
}
export interface CheckerViolation { export interface CheckerViolation {
chain: ChainName; chain: ChainName;
type: string; type: string;

@ -3,15 +3,16 @@ import { BigNumber } from 'ethers';
import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; import { InterchainGasPaymaster } from '@hyperlane-xyz/core';
import type { Address } from '@hyperlane-xyz/utils'; import type { Address } from '@hyperlane-xyz/utils';
import type { CheckerViolation } from '../deploy/types'; import type { CheckerViolation, OwnableConfig } from '../deploy/types';
import { ChainMap } from '../types'; import { ChainMap } from '../types';
import { IgpFactories } from './contracts';
export enum GasOracleContractType { export enum GasOracleContractType {
StorageGasOracle = 'StorageGasOracle', StorageGasOracle = 'StorageGasOracle',
} }
export type IgpConfig = { export type IgpConfig = OwnableConfig<keyof IgpFactories> & {
owner: Address;
beneficiary: Address; beneficiary: Address;
gasOracleType: ChainMap<GasOracleContractType>; gasOracleType: ChainMap<GasOracleContractType>;
oracleKey: Address; oracleKey: Address;

@ -22,7 +22,7 @@ import { IsmType, OpStackIsmConfig } from '../ism/types';
import { MultiProvider } from '../providers/MultiProvider'; import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types'; import { ChainMap, ChainName } from '../types';
import { HookFactories, hookFactories } from './contracts'; import { DeployedHook, HookFactories, hookFactories } from './contracts';
import { import {
AggregationHookConfig, AggregationHookConfig,
DomainRoutingHookConfig, DomainRoutingHookConfig,
@ -59,17 +59,20 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer<
config: HookConfig, config: HookConfig,
coreAddresses = this.core[chain], coreAddresses = this.core[chain],
): Promise<HyperlaneContracts<HookFactories>> { ): Promise<HyperlaneContracts<HookFactories>> {
// other simple hooks can go here let hook: DeployedHook;
let hook;
if (config.type === HookType.MERKLE_TREE) { if (config.type === HookType.MERKLE_TREE) {
const mailbox = coreAddresses.mailbox; const mailbox = coreAddresses.mailbox;
if (!mailbox) { if (!mailbox) {
throw new Error(`Mailbox address is required for ${config.type}`); throw new Error(`Mailbox address is required for ${config.type}`);
} }
hook = await this.deployContract(chain, config.type, [mailbox]); hook = await this.deployContract(chain, config.type, [mailbox]);
return { [config.type]: hook } as any;
} else if (config.type === HookType.INTERCHAIN_GAS_PAYMASTER) { } else if (config.type === HookType.INTERCHAIN_GAS_PAYMASTER) {
return this.deployIgp(chain, config, coreAddresses) as any; const { interchainGasPaymaster } = await this.deployIgp(
chain,
config,
coreAddresses,
);
hook = interchainGasPaymaster;
} else if (config.type === HookType.AGGREGATION) { } else if (config.type === HookType.AGGREGATION) {
return this.deployAggregation(chain, config, coreAddresses); // deploy from factory return this.deployAggregation(chain, config, coreAddresses); // deploy from factory
} else if (config.type === HookType.PROTOCOL_FEE) { } else if (config.type === HookType.PROTOCOL_FEE) {
@ -81,8 +84,14 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer<
config.type === HookType.FALLBACK_ROUTING config.type === HookType.FALLBACK_ROUTING
) { ) {
hook = await this.deployRouting(chain, config, coreAddresses); hook = await this.deployRouting(chain, config, coreAddresses);
} else if (config.type === HookType.PAUSABLE) {
hook = await this.deployContract(chain, config.type, []);
await this.transferOwnershipOfContracts(chain, config.owner, { hook });
} else {
throw new Error(`Unsupported hook config: ${config}`);
} }
const deployedContracts = { [config.type]: hook } as any;
const deployedContracts = { [config.type]: hook } as any; // partial
this.addDeployedContracts(chain, deployedContracts); this.addDeployedContracts(chain, deployedContracts);
return deployedContracts; return deployedContracts;
} }

@ -4,9 +4,11 @@ import {
InterchainGasPaymaster__factory, InterchainGasPaymaster__factory,
MerkleTreeHook__factory, MerkleTreeHook__factory,
OPStackHook__factory, OPStackHook__factory,
PausableHook__factory,
ProtocolFee__factory, ProtocolFee__factory,
StaticAggregationHook__factory, StaticAggregationHook__factory,
} from '@hyperlane-xyz/core'; } from '@hyperlane-xyz/core';
import { ValueOf } from '@hyperlane-xyz/utils';
import { HookType } from './types'; import { HookType } from './types';
@ -18,6 +20,11 @@ export const hookFactories = {
[HookType.OP_STACK]: new OPStackHook__factory(), [HookType.OP_STACK]: new OPStackHook__factory(),
[HookType.ROUTING]: new DomainRoutingHook__factory(), [HookType.ROUTING]: new DomainRoutingHook__factory(),
[HookType.FALLBACK_ROUTING]: new FallbackDomainRoutingHook__factory(), [HookType.FALLBACK_ROUTING]: new FallbackDomainRoutingHook__factory(),
[HookType.PAUSABLE]: new PausableHook__factory(),
}; };
export type HookFactories = typeof hookFactories; export type HookFactories = typeof hookFactories;
export type DeployedHook = Awaited<
ReturnType<ValueOf<HookFactories>['deploy']>
>;

@ -1,5 +1,6 @@
import { Address } from '@hyperlane-xyz/utils'; import { Address } from '@hyperlane-xyz/utils';
import { OwnableConfig } from '../deploy/types';
import { IgpConfig } from '../gas/types'; import { IgpConfig } from '../gas/types';
import { ChainMap, ChainName } from '../types'; import { ChainMap, ChainName } from '../types';
@ -11,6 +12,7 @@ export enum HookType {
OP_STACK = 'opStackHook', OP_STACK = 'opStackHook',
ROUTING = 'domainRoutingHook', ROUTING = 'domainRoutingHook',
FALLBACK_ROUTING = 'fallbackRoutingHook', FALLBACK_ROUTING = 'fallbackRoutingHook',
PAUSABLE = 'pausableHook',
} }
export type MerkleTreeHookConfig = { export type MerkleTreeHookConfig = {
@ -26,12 +28,15 @@ export type IgpHookConfig = IgpConfig & {
type: HookType.INTERCHAIN_GAS_PAYMASTER; type: HookType.INTERCHAIN_GAS_PAYMASTER;
}; };
export type ProtocolFeeHookConfig = { export type ProtocolFeeHookConfig = OwnableConfig & {
type: HookType.PROTOCOL_FEE; type: HookType.PROTOCOL_FEE;
maxProtocolFee: string; maxProtocolFee: string;
protocolFee: string; protocolFee: string;
beneficiary: Address; beneficiary: Address;
owner: Address; };
export type PausableHookConfig = OwnableConfig & {
type: HookType.PAUSABLE;
}; };
export type OpStackHookConfig = { export type OpStackHookConfig = {
@ -40,8 +45,7 @@ export type OpStackHookConfig = {
destinationChain: ChainName; destinationChain: ChainName;
}; };
type RoutingHookConfig = { type RoutingHookConfig = OwnableConfig & {
owner: Address;
domains: ChainMap<HookConfig>; domains: ChainMap<HookConfig>;
}; };
@ -61,7 +65,8 @@ export type HookConfig =
| ProtocolFeeHookConfig | ProtocolFeeHookConfig
| OpStackHookConfig | OpStackHookConfig
| DomainRoutingHookConfig | DomainRoutingHookConfig
| FallbackRoutingHookConfig; | FallbackRoutingHookConfig
| PausableHookConfig;
export type HooksConfig = { export type HooksConfig = {
required: HookConfig; required: HookConfig;

@ -82,6 +82,7 @@ export { DeployerOptions, HyperlaneDeployer } from './deploy/HyperlaneDeployer';
export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer'; export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer';
export { export {
CheckerViolation, CheckerViolation,
OwnableConfig,
OwnerViolation, OwnerViolation,
ViolationType, ViolationType,
} from './deploy/types'; } from './deploy/types';
@ -125,6 +126,7 @@ export {
IgpHookConfig, IgpHookConfig,
MerkleTreeHookConfig, MerkleTreeHookConfig,
OpStackHookConfig, OpStackHookConfig,
PausableHookConfig,
ProtocolFeeHookConfig, ProtocolFeeHookConfig,
} from './hook/types'; } from './hook/types';
export { export {
@ -145,6 +147,7 @@ export {
MultisigConfig, MultisigConfig,
MultisigIsmConfig, MultisigIsmConfig,
OpStackIsmConfig, OpStackIsmConfig,
PausableIsmConfig,
RoutingIsmConfig, RoutingIsmConfig,
} from './ism/types'; } from './ism/types';
export { export {
@ -303,7 +306,6 @@ export {
GasConfig, GasConfig,
GasRouterConfig, GasRouterConfig,
MailboxClientConfig, MailboxClientConfig,
OwnableConfig,
ProxiedFactories, ProxiedFactories,
ProxiedRouterConfig, ProxiedRouterConfig,
RouterAddress, RouterAddress,

@ -16,6 +16,7 @@ import {
MailboxClient__factory, MailboxClient__factory,
OPStackIsm, OPStackIsm,
OPStackIsm__factory, OPStackIsm__factory,
PausableIsm__factory,
StaticAddressSetFactory, StaticAddressSetFactory,
StaticAggregationIsm__factory, StaticAggregationIsm__factory,
StaticThresholdAddressSetFactory, StaticThresholdAddressSetFactory,
@ -145,6 +146,13 @@ export class HyperlaneIsmFactory extends HyperlaneApp<ProxyFactoryFactories> {
case IsmType.OP_STACK: case IsmType.OP_STACK:
contract = await this.deployOpStackIsm(destination, config); contract = await this.deployOpStackIsm(destination, config);
break; break;
case IsmType.PAUSABLE:
contract = await this.multiProvider.handleDeploy(
destination,
new PausableIsm__factory(),
[config.owner],
);
break;
case IsmType.TEST_ISM: case IsmType.TEST_ISM:
contract = await this.multiProvider.handleDeploy( contract = await this.multiProvider.handleDeploy(
destination, destination,
@ -616,7 +624,7 @@ export async function moduleMatchesConfig(
); );
// Check that the RoutingISM owner matches the config // Check that the RoutingISM owner matches the config
const owner = await routingIsm.owner(); const owner = await routingIsm.owner();
matches = matches && eqAddress(owner, config.owner); matches &&= eqAddress(owner, config.owner);
// check if the mailbox matches the config for fallback routing // check if the mailbox matches the config for fallback routing
if (config.type === IsmType.FALLBACK_ROUTING) { if (config.type === IsmType.FALLBACK_ROUTING) {
const client = MailboxClient__factory.connect(moduleAddress, provider); const client = MailboxClient__factory.connect(moduleAddress, provider);
@ -653,8 +661,8 @@ export async function moduleMatchesConfig(
const [subModules, threshold] = await aggregationIsm.modulesAndThreshold( const [subModules, threshold] = await aggregationIsm.modulesAndThreshold(
'0x', '0x',
); );
matches = matches && threshold === config.threshold; matches &&= threshold === config.threshold;
matches = matches && subModules.length === config.modules.length; matches &&= subModules.length === config.modules.length;
const configIndexMatched = new Map(); const configIndexMatched = new Map();
for (const subModule of subModules) { for (const subModule of subModules) {
@ -666,12 +674,12 @@ export async function moduleMatchesConfig(
// The submodule returned by the ISM must match exactly one // The submodule returned by the ISM must match exactly one
// entry in the config. // entry in the config.
const count = subModuleMatchesConfig.filter(Boolean).length; const count = subModuleMatchesConfig.filter(Boolean).length;
matches = matches && count === 1; matches &&= count === 1;
// That entry in the config should not have been matched already. // That entry in the config should not have been matched already.
subModuleMatchesConfig.forEach((matched, index) => { subModuleMatchesConfig.forEach((matched, index) => {
if (matched) { if (matched) {
matches = matches && !configIndexMatched.has(index); matches &&= !configIndexMatched.has(index);
configIndexMatched.set(index, true); configIndexMatched.set(index, true);
} }
}); });
@ -681,7 +689,7 @@ export async function moduleMatchesConfig(
case IsmType.OP_STACK: { case IsmType.OP_STACK: {
const opStackIsm = OPStackIsm__factory.connect(moduleAddress, provider); const opStackIsm = OPStackIsm__factory.connect(moduleAddress, provider);
const type = await opStackIsm.moduleType(); const type = await opStackIsm.moduleType();
matches = matches && type === ModuleType.NULL; matches &&= type === ModuleType.NULL;
break; break;
} }
case IsmType.TEST_ISM: { case IsmType.TEST_ISM: {
@ -689,6 +697,17 @@ export async function moduleMatchesConfig(
matches = true; matches = true;
break; break;
} }
case IsmType.PAUSABLE: {
const pausableIsm = PausableIsm__factory.connect(moduleAddress, provider);
const owner = await pausableIsm.owner();
matches &&= eqAddress(owner, config.owner);
if (config.paused) {
const isPaused = await pausableIsm.paused();
matches &&= config.paused === isPaused;
}
break;
}
default: { default: {
throw new Error('Unsupported ModuleType'); throw new Error('Unsupported ModuleType');
} }
@ -789,8 +808,10 @@ export function collectValidators(
aggregatedValidators.forEach((set) => { aggregatedValidators.forEach((set) => {
validators = validators.concat([...set]); validators = validators.concat([...set]);
}); });
} else if (config.type === IsmType.TEST_ISM) { } else if (
// This is just a TestISM config.type === IsmType.TEST_ISM ||
config.type === IsmType.PAUSABLE
) {
return new Set([]); return new Set([]);
} else { } else {
throw new Error('Unsupported ModuleType'); throw new Error('Unsupported ModuleType');

@ -3,10 +3,12 @@ import {
IMultisigIsm, IMultisigIsm,
IRoutingIsm, IRoutingIsm,
OPStackIsm, OPStackIsm,
PausableIsm,
TestIsm, TestIsm,
} from '@hyperlane-xyz/core'; } from '@hyperlane-xyz/core';
import type { Address, Domain, ValueOf } from '@hyperlane-xyz/utils'; import type { Address, Domain, ValueOf } from '@hyperlane-xyz/utils';
import { OwnableConfig } from '../deploy/types';
import { ChainMap } from '../types'; import { ChainMap } from '../types';
// this enum should match the IInterchainSecurityModule.sol enum // this enum should match the IInterchainSecurityModule.sol enum
@ -31,6 +33,7 @@ export enum IsmType {
MERKLE_ROOT_MULTISIG = 'merkleRootMultisigIsm', MERKLE_ROOT_MULTISIG = 'merkleRootMultisigIsm',
MESSAGE_ID_MULTISIG = 'messageIdMultisigIsm', MESSAGE_ID_MULTISIG = 'messageIdMultisigIsm',
TEST_ISM = 'testIsm', TEST_ISM = 'testIsm',
PAUSABLE = 'pausableIsm',
} }
// mapping between the two enums // mapping between the two enums
@ -50,6 +53,8 @@ export function ismTypeToModuleType(ismType: IsmType): ModuleType {
return ModuleType.MESSAGE_ID_MULTISIG; return ModuleType.MESSAGE_ID_MULTISIG;
case IsmType.TEST_ISM: case IsmType.TEST_ISM:
return ModuleType.NULL; return ModuleType.NULL;
case IsmType.PAUSABLE:
return ModuleType.NULL;
} }
} }
@ -66,9 +71,13 @@ export type TestIsmConfig = {
type: IsmType.TEST_ISM; type: IsmType.TEST_ISM;
}; };
export type RoutingIsmConfig = { export type PausableIsmConfig = OwnableConfig & {
type: IsmType.PAUSABLE;
paused?: boolean;
};
export type RoutingIsmConfig = OwnableConfig & {
type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING; type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING;
owner: Address;
domains: ChainMap<IsmConfig>; domains: ChainMap<IsmConfig>;
}; };
@ -90,7 +99,8 @@ export type IsmConfig =
| MultisigIsmConfig | MultisigIsmConfig
| AggregationIsmConfig | AggregationIsmConfig
| OpStackIsmConfig | OpStackIsmConfig
| TestIsmConfig; | TestIsmConfig
| PausableIsmConfig;
export type DeployedIsmType = { export type DeployedIsmType = {
[IsmType.ROUTING]: IRoutingIsm; [IsmType.ROUTING]: IRoutingIsm;
@ -100,6 +110,7 @@ export type DeployedIsmType = {
[IsmType.MESSAGE_ID_MULTISIG]: IMultisigIsm; [IsmType.MESSAGE_ID_MULTISIG]: IMultisigIsm;
[IsmType.OP_STACK]: OPStackIsm; [IsmType.OP_STACK]: OPStackIsm;
[IsmType.TEST_ISM]: TestIsm; [IsmType.TEST_ISM]: TestIsm;
[IsmType.PAUSABLE]: PausableIsm;
}; };
export type DeployedIsm = ValueOf<DeployedIsmType>; export type DeployedIsm = ValueOf<DeployedIsmType>;

@ -19,7 +19,6 @@ import {
ClientViolation, ClientViolation,
ClientViolationType, ClientViolationType,
MailboxClientConfig, MailboxClientConfig,
OwnableConfig,
RouterConfig, RouterConfig,
RouterViolation, RouterViolation,
RouterViolationType, RouterViolationType,
@ -49,7 +48,7 @@ export class HyperlaneRouterChecker<
const router = this.app.router(this.app.getContracts(chain)); const router = this.app.router(this.app.getContracts(chain));
const checkMailboxClientProperty = async ( const checkMailboxClientProperty = async (
property: keyof (MailboxClientConfig & OwnableConfig), property: keyof MailboxClientConfig,
actual: string, actual: string,
violationType: ClientViolationType, violationType: ClientViolationType,
) => { ) => {

@ -8,17 +8,13 @@ import type { Address } from '@hyperlane-xyz/utils';
import { HyperlaneFactories } from '../contracts/types'; import { HyperlaneFactories } from '../contracts/types';
import { UpgradeConfig } from '../deploy/proxy'; import { UpgradeConfig } from '../deploy/proxy';
import { CheckerViolation } from '../deploy/types'; import { CheckerViolation, OwnableConfig } from '../deploy/types';
import { IsmConfig } from '../ism/types'; import { IsmConfig } from '../ism/types';
export type RouterAddress = { export type RouterAddress = {
router: Address; router: Address;
}; };
export type OwnableConfig = {
owner: Address;
};
export type ForeignDeploymentConfig = { export type ForeignDeploymentConfig = {
foreignDeployment?: Address; foreignDeployment?: Address;
}; };

Loading…
Cancel
Save