fix: reuse default validators for CLI (#2922)

### Description

- if the default multisig config contains the config for the chain we
want to deploy on and we've not explicitiy provided a config, the CLI
will use the default config

### Drive-by changes

- ignoring the ismType in `multisig-ism.yaml` and deploying
messageIdMultisig for all multisig configs provided indiscriminately.
Also, added a note regarding this in the `example/multisig-ism.yaml`
file. (otherwise we end up using `MultisigIsmConfig` and
`MultisigConfig` interchangeably like in the `buildIgpConfig` which
leads to confusion)

**NOTE**: this doesn't affect our inability to not deploy the same
multisig with different types for different remote chains because the
config provided is origin specific, eg, you cannot use goerli multisig
as messageId for arbitrumsepolia and for scrollsepolia as merkleRoot.

- moved `buildMultisigIsmConfigs` to sdk as an effort to dedupe the two
variations in infra and cli.

### Related issues

- aimed at
https://github.com/hyperlane-xyz/issues/issues/530#issuecomment-1802766305

### Backward compatibility

Yes

### Testing

Manual

---------

Co-authored-by: Trevor Porter <trkporter@ucdavis.edu>
Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com>
Co-authored-by: J M Rossy <jm.rossy@gmail.com>
Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com>
Co-authored-by: Rohan Shrothrium <shrothriumrohan@gmail.com>
Co-authored-by: Rohan Shrothrium <rohan.shrothrium@intellecteu.com>
Co-authored-by: Mattie Conover <git@mconover.dev>
Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz>
pull/2923/head
Kunal Arora 12 months ago committed by GitHub
parent 00a91f8e68
commit 69d87c90c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      typescript/cli/examples/multisig-ism.yaml
  2. 8
      typescript/cli/src/config/hooks.ts
  3. 6
      typescript/cli/src/config/multisig.ts
  4. 70
      typescript/cli/src/deploy/core.ts
  5. 6
      typescript/infra/config/environments/mainnet3/igp.ts
  6. 4
      typescript/infra/config/environments/testnet4/core.ts
  7. 6
      typescript/infra/config/environments/testnet4/igp.ts
  8. 27
      typescript/infra/config/multisigIsm.ts
  9. 2
      typescript/sdk/src/consts/multisigIsm.ts
  10. 3
      typescript/sdk/src/index.ts
  11. 27
      typescript/sdk/src/ism/multisig.ts

@ -4,12 +4,13 @@
# Valid module types:
# routing
# aggregation
# legacy_multisig
# merkle_root_multisig
# message_id_multisig
# merkleRootMultisig
# messageIdMultisigIsm
# ism type don't work currently (sets to messageIdMultisigIsm for all)
---
anvil1:
type: 'merkleRootMultisigIsm'
type: 'messageIdMultisigIsm'
threshold: 1 # Number: Signatures required to approve a message
validators: # Array: List of validator addresses
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'

@ -13,7 +13,7 @@ import {
MerkleTreeHookConfig,
MultisigIsmConfig,
ProtocolFeeHookConfig,
defaultMultisigIsmConfigs,
defaultMultisigConfigs,
multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk';
import {
@ -72,9 +72,9 @@ export function presetHookConfigs(
if (ismConfig) {
validatorThreshold = ismConfig.threshold;
validatorCount = ismConfig.validators.length;
} else if (local in defaultMultisigIsmConfigs) {
validatorThreshold = defaultMultisigIsmConfigs[local].threshold;
validatorCount = defaultMultisigIsmConfigs[local].validators.length;
} else if (local in defaultMultisigConfigs) {
validatorThreshold = defaultMultisigConfigs[local].threshold;
validatorCount = defaultMultisigConfigs[local].validators.length;
} else {
throw new Error('Cannot estimate gas overhead for IGP hook');
}

@ -1,7 +1,7 @@
import { input, select } from '@inquirer/prompts';
import { z } from 'zod';
import { ChainMap, IsmType, MultisigIsmConfig } from '@hyperlane-xyz/sdk';
import { ChainMap, IsmType, MultisigConfig } from '@hyperlane-xyz/sdk';
import { objMap } from '@hyperlane-xyz/utils';
import { errorRed, log, logBlue, logGreen } from '../../logger.js';
@ -30,14 +30,14 @@ export function readMultisigConfig(filePath: string) {
);
}
const parsedConfig = result.data;
const formattedConfig: ChainMap<MultisigIsmConfig> = objMap(
const formattedConfig: ChainMap<MultisigConfig> = objMap(
parsedConfig,
(_, config) =>
({
type: config.type as IsmType,
threshold: config.threshold,
validators: config.validators,
} as MultisigIsmConfig),
} as MultisigConfig),
);
logGreen(`All multisig configs in ${filePath} are valid`);

@ -18,11 +18,12 @@ import {
IgpConfig,
IsmType,
MultiProvider,
MultisigIsmConfig,
MultisigConfig,
RoutingIsmConfig,
agentStartBlocks,
buildAgentConfig,
defaultMultisigIsmConfigs,
buildMultisigIsmConfigs,
defaultMultisigConfigs,
multisigIsmVerificationCost,
serializeContractsMap,
} from '@hyperlane-xyz/sdk';
@ -149,26 +150,26 @@ async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) {
);
}
// first we check for user provided chains
const configs = readMultisigConfig(ismConfigPath);
const userProvidedConfigChains = Object.keys(configs).filter((c) =>
selectedChains.includes(c),
const multisigConfigs = {
...defaultMultisigConfigs,
...readMultisigConfig(ismConfigPath),
} as ChainMap<MultisigConfig>;
const requiredMultisigs = objFilter(
multisigConfigs,
(chain, config): config is MultisigConfig => selectedChains.includes(chain),
);
// then our SDK defaults
const configsUnion = [
...userProvidedConfigChains,
...Object.keys(defaultMultisigIsmConfigs),
];
// in case where the chains provided - all_configs = missing_configs
// selected chains - (user configs + default configs) = missing config
const missingConfigs = selectedChains.filter(
(c) => !configsUnion.includes(c),
(c) => !Object.keys(requiredMultisigs).includes(c),
);
if (missingConfigs.length > 0) {
throw new Error(
`Missing ISM config for one or more chains: ${missingConfigs.join(', ')}`,
);
}
log(`Found configs for chains: ${selectedChains.join(', ')}`);
return configs;
return requiredMultisigs;
}
async function runHookStep(
@ -200,7 +201,7 @@ interface DeployParams {
signer: ethers.Signer;
multiProvider: MultiProvider;
artifacts?: HyperlaneAddressesMap<any>;
multisigConfig?: ChainMap<MultisigIsmConfig>;
multisigConfig?: ChainMap<MultisigConfig>;
outPath: string;
skipConfirmation: boolean;
}
@ -254,7 +255,7 @@ async function executeDeploy({
const mergedContractAddrs = getMergedContractAddresses(artifacts);
// 1. Deploy ISM factories to all deployable chains that don't have them.
log('Deploying ISM factory contracts');
logBlue('Deploying ISM factory contracts');
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider);
ismFactoryDeployer.cacheAddressesMap(mergedContractAddrs);
@ -279,7 +280,7 @@ async function executeDeploy({
);
// 3. Deploy ISM contracts to remote deployable chains
log('Deploying ISMs');
logBlue('Deploying ISMs');
const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {};
const defaultIsms: ChainMap<Address> = {};
for (const ismOrigin of chains) {
@ -288,12 +289,8 @@ async function executeDeploy({
defaultIsms[ismOrigin] = artifacts[ismOrigin].multisigIsm;
continue;
}
log(`Deploying ISM to ${ismOrigin}`);
const ismConfig = buildIsmConfig(
owner,
chains.filter((r) => r !== ismOrigin),
multisigConfig,
);
logBlue(`Deploying ISM to ${ismOrigin}`);
const ismConfig = buildIsmConfig(owner, ismOrigin, chains, multisigConfig);
ismContracts[ismOrigin] = {
multisigIsm: await ismFactory.deploy(ismOrigin, ismConfig),
};
@ -303,7 +300,7 @@ async function executeDeploy({
logGreen('ISM contracts deployed');
// 4. Deploy core contracts to chains
log(`Deploying core contracts to ${chains.join(', ')}`);
logBlue(`Deploying core contracts to ${chains.join(', ')}`);
const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory);
coreDeployer.cacheAddressesMap(artifacts);
const coreConfigs = buildCoreConfigMap(
@ -342,19 +339,20 @@ async function executeDeploy({
function buildIsmConfig(
owner: Address,
remotes: ChainName[],
multisigIsmConfigs: ChainMap<MultisigIsmConfig>,
local: ChainName,
chains: ChainName[],
multisigIsmConfigs: ChainMap<MultisigConfig>,
): RoutingIsmConfig {
const mergedMultisigIsmConfig: ChainMap<MultisigIsmConfig> = objMerge(
defaultMultisigIsmConfigs,
const multisigConfigs = buildMultisigIsmConfigs(
IsmType.MESSAGE_ID_MULTISIG,
local,
chains,
multisigIsmConfigs,
);
return {
owner,
type: IsmType.ROUTING,
domains: Object.fromEntries(
remotes.map((remote) => [remote, mergedMultisigIsmConfig[remote]]),
),
domains: multisigConfigs,
};
}
@ -362,7 +360,7 @@ function buildCoreConfigMap(
owner: Address,
chains: ChainName[],
defaultIsms: ChainMap<Address>,
multisigConfig: ChainMap<MultisigIsmConfig>,
multisigConfig: ChainMap<MultisigConfig>,
): ChainMap<CoreConfig> {
return chains.reduce<ChainMap<CoreConfig>>((config, chain) => {
const igpConfig = buildIgpConfigMap(owner, chains, multisigConfig);
@ -414,12 +412,8 @@ function buildTestRecipientConfigMap(
function buildIgpConfigMap(
owner: Address,
chains: ChainName[],
multisigIsmConfigs: ChainMap<MultisigIsmConfig>,
multisigConfigs: ChainMap<MultisigConfig>,
): ChainMap<IgpConfig> {
const mergedMultisigIsmConfig: ChainMap<MultisigIsmConfig> = objMerge(
defaultMultisigIsmConfigs,
multisigIsmConfigs,
);
const configMap: ChainMap<IgpConfig> = {};
for (const chain of chains) {
const overhead: ChainMap<number> = {};
@ -427,8 +421,8 @@ function buildIgpConfigMap(
for (const remote of chains) {
if (chain === remote) continue;
overhead[remote] = multisigIsmVerificationCost(
mergedMultisigIsmConfig[chain].threshold,
mergedMultisigIsmConfig[chain].validators.length,
multisigConfigs[chain].threshold,
multisigConfigs[chain].validators.length,
);
gasOracleType[remote] = GasOracleContractType.StorageGasOracle;
}

@ -2,7 +2,7 @@ import {
ChainMap,
GasOracleContractType,
IgpConfig,
defaultMultisigIsmConfigs,
defaultMultisigConfigs,
multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk';
import { exclude, objMap } from '@hyperlane-xyz/utils';
@ -37,8 +37,8 @@ export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, owner) => ({
exclude(chain, ethereumChainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
defaultMultisigConfigs[remote].threshold,
defaultMultisigConfigs[remote].validators.length,
),
]),
),

@ -14,7 +14,7 @@ import {
MultisigIsmConfig,
ProtocolFeeHookConfig,
RoutingIsmConfig,
defaultMultisigIsmConfigs,
defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk';
import { objMap } from '@hyperlane-xyz/utils';
@ -26,7 +26,7 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
const originMultisigs: ChainMap<MultisigConfig> = Object.fromEntries(
supportedChainNames
.filter((chain) => chain !== local)
.map((origin) => [origin, defaultMultisigIsmConfigs[origin]]),
.map((origin) => [origin, defaultMultisigConfigs[origin]]),
);
const merkleRoot = (multisig: MultisigConfig): MultisigIsmConfig => ({

@ -2,7 +2,7 @@ import {
ChainMap,
GasOracleContractType,
IgpConfig,
defaultMultisigIsmConfigs,
defaultMultisigConfigs,
multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk';
import { exclude, objMap } from '@hyperlane-xyz/utils';
@ -30,8 +30,8 @@ export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, owner) => {
remote,
multisigIsmVerificationCost(
// TODO: parameterize this
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
defaultMultisigConfigs[remote].threshold,
defaultMultisigConfigs[remote].validators.length,
),
]),
),

@ -2,9 +2,9 @@ import {
ChainMap,
ChainName,
MultisigIsmConfig,
defaultMultisigIsmConfigs,
buildMultisigIsmConfigs,
defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk';
import { objFilter, objMap } from '@hyperlane-xyz/utils';
import { DeployEnvironment } from '../src/config';
@ -25,20 +25,13 @@ export const multisigIsms = (
local: ChainName,
type: MultisigIsmConfig['type'],
context: Contexts,
): ChainMap<MultisigIsmConfig> =>
objMap(
objFilter(
context === Contexts.ReleaseCandidate
? rcMultisigIsmConfigs
: defaultMultisigIsmConfigs,
(chain, config): config is MultisigIsmConfig =>
chain !== local && chains[env].includes(chain),
),
(_, config) => ({
...config,
type,
}),
);
): ChainMap<MultisigIsmConfig> => {
const multisigConfigs =
context === Contexts.ReleaseCandidate
? rcMultisigIsmConfigs
: defaultMultisigConfigs;
return buildMultisigIsmConfigs(type, local, chains[env], multisigConfigs);
};
export const multisigIsm = (
remote: ChainName,
@ -48,7 +41,7 @@ export const multisigIsm = (
const configs =
context === Contexts.ReleaseCandidate
? rcMultisigIsmConfigs
: defaultMultisigIsmConfigs;
: defaultMultisigConfigs;
return {
...configs[remote],

@ -1,7 +1,7 @@
import { MultisigConfig } from '../ism/types';
import { ChainMap } from '../types';
export const defaultMultisigIsmConfigs: ChainMap<MultisigConfig> = {
export const defaultMultisigConfigs: ChainMap<MultisigConfig> = {
// ----------------- Mainnets -----------------
celo: {
threshold: 2,

@ -30,7 +30,7 @@ export {
hyperlaneContractAddresses,
hyperlaneEnvironments,
} from './consts/environments';
export { defaultMultisigIsmConfigs } from './consts/multisigIsm';
export { defaultMultisigConfigs } from './consts/multisigIsm';
export { SEALEVEL_SPL_NOOP_ADDRESS } from './consts/sealevel';
export {
attachContracts,
@ -127,6 +127,7 @@ export {
collectValidators,
moduleCanCertainlyVerify,
} from './ism/HyperlaneIsmFactory';
export { buildMultisigIsmConfigs } from './ism/multisig';
export {
AggregationIsmConfig,
DeployedIsm,

@ -0,0 +1,27 @@
import { objFilter, objMap } from '@hyperlane-xyz/utils';
import { ChainMap, ChainName } from '../types';
import { MultisigConfig, MultisigIsmConfig } from './types';
// build multisigIsmConfig from multisigConfig
// eg. for { sepolia (local), arbitrumsepolia, scrollsepolia }
// arbitrumsepolia => Ism, scrollsepolia => Ism
export const buildMultisigIsmConfigs = (
type: MultisigIsmConfig['type'],
local: ChainName,
chains: ChainName[],
multisigConfigs: ChainMap<MultisigConfig>,
): ChainMap<MultisigIsmConfig> => {
return objMap(
objFilter(
multisigConfigs,
(chain, config): config is MultisigConfig =>
chain !== local && chains.includes(chain),
),
(_, config) => ({
...config,
type,
}),
);
};
Loading…
Cancel
Save