Skip to content

Commit

Permalink
feat: add dry-run support for warp deploy + gas stats (#3586)
Browse files Browse the repository at this point in the history
### Description

* New feature to allow users to dry-run the warp deploy script against a
forked (base/origin) network of their choice
* To run: `yarn build && yarn hyperlane deploy warp --dry-run` || `yarn
build && yarn hyperlane deploy warp -d`
* Externally enables: `hyperlane deploy warp --dry-run` || `hyperlane
deploy warp -d`
* Also adds gas usage util for both warp & core deployments in all
contexts, e.g.
* When running a vanilla core deploy, for example, between alfajores and
fuji, you will now see:
```
⛽️ Gas Usage Statistics
        - Gas required for core deploy on alfajores: 0.0058686745 CELO
        - Gas required for core deploy on fuji: 0.0239308515 AVAX
```

### Drive-by changes

* None

### Related issues

* Fixes https://github.com/hyperlane-xyz/issues/issues/819

### Backward compatibility

* Yes

### Testing

* Note: `hl` == `yarn build && yarn hyperlane`. The below tests are only
a sample and are not inclusive.

#### Manual Testing
  * With `anvil` NOT running in separate instance:
    * `hl deploy warp -d`
      * Throws:
```
Error: No active anvil node detected.
        Please run `anvil` in a separate instance.
```
  * With `anvil` running in separate instance:
* `hl deploy warp -d -k
c0052e22df5d1f4ae7c51e254Xx00Xx0eb833453eaed6301xXxxx8a30d92d10a` (any
private key)
* Throws `Error: Invalid address length. Please ensure you are passing
an address and not a private key.`
* `hl deploy warp -d -k 0x16F4898F47c085C41d7Cc6b1dc0xX0xXX017dcBb` (any
public address)
       * Output:
```
🔎 Verifying anvil node is running...
✅ Successfully verified anvil node is running
Using warp route deployment config at ./configs/warp-route-deployment.yaml
No chain config file provided
? Do you want to use some core deployment address artifacts? This is required for PI chains (non-core chains). no
Forking alfajores for dry-run...
✅ Successfully forked alfajores for dry-run
Impersonating account (0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb)...
✅ Successfully impersonated account (0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb)
...
✅ Hyp token deployments complete
Writing deployment artifacts
Deployment is complete!
Contract address artifacts are in artifacts/dry-run_warp-route-deployment-2024-04-16-12-27-02.json
Warp config is in artifacts/dry-run_warp-config-2024-04-16-12-27-02.json
⛽️ Gas Usage Statistics
        - Gas required for warp dry-run on alfajores: 0.013310162514578124 CELO
Resetting forked network...
✅ Successfully reset forked network
✅ Warp dry-run completed successfully
```

#### CI Testing
  * Successful CI-backed integration/regression testing via `ci-test.sh`
  • Loading branch information
nbayindirli authored Apr 16, 2024
1 parent 2b3f758 commit aea79c6
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-crabs-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': minor
---

Adds single-chain dry-run support for deploying warp routes & gas estimation for core and warp route dry-run deployments.
1 change: 1 addition & 0 deletions rust/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ relayerdb
kathydb
hyperlane_db
config/test_config.json
validator_db_anvil*
35 changes: 34 additions & 1 deletion typescript/cli/ci-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ _main() {
DEPLOYER=$(cast rpc eth_accounts | jq -r '.[0]');

run_hyperlane_deploy_core_dry_run;
run_hyperlane_deploy_warp_dry_run;

reset_anvil;

Expand Down Expand Up @@ -124,12 +125,16 @@ kill_anvil() {
}

run_hyperlane_deploy_core_dry_run() {
if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then
return;
fi

BEFORE_CORE_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT});

echo -e "\nDry-running contract deployments to Alfajores"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy core --dry-run \
--targets alfajores \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \
--chains ${EXAMPLES_PATH}/dry-run/anvil-chains.yaml \
--artifacts /tmp/empty-artifacts.json \
$(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \
--ism ${EXAMPLES_PATH}/ism.yaml \
Expand All @@ -150,6 +155,33 @@ run_hyperlane_deploy_core_dry_run() {
AGENT_CONFIG_FILENAME=`ls -t1 /tmp | grep agent-config | head -1`
}

run_hyperlane_deploy_warp_dry_run() {
if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then
return;
fi

BEFORE_WARP_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT});

echo -e "\nDry-running warp route deployments to Alfajores"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp --dry-run \
--chains ${EXAMPLES_PATH}/dry-run/anvil-chains.yaml \
--core $CORE_ARTIFACTS_PATH \
--config ${EXAMPLES_PATH}/dry-run/warp-route-deployment.yaml \
--out /tmp \
--key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \
--yes

AFTER_WARP_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT})
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
WARP_MIN_GAS=$(bc <<< "($BEFORE_WARP_DRY_RUN - $AFTER_WARP_DRY_RUN) / $GAS_PRICE")
echo "Gas used: $WARP_MIN_GAS"

