Skip to content

Commit

Permalink
Pausable ISM (#3141)
Browse files Browse the repository at this point in the history
### 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

- hyperlane-xyz/issues#706

### Backward compatibility

- Yes

### Testing

- Unit Tests and fork tests
  • Loading branch information
yorhodes authored Jan 17, 2024
1 parent 3c298d0 commit 0727a61
Show file tree
Hide file tree
Showing 17 changed files with 194 additions and 52 deletions.
4 changes: 4 additions & 0 deletions solidity/contracts/isms/PausableIsm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {IInterchainSecurityModule} from "../interfaces/IInterchainSecurityModule
contract PausableIsm is IInterchainSecurityModule, Ownable, Pausable {
uint8 public constant override moduleType = uint8(Types.NULL);

constructor(address owner) Ownable() Pausable() {
_transferOwnership(owner);
}

/**
* @inheritdoc IInterchainSecurityModule
* @dev Reverts when paused, otherwise returns `true`.
Expand Down
13 changes: 12 additions & 1 deletion solidity/test/isms/PausableIsm.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,35 @@ import {PausableIsm} from "../../contracts/isms/PausableIsm.sol";
contract PausableIsmTest is Test {
PausableIsm ism;

address owner;

function setUp() public {
ism = new PausableIsm();
owner = msg.sender;
ism = new PausableIsm(owner);
}

function test_verify() public {
assertTrue(ism.verify("", ""));
vm.prank(owner);
ism.pause();
vm.expectRevert(bytes("Pausable: paused"));
ism.verify("", "");
}

function test_pause() public {
vm.expectRevert(bytes("Ownable: caller is not the owner"));
ism.pause();
vm.prank(owner);
ism.pause();
assertTrue(ism.paused());
}

function test_unpause() public {
vm.prank(owner);
ism.pause();
vm.expectRevert(bytes("Ownable: caller is not the owner"));
ism.unpause();
vm.prank(owner);
ism.unpause();
assertFalse(ism.paused());
}
Expand Down
59 changes: 54 additions & 5 deletions typescript/infra/config/environments/mainnet3/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,67 @@ import { BigNumber, ethers } from 'ethers';

import {
AggregationHookConfig,
AggregationIsmConfig,
ChainMap,
CoreConfig,
HookType,
IgpHookConfig,
IsmType,
MerkleTreeHookConfig,
MultisigConfig,
MultisigIsmConfig,
PausableHookConfig,
PausableIsmConfig,
ProtocolFeeHookConfig,
RoutingIsmConfig,
defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk';
import { objMap } from '@hyperlane-xyz/utils';

import { Contexts } from '../../contexts';
import { routingIsm } from '../../routingIsm';

import { supportedChainNames } from './chains';
import { igp } from './igp';
import { owners, safes } from './owners';

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 = {
type: HookType.MERKLE_TREE,
Expand All @@ -29,9 +73,14 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
...igp[local],
};

const pausableHook: PausableHookConfig = {
type: HookType.PAUSABLE,
owner,
};

const defaultHook: AggregationHookConfig = {
type: HookType.AGGREGATION,
hooks: [merkleHook, igpHook],
hooks: [pausableHook, merkleHook, igpHook],
};

const requiredHook: ProtocolFeeHookConfig = {
Expand Down
29 changes: 23 additions & 6 deletions typescript/infra/config/environments/testnet4/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import {
AggregationIsmConfig,
ChainMap,
CoreConfig,
FallbackRoutingHookConfig,
HookType,
IgpHookConfig,
IsmType,
MerkleTreeHookConfig,
MultisigConfig,
MultisigIsmConfig,
PausableHookConfig,
PausableIsmConfig,
ProtocolFeeHookConfig,
RoutingIsmConfig,
defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk';
import { DomainRoutingHookConfig } from '@hyperlane-xyz/sdk/src/hook/types';
import { objMap } from '@hyperlane-xyz/utils';

import { supportedChainNames } from './chains';
Expand All @@ -39,7 +41,7 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
...multisig,
});

const defaultIsm: RoutingIsmConfig = {
const routingIsm: RoutingIsmConfig = {
type: IsmType.ROUTING,
domains: objMap(
originMultisigs,
Expand All @@ -52,6 +54,17 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
owner,
};

const pausableIsm: PausableIsmConfig = {
type: IsmType.PAUSABLE,
owner,
};

const defaultIsm: AggregationIsmConfig = {
type: IsmType.AGGREGATION,
modules: [routingIsm, pausableIsm],
threshold: 2,
};

const merkleHook: MerkleTreeHookConfig = {
type: HookType.MERKLE_TREE,
};
Expand All @@ -61,18 +74,22 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
...igp[local],
};

const pausableHook: PausableHookConfig = {
type: HookType.PAUSABLE,
owner,
};

const aggregationHooks = objMap(
originMultisigs,
(_origin, _): AggregationHookConfig => ({
type: HookType.AGGREGATION,
hooks: [igpHook, merkleHook],
hooks: [pausableHook, merkleHook, igpHook],
}),
);

const defaultHook: FallbackRoutingHookConfig = {
type: HookType.FALLBACK_ROUTING,
const defaultHook: DomainRoutingHookConfig = {
type: HookType.ROUTING,
owner,
fallback: merkleHook,
domains: aggregationHooks,
};

Expand Down
4 changes: 2 additions & 2 deletions typescript/infra/src/deployment/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
writeMergedJSONAtPath,
} from '../utils/utils';

export async function deployWithArtifacts<Config>(
export async function deployWithArtifacts<Config extends object>(
configMap: ChainMap<Config>,
deployer: HyperlaneDeployer<Config, any>,
cache: {
Expand Down Expand Up @@ -71,7 +71,7 @@ export async function deployWithArtifacts<Config>(
await postDeploy(deployer, cache, agentConfig);
}

export async function postDeploy<Config>(
export async function postDeploy<Config extends object>(
deployer: HyperlaneDeployer<Config, any>,
cache: {
addresses: string;
Expand Down
8 changes: 4 additions & 4 deletions typescript/sdk/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import type { Mailbox } from '@hyperlane-xyz/core';
import type { Address, ParsedMessage } from '@hyperlane-xyz/utils';

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 type { IsmConfig } from '../ism/types';
import type { ChainName } from '../types';

export type CoreConfig = {
import { CoreFactories } from './contracts';

export type CoreConfig = OwnableConfig<keyof CoreFactories> & {
defaultIsm: IsmConfig;
defaultHook: HookConfig;
requiredHook: HookConfig;
owner: Address;
ownerOverrides?: Record<string, string>;
remove?: boolean;
upgrade?: UpgradeConfig;
};
Expand Down
2 changes: 1 addition & 1 deletion typescript/sdk/src/deploy/HyperlaneDeployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export interface DeployerOptions {
}

export abstract class HyperlaneDeployer<
Config,
Config extends object,
Factories extends HyperlaneFactories,
> {
public verificationInputs: ChainMap<ContractVerificationInput[]> = {};
Expand Down
10 changes: 10 additions & 0 deletions typescript/sdk/src/deploy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ import type {
Ownable,
TimelockController,
} from '@hyperlane-xyz/core';
import { Address } from '@hyperlane-xyz/utils';

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 {
chain: ChainName;
type: string;
Expand Down
7 changes: 4 additions & 3 deletions typescript/sdk/src/gas/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { BigNumber } from 'ethers';
import { InterchainGasPaymaster } from '@hyperlane-xyz/core';
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 { IgpFactories } from './contracts';

export enum GasOracleContractType {
StorageGasOracle = 'StorageGasOracle',
}

export type IgpConfig = {
owner: Address;
export type IgpConfig = OwnableConfig<keyof IgpFactories> & {
beneficiary: Address;
gasOracleType: ChainMap<GasOracleContractType>;
oracleKey: Address;
Expand Down
21 changes: 15 additions & 6 deletions typescript/sdk/src/hook/HyperlaneHookDeployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { IsmType, OpStackIsmConfig } from '../ism/types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';

import { HookFactories, hookFactories } from './contracts';
import { DeployedHook, HookFactories, hookFactories } from './contracts';
import {
AggregationHookConfig,
DomainRoutingHookConfig,
Expand Down Expand Up @@ -59,17 +59,20 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer<
config: HookConfig,
coreAddresses = this.core[chain],
): Promise<HyperlaneContracts<HookFactories>> {
// other simple hooks can go here
let hook;
let hook: DeployedHook;
if (config.type === HookType.MERKLE_TREE) {
const mailbox = coreAddresses.mailbox;
if (!mailbox) {
throw new Error(`Mailbox address is required for ${config.type}`);
}
hook = await this.deployContract(chain, config.type, [mailbox]);
return { [config.type]: hook } as any;
} 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) {
return this.deployAggregation(chain, config, coreAddresses); // deploy from factory
} else if (config.type === HookType.PROTOCOL_FEE) {
Expand All @@ -81,8 +84,14 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer<
config.type === HookType.FALLBACK_ROUTING
) {
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);
return deployedContracts;
}
Expand Down
7 changes: 7 additions & 0 deletions typescript/sdk/src/hook/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
InterchainGasPaymaster__factory,
MerkleTreeHook__factory,
OPStackHook__factory,
PausableHook__factory,
ProtocolFee__factory,
StaticAggregationHook__factory,
} from '@hyperlane-xyz/core';
import { ValueOf } from '@hyperlane-xyz/utils';

import { HookType } from './types';

Expand All @@ -18,6 +20,11 @@ export const hookFactories = {
[HookType.OP_STACK]: new OPStackHook__factory(),
[HookType.ROUTING]: new DomainRoutingHook__factory(),
[HookType.FALLBACK_ROUTING]: new FallbackDomainRoutingHook__factory(),
[HookType.PAUSABLE]: new PausableHook__factory(),
};

export type HookFactories = typeof hookFactories;

export type DeployedHook = Awaited<
ReturnType<ValueOf<HookFactories>['deploy']>
>;
Loading

0 comments on commit 0727a61

Please sign in to comment.