Skip to content

Commit

Permalink
feat: Add deploy hook with config to warp deploy (#4977)
Browse files Browse the repository at this point in the history
### Description
This PR adds hook deployment within a warp config

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

Yes

### Testing
Manual/Unit Tests
  • Loading branch information
ltyu authored Dec 9, 2024
1 parent 64cce47 commit bb44f9b
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 76 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-lemons-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': minor
---

Add support for deploying Hooks using a HookConfig within a WarpConfig
224 changes: 149 additions & 75 deletions typescript/cli/src/deploy/warp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { confirm } from '@inquirer/prompts';
import { groupBy } from 'lodash-es';
import { stringify as yamlStringify } from 'yaml';

import { ProxyAdmin__factory } from '@hyperlane-xyz/core';
import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js';
import { ChainAddresses, IRegistry } from '@hyperlane-xyz/registry';
import { ChainAddresses } from '@hyperlane-xyz/registry';
import {
AggregationIsmConfig,
AnnotatedEV5Transaction,
Expand All @@ -15,16 +16,19 @@ import {
DestinationGas,
EvmERC20WarpModule,
EvmERC20WarpRouteReader,
EvmHookModule,
EvmIsmModule,
ExplorerLicenseType,
HookConfig,
HypERC20Deployer,
HypERC20Factories,
HypERC721Deployer,
HypERC721Factories,
HyperlaneAddresses,
HypTokenRouterConfig,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneProxyFactoryDeployer,
IsmConfig,
IsmType,
MultiProvider,
MultisigIsmConfig,
Expand All @@ -48,7 +52,6 @@ import {
hypERC20factories,
isCollateralTokenConfig,
isTokenMetadata,
serializeContracts,
} from '@hyperlane-xyz/sdk';
import {
Address,
Expand Down Expand Up @@ -192,7 +195,7 @@ async function executeDeploy(

const {
warpDeployConfig,
context: { registry, multiProvider, isDryRun, dryRunChain },
context: { multiProvider, isDryRun, dryRunChain },
} = params;

const deployer = warpDeployConfig.isNft
Expand All @@ -217,11 +220,10 @@ async function executeDeploy(
);

// 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(
// Then return a modified config with the ism and/or hook address as a string
const modifiedConfig = await resolveWarpIsmAndHook(
config,
multiProvider,
registry,
params.context,
ismFactoryDeployer,
contractVerifier,
);
Expand All @@ -243,71 +245,38 @@ async function writeDeploymentArtifacts(
log(indentYamlOrJson(yamlStringify(warpCoreConfig, null, 2), 4));
}

async function deployAndResolveWarpIsm(
async function resolveWarpIsmAndHook(
warpConfig: WarpRouteDeployConfig,
multiProvider: MultiProvider,
registry: IRegistry,
context: WriteCommandContext,
ismFactoryDeployer: HyperlaneProxyFactoryDeployer,
contractVerifier?: ContractVerifier,
): Promise<WarpRouteDeployConfig> {
return promiseObjAll(
objMap(warpConfig, async (chain, config) => {
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 for ${chain}...`);
let chainAddresses = await registry.getChainAddresses(chain);
const chainAddresses = await context.registry.getChainAddresses(chain);

if (!chainAddresses) {
logGray(
`Registry factory addresses not found for ${chain}. Deploying...`,
);
chainAddresses = serializeContracts(
await ismFactoryDeployer.deployContracts(chain),
) as Record<string, string>;
throw `Registry factory addresses not found for ${chain}.`;
}

logGray(
`Creating ${config.interchainSecurityModule.type} ISM for ${config.type} token on ${chain} chain...`,
);

const deployedIsm = await createWarpIsm(
config.interchainSecurityModule = await createWarpIsm({
chain,
warpConfig,
multiProvider,
{
domainRoutingIsmFactory: chainAddresses.domainRoutingIsmFactory,
staticAggregationHookFactory:
chainAddresses.staticAggregationHookFactory,
staticAggregationIsmFactory:
chainAddresses.staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory:
chainAddresses.staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory:
chainAddresses.staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory:
chainAddresses.staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory:
chainAddresses.staticMessageIdWeightedMultisigIsmFactory,
},
chainAddresses,
context,
contractVerifier,
);
ismFactoryDeployer,
warpConfig: config,
}); // TODO write test

logGreen(
`Finished creating ${config.interchainSecurityModule.type} ISM for ${config.type} token on ${chain} chain.`,
);
return { ...warpConfig[chain], interchainSecurityModule: deployedIsm };
config.hook = await createWarpHook({
chain,
chainAddresses,
context,
contractVerifier,
ismFactoryDeployer,
warpConfig: config,
});
return config;
}),
);
}
Expand All @@ -317,26 +286,57 @@ async function deployAndResolveWarpIsm(
*
* @returns The deployed ism address
*/
async function createWarpIsm(
chain: string,
warpConfig: WarpRouteDeployConfig,
multiProvider: MultiProvider,
factoryAddresses: HyperlaneAddresses<any>,
contractVerifier?: ContractVerifier,
): Promise<string> {
async function createWarpIsm({
chain,
chainAddresses,
context,
contractVerifier,
warpConfig,
}: {
chain: string;
chainAddresses: Record<string, string>;
context: WriteCommandContext;
contractVerifier?: ContractVerifier;
warpConfig: HypTokenRouterConfig;
ismFactoryDeployer: HyperlaneProxyFactoryDeployer;
}): Promise<IsmConfig | undefined> {
const { interchainSecurityModule } = warpConfig;
if (
!interchainSecurityModule ||
typeof interchainSecurityModule === 'string'
) {
logGray(
`Config Ism is ${
!interchainSecurityModule ? 'empty' : interchainSecurityModule
}, skipping deployment.`,
);
return interchainSecurityModule;
}

logBlue(`Loading registry factory addresses for ${chain}...`);

logGray(
`Creating ${interchainSecurityModule.type} ISM for token on ${chain} chain...`,
);

logGreen(
`Finished creating ${interchainSecurityModule.type} ISM for token on ${chain} chain.`,
);

const {
mailbox,
domainRoutingIsmFactory,
staticAggregationHookFactory,
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory,
} = factoryAddresses;
} = chainAddresses;
const evmIsmModule = await EvmIsmModule.create({
chain,
multiProvider,
mailbox: warpConfig[chain].mailbox,
mailbox,
multiProvider: context.multiProvider,
proxyFactoryFactories: {
domainRoutingIsmFactory,
staticAggregationHookFactory,
Expand All @@ -346,13 +346,85 @@ async function createWarpIsm(
staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory,
},
config: warpConfig[chain].interchainSecurityModule!,
config: interchainSecurityModule,
contractVerifier,
});
const { deployedIsm } = evmIsmModule.serialize();
return deployedIsm;
}

async function createWarpHook({
chain,
chainAddresses,
context,
contractVerifier,
warpConfig,
}: {
chain: string;
chainAddresses: Record<string, string>;
context: WriteCommandContext;
contractVerifier?: ContractVerifier;
warpConfig: HypTokenRouterConfig;
ismFactoryDeployer: HyperlaneProxyFactoryDeployer;
}): Promise<HookConfig | undefined> {
const { hook } = warpConfig;

if (!hook || typeof hook === 'string') {
logGray(`Config Hook is ${!hook ? 'empty' : hook}, skipping deployment.`);
return hook;
}

logBlue(`Loading registry factory addresses for ${chain}...`);

logGray(`Creating ${hook.type} Hook for token on ${chain} chain...`);

const {
mailbox,
domainRoutingIsmFactory,
staticAggregationHookFactory,
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory,
} = chainAddresses;
const proxyFactoryFactories = {
domainRoutingIsmFactory,
staticAggregationHookFactory,
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory,
};

// If config.proxyadmin.address exists, then use that. otherwise deploy a new proxyAdmin
const proxyAdminAddress: Address =
warpConfig.proxyAdmin?.address ??
(
await context.multiProvider.handleDeploy(
chain,
new ProxyAdmin__factory(),
[],
)
).address;

const evmHookModule = await EvmHookModule.create({
chain,
multiProvider: context.multiProvider,
coreAddresses: {
mailbox,
proxyAdmin: proxyAdminAddress,
},
config: hook,
contractVerifier,
proxyFactoryFactories,
});
logGreen(`Finished creating ${hook.type} Hook for token on ${chain} chain.`);
const { deployedHook } = evmHookModule.serialize();
return deployedHook;
}

async function getWarpCoreConfig(
{ warpDeployConfig, context }: DeployParams,
contracts: HyperlaneContractsMap<TokenFactories>,
Expand Down Expand Up @@ -818,7 +890,7 @@ function displayWarpDeployPlan(deployConfig: WarpRouteDeployConfig) {
}

/* only used for transformIsmForDisplay type-sense */
type IsmConfig =
type IsmDisplayConfig =
| RoutingIsmConfig // type, owner, ownerOverrides, domain
| AggregationIsmConfig // type, modules, threshold
| MultisigIsmConfig // type, validators, threshold
Expand All @@ -831,7 +903,7 @@ function transformDeployConfigForDisplay(deployConfig: WarpRouteDeployConfig) {
const transformedDeployConfig = objMap(deployConfig, (chain, config) => {
if (config.interchainSecurityModule)
transformedIsmConfigs[chain] = transformIsmConfigForDisplay(
config.interchainSecurityModule as IsmConfig,
config.interchainSecurityModule as IsmDisplayConfig,
);

return {
Expand All @@ -851,7 +923,7 @@ function transformDeployConfigForDisplay(deployConfig: WarpRouteDeployConfig) {
};
}

function transformIsmConfigForDisplay(ismConfig: IsmConfig): any[] {
function transformIsmConfigForDisplay(ismConfig: IsmDisplayConfig): any[] {
const ismConfigs: any[] = [];
switch (ismConfig.type) {
case IsmType.AGGREGATION:
Expand All @@ -861,7 +933,9 @@ function transformIsmConfigForDisplay(ismConfig: IsmConfig): any[] {
Modules: 'See table(s) below.',
});
ismConfig.modules.forEach((module) => {
ismConfigs.push(...transformIsmConfigForDisplay(module as IsmConfig));
ismConfigs.push(
...transformIsmConfigForDisplay(module as IsmDisplayConfig),
);
});
return ismConfigs;
case IsmType.ROUTING:
Expand Down
Loading

0 comments on commit bb44f9b

Please sign in to comment.