WARP_ARTIFACTS_PATH=`find /tmp/dry-run_warp-route-deployment* -type f -exec ls -t1 {} + | head -1`
echo "Warp dry-run artifacts:"
echo $WARP_ARTIFACTS_PATH
cat $WARP_ARTIFACTS_PATH
}

run_hyperlane_deploy_core() {
BEFORE_CORE=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT});

Expand Down Expand Up @@ -346,4 +378,5 @@ run_hyperlane_status() {
}

_main "$@";

exit;
17 changes: 17 additions & 0 deletions typescript/cli/examples/dry-run/anvil-chains.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
anvil:
chainId: 31337
domainId: 31337
name: anvil
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18
alfajores:
rpcUrls:
- http: https://alfajores-forno.celo-testnet.org
blocks:
confirmations: 1
estimateBlockTime: 1
8 changes: 8 additions & 0 deletions typescript/cli/examples/dry-run/ism.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
anvil:
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
alfajores:
threshold: 1
validators:
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'
5 changes: 5 additions & 0 deletions typescript/cli/examples/dry-run/warp-route-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
base:
chainName: alfajores
type: native
synthetics:
- chainName: fuji
45 changes: 33 additions & 12 deletions typescript/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@ import {
warpConfigCommandOption,
} from './options.js';

export enum Command {
DEPLOY = 'deploy',
KURTOSIS_AGENTS = 'kurtosis-agents',
CORE = 'core',
WARP = 'warp',
}

/**
* Parent command
*/
export const deployCommand: CommandModule = {
command: 'deploy',
command: Command.DEPLOY,
describe: 'Permissionlessly deploy a Hyperlane contracts or extensions',
builder: (yargs) =>
yargs
Expand All @@ -45,7 +52,7 @@ export const deployCommand: CommandModule = {
* Agent command
*/
const agentCommand: CommandModule = {
command: 'kurtosis-agents',
command: Command.KURTOSIS_AGENTS,
describe: 'Deploy Hyperlane agents with Kurtosis',
builder: (yargs) =>
yargs.options<AgentCommandOptions>({
Expand Down Expand Up @@ -75,7 +82,7 @@ const agentCommand: CommandModule = {
* Core command
*/
const coreCommand: CommandModule = {
command: 'core',
command: Command.CORE,
describe: 'Deploy core Hyperlane contracts',
builder: (yargs) =>
yargs.options<CoreCommandOptions>({
Expand Down Expand Up @@ -133,7 +140,7 @@ const coreCommand: CommandModule = {
* Warp command
*/
const warpCommand: CommandModule = {
command: 'warp',
command: Command.WARP,
describe: 'Deploy Warp Route contracts',
builder: (yargs) =>
yargs.options<WarpCommandOptions>({
Expand All @@ -143,6 +150,7 @@ const warpCommand: CommandModule = {
out: outDirCommandOption,
key: keyCommandOption,
yes: skipConfirmationOption,
'dry-run': dryRunOption,
}),
handler: async (argv: any) => {
const key: string | undefined = argv.key;
Expand All @@ -151,14 +159,27 @@ const warpCommand: CommandModule = {
const coreArtifactsPath: string | undefined = argv.core;
const outPath: string = argv.out;
const skipConfirmation: boolean = argv.yes;
await runWarpRouteDeploy({
key,
chainConfigPath,
warpRouteDeploymentConfigPath,
coreArtifactsPath,
outPath,
skipConfirmation,
});
const dryRun: boolean = argv.dryRun;

logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`);
logGray('------------------------------------------------');

if (dryRun) await verifyAnvil();

try {
await runWarpRouteDeploy({
key,
chainConfigPath,
warpRouteDeploymentConfigPath,
coreArtifactsPath,
outPath,
skipConfirmation,
dryRun,
});
} catch (error: any) {
evaluateIfDryRunFailure(error, dryRun);
throw error;
}
process.exit(0);
},
};
3 changes: 2 additions & 1 deletion typescript/cli/src/commands/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type WarpCommandOptions = CommandOptions & {
out: Options;
key: Options;
yes: Options;
'dry-run': Options;
};

export const coreTargetsCommandOption: Options = {
Expand Down Expand Up @@ -144,5 +145,5 @@ export const dryRunOption: Options = {
description:
'Simulate deployment on forked network. Please ensure an anvil node instance is running during execution via `anvil`.',
default: false,
alias: 'd',
alias: ['d', 'dr'],
};
52 changes: 29 additions & 23 deletions typescript/cli/src/deploy/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from '@hyperlane-xyz/sdk';
import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils';

import { Command } from '../commands/deploy.js';
import { runDeploymentArtifactStep } from '../config/artifacts.js';
import { presetHookConfigs, readHooksConfigMap } from '../config/hooks.js';
import { readIsmConfig } from '../config/ism.js';
Expand All @@ -50,16 +51,17 @@ import {
} from '../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js';
import {
ArtifactsFile,
getArtifactsFiles,
prepNewArtifactsFiles,
runFileSelectionStep,
writeJson,
} from '../utils/files.js';
import { resetFork } from '../utils/fork.js';

import {
completeDeploy,
isISMConfig,
isZODISMConfig,
prepareDeploy,
runPreflightChecksForChains,
} from './utils.js';

Expand Down Expand Up @@ -146,9 +148,25 @@ export async function runCoreDeploy({
...deploymentParams,
minGas: MINIMUM_CORE_DEPLOY_GAS,
});

const userAddress = dryRun ? key! : await signer.getAddress();

const initialBalances = await prepareDeploy(
multiProvider,
userAddress,
chains,
);

await executeDeploy(deploymentParams);

if (dryRun) await resetFork();
await completeDeploy(
Command.CORE,
initialBalances,
multiProvider,
userAddress,
chains,
dryRun,
);
}

function runArtifactStep(
Expand Down Expand Up @@ -305,7 +323,13 @@ async function executeDeploy({

const [contractsFilePath, agentFilePath] = prepNewArtifactsFiles(
outPath,
getArtifactsFiles(dryRun),
getArtifactsFiles(
[
{ filename: 'core-deployment', description: 'Contract addresses' },
{ filename: 'agent-config', description: 'Agent configs' },
],
dryRun,
),
);

const owner = await signer.getAddress();
Expand Down Expand Up @@ -367,7 +391,7 @@ async function executeDeploy({
}
artifacts = objMerge(artifacts, isms);
artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts);
logGreen('Core contracts deployed');
logGreen('Core contracts deployed');

log('Writing agent configs');
await writeAgentConfig(agentFilePath, artifacts, chains, multiProvider);
Expand All @@ -378,24 +402,6 @@ async function executeDeploy({
logBlue(`Agent configs are in ${agentFilePath}`);
}

/**
* Retrieves artifacts file metadata for the current command.
* @param dryRun whether or not the current command is being dry-run
* @returns the artifacts files
*/
function getArtifactsFiles(dryRun: boolean): Array<ArtifactsFile> {
const coreDeploymentFile = {
filename: dryRun ? 'dry-run_core-deployment' : 'core-deployment',
description: 'Contract addresses',
};
const agentConfigFile = {
filename: dryRun ? 'dry-run_agent-config' : 'agent-config',
description: 'Agent configs',
};

return [coreDeploymentFile, agentConfigFile];
}

function buildIsmConfig(
owner: Address,
local: ChainName,
Expand Down
20 changes: 17 additions & 3 deletions typescript/cli/src/deploy/dry-run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { MultiProvider } from '@hyperlane-xyz/sdk';

import { Command } from '../commands/deploy.js';
import { logGray, logGreen, warnYellow } from '../logger.js';
import { ANVIL_RPC_METHODS, getLocalProvider, setFork } from '../utils/fork.js';
import {
ANVIL_RPC_METHODS,
getLocalProvider,
resetFork,
setFork,
} from '../utils/fork.js';

import { toUpperCamelCase } from './utils.js';

/**
* Forks a provided network onto MultiProvider
Expand All @@ -23,7 +31,7 @@ export async function forkNetworkToMultiProvider(
* Ensures an anvil node is running locally.
*/
export async function verifyAnvil() {
logGray('Verifying anvil node is running...');
logGray('🔎 Verifying anvil node is running...');

const provider = getLocalProvider();
try {
Expand All @@ -34,7 +42,7 @@ export async function verifyAnvil() {
\tPlease run \`anvil\` in a separate instance.`);
}

logGreen('Successfully verified anvil node is running');
logGreen('Successfully verified anvil node is running');
}

/**
Expand All @@ -48,3 +56,9 @@ export function evaluateIfDryRunFailure(error: any, dryRun: boolean) {
'⛔️ [dry-run] The current RPC may not support forking. Please consider using a different RPC provider.',
);
}

export async function completeDryRun(command: Command) {
await resetFork();

logGreen(`✅ ${toUpperCamelCase(command)} dry-run completed successfully`);
}
Loading

0 comments on commit aea79c6

Please sign in to comment.