Skip to content

Commit

Permalink
Improve warp and kurtosis deploy command dx (#2994)
Browse files Browse the repository at this point in the history
### Description

Add artifact selection step to warp deploy
Improve chain selection for kurtosis agent deploy

### Drive-by changes

Add chain metadata consts validation unit test

### Related issues

Fixes hyperlane-xyz/issues#774

### Backward compatibility

Small break in kurtosis deploy args, likely won't be noticed.

### Testing

Manual
  • Loading branch information
jmrossy authored Nov 29, 2023
1 parent 2a547ee commit f44589e
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 71 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-beans-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': patch
---

Improve warp and kurtosis deploy command UX
17 changes: 10 additions & 7 deletions typescript/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,29 @@ const agentCommand: CommandModule = {
describe: 'Deploy Hyperlane agents with Kurtosis',
builder: (yargs) =>
yargs.options({
originChain: {
origin: {
type: 'string',
description: 'The name of the origin chain to deploy to',
},
agentConfiguration: agentConfigurationOption,
relayChains: {
targets: {
type: 'string',
description: 'Comma separated list of chains to relay between',
},
chains: chainsCommandOption,
config: agentConfigurationOption,
}),
handler: async (argv: any) => {
logGray('Hyperlane Agent Deployment with Kurtosis');
logGray('----------------------------------------');
const originChain: string = argv.originChain;
const agentConfigurationPath: string = argv.agentConfiguration;
const relayChains: string = argv.relayChains;
const chainConfigPath: string = argv.chains;
const originChain: string = argv.origin;
const agentConfigurationPath: string = argv.config;
const relayChains: string = argv.targets;
await runKurtosisAgentDeploy({
originChain,
agentConfigurationPath,
relayChains,
chainConfigPath,
agentConfigurationPath,
});
process.exit(0);
},
Expand Down
3 changes: 1 addition & 2 deletions typescript/cli/src/commands/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ export const outDirCommandOption: Options = {
export const coreArtifactsOption: Options = {
type: 'string',
description: 'File path to core deployment output artifacts',
alias: 'ca',
alias: 'a',
};

export const agentConfigurationOption: Options = {
type: 'string',
description: 'File path to agent configuration artifacts',
alias: 'ac',
};

export const fileFormatOption: Options = {
Expand Down
40 changes: 37 additions & 3 deletions typescript/cli/src/config/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { confirm } from '@inquirer/prompts';
import { ZodTypeAny, z } from 'zod';

import { HyperlaneContractsMap } from '@hyperlane-xyz/sdk';
import { ChainName, HyperlaneContractsMap } from '@hyperlane-xyz/sdk';

import { logBlue } from '../../logger.js';
import { readYamlOrJson } from '../utils/files.js';
import { log, logBlue, logRed } from '../../logger.js';
import { readYamlOrJson, runFileSelectionStep } from '../utils/files.js';

const RecursiveObjectSchema: ZodTypeAny = z.lazy(() =>
z.object({}).catchall(z.union([z.string(), RecursiveObjectSchema])),
Expand Down Expand Up @@ -31,3 +32,36 @@ export function readDeploymentArtifacts(filePath: string) {
}
return artifacts;
}

export async function runDeploymentArtifactStep(
artifactsPath?: string,
message?: string,
selectedChains?: ChainName[],
) {
if (!artifactsPath) {
const useArtifacts = await confirm({
message: message || 'Do you want use some existing contract addresses?',
});
if (!useArtifacts) return undefined;

artifactsPath = await runFileSelectionStep(
'./artifacts',
'contract artifacts',
'core-deployment',
);
}
const artifacts = readDeploymentArtifacts(artifactsPath);

if (selectedChains) {
const artifactChains = Object.keys(artifacts).filter((c) =>
selectedChains.includes(c),
);
if (artifactChains.length === 0) {
logRed('No artifacts found for selected chains');
} else {
log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`);
}
}

return artifacts;
}
49 changes: 24 additions & 25 deletions typescript/cli/src/deploy/agent.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import { input } from '@inquirer/prompts';
import terminalLink from 'terminal-link';

import { logBlue, logGreen, logRed } from '../../logger.js';
import { toBase64 } from '@hyperlane-xyz/utils';

import { logBlue, logGreen } from '../../logger.js';
import { getContext } from '../context.js';
import {
runMultiChainSelectionStep,
runSingleChainSelectionStep,
} from '../utils/chains.js';
import { readJson, runFileSelectionStep } from '../utils/files.js';

export async function runKurtosisAgentDeploy({
originChain,
agentConfigurationPath,
relayChains,
chainConfigPath,
agentConfigurationPath,
}: {
originChain: string;
agentConfigurationPath: string;
relayChains: string;
chainConfigPath: string;
agentConfigurationPath: string;
}) {
const { customChains } = getContext(chainConfigPath);

if (!originChain) {
originChain = await input({ message: 'Enter the origin chain' });
originChain = await runSingleChainSelectionStep(
customChains,
'Select the origin chain',
);
}
if (!relayChains) {
relayChains = await input({
message: 'Enter a comma separated list of chains to relay between',
});
relayChains = trimSpaces(relayChains);
const selectedRelayChains = await runMultiChainSelectionStep(
customChains,
'Select chains to relay between',
);
relayChains = selectedRelayChains.join(',');
}

if (!agentConfigurationPath) {
Expand Down Expand Up @@ -48,7 +62,7 @@ export async function runKurtosisAgentDeploy({
args: hyperlanePackageArgs,
};

const base64EncodedPackageConfig = jsonToBase64(kurtosisPackageConfig);
const base64EncodedPackageConfig = toBase64(kurtosisPackageConfig) || '';
const kurtosisCloudUrl = getKurtosisCloudUrl(base64EncodedPackageConfig);

const kurtosisCloudLink = terminalLink(
Expand All @@ -67,18 +81,3 @@ export async function runKurtosisAgentDeploy({

const getKurtosisCloudUrl = (base64Params: string) =>
`https://cloud.kurtosis.com/enclave-manager?package-id=github.com%2Fkurtosis-tech%2Fhyperlane-package&package-args=${base64Params}`;

const trimSpaces = (a: string) =>
a
.split('')
.filter((char) => char !== ' ')
.join('');

function jsonToBase64(jsonData: any): string {
try {
return btoa(JSON.stringify(jsonData));
} catch (error) {
logRed('Error occurred creating kurtosis cloud url.');
return '';
}
}
36 changes: 6 additions & 30 deletions typescript/cli/src/deploy/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils';

import { log, logBlue, logGray, logGreen, logRed } from '../../logger.js';
import { readDeploymentArtifacts } from '../config/artifacts.js';
import { runDeploymentArtifactStep } from '../config/artifacts.js';
import { readHookConfig } from '../config/hooks.js';
import { readIsmConfig } from '../config/ism.js';
import { readMultisigConfig } from '../config/multisig.js';
Expand Down Expand Up @@ -118,36 +118,12 @@ export async function runCoreDeploy({
await executeDeploy(deploymentParams);
}

async function runArtifactStep(
selectedChains: ChainName[],
artifactsPath?: string,
) {
if (!artifactsPath) {
logBlue(
'\n',
'Deployments can be totally new or can use some existing contract addresses.',
);
const isResume = await confirm({
message: 'Do you want use some existing contract addresses?',
});
if (!isResume) return undefined;

artifactsPath = await runFileSelectionStep(
'./artifacts',
'contract artifacts',
'core-deployment',
);
}
const artifacts = readDeploymentArtifacts(artifactsPath);
const artifactChains = Object.keys(artifacts).filter((c) =>
selectedChains.includes(c),
function runArtifactStep(selectedChains: ChainName[], artifactsPath?: string) {
logBlue(
'\n',
'Deployments can be totally new or can use some existing contract addresses.',
);
if (artifactChains.length === 0) {
logGray('No artifacts found for selected chains');
} else {
log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`);
}
return artifacts;
return runDeploymentArtifactStep(artifactsPath, undefined, selectedChains);
}

async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) {
Expand Down
9 changes: 5 additions & 4 deletions typescript/cli/src/deploy/warp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { Address, ProtocolType, objMap } from '@hyperlane-xyz/utils';

import { log, logBlue, logGray, logGreen } from '../../logger.js';
import { readDeploymentArtifacts } from '../config/artifacts.js';
import { runDeploymentArtifactStep } from '../config/artifacts.js';
import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js';
import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js';
import {
Expand Down Expand Up @@ -65,9 +65,10 @@ export async function runWarpDeploy({
}
const warpRouteConfig = readWarpRouteConfig(warpConfigPath);

const artifacts = coreArtifactsPath
? readDeploymentArtifacts(coreArtifactsPath)
: undefined;
const artifacts = await runDeploymentArtifactStep(
coreArtifactsPath,
'Do you want use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).',
);

const configs = await runBuildConfigStep({
warpRouteConfig,
Expand Down
12 changes: 12 additions & 0 deletions typescript/sdk/src/metadata/chainMetadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { expect } from 'chai';

import { ProtocolType } from '@hyperlane-xyz/utils';

import { chainMetadata } from '../consts/chainMetadata';

import { ChainMetadata, isValidChainMetadata } from './chainMetadataTypes';

const minimalSchema: ChainMetadata = {
Expand Down Expand Up @@ -61,6 +63,7 @@ describe('ChainMetadataSchema', () => {
}),
).to.eq(true);
});

it('Rejects invalid schemas', () => {
expect(
//@ts-ignore
Expand Down Expand Up @@ -106,4 +109,13 @@ describe('ChainMetadataSchema', () => {
}),
).to.eq(false);
});

it('Works for all SDK chain metadata consts', () => {
for (const chain of Object.keys(chainMetadata)) {
const isValid = isValidChainMetadata(chainMetadata[chain]);
// eslint-disable-next-line no-console
if (!isValid) console.error(`Invalid chain metadata for ${chain}`);
expect(isValid).to.eq(true);
}
});
});

0 comments on commit f44589e

Please sign in to comment.