feat: implement `hyperlane warp deploy` (#3920)

### Description
- Update `hyperlane warp deploy` command

### Drive-by changes
- Move Core deploy to core.ts
- Update Module return args to match TokenType

### Related issues
- Fixes
#[3541](https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3541)


### Backward compatibility
yes, but old command no longer applies

### Testing
Manual

---------

Signed-off-by: Paul Balaji <paul@hyperlane.xyz>
Co-authored-by: Paul Balaji <paul@hyperlane.xyz>
Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz>
Co-authored-by: J M Rossy <jm.rossy@gmail.com>
Co-authored-by: Noah Bayindirli 🥂 <noah@primeprotocol.xyz>
Co-authored-by: Noah Bayindirli 🥂 <noah@hyperlane.xyz>
pull/3937/head
Lee 5 months ago committed by GitHub
parent 63367c5504
commit 6db9fa9ada
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/slimy-toys-argue.md
  2. 6
      typescript/cli/ci-test.sh
  3. 59
      typescript/cli/src/commands/core.ts
  4. 94
      typescript/cli/src/commands/deploy.ts
  5. 35
      typescript/cli/src/commands/warp.ts
  6. 2
      typescript/sdk/src/index.ts
  7. 37
      typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts
  8. 1
      typescript/sdk/src/token/EvmERC20WarpModule.ts

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Implement hyperlane warp deploy

@ -162,7 +162,7 @@ run_hyperlane_deploy_warp_dry_run() {
update_deployer_balance; update_deployer_balance;
echo -e "\nDry-running warp route deployments to Alfajores" echo -e "\nDry-running warp route deployments to Alfajores"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \
--dry-run alfajores \ --dry-run alfajores \
--overrides ${TEST_CONFIGS_PATH}/dry-run \ --overrides ${TEST_CONFIGS_PATH}/dry-run \
--config ${TEST_CONFIGS_PATH}/dry-run/warp-route-deployment.yaml \ --config ${TEST_CONFIGS_PATH}/dry-run/warp-route-deployment.yaml \
@ -200,7 +200,7 @@ run_hyperlane_deploy_warp() {
update_deployer_balance; update_deployer_balance;
echo -e "\nDeploying hypNative warp route" echo -e "\nDeploying hypNative warp route"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \
--registry $REGISTRY_PATH \ --registry $REGISTRY_PATH \
--overrides " " \ --overrides " " \
--config $WARP_DEPLOY_CONFIG_PATH \ --config $WARP_DEPLOY_CONFIG_PATH \
@ -213,7 +213,7 @@ run_hyperlane_deploy_warp() {
/tmp/warp-collateral-deployment.json \ /tmp/warp-collateral-deployment.json \
echo "Deploying hypCollateral warp route" echo "Deploying hypCollateral warp route"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \
--registry $REGISTRY_PATH \ --registry $REGISTRY_PATH \
--overrides " " \ --overrides " " \
--config /tmp/warp-collateral-deployment.json \ --config /tmp/warp-collateral-deployment.json \

@ -4,7 +4,12 @@ import { CoreConfigSchema, EvmCoreReader, IsmConfig } from '@hyperlane-xyz/sdk';
import { createHookConfig } from '../config/hooks.js'; import { createHookConfig } from '../config/hooks.js';
import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js'; import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js';
import { CommandModuleWithContext } from '../context/types.js'; import {
CommandModuleWithContext,
CommandModuleWithWriteContext,
} from '../context/types.js';
import { runCoreDeploy } from '../deploy/core.js';
import { evaluateIfDryRunFailure } from '../deploy/dry-run.js';
import { import {
log, log,
logBlue, logBlue,
@ -13,10 +18,14 @@ import {
logRed, logRed,
} from '../logger.js'; } from '../logger.js';
import { detectAndConfirmOrPrompt } from '../utils/chains.js'; import { detectAndConfirmOrPrompt } from '../utils/chains.js';
import { writeYamlOrJson } from '../utils/files.js'; import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js';
import { deploy } from './deploy.js'; import {
import { chainCommandOption, outputFileCommandOption } from './options.js'; chainCommandOption,
dryRunCommandOption,
fromAddressCommandOption,
outputFileCommandOption,
} from './options.js';
/** /**
* Parent command * Parent command
@ -34,6 +43,48 @@ export const coreCommand: CommandModule = {
handler: () => log('Command required'), handler: () => log('Command required'),
}; };
/**
* Generates a command module for deploying Hyperlane contracts, given a command
*
* @param commandName - the deploy command key used to look up the deployFunction
* @returns A command module used to deploy Hyperlane contracts.
*/
export const deploy: CommandModuleWithWriteContext<{
chain: string;
config: string;
dryRun: string;
fromAddress: string;
}> = {
command: 'deploy',
describe: 'Deploy Hyperlane contracts',
builder: {
chain: chainCommandOption,
config: outputFileCommandOption(
'./configs/core-config.yaml',
false,
'The path to a JSON or YAML file with a core deployment config.',
),
'dry-run': dryRunCommandOption,
'from-address': fromAddressCommandOption,
},
handler: async ({ context, chain, config: configFilePath, dryRun }) => {
logGray(`Hyperlane permissionless deployment${dryRun ? ' dry-run' : ''}`);
logGray(`------------------------------------------------`);
try {
await runCoreDeploy({
context,
chain,
config: readYamlOrJson(configFilePath),
});
} catch (error: any) {
evaluateIfDryRunFailure(error, dryRun);
throw error;
}
process.exit(0);
},
};
export const configure: CommandModuleWithContext<{ export const configure: CommandModuleWithContext<{
ismAdvanced: boolean; ismAdvanced: boolean;
config: string; config: string;

@ -1,25 +1,13 @@
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { import { CommandModuleWithContext } from '../context/types.js';
CommandModuleWithContext,
CommandModuleWithWriteContext,
} from '../context/types.js';
import { runKurtosisAgentDeploy } from '../deploy/agent.js'; import { runKurtosisAgentDeploy } from '../deploy/agent.js';
import { runCoreDeploy } from '../deploy/core.js';
import { evaluateIfDryRunFailure } from '../deploy/dry-run.js';
import { runWarpRouteDeploy } from '../deploy/warp.js';
import { log, logGray } from '../logger.js'; import { log, logGray } from '../logger.js';
import { readYamlOrJson } from '../utils/files.js';
import { import {
agentConfigCommandOption, agentConfigCommandOption,
agentTargetsCommandOption, agentTargetsCommandOption,
chainCommandOption,
dryRunCommandOption,
fromAddressCommandOption,
originCommandOption, originCommandOption,
outputFileCommandOption,
warpDeploymentConfigCommandOption,
} from './options.js'; } from './options.js';
/** /**
@ -29,11 +17,7 @@ export const deployCommand: CommandModule = {
command: 'deploy', command: 'deploy',
describe: 'Permissionlessly deploy a Hyperlane contracts or extensions', describe: 'Permissionlessly deploy a Hyperlane contracts or extensions',
builder: (yargs) => builder: (yargs) =>
yargs yargs.command(agentCommand).version(false).demandCommand(),
.command(warpCommand)
.command(agentCommand)
.version(false)
.demandCommand(),
handler: () => log('Command required'), handler: () => log('Command required'),
}; };
@ -64,77 +48,3 @@ const agentCommand: CommandModuleWithContext<{
process.exit(0); process.exit(0);
}, },
}; };
/**
* Generates a command module for deploying Hyperlane contracts, given a command
*
* @param commandName - the deploy command key used to look up the deployFunction
* @returns A command module used to deploy Hyperlane contracts.
*/
export const deploy: CommandModuleWithWriteContext<{
chain: string;
config: string;
dryRun: string;
fromAddress: string;
}> = {
command: 'deploy',
describe: 'Deploy Hyperlane contracts',
builder: {
chain: chainCommandOption,
config: outputFileCommandOption(
'./configs/core-config.yaml',
false,
'The path to a JSON or YAML file with a core deployment config.',
),
'dry-run': dryRunCommandOption,
'from-address': fromAddressCommandOption,
},
handler: async ({ context, chain, config: configFilePath, dryRun }) => {
logGray(`Hyperlane permissionless deployment${dryRun ? ' dry-run' : ''}`);
logGray(`------------------------------------------------`);
try {
await runCoreDeploy({
context,
chain,
config: readYamlOrJson(configFilePath),
});
} catch (error: any) {
evaluateIfDryRunFailure(error, dryRun);
throw error;
}
process.exit(0);
},
};
/**
* Warp command
*/
const warpCommand: CommandModuleWithWriteContext<{
config: string;
'dry-run': string;
'from-address': string;
}> = {
command: 'warp',
describe: 'Deploy Warp Route contracts',
builder: {
config: warpDeploymentConfigCommandOption,
'dry-run': dryRunCommandOption,
'from-address': fromAddressCommandOption,
},
handler: async ({ context, config, dryRun }) => {
logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`);
logGray('------------------------------------------------');
try {
await runWarpRouteDeploy({
context,
warpRouteDeploymentConfigPath: config,
});
} catch (error: any) {
evaluateIfDryRunFailure(error, dryRun);
throw error;
}
process.exit(0);
},
};

@ -7,6 +7,8 @@ import {
CommandModuleWithContext, CommandModuleWithContext,
CommandModuleWithWriteContext, CommandModuleWithWriteContext,
} from '../context/types.js'; } from '../context/types.js';
import { evaluateIfDryRunFailure } from '../deploy/dry-run.js';
import { runWarpRouteDeploy } from '../deploy/warp.js';
import { log, logGray, logGreen } from '../logger.js'; import { log, logGray, logGreen } from '../logger.js';
import { sendTestTransfer } from '../send/transfer.js'; import { sendTestTransfer } from '../send/transfer.js';
import { writeFileAtPath } from '../utils/files.js'; import { writeFileAtPath } from '../utils/files.js';
@ -14,8 +16,11 @@ import { writeFileAtPath } from '../utils/files.js';
import { import {
addressCommandOption, addressCommandOption,
chainCommandOption, chainCommandOption,
dryRunCommandOption,
fromAddressCommandOption,
outputFileCommandOption, outputFileCommandOption,
warpCoreConfigCommandOption, warpCoreConfigCommandOption,
warpDeploymentConfigCommandOption,
} from './options.js'; } from './options.js';
import { MessageOptionsArgTypes, messageOptions } from './send.js'; import { MessageOptionsArgTypes, messageOptions } from './send.js';
@ -28,6 +33,7 @@ export const warpCommand: CommandModule = {
builder: (yargs) => builder: (yargs) =>
yargs yargs
.command(configure) .command(configure)
.command(deploy)
.command(read) .command(read)
.command(send) .command(send)
.version(false) .version(false)
@ -36,6 +42,35 @@ export const warpCommand: CommandModule = {
handler: () => log('Command required'), handler: () => log('Command required'),
}; };
export const deploy: CommandModuleWithWriteContext<{
config: string;
'dry-run': string;
'from-address': string;
}> = {
command: 'deploy',
describe: 'Deploy Warp Route contracts',
builder: {
config: warpDeploymentConfigCommandOption,
'dry-run': dryRunCommandOption,
'from-address': fromAddressCommandOption,
},
handler: async ({ context, config, dryRun }) => {
logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`);
logGray('------------------------------------------------');
try {
await runWarpRouteDeploy({
context,
warpRouteDeploymentConfigPath: config,
});
} catch (error: any) {
evaluateIfDryRunFailure(error, dryRun);
throw error;
}
process.exit(0);
},
};
export const configure: CommandModuleWithContext<{ export const configure: CommandModuleWithContext<{
ismAdvanced: boolean; ismAdvanced: boolean;
out: string; out: string;

@ -447,6 +447,7 @@ export { HypERC20App } from './token/app.js';
export { HypERC20Checker } from './token/checker.js'; export { HypERC20Checker } from './token/checker.js';
export { TokenType } from './token/config.js'; export { TokenType } from './token/config.js';
export { export {
hypERC20factories,
HypERC20Factories, HypERC20Factories,
HypERC721Factories, HypERC721Factories,
TokenFactories, TokenFactories,
@ -505,3 +506,4 @@ export { S3Config, S3Wrapper, S3Receipt } from './aws/s3.js';
export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService } from './utils/gnosisSafe.js'; export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService } from './utils/gnosisSafe.js';
export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js'; export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js';
export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js';

@ -132,13 +132,16 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
expect(tokenType).to.equal(TokenType.collateralVault); expect(tokenType).to.equal(TokenType.collateralVault);
// Validate onchain token values // Validate onchain token values
const collateralVault = HypERC20CollateralVaultDeposit__factory.connect( const collateralVaultContract =
deployedTokenRoute, HypERC20CollateralVaultDeposit__factory.connect(
signer, deployedTokenRoute,
signer,
);
await validateCoreValues(collateralVaultContract);
expect(await collateralVaultContract.vault()).to.equal(vault.address);
expect(await collateralVaultContract.wrappedToken()).to.equal(
token.address,
); );
await validateCoreValues(collateralVault);
expect(await collateralVault.vault()).to.equal(vault.address);
expect(await collateralVault.wrappedToken()).to.equal(token.address);
}); });
it('should create with a a synthetic config', async () => { it('should create with a a synthetic config', async () => {
@ -167,12 +170,15 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
expect(tokenType).to.equal(TokenType.synthetic); expect(tokenType).to.equal(TokenType.synthetic);
// Validate onchain token values // Validate onchain token values
const synthetic = HypERC20__factory.connect(deployedTokenRoute, signer); const syntheticContract = HypERC20__factory.connect(
await validateCoreValues(synthetic); deployedTokenRoute,
expect(await synthetic.name()).to.equal(TOKEN_NAME); signer,
expect(await synthetic.symbol()).to.equal(TOKEN_NAME); );
expect(await synthetic.decimals()).to.equal(TOKEN_DECIMALS); await validateCoreValues(syntheticContract);
expect(await synthetic.totalSupply()).to.equal(TOKEN_SUPPLY); expect(await syntheticContract.name()).to.equal(TOKEN_NAME);
expect(await syntheticContract.symbol()).to.equal(TOKEN_NAME);
expect(await syntheticContract.decimals()).to.equal(TOKEN_DECIMALS);
expect(await syntheticContract.totalSupply()).to.equal(TOKEN_SUPPLY);
}); });
it('should create with a a native config', async () => { it('should create with a a native config', async () => {
@ -196,7 +202,10 @@ describe('EvmERC20WarpHyperlaneModule', async () => {
expect(tokenType).to.equal(TokenType.native); expect(tokenType).to.equal(TokenType.native);
// Validate onchain token values // Validate onchain token values
const native = HypNative__factory.connect(deployedTokenRoute, signer); const nativeContract = HypNative__factory.connect(
await validateCoreValues(native); deployedTokenRoute,
signer,
);
await validateCoreValues(nativeContract);
}); });
}); });

@ -34,7 +34,6 @@ export class EvmERC20WarpModule extends HyperlaneModule<
>, >,
) { ) {
super(args); super(args);
this.reader = new EvmERC20WarpRouteReader(multiProvider, args.chain); this.reader = new EvmERC20WarpRouteReader(multiProvider, args.chain);
} }

Loading…
Cancel
Save