feat: add deploy support for IsmConfig in WarpConfig (#3939)

### Description
Adds deployment support for IsmConfig within a WarpRouteConfig

### Drive-by changes
- Update dry-run confirmation to 1

### Related issues
- Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3933

### Backward compatibility
Yes

### Testing
Manual
- [x] Deploy Warp with Ism as object
- [x] Deploy Warp with Ism as string
- [x] Deploy Warp with Ism as empty
- [x] Deploy synthetic on sepolia and collateral holesky with default
ism config (aggregation, trusted, domain)
- [x] Deploy collateral on sepolia with default ism config (aggregation,
trusted, domain)
pull/3969/head
Lee 5 months ago committed by GitHub
parent 4040db723b
commit 6b63c5d823
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/late-rings-attack.md
  2. 2
      typescript/cli/src/deploy/dry-run.ts
  3. 131
      typescript/cli/src/deploy/warp.ts
  4. 1
      typescript/sdk/src/index.ts
  5. 15
      typescript/sdk/src/ism/EvmIsmModule.ts
  6. 7
      typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts
  7. 4
      typescript/sdk/src/token/deploy.ts

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': patch
'@hyperlane-xyz/sdk': patch
---
Adds deployment support for IsmConfig within a WarpRouteConfig

@ -21,7 +21,7 @@ export async function forkNetworkToMultiProvider(
chain: string,
) {
multiProvider = multiProvider.extendChainMetadata({
[chain]: { blocks: { confirmations: 0 } },
[chain]: { blocks: { confirmations: 1 } },
});
await setFork(multiProvider, chain);

@ -1,9 +1,16 @@
import { confirm } from '@inquirer/prompts';
import { stringify as yamlStringify } from 'yaml';
import { IRegistry } from '@hyperlane-xyz/registry';
import {
EvmIsmModule,
HypERC20Deployer,
HypERC721Deployer,
HyperlaneAddresses,
HyperlaneContractsMap,
HyperlaneDeployer,
HyperlaneProxyFactoryDeployer,
MultiProvider,
TOKEN_TYPE_TO_STANDARD,
TokenFactories,
TokenType,
@ -11,14 +18,19 @@ import {
WarpRouteDeployConfig,
getTokenConnectionId,
isTokenMetadata,
serializeContracts,
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ProtocolType, objMap, promiseObjAll } from '@hyperlane-xyz/utils';
import { readWarpRouteDeployConfig } from '../config/warp.js';
import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js';
import { WriteCommandContext } from '../context/types.js';
import { log, logBlue, logGray, logGreen, logTable } from '../logger.js';
import { isFile, runFileSelectionStep } from '../utils/files.js';
import {
indentYamlOrJson,
isFile,
runFileSelectionStep,
} from '../utils/files.js';
import {
completeDeploy,
@ -119,7 +131,18 @@ async function executeDeploy(params: DeployParams) {
? { [dryRunChain]: configMap[dryRunChain] }
: configMap;
const deployedContracts = await deployer.deploy(config);
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider);
// For each chain in WarpRouteConfig, deploy each Ism Factory, if it's not in the registry
// Then return a modified config with the ism address as a string
const modifiedConfig = await deployAndResolveWarpIsm(
config,
multiProvider,
registry,
ismFactoryDeployer,
);
const deployedContracts = await deployer.deploy(modifiedConfig);
logGreen('✅ Hyp token deployments complete');
@ -128,10 +151,110 @@ async function executeDeploy(params: DeployParams) {
log('Writing deployment artifacts');
await registry.addWarpRoute(warpCoreConfig);
}
log(JSON.stringify(warpCoreConfig, null, 2));
log(indentYamlOrJson(yamlStringify(warpCoreConfig, null, 2), 4));
logBlue('Deployment is complete!');
}
async function deployAndResolveWarpIsm(
warpConfig: WarpRouteDeployConfig,
multiProvider: MultiProvider,
registry: IRegistry,
ismFactoryDeployer: HyperlaneProxyFactoryDeployer,
): Promise<WarpRouteDeployConfig> {
return promiseObjAll(
objMap(warpConfig, async (chain, config) => {
// Skip deployment if Ism is empty, or a string
if (
!config.interchainSecurityModule ||
typeof config.interchainSecurityModule === 'string'
) {
logGray(
`Config Ism is ${
!config.interchainSecurityModule
? 'empty'
: config.interchainSecurityModule
}, skipping deployment`,
);
return config;
}
logBlue('Loading Registry factory addresses');
let chainAddresses = await registry.getChainAddresses(chain); // Can includes other addresses
if (!chainAddresses) {
logGray('Registry factory addresses not found, deploying');
chainAddresses = serializeContracts(
await ismFactoryDeployer.deployContracts(chain),
) as Record<string, string>;
}
logGray(
`Creating ${config.interchainSecurityModule.type} Ism for ${config.type} token on ${chain} chain`,
);
const deployedIsm = await createWarpIsm(
chain,
warpConfig,
multiProvider,
ismFactoryDeployer,
{
domainRoutingIsmFactory: chainAddresses.domainRoutingIsmFactory,
staticAggregationHookFactory:
chainAddresses.staticAggregationHookFactory,
staticAggregationIsmFactory:
chainAddresses.staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory:
chainAddresses.staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory:
chainAddresses.staticMessageIdMultisigIsmFactory,
},
);
logGreen(
`Finished creating ${config.interchainSecurityModule.type} Ism for ${config.type} token on ${chain} chain`,
);
return { ...warpConfig[chain], interchainSecurityModule: deployedIsm };
}),
);
}
/**
* Deploys the Warp ISM for a given config
*
* @returns The deployed ism address
*/
async function createWarpIsm(
chain: string,
warpConfig: WarpRouteDeployConfig,
multiProvider: MultiProvider,
ismFactoryDeployer: HyperlaneDeployer<any, any>,
factoryAddresses: HyperlaneAddresses<any>,
): Promise<string> {
const {
domainRoutingIsmFactory,
staticAggregationHookFactory,
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
} = factoryAddresses;
const evmIsmModule = await EvmIsmModule.create({
chain,
multiProvider,
deployer: ismFactoryDeployer,
mailbox: warpConfig[chain].mailbox,
factories: {
domainRoutingIsmFactory,
staticAggregationHookFactory,
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
},
config: warpConfig[chain].interchainSecurityModule!,
});
const { deployedIsm } = evmIsmModule.serialize();
return deployedIsm;
}
async function getWarpCoreConfig(
{ configMap, context }: DeployParams,
contracts: HyperlaneContractsMap<TokenFactories>,

@ -507,3 +507,4 @@ export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService }
export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js';
export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js';
export { EvmIsmModule } from './ism/EvmIsmModule.js';

@ -468,12 +468,13 @@ export class EvmIsmModule extends HyperlaneModule<
// initialize the fallback routing ISM
logger.debug('Initializing fallback routing ISM ...');
await ism['initialize(address,uint32[],address[])'](
const tx = await ism['initialize(address,uint32[],address[])'](
config.owner,
availableDomainIds,
submoduleAddresses,
);
await this.multiProvider.handleTx(this.chain, tx);
// return the fallback routing ISM
return ism;
}
@ -536,12 +537,12 @@ export class EvmIsmModule extends HyperlaneModule<
config: AggregationIsmConfig;
logger: Logger;
}): Promise<IAggregationIsm> {
const addresses: Address[] = await Promise.all(
config.modules.map(async (module) => {
const submodule = await this.deploy({ config: module });
return submodule.address;
}),
);
const addresses: Address[] = [];
// Needs to be deployed sequentially because Ethers will throw `Error: replacement fee too low`
for (const module of config.modules) {
const submodule = await this.deploy({ config: module });
addresses.push(submodule.address);
}
const factoryName = 'staticAggregationIsmFactory';
const address = await EvmIsmModule.deployStaticAddressSet({

@ -57,6 +57,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
);
expect(await deployedToken.owner()).to.equal(signer.address);
}
before(async () => {
[signer] = await hre.ethers.getSigners();
multiProvider = MultiProvider.createTestMultiProvider({ signer });
@ -82,7 +83,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
hookAddress = await mailbox.defaultHook();
});
it('should create with a a collateral config', async () => {
it('should create with a collateral config', async () => {
const config = {
...baseConfig,
type: TokenType.collateral,
@ -144,7 +145,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
);
});
it('should create with a a synthetic config', async () => {
it('should create with a synthetic config', async () => {
const config = {
type: TokenType.synthetic,
token: token.address,
@ -181,7 +182,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
expect(await syntheticContract.totalSupply()).to.equal(TOKEN_SUPPLY);
});
it('should create with a a native config', async () => {
it('should create with a native config', async () => {
const config = {
type: TokenType.native,
hook: hookAddress,

@ -65,10 +65,6 @@ abstract class TokenDeployer<
}
async initializeArgs(_: ChainName, config: TokenRouterConfig): Promise<any> {
// ISM config can be an object, but is not supported right now.
if (typeof config.interchainSecurityModule === 'object') {
throw new Error('Token deployer does not support ISM objects currently');
}
const defaultArgs = [
config.hook ?? constants.AddressZero,
config.interchainSecurityModule ?? constants.AddressZero,

Loading…
Cancel
Save