From 559ae95be1cdd2befe15f7bba7fa03a629ee92de Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 26 Jul 2023 18:51:42 -0400 Subject: [PATCH 01/82] Create new CLI package Upgrade prettier, typescript, and lint packages Update TSConfig output target to ES2020 Copy code from hyp-deploy/src to cli/src --- .eslintrc | 20 +- .github/CODEOWNERS | 3 + .github/workflows/cli.yml | 76 ++ package.json | 15 +- solidity/package.json | 4 +- tsconfig.json | 7 +- typescript/cli/.eslintignore | 2 + typescript/cli/.eslintrc | 5 + typescript/cli/.gitignore | 3 + typescript/cli/README.md | 109 ++ typescript/cli/ci-test.sh | 105 ++ typescript/cli/cli.ts | 20 + typescript/cli/examples/chains.ts | 49 + typescript/cli/examples/multisig_ism.ts | 21 + typescript/cli/examples/start_blocks.ts | 25 + typescript/cli/examples/warp_tokens.ts | 31 + typescript/cli/package.json | 56 ++ typescript/cli/src/commands/chains.ts | 35 + typescript/cli/src/config.ts | 264 +++++ .../core/HyperlanePermissionlessDeployer.ts | 268 +++++ .../cli/src/core/TestRecipientDeployer.ts | 54 + typescript/cli/src/index.ts | 0 typescript/cli/src/json.ts | 55 + typescript/cli/src/logger.ts | 21 + typescript/cli/src/test/run.ts | 12 + typescript/cli/src/test/test-messages.ts | 149 +++ typescript/cli/src/test/test-warp-transfer.ts | 228 +++++ typescript/cli/src/warp/WarpRouteDeployer.ts | 310 ++++++ typescript/cli/src/warp/config.ts | 82 ++ typescript/cli/src/warp/types.ts | 27 + typescript/cli/tsconfig.json | 10 + typescript/helloworld/.eslintrc | 8 + typescript/helloworld/package.json | 18 +- typescript/helloworld/tsconfig.json | 7 +- typescript/infra/package.json | 10 +- typescript/infra/src/config/environment.ts | 2 +- typescript/sdk/.eslintrc | 10 +- typescript/sdk/package.json | 6 +- .../sdk/src/consts/environments/index.ts | 7 +- typescript/token/.eslintrc | 28 +- typescript/token/package.json | 16 +- typescript/token/tsconfig.json | 27 +- typescript/utils/package.json | 4 +- yarn.lock | 950 +++++++++--------- 44 files changed, 2570 insertions(+), 589 deletions(-) create mode 100644 .github/workflows/cli.yml create mode 100644 typescript/cli/.eslintignore create mode 100644 typescript/cli/.eslintrc create mode 100644 typescript/cli/.gitignore create mode 100644 typescript/cli/README.md create mode 100755 typescript/cli/ci-test.sh create mode 100644 typescript/cli/cli.ts create mode 100644 typescript/cli/examples/chains.ts create mode 100644 typescript/cli/examples/multisig_ism.ts create mode 100644 typescript/cli/examples/start_blocks.ts create mode 100644 typescript/cli/examples/warp_tokens.ts create mode 100644 typescript/cli/package.json create mode 100644 typescript/cli/src/commands/chains.ts create mode 100644 typescript/cli/src/config.ts create mode 100644 typescript/cli/src/core/HyperlanePermissionlessDeployer.ts create mode 100644 typescript/cli/src/core/TestRecipientDeployer.ts create mode 100644 typescript/cli/src/index.ts create mode 100644 typescript/cli/src/json.ts create mode 100644 typescript/cli/src/logger.ts create mode 100644 typescript/cli/src/test/run.ts create mode 100644 typescript/cli/src/test/test-messages.ts create mode 100644 typescript/cli/src/test/test-warp-transfer.ts create mode 100644 typescript/cli/src/warp/WarpRouteDeployer.ts create mode 100644 typescript/cli/src/warp/config.ts create mode 100644 typescript/cli/src/warp/types.ts create mode 100644 typescript/cli/tsconfig.json diff --git a/.eslintrc b/.eslintrc index 9796ac5114..c1ce429fda 100644 --- a/.eslintrc +++ b/.eslintrc @@ -29,5 +29,23 @@ "@typescript-eslint/no-floating-promises": ["error"], "@typescript-eslint/no-non-null-assertion": ["off"], "@typescript-eslint/no-require-imports": ["warn"], + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "@typescript-eslint/ban-types": [ + "error", + { + "types": { + // Unban the {} type which is a useful shorthand for non-nullish value + "{}": false + }, + "extendDefaults": true + } + ] } -} +} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 109384a46c..893c4ef067 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,5 +22,8 @@ typescript/token @yorhodes @jmrossy @tkporter ## Hello World typescript/helloworld @yorhodes @nambrot +## CLI +typescript/cli @jmrossy @asaj + ## Infra typescript/infra @tkporter @asaj @mattiecnvr diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml new file mode 100644 index 0000000000..b7db4f6662 --- /dev/null +++ b/.github/workflows/cli.yml @@ -0,0 +1,76 @@ +name: CLI + +on: + push: + branches: [main] + pull_request: + branches: [main] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + DEBUG: 'hyperlane:*' + +jobs: + yarn-install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn/cache + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + - name: yarn-install + run: | + yarn install + CHANGES=$(git status -s --ignore-submodules) + if [[ ! -z $CHANGES ]]; then + echo "Changes found: $CHANGES" + git diff + exit 1 + fi + + yarn-build: + runs-on: ubuntu-latest + needs: [yarn-install] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn/cache + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + - name: build + run: yarn build + + deploy: + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn/cache + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + - name: deploy + run: ./ci-test.sh \ No newline at end of file diff --git a/package.json b/package.json index b9213ac19b..0c82bedd94 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,14 @@ "description": "A yarn workspace of core Hyperlane packages", "version": "0.0.0", "devDependencies": { - "@trivago/prettier-plugin-sort-imports": "^3.2.0", - "@typescript-eslint/eslint-plugin": "^5.27.0", - "@typescript-eslint/parser": "^5.27.0", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", "husky": "^8.0.0", "lint-staged": "^12.4.3", - "prettier": "^2.4.1" + "prettier": "^2.8.8" }, "packageManager": "yarn@3.2.0", "private": true, @@ -37,6 +37,7 @@ "lodash": "^4.17.21", "recursive-readdir": "^2.2.3", "underscore": "^1.13", - "undici": "^5.11" + "undici": "^5.11", + "@trivago/prettier-plugin-sort-imports/@babel/parser": "^7.22.7" } } diff --git a/solidity/package.json b/solidity/package.json index de3cfeefc1..d40e32ea61 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -18,14 +18,14 @@ "ethers": "^5.7.2", "hardhat": "^2.16.1", "hardhat-gas-reporter": "^1.0.9", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.0.0-beta.5", "solhint": "^3.3.2", "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.3", "ts-generator": "^0.1.1", "typechain": "^8.1.1", - "typescript": "^4.7.2" + "typescript": "^5.1.6" }, "directories": { "test": "test" diff --git a/tsconfig.json b/tsconfig.json index 9ae090cfd4..75eb47143b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,10 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "incremental": false, - "lib": ["es2015", "es5", "dom", "es2021"], + "lib": [ + "ES2015", "ES2016", "ES2017", "ES2018", + "ES2019", "ES2020","ES2021", "DOM" + ], "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, @@ -18,7 +21,7 @@ "pretty": false, "resolveJsonModule": true, "sourceMap": true, - "target": "es6", + "target": "ES2020", "strict": true } } diff --git a/typescript/cli/.eslintignore b/typescript/cli/.eslintignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/typescript/cli/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/typescript/cli/.eslintrc b/typescript/cli/.eslintrc new file mode 100644 index 0000000000..e3f712414b --- /dev/null +++ b/typescript/cli/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": ["off"] + } +} diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore new file mode 100644 index 0000000000..b1ead9840b --- /dev/null +++ b/typescript/cli/.gitignore @@ -0,0 +1,3 @@ +.env +dist +cache \ No newline at end of file diff --git a/typescript/cli/README.md b/typescript/cli/README.md new file mode 100644 index 0000000000..77d649174e --- /dev/null +++ b/typescript/cli/README.md @@ -0,0 +1,109 @@ +# Hyperlane CLI + +The Hyperlane CLI is a command-line tool written in Typescript that facilitates common operations on Hyperlane, such as deploying the core contracts and/or warp routes to new chains. + +## Hyperlane overview + +Hyperlane is an interchain messaging protocol that allows applications to communicate between blockchains. + +Developers can use Hyperlane to share state between blockchains, allowing them to build interchain applications that live natively across multiple chains. + +To read more about interchain applications, how the protocol works, and how to integrate with Hyperlane, please see the [documentation](https://docs.hyperlane.xyz). + +## Setup + +Node 16 or newer is required. + +**Option 1: Global install:** + +```bash +# Install with NPM +npm install -g @hyperlane-xyz/cli +# Or uninstall old versions +npm uninstall -g @hyperlane-xyz/cli +``` + +**Option 2: Temp install:** + +```bash +# Run via NPM's npx command +npx @hyperlane-xyz/cli +# Or via Yarn's dlx command +yarn dlx @hyperlane-xyz/cli +``` + +**Option 3: Run from source:** + +```bash +git clone https://github.com/hyperlane-xyz/hyperlane-monorepo.git +cd hyperlane-monorepo +yarn install && yarn build +cd typescript/cli +node ./dist/cli.js +``` + +## Deploying Hyperlane + +See below for instructions on using the scripts in this repo to deploy a Hyperlane core instance. For more details see the [deploy documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-hyperlane). + +### Deploying core contracts + +If you're deploying to a new chain, ensure there is a corresponding entry `config/chains.ts`, `config/multisig_ism.ts`, and `config/start_blocks.ts`. + +This script is used to deploy the following core Hyperlane contracts to a new chain. The Hyperlane protocol expects exactly one instance of these contracts on every supported chain. + +- `Mailbox`: for sending and receiving messages +- `ValidatorAnnounce`: for registering validators + +This script also deploys the following contracts to all chains, new and existing. The Hyperlane protocol supports many instances of these contracts on every supported chains. + +- `ISM (e.g. MultisigISM)`: for verifying inbound messages from remote chains +- `InterchainGasPaymaster`: for paying relayers for message delivery +- `TestRecipient`: used to test that interchain messages can be delivered + +```bash +yarn ts-node scripts/deploy-hyperlane.ts --local anvil \ + --remotes goerli sepolia \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +``` + +### Sending test messages + +This script is used to verify that Hyperlane messages can be sent between specified chains. + +Users should have first deployed `TestRecipient` contracts to each of the specified chains. + +```sh +yarn ts-node scripts/test-messages.ts \ + --chains anvil goerli sepolia \ + --key 0x6f0311f4a0722954c46050bb9f088c4890999e16b64ad02784d24b5fd6d09061 +``` + +## Deploying Warp Routes + +Warp Routes are Hyperlane's unique take on the concept of token bridging, allowing you to permissionlessly bridge any ERC20-like asset to any chain. You can combine Warp Routes with a Hyperlane deployment to create economic trade routes between any chains already connected through Hyperlane. + +See below for instructions on using the scripts in this repo to deploy Hyperlane Warp Routes. For more details see the [warp route documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route). + +### Deploying Warp contracts + +Establishing a warp route requires deployment of `HypERC20` contracts to the desired chains. Ensure there is an entry for all chains in `config/chains.ts`. + +The deployment also require details about the existing (collateral) token and the new synthetics that will be created. Ensure there are entries for them in `config/warp_tokens.ts`. + +```sh +yarn ts-node scripts/deploy-warp-routes.ts \ + --key 0x6f0311f4a0722954c46050bb9f088c4890999e16b64ad02784d24b5fd6d09061 +``` + +### Sending a test transfer + +```sh +yarn ts-node scripts/test-warp-transfer.ts \ + --origin goerli --destination alfajores --wei 100000000000000 \ + --key 0x6f0311f4a0722954c46050bb9f088c4890999e16b64ad02784d24b5fd6d09061 +``` + +### Deploying a Warp UI + +If you'd like to create a web-based user interface for your warp routes, see the [Warp UI documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route/deploy-the-ui-for-your-warp-route) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh new file mode 100755 index 0000000000..5681ad29a0 --- /dev/null +++ b/typescript/cli/ci-test.sh @@ -0,0 +1,105 @@ +for CHAIN in anvil1 anvil2 +do + mkdir /tmp/$CHAIN \ + /tmp/$CHAIN/state \ + /tmp/$CHAIN/validator \ + /tmp/$CHAIN/relayer && \ + chmod 777 /tmp/$CHAIN -R +done + +anvil --chain-id 31337 -p 8545 --state /tmp/anvil1/state > /dev/null & +ANVIL_1_PID=$! + +anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state > /dev/null & +ANVIL_2_PID=$! + +sleep 1 + +set -e + +for i in "anvil1 anvil2 --no-write-agent-config" "anvil2 anvil1 --write-agent-config" +do + set -- $i + echo "Deploying contracts to $1" + yarn ts-node scripts/deploy-hyperlane.ts --local $1 --remotes $2 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 $3 +done + +echo "Deploying warp routes" +yarn ts-node scripts/deploy-warp-routes.ts \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +kill $ANVIL_1_PID +kill $ANVIL_2_PID + +anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & +ANVIL_1_PID=$! + +anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & +ANVIL_2_PID=$! + +for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" +do + set -- $i + echo "Running validator on $1" + # Won't work on anything but linux due to -net=host + docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/config/agent_config.json -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ + -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ + -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ + -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ + -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ + -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ + -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ + -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ + gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./validator & +done + +sleep 10 + +for i in "anvil1 8545" "anvil2 8555" +do + set -- $i + echo "Announcing validator on $1" + VALIDATOR_ANNOUNCE_ADDRESS=$(cat ./artifacts/addresses.json | jq -r ".$1.validatorAnnounce") + VALIDATOR=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.validator') + STORAGE_LOCATION=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location') + SIGNATURE=$(cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature') + cast send $VALIDATOR_ANNOUNCE_ADDRESS \ + "announce(address, string calldata, bytes calldata)(bool)" \ + $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ + --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +done + + +for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" +do + set -- $i + echo "Running relayer on $1" + docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/config/agent_config.json \ + -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ + -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ + -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ + -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ + -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ + -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ + -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ + -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ + gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./relayer & +done + +echo "Testing message sending" +yarn ts-node scripts/test-messages.ts --chains anvil1 anvil2 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --timeout 60 + +echo "Sending a test warp transfer" +yarn ts-node scripts/test-warp-transfer.ts \ + --origin anvil1 --destination anvil2 --wei 1 --recipient 0xac0974bec39a17e36ba4a6b4d238ff944bacb4a5 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --timeout 60 + +docker ps -aq | xargs docker stop | xargs docker rm +kill $ANVIL_1_PID +kill $ANVIL_2_PID diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts new file mode 100644 index 0000000000..d021dcc09d --- /dev/null +++ b/typescript/cli/cli.ts @@ -0,0 +1,20 @@ +#! /usr/bin/env node +import chalk from 'chalk'; +import yargs from 'yargs'; + +import { chainsCommand } from './src/commands/chains.js'; +import './src/logger.js'; + +console.log('⏩', chalk.blue('Hyperlane'), chalk.magentaBright('CLI'), '⏩'); +console.log(chalk.gray('===================')); + +try { + await yargs(process.argv.slice(2)) + .scriptName('hyperlane') + .command(chainsCommand) + .demandCommand() + .strict() + .help().argv; +} catch (error: any) { + console.error(chalk.red('Error:' + error.message)); +} diff --git a/typescript/cli/examples/chains.ts b/typescript/cli/examples/chains.ts new file mode 100644 index 0000000000..4f8532f2cf --- /dev/null +++ b/typescript/cli/examples/chains.ts @@ -0,0 +1,49 @@ +import { ChainMap, ChainMetadata, ProtocolType } from '@hyperlane-xyz/sdk'; + +// import { chainMetadata } from '@hyperlane-xyz/sdk'; +// A map of chain names to ChainMetadata +export const chains: ChainMap = { + // ----------- Add your chains here ----------------- + anvil1: { + name: 'anvil1', + protocol: ProtocolType.Ethereum, + // anvil default chain id + chainId: 31337, + // Used to configure a Warp Route to bridge anvil1 ETH + // to anvil2 in CI tests. + nativeToken: { + name: 'ether', + symbol: 'ETH', + decimals: 18, + }, + rpcUrls: [ + { + http: 'http://127.0.0.1:8545', + }, + ], + // You can set overrides for transaction fields here + // transactionOverrides: { + // gasLimit: 1000000 + // }, + }, + anvil2: { + name: 'anvil2', + protocol: ProtocolType.Ethereum, + chainId: 31338, + rpcUrls: [ + { + http: 'http://127.0.0.1:8555', + }, + ], + }, + // -------------------------------------------------- + // You can also override the default chain metadata (completely) + // ethereum: { + // ...chainMetadata.ethereum, + // publicRpcUrls: [ + // { + // http: 'my.custom.rpc.url', + // } + // ], + // } +}; diff --git a/typescript/cli/examples/multisig_ism.ts b/typescript/cli/examples/multisig_ism.ts new file mode 100644 index 0000000000..208ed5bc6c --- /dev/null +++ b/typescript/cli/examples/multisig_ism.ts @@ -0,0 +1,21 @@ +import { ChainMap, ModuleType, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; + +export const multisigIsmConfig: ChainMap = { + // ----------- Your chains here ----------------- + anvil1: { + type: ModuleType.LEGACY_MULTISIG, + threshold: 1, + validators: [ + // Last anvil address + '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', + ], + }, + anvil2: { + type: ModuleType.LEGACY_MULTISIG, + threshold: 1, + validators: [ + // Last anvil address + '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', + ], + }, +}; diff --git a/typescript/cli/examples/start_blocks.ts b/typescript/cli/examples/start_blocks.ts new file mode 100644 index 0000000000..cedfbb3468 --- /dev/null +++ b/typescript/cli/examples/start_blocks.ts @@ -0,0 +1,25 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; + +// TODO move to SDK +export const startBlocks: ChainMap = { + // --------------- Mainnets --------------------- + celo: 16884144, + ethereum: 16271503, + avalanche: 24145479, + polygon: 37313389, + bsc: 25063295, + arbitrum: 49073182, + optimism: 55698988, + moonbeam: 2595747, + gnosis: 25900000, + // --------------- Testnets --------------------- + alfajores: 14863532, + fuji: 16330615, + mumbai: 29390033, + bsctestnet: 25001629, + goerli: 8039005, + sepolia: 3082913, + moonbasealpha: 3310405, + optimismgoerli: 3055263, + arbitrumgoerli: 1941997, +}; diff --git a/typescript/cli/examples/warp_tokens.ts b/typescript/cli/examples/warp_tokens.ts new file mode 100644 index 0000000000..3b939fbaec --- /dev/null +++ b/typescript/cli/examples/warp_tokens.ts @@ -0,0 +1,31 @@ +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; + +import type { WarpRouteConfig } from '../src/warp/config.js'; + +// A config for deploying Warp Routes to a set of chains +// Not required for Hyperlane core deployments +export const warpRouteConfig: WarpRouteConfig = { + base: { + // Chain name must be in the Hyperlane SDK or in the chains.ts config + chainName: 'anvil1', + type: TokenType.native, // TokenType.native or TokenType.collateral + // If type is collateral, a token address is required: + // address: '0x123...' + // If the token is an NFT (ERC721), set to true: + // isNft: boolean + + // Optionally, specify owner, mailbox, and interchainGasPaymaster addresses + // If not specified, the Permissionless Deployment artifacts or the SDK's defaults will be used + }, + synthetics: [ + { + chainName: 'anvil2', + + // Optionally specify a name, symbol, and totalSupply + // If not specified, the base token's properties will be used + + // Optionally, specify owner, mailbox, and interchainGasPaymaster addresses + // If not specified, the Permissionless Deployment artifacts or the SDK's defaults will be used + }, + ], +}; diff --git a/typescript/cli/package.json b/typescript/cli/package.json new file mode 100644 index 0000000000..f08175fb73 --- /dev/null +++ b/typescript/cli/package.json @@ -0,0 +1,56 @@ +{ + "name": "@hyperlane-xyz/cli", + "version": "1.4.2", + "description": "A command-line utility for common Hyperlane operations", + "dependencies": { + "@hyperlane-xyz/hyperlane-token": "1.4.2", + "@hyperlane-xyz/sdk": "1.4.2", + "chalk": "^5.3.0", + "ethers": "^5.7.2", + "inquirer": "^9.2.8", + "yargs": "^17.7.2", + "zod": "^3.21.2" + }, + "devDependencies": { + "@types/node": "^18.14.5", + "@types/yargs": "^17.0.24", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", + "prettier": "^2.8.8", + "typescript": "^5.1.6" + }, + "scripts": { + "hyperlane": "node ./dist/cli.js", + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf ./dist", + "lint": "eslint . --ext .ts", + "prettier": "prettier --write ./src ./examples" + }, + "files": [ + "./dist", + "./examples" + ], + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "bin": { + "hyperlane": "./dist/cli.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/hyperlane-xyz/hyperlane-monorepo" + }, + "license": "Apache 2.0", + "homepage": "https://www.hyperlane.xyz", + "keywords": [ + "Hyperlane", + "CLI", + "Permissionless", + "Deployment", + "Typescript" + ], + "packageManager": "yarn@3.2.0" +} diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts new file mode 100644 index 0000000000..a08dfcef47 --- /dev/null +++ b/typescript/cli/src/commands/chains.ts @@ -0,0 +1,35 @@ +import chalk from 'chalk'; +import { CommandModule } from 'yargs'; + +import { Mainnets, Testnets, chainMetadata } from '@hyperlane-xyz/sdk'; + +/** + * Parent command + */ +export const chainsCommand: CommandModule = { + command: 'chains', + describe: 'View information about core Hyperlane chains', + builder: (yargs) => yargs.command(listCommand).demandCommand(), + handler: () => console.log('Command required'), +}; + +/** + * List command + */ +const listCommand: CommandModule = { + command: 'list', + describe: 'List all core chains included in the Hyperlane SDK', + handler: () => { + console.log(chalk.blue('Hyperlane core mainnet chains:')); + console.log(chalk.gray('------------------------------')); + console.log( + Mainnets.map((chain) => chainMetadata[chain].displayName).join(', '), + ); + console.log(''); + console.log(chalk.blue('Hyperlane core testnet chains:')); + console.log(chalk.gray('------------------------------')); + console.log( + Testnets.map((chain) => chainMetadata[chain].displayName).join(', '), + ); + }, +}; diff --git a/typescript/cli/src/config.ts b/typescript/cli/src/config.ts new file mode 100644 index 0000000000..b83e512c78 --- /dev/null +++ b/typescript/cli/src/config.ts @@ -0,0 +1,264 @@ +import { ethers } from 'ethers'; + +import { Mailbox__factory, OverheadIgp__factory } from '@hyperlane-xyz/core'; +import { + ChainMap, + ChainName, + CoreConfig, + GasOracleContractType, + HyperlaneAddresses, + HyperlaneAddressesMap, + HyperlaneContractsMap, + HyperlaneDeploymentArtifacts, + ModuleType, + MultiProvider, + MultisigIsmConfig, + OverheadIgpConfig, + RouterConfig, + RoutingIsmConfig, + buildAgentConfigDeprecated, + chainMetadata, + defaultMultisigIsmConfigs, + filterAddressesMap, + multisigIsmVerificationCost, + objFilter, + objMerge, +} from '@hyperlane-xyz/sdk'; +import { hyperlaneEnvironments } from '@hyperlane-xyz/sdk/dist/consts/environments'; +import { types, utils } from '@hyperlane-xyz/utils'; + +import { chains } from '../examples/chains.js'; +import { multisigIsmConfig } from '../examples/multisig_ism.js'; + +import { TestRecipientConfig } from './core/TestRecipientDeployer.js'; +import { tryReadJSON } from './json.js'; + +let multiProvider: MultiProvider; + +export function getMultiProvider() { + if (!multiProvider) { + const chainConfigs = { ...chainMetadata, ...chains }; + multiProvider = new MultiProvider(chainConfigs); + } + return multiProvider; +} +export function assertBytesN(value: string, length: number): string { + const valueWithPrefix = utils.ensure0x(value); + if ( + ethers.utils.isHexString(valueWithPrefix) && + ethers.utils.hexDataLength(valueWithPrefix) == length + ) { + return valueWithPrefix; + } + throw new Error( + `Invalid value ${value}, must be a ${length} byte hex string`, + ); +} + +export function assertBytes32(value: string): string { + return assertBytesN(value, 32); +} + +export function assertBytes20(value: string): string { + return assertBytesN(value, 20); +} + +export function assertUnique( + values: (argv: any) => string[], +): (argv: any) => void { + return (argv: any) => { + const _values = values(argv); + const hasDuplicates = new Set(_values).size !== _values.length; + if (hasDuplicates) { + throw new Error(`Must provide unique values, got ${_values}`); + } + }; +} + +export function assertBalances( + multiProvider: MultiProvider, + chainsFunc: (argv: any) => ChainName[], +): (argv: any) => Promise { + return async (argv: any) => { + const chains = chainsFunc(argv); + const signer = new ethers.Wallet(argv.key); + const address = await signer.getAddress(); + await Promise.all( + chains.map(async (chain: ChainName) => { + const balance = await multiProvider + .getProvider(chain) + .getBalance(address); + if (balance.isZero()) + throw new Error(`${address} has no balance on ${chain}`); + }), + ); + }; +} + +export function coerceAddressToBytes32(value: string): string { + if (ethers.utils.isHexString(value)) { + const length = ethers.utils.hexDataLength(value); + if (length == 32) { + return value; + } else if (length == 20) { + return utils.addressToBytes32(value); + } + } + throw new Error(`Invalid value ${value}, must be a 20 or 32 byte hex string`); +} + +export function buildIsmConfig( + owner: types.Address, + remotes: ChainName[], +): RoutingIsmConfig { + const mergedMultisigIsmConfig: ChainMap = objMerge( + defaultMultisigIsmConfigs, + multisigIsmConfig, + ); + return { + owner, + type: ModuleType.ROUTING, + domains: Object.fromEntries( + remotes.map((remote) => [remote, mergedMultisigIsmConfig[remote]]), + ), + }; +} + +export function buildIsmConfigMap( + owner: types.Address, + chains: ChainName[], + remotes: ChainName[], +): ChainMap { + return Object.fromEntries( + chains.map((chain) => { + const ismConfig = buildIsmConfig( + owner, + remotes.filter((r) => r !== chain), + ); + return [chain, ismConfig]; + }), + ); +} + +export function buildCoreConfigMap( + owner: types.Address, + local: ChainName, + remotes: ChainName[], +): ChainMap { + const configMap: ChainMap = {}; + configMap[local] = { + owner, + defaultIsm: buildIsmConfig(owner, remotes), + }; + return configMap; +} + +export function buildRouterConfigMap( + owner: types.Address, + chains: ChainName[], + addressesMap: HyperlaneAddressesMap, +): ChainMap { + const routerConfigFactories = { + mailbox: new Mailbox__factory(), + defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(), + }; + const filteredAddressesMap = filterAddressesMap( + addressesMap, + routerConfigFactories, + ); + return Object.fromEntries( + chains.map((chain) => { + const routerConfig: RouterConfig = { + owner, + mailbox: filteredAddressesMap[chain].mailbox, + interchainGasPaymaster: + filteredAddressesMap[chain].defaultIsmInterchainGasPaymaster, + }; + return [chain, routerConfig]; + }), + ); +} + +export function buildTestRecipientConfigMap( + chains: ChainName[], + addressesMap: HyperlaneAddressesMap, +): ChainMap { + return Object.fromEntries( + chains.map((chain) => { + const interchainSecurityModule = + addressesMap[chain].interchainSecurityModule ?? + ethers.constants.AddressZero; + return [chain, { interchainSecurityModule }]; + }), + ); +} + +export function buildIgpConfigMap( + owner: types.Address, + deployChains: ChainName[], + allChains: ChainName[], +): ChainMap { + const mergedMultisigIsmConfig: ChainMap = objMerge( + defaultMultisigIsmConfigs, + multisigIsmConfig, + ); + const configMap: ChainMap = {}; + for (const local of deployChains) { + const overhead: ChainMap = {}; + const gasOracleType: ChainMap = {}; + for (const remote of allChains) { + if (local === remote) continue; + overhead[remote] = multisigIsmVerificationCost( + mergedMultisigIsmConfig[remote].threshold, + mergedMultisigIsmConfig[remote].validators.length, + ); + gasOracleType[remote] = GasOracleContractType.StorageGasOracle; + } + configMap[local] = { + owner, + beneficiary: owner, + gasOracleType, + overhead, + oracleKey: 'TODO', + }; + } + return configMap; +} + +export const sdkContractAddressesMap = { + ...hyperlaneEnvironments.testnet, + ...hyperlaneEnvironments.mainnet, +}; + +export function artifactsAddressesMap(): HyperlaneContractsMap { + return ( + tryReadJSON>('./artifacts', 'addresses.json') || + {} + ); +} + +export function buildOverriddenAgentConfig( + chains: ChainName[], + multiProvider: MultiProvider, + startBlocks: ChainMap, +) { + const mergedAddressesMap: HyperlaneAddressesMap = objMerge( + sdkContractAddressesMap, + artifactsAddressesMap(), + ); + const filteredAddressesMap = objFilter( + mergedAddressesMap, + (chain, v): v is HyperlaneAddresses => + chains.includes(chain) && + !!v.mailbox && + !!v.interchainGasPaymaster && + !!v.validatorAnnounce, + ) as unknown as ChainMap; + + return buildAgentConfigDeprecated( + chains, + multiProvider, + filteredAddressesMap, + startBlocks, + ); +} diff --git a/typescript/cli/src/core/HyperlanePermissionlessDeployer.ts b/typescript/cli/src/core/HyperlanePermissionlessDeployer.ts new file mode 100644 index 0000000000..5b33b53d09 --- /dev/null +++ b/typescript/cli/src/core/HyperlanePermissionlessDeployer.ts @@ -0,0 +1,268 @@ +import { ethers } from 'ethers'; +import yargs from 'yargs'; + +import { + ChainMap, + ChainName, + DeployedIsm, + HyperlaneAddresses, + HyperlaneAddressesMap, + HyperlaneContractsMap, + HyperlaneCoreDeployer, + HyperlaneIgpDeployer, + HyperlaneIsmFactory, + HyperlaneIsmFactoryDeployer, + MultiProvider, + ProtocolType, + defaultMultisigIsmConfigs, + objFilter, + objMap, + objMerge, + serializeContractsMap, +} from '@hyperlane-xyz/sdk'; + +import { multisigIsmConfig } from '../../examples/multisig_ism.js'; +import { startBlocks } from '../../examples/start_blocks.js'; +import { + artifactsAddressesMap, + assertBalances, + assertBytes32, + assertUnique, + buildCoreConfigMap, + buildIgpConfigMap, + buildIsmConfigMap, + buildOverriddenAgentConfig, + buildTestRecipientConfigMap, + getMultiProvider, + sdkContractAddressesMap, +} from '../config.js'; +import { mergeJSON, writeJSON } from '../json.js'; +import { createLogger } from '../logger.js'; + +import { HyperlaneTestRecipientDeployer } from './TestRecipientDeployer.js'; + +export function getArgs(multiProvider: MultiProvider) { + // For each chain, we need: + // - ChainMetadata for the MultiProvider + // - A MultisigIsmConfig + const { intersection } = multiProvider.intersect( + Object.keys(objMerge(defaultMultisigIsmConfigs, multisigIsmConfig)), + ); + + return yargs(process.argv.slice(2)) + .describe('local', 'The chain to deploy to') + .choices('local', intersection) + .demandOption('local') + .array('remotes') + .describe( + 'remotes', + "The chains with which 'local' will be able to send and receive messages", + ) + .choices('remotes', intersection) + .demandOption('remotes') + .middleware(assertUnique((argv) => argv.remotes.concat(argv.local))) + .describe('key', 'A hexadecimal private key for transaction signing') + .string('key') + .coerce('key', assertBytes32) + .demandOption('key') + .middleware( + assertBalances(multiProvider, (argv) => + argv.remotes + .concat(argv.local) + .filter( + (chain: string) => + multiProvider.getChainMetadata(chain).protocol === + ProtocolType.Ethereum, + ), + ), + ) + .describe('write-agent-config', 'Whether or not to write agent config') + .default('write-agent-config', true) + .boolean('write-agent-config').argv; +} + +export class HyperlanePermissionlessDeployer { + constructor( + public readonly multiProvider: MultiProvider, + public readonly signer: ethers.Signer, + public readonly local: ChainName, + public readonly remotes: ChainName[], + public readonly writeAgentConfig?: boolean, + protected readonly logger = createLogger('HyperlanePermissionlessDeployer'), + ) {} + + static async fromArgs(): Promise { + const multiProvider = getMultiProvider(); + const { local, remotes, key, writeAgentConfig } = await getArgs( + multiProvider, + ); + if (remotes.includes(local)) + throw new Error('Local and remotes must be distinct'); + const signer = new ethers.Wallet(key); + multiProvider.setSharedSigner(signer); + + return new HyperlanePermissionlessDeployer( + multiProvider, + signer, + local, + remotes as unknown as string[], + writeAgentConfig, + ); + } + + skipLocalDeploy(): boolean { + return !this.isDeployableChain(this.local); + } + + remoteDeployableChains(): ChainName[] { + return this.remotes.filter((chain) => this.isDeployableChain(chain)); + } + + deployableChains(): ChainName[] { + return this.remotes + .concat([this.local]) + .filter((chain) => this.isDeployableChain(chain)); + } + + allChains(): ChainName[] { + return this.remotes.concat([this.local]); + } + + async deploy(): Promise { + let addressesMap = artifactsAddressesMap(); + const owner = await this.signer.getAddress(); + + const deployableChains = this.deployableChains(); + const remoteDeployableChains = this.remoteDeployableChains(); + const allChains = this.allChains(); + const skipLocalDeploy = this.skipLocalDeploy(); + + // 1. Deploy ISM factories to all deployable chains that don't have them. + this.logger('Deploying ISM factory contracts'); + const ismDeployer = new HyperlaneIsmFactoryDeployer(this.multiProvider); + ismDeployer.cacheAddressesMap( + objMerge(sdkContractAddressesMap, addressesMap), + ); + const ismFactoryContracts = await ismDeployer.deploy(deployableChains); + addressesMap = this.writeMergedAddresses(addressesMap, ismFactoryContracts); + this.logger(`ISM factory deployment complete`); + + // 2. Deploy IGPs to all deployable chains. + this.logger(`Deploying IGP contracts`); + const igpConfig = buildIgpConfigMap(owner, deployableChains, allChains); + const igpDeployer = new HyperlaneIgpDeployer(this.multiProvider); + igpDeployer.cacheAddressesMap(addressesMap); + const igpContracts = await igpDeployer.deploy(igpConfig); + addressesMap = this.writeMergedAddresses(addressesMap, igpContracts); + this.logger(`IGP deployment complete`); + + // Build an IsmFactory that covers all chains so that we can + // use it later to deploy ISMs to remote chains. + const ismFactory = HyperlaneIsmFactory.fromAddressesMap( + objMerge(sdkContractAddressesMap, addressesMap), + this.multiProvider, + ); + + // 3. Deploy core contracts to local chain + if (!skipLocalDeploy) { + this.logger(`Deploying core contracts to ${this.local}`); + const coreDeployer = new HyperlaneCoreDeployer( + this.multiProvider, + ismFactory, + ); + coreDeployer.cacheAddressesMap(addressesMap); + const coreConfig = buildCoreConfigMap(owner, this.local, this.remotes); + const coreContracts = await coreDeployer.deploy(coreConfig); + addressesMap = this.writeMergedAddresses(addressesMap, coreContracts); + this.logger(`Core deployment complete`); + } else { + this.logger(`Skipping core deployment to local ${this.local}`); + } + + // 4. Deploy ISM contracts to remote deployable chains + this.logger(`Deploying ISMs to ${remoteDeployableChains}`); + const ismConfigs = buildIsmConfigMap( + owner, + remoteDeployableChains, + allChains, + ); + const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = + {}; + for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { + this.logger(`Deploying ISM to ${ismChain}`); + ismContracts[ismChain] = { + interchainSecurityModule: await ismFactory.deploy(ismChain, ismConfig), + }; + } + addressesMap = this.writeMergedAddresses(addressesMap, ismContracts); + this.logger(`ISM deployment complete`); + + // 5. Deploy TestRecipients to all deployable chains + this.logger(`Deploying test recipient contracts`); + const testRecipientConfig = buildTestRecipientConfigMap( + deployableChains, + addressesMap, + ); + const testRecipientDeployer = new HyperlaneTestRecipientDeployer( + this.multiProvider, + ); + testRecipientDeployer.cacheAddressesMap(addressesMap); + const testRecipients = await testRecipientDeployer.deploy( + testRecipientConfig, + ); + addressesMap = this.writeMergedAddresses(addressesMap, testRecipients); + this.logger(`Test recipient deployment complete`); + + if (!skipLocalDeploy) { + startBlocks[this.local] = await this.multiProvider + .getProvider(this.local) + .getBlockNumber(); + } + + if (this.writeAgentConfig) { + const agentConfig = buildOverriddenAgentConfig( + deployableChains, + this.multiProvider, + startBlocks, + ); + + this.logger(`Writing agent config to artifacts/agent_config.json`); + writeJSON('./artifacts/', 'agent_config.json', agentConfig); + } + } + + protected writeMergedAddresses( + aAddresses: HyperlaneAddressesMap, + bContracts: HyperlaneContractsMap, + ): HyperlaneAddressesMap { + // Only write addresses that aren't present in the SDK + const bAddresses = serializeContractsMap(bContracts); + const mergedAddresses = objMerge(aAddresses, bAddresses); + const filteredAddresses = objMap( + mergedAddresses, + (chain: string, addresses) => + objFilter(addresses, (contract, address): address is string => { + // @ts-ignore + const chainAddresses = sdkContractAddressesMap[chain]; + return !chainAddresses || chainAddresses[contract] !== address; + }), + ); + this.logger(`Writing contract addresses to artifacts/addresses.json`); + mergeJSON( + './artifacts/', + 'addresses.json', + objFilter( + filteredAddresses, + (_, value): value is HyperlaneAddresses => !!value, + ), + ); + return mergedAddresses; + } + + isDeployableChain(chain: ChainName): boolean { + return ( + this.multiProvider.getChainMetadata(chain).protocol === + ProtocolType.Ethereum + ); + } +} diff --git a/typescript/cli/src/core/TestRecipientDeployer.ts b/typescript/cli/src/core/TestRecipientDeployer.ts new file mode 100644 index 0000000000..c91b5f94e4 --- /dev/null +++ b/typescript/cli/src/core/TestRecipientDeployer.ts @@ -0,0 +1,54 @@ +import debug from 'debug'; + +import { TestRecipient, TestRecipient__factory } from '@hyperlane-xyz/core'; +import { + ChainName, + HyperlaneDeployer, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { types, utils } from '@hyperlane-xyz/utils'; + +// Maps chain name to ISM address +export type TestRecipientConfig = { + interchainSecurityModule: types.Address; +}; + +export type TestRecipientContracts = { + testRecipient: TestRecipient; +}; + +export type TestRecipientAddresses = { + testRecipient: types.Address; +}; + +export const testRecipientFactories = { + testRecipient: new TestRecipient__factory(), +}; + +export class HyperlaneTestRecipientDeployer extends HyperlaneDeployer< + TestRecipientConfig, + typeof testRecipientFactories +> { + constructor(multiProvider: MultiProvider) { + super(multiProvider, testRecipientFactories, { + logger: debug('hyperlane:TestRecipientDeployer'), + }); + } + + async deployContracts( + chain: ChainName, + config: TestRecipientConfig, + ): Promise { + const testRecipient = await this.deployContract(chain, 'testRecipient', []); + const ism = await testRecipient.interchainSecurityModule(); + if (!utils.eqAddress(ism, config.interchainSecurityModule)) { + const tx = testRecipient.setInterchainSecurityModule( + config.interchainSecurityModule, + ); + await this.multiProvider.handleTx(chain, tx); + } + return { + testRecipient, + }; + } +} diff --git a/typescript/cli/src/index.ts b/typescript/cli/src/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/typescript/cli/src/json.ts b/typescript/cli/src/json.ts new file mode 100644 index 0000000000..39dbaaf9db --- /dev/null +++ b/typescript/cli/src/json.ts @@ -0,0 +1,55 @@ +import fs from 'fs'; +import path from 'path'; + +import { objMerge } from '@hyperlane-xyz/sdk'; + +export function readFileAtPath(filepath: string) { + if (!fs.existsSync(filepath)) { + throw Error(`file doesn't exist at ${filepath}`); + } + return fs.readFileSync(filepath, 'utf8'); +} + +export function readJSONAtPath(filepath: string): T { + return JSON.parse(readFileAtPath(filepath)) as T; +} + +export function readJSON(directory: string, filename: string): T { + return readJSONAtPath(path.join(directory, filename)); +} + +export function tryReadJSON(directory: string, filename: string): T | null { + try { + return readJSONAtPath(path.join(directory, filename)) as T; + } catch (error) { + return null; + } +} + +export function writeFileAtPath( + directory: string, + filename: string, + value: string, +) { + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + fs.writeFileSync(path.join(directory, filename), value); +} + +export function writeJSON(directory: string, filename: string, obj: any) { + writeFileAtPath(directory, filename, JSON.stringify(obj, null, 2) + '\n'); +} + +export function mergeJSON>( + directory: string, + filename: string, + obj: T, +) { + if (fs.existsSync(path.join(directory, filename))) { + const previous = readJSON(directory, filename); + writeJSON(directory, filename, objMerge(previous, obj)); + } else { + writeJSON(directory, filename, obj); + } +} diff --git a/typescript/cli/src/logger.ts b/typescript/cli/src/logger.ts new file mode 100644 index 0000000000..e39d4c68b7 --- /dev/null +++ b/typescript/cli/src/logger.ts @@ -0,0 +1,21 @@ +import debug from 'debug'; + +const HYPERLANE_NS = 'hyperlane'; + +// Default root logger for use in utils/scripts +export const logger = debug(HYPERLANE_NS); +export const error = debug(`${HYPERLANE_NS}:ERROR`); + +export function createLogger(namespace: string, isError = false) { + return isError ? error.extend(namespace) : logger.extend(namespace); +} + +// Ensure hyperlane logging is enabled +const activeNamespaces = debug.disable(); +const otherNamespaces = activeNamespaces + .split(',') + .filter((ns) => ns.includes(HYPERLANE_NS)); +const hypNamespaces = `${HYPERLANE_NS},${HYPERLANE_NS}:*`; +debug.enable( + otherNamespaces ? `${otherNamespaces},${hypNamespaces}` : `${hypNamespaces}`, +); diff --git a/typescript/cli/src/test/run.ts b/typescript/cli/src/test/run.ts new file mode 100644 index 0000000000..9e35cb639f --- /dev/null +++ b/typescript/cli/src/test/run.ts @@ -0,0 +1,12 @@ +import { error, logger } from '../logger.js'; + +//TODO remove +export function run(name: string, fn: () => Promise) { + logger(`Beginning ${name} script`); + fn() + .then(() => logger(`${name} completed successfully`)) + .catch((e: any) => { + error(`Error running ${name}`, e); + process.exit(1); + }); +} diff --git a/typescript/cli/src/test/test-messages.ts b/typescript/cli/src/test/test-messages.ts new file mode 100644 index 0000000000..07e48a7131 --- /dev/null +++ b/typescript/cli/src/test/test-messages.ts @@ -0,0 +1,149 @@ +import { ethers } from 'ethers'; +import yargs from 'yargs'; + +import { + DispatchedMessage, + HyperlaneCore, + HyperlaneIgp, + MultiProvider, + objMerge, +} from '@hyperlane-xyz/sdk'; +import { utils } from '@hyperlane-xyz/utils'; + +import { + artifactsAddressesMap, + assertBalances, + assertBytes32, + assertUnique, + getMultiProvider, + sdkContractAddressesMap, +} from '../config.js'; +import { createLogger } from '../logger.js'; + +import { run } from './run.js'; + +const logger = createLogger('MessageDeliveryTest'); +const error = createLogger('MessageDeliveryTest', true); +const mergedContractAddresses = objMerge( + sdkContractAddressesMap, + artifactsAddressesMap(), +); + +function getArgs(multiProvider: MultiProvider) { + // Only accept chains for which we have both a connection and contract addresses + const { intersection } = multiProvider.intersect( + Object.keys(mergedContractAddresses), + ); + return yargs(process.argv.slice(2)) + .describe('chains', 'chain to send message from') + .choices('chains', intersection) + .demandOption('chains') + .array('chains') + .middleware(assertUnique((argv) => argv.chains)) + .describe('key', 'hexadecimal private key for transaction signing') + .string('key') + .coerce('key', assertBytes32) + .demandOption('key') + .describe('timeout', 'timeout in seconds') + .number('timeout') + .default('timeout', 10 * 60) + .middleware(assertBalances(multiProvider, (argv) => argv.chains)).argv; +} + +run('Message delivery test', async () => { + let timedOut = false; + const multiProvider = getMultiProvider(); + const { chains, key, timeout } = await getArgs(multiProvider); + const timeoutId = setTimeout(() => { + timedOut = true; + }, timeout * 1000); + const signer = new ethers.Wallet(key); + multiProvider.setSharedSigner(signer); + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddresses, + multiProvider, + ); + const igp = HyperlaneIgp.fromAddressesMap( + mergedContractAddresses, + multiProvider, + ); + const messages: Set = new Set(); + for (const origin of chains) { + const mailbox = core.getContracts(origin).mailbox; + const defaultIgp = + igp.getContracts(origin).defaultIsmInterchainGasPaymaster; + for (const destination of chains) { + const destinationDomain = multiProvider.getDomainId(destination); + if (origin === destination) continue; + try { + const recipient = mergedContractAddresses[destination] + .testRecipient as string; + if (!recipient) { + throw new Error(`Unable to find TestRecipient for ${destination}`); + } + const messageTx = await mailbox.dispatch( + destinationDomain, + utils.addressToBytes32(recipient), + '0xdeadbeef', + ); + const messageReceipt = await multiProvider.handleTx(origin, messageTx); + const dispatchedMessages = core.getDispatchedMessages(messageReceipt); + if (dispatchedMessages.length !== 1) continue; + const dispatchedMessage = dispatchedMessages[0]; + logger( + `Sent message from ${origin} to ${recipient} on ${destination} with message ID ${dispatchedMessage.id}`, + ); + // Make gas payment... + const gasAmount = 100_000; + const value = await defaultIgp.quoteGasPayment( + destinationDomain, + gasAmount, + ); + const paymentTx = await defaultIgp.payForGas( + dispatchedMessage.id, + destinationDomain, + gasAmount, + await multiProvider.getSignerAddress(origin), + { value }, + ); + await paymentTx.wait(); + messages.add(dispatchedMessage); + } catch (e) { + error( + `Encountered error sending message from ${origin} to ${destination}`, + ); + error(e); + } + } + } + while (messages.size > 0 && !timedOut) { + for (const message of messages.values()) { + const origin = multiProvider.getChainName(message.parsed.origin); + const destination = multiProvider.getChainName( + message.parsed.destination, + ); + const mailbox = core.getContracts(destination).mailbox; + const delivered = await mailbox.delivered(message.id); + if (delivered) { + messages.delete(message); + logger( + `Message from ${origin} to ${destination} with ID ${ + message!.id + } was delivered`, + ); + } else { + logger( + `Message from ${origin} to ${destination} with ID ${ + message!.id + } has not yet been delivered`, + ); + } + await utils.sleep(5000); + } + } + clearTimeout(timeoutId); + if (timedOut) { + error('Timed out waiting for messages to be delivered'); + process.exit(1); + } +}); diff --git a/typescript/cli/src/test/test-warp-transfer.ts b/typescript/cli/src/test/test-warp-transfer.ts new file mode 100644 index 0000000000..74122a4890 --- /dev/null +++ b/typescript/cli/src/test/test-warp-transfer.ts @@ -0,0 +1,228 @@ +import assert from 'assert'; +import { BigNumber, ContractReceipt, ethers } from 'ethers'; +import yargs from 'yargs'; + +import { + ERC20__factory, + HypERC20, + HypERC20App, + HypERC20Collateral, + HypERC20Collateral__factory, + HypERC20__factory, + HypNative, + HypNative__factory, + TokenType, +} from '@hyperlane-xyz/hyperlane-token'; +import { + ChainMap, + HyperlaneCore, + MultiProvider, + objMap, + objMerge, +} from '@hyperlane-xyz/sdk'; +import { utils } from '@hyperlane-xyz/utils'; + +import { + artifactsAddressesMap, + assertBalances, + assertBytes20, + assertBytes32, + getMultiProvider, + sdkContractAddressesMap, +} from '../config.js'; +import { readJSONAtPath } from '../json.js'; +import { createLogger } from '../logger.js'; +import { WarpRouteArtifacts } from '../warp/WarpRouteDeployer.js'; + +import { run } from './run.js'; + +const logger = createLogger('WarpTransferTest'); +const error = createLogger('WarpTransferTest', true); + +const mergedContractAddresses = objMerge( + sdkContractAddressesMap, + artifactsAddressesMap(), +); + +function getArgs(multiProvider: MultiProvider) { + // Only accept chains for which we have both a connection and contract addresses + const { intersection } = multiProvider.intersect( + Object.keys(mergedContractAddresses), + ); + return yargs(process.argv.slice(2)) + .describe('origin', 'chain to send tokens from') + .choices('origin', intersection) + .demandOption('origin') + .string('origin') + .describe('destination', 'chain to send tokens to') + .choices('destination', intersection) + .demandOption('destination') + .string('destination') + .describe('wei', 'amount in wei to send') + .demandOption('wei') + .number('wei') + .describe('key', 'hexadecimal private key for transaction signing') + .string('key') + .coerce('key', assertBytes32) + .demandOption('key') + .describe('recipient', 'token recipient address') + .string('recipient') + .coerce('recipient', assertBytes20) + .demandOption('recipient') + .describe('timeout', 'timeout in seconds') + .number('timeout') + .default('timeout', 10 * 60) + .middleware(assertBalances(multiProvider, (argv) => [argv.origin])).argv; +} + +function hypErc20FromAddressesMap( + artifactsMap: ChainMap, + multiProvider: MultiProvider, +): HypERC20App { + const contractsMap = objMap(artifactsMap, (chain, artifacts) => { + const signer = multiProvider.getSigner(chain); + switch (artifacts.tokenType) { + case TokenType.collateral: { + const router = HypERC20Collateral__factory.connect( + artifacts.router, + signer, + ); + return { router }; + } + case TokenType.native: { + const router = HypNative__factory.connect(artifacts.router, signer); + return { router }; + } + case TokenType.synthetic: { + const router = HypERC20__factory.connect(artifacts.router, signer); + return { router }; + } + default: { + throw new Error('Unsupported token type'); + } + } + }); + return new HypERC20App(contractsMap, multiProvider); +} + +run('Warp transfer test', async () => { + let timedOut = false; + const multiProvider = getMultiProvider(); + const { recipient, origin, destination, wei, key, timeout } = await getArgs( + multiProvider, + ); + const timeoutId = setTimeout(() => { + timedOut = true; + }, timeout * 1000); + const signer = new ethers.Wallet(key); + multiProvider.setSharedSigner(signer); + const artifacts: ChainMap = readJSONAtPath( + './artifacts/warp-token-addresses.json', + ); + const app = hypErc20FromAddressesMap(artifacts, multiProvider); + + const getDestinationBalance = async (): Promise => { + switch (artifacts[destination].tokenType) { + case TokenType.collateral: { + const router = app.getContracts(destination) + .router as HypERC20Collateral; + const tokenAddress = await router.wrappedToken(); + const token = ERC20__factory.connect(tokenAddress, signer); + return token.balanceOf(recipient); + } + case TokenType.native: { + return multiProvider.getProvider(destination).getBalance(recipient); + } + case TokenType.synthetic: { + const router = app.getContracts(destination).router as HypERC20; + return router.balanceOf(recipient); + } + default: { + throw new Error('Unsupported collateral type'); + } + } + }; + const balanceBefore = await getDestinationBalance(); + + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddresses, + multiProvider, + ); + + let receipt: ContractReceipt; + switch (artifacts[origin].tokenType) { + case TokenType.collateral: { + const router = app.getContracts(origin).router as HypERC20Collateral; + const tokenAddress = await router.wrappedToken(); + const token = ERC20__factory.connect(tokenAddress, signer); + const approval = await token.allowance( + await signer.getAddress(), + router.address, + ); + if (approval.lt(wei)) { + await token.approve(router.address, wei); + } + receipt = await app.transfer( + origin, + destination, + utils.addressToBytes32(recipient), + wei, + ); + break; + } + case TokenType.native: { + const destinationDomain = multiProvider.getDomainId(destination); + const router = app.getContracts(origin).router as HypNative; + const gasPayment = await router.quoteGasPayment(destinationDomain); + const value = gasPayment.add(wei); + const tx = await router.transferRemote( + destinationDomain, + utils.addressToBytes32(recipient), + wei, + { value }, + ); + receipt = await tx.wait(); + break; + } + case TokenType.synthetic: { + receipt = await app.transfer( + origin, + destination, + utils.addressToBytes32(recipient), + wei, + ); + break; + } + default: { + throw new Error('Unsupported token type'); + } + } + + const messages = await core.getDispatchedMessages(receipt); + const message = messages[0]; + const msgDestination = multiProvider.getChainName(message.parsed.destination); + assert(destination === msgDestination); + + while ( + !(await core.getContracts(destination).mailbox.delivered(message.id)) && + !timedOut + ) { + logger(`Waiting for message delivery on destination chain`); + await utils.sleep(1000); + } + + if (!timedOut) { + logger(`Message delivered on destination chain!`); + const balanceAfter = await getDestinationBalance(); + if (!balanceAfter.gt(balanceBefore)) { + throw new Error('Destination chain balance did not increase'); + } + logger(`Confirmed balance increase`); + } + + clearTimeout(timeoutId); + if (timedOut) { + error('Timed out waiting for messages to be delivered'); + process.exit(1); + } +}); diff --git a/typescript/cli/src/warp/WarpRouteDeployer.ts b/typescript/cli/src/warp/WarpRouteDeployer.ts new file mode 100644 index 0000000000..c02c3aad53 --- /dev/null +++ b/typescript/cli/src/warp/WarpRouteDeployer.ts @@ -0,0 +1,310 @@ +import { ethers } from 'ethers'; +import yargs from 'yargs'; + +import { + ERC20__factory, + ERC721__factory, + HypERC20Deployer, + HypERC721Deployer, + TokenConfig, + TokenFactories, + TokenType, +} from '@hyperlane-xyz/hyperlane-token'; +import { + ChainMap, + HyperlaneContractsMap, + MultiProvider, + RouterConfig, + chainMetadata, + objMap, + objMerge, +} from '@hyperlane-xyz/sdk'; +import { types } from '@hyperlane-xyz/utils'; + +import { warpRouteConfig } from '../../examples/warp_tokens.js'; +import { + artifactsAddressesMap, + assertBalances, + assertBytes32, + getMultiProvider, + sdkContractAddressesMap, +} from '../config.js'; +import { mergeJSON, tryReadJSON, writeFileAtPath, writeJSON } from '../json.js'; +import { createLogger } from '../logger.js'; + +import { + WarpBaseTokenConfig, + getWarpConfigChains, + validateWarpTokenConfig, +} from './config.js'; +import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; + +export async function getArgs(multiProvider: MultiProvider) { + const args = await yargs(process.argv.slice(2)) + .describe('key', 'A hexadecimal private key for transaction signing') + .string('key') + .coerce('key', assertBytes32) + .demandOption('key') + .middleware( + assertBalances(multiProvider, () => getWarpConfigChains(warpRouteConfig)), + ); + return args.argv; +} + +export type WarpRouteArtifacts = { + router: types.Address; + tokenType: TokenType; +}; + +export class WarpRouteDeployer { + constructor( + public readonly multiProvider: MultiProvider, + public readonly signer: ethers.Signer, + protected readonly logger = createLogger('WarpRouteDeployer'), + ) {} + + static async fromArgs(): Promise { + const multiProvider = getMultiProvider(); + const { key } = await getArgs(multiProvider); + const signer = new ethers.Wallet(key); + multiProvider.setSharedSigner(signer); + return new WarpRouteDeployer(multiProvider, signer); + } + + async deploy(): Promise { + const { configMap, baseToken } = await this.buildHypTokenConfig(); + + this.logger('Initiating hyp token deployments'); + const deployer = baseToken.isNft + ? new HypERC721Deployer(this.multiProvider) + : new HypERC20Deployer(this.multiProvider); + + await deployer.deploy(configMap); + this.logger('Hyp token deployments complete'); + + this.writeDeploymentResult( + deployer.deployedContracts, + configMap, + baseToken, + ); + } + + async buildHypTokenConfig() { + validateWarpTokenConfig(warpRouteConfig); + const { base, synthetics } = warpRouteConfig; + const { type: baseType, chainName: baseChainName } = base; + + const isCollateral = baseType === TokenType.collateral; + const baseTokenAddr = isCollateral + ? base.address + : ethers.constants.AddressZero; + const isNft = !!(isCollateral && base.isNft); + + const owner = await this.signer.getAddress(); + + const mergedContractAddresses = objMerge( + sdkContractAddressesMap, + artifactsAddressesMap(), + ); + + const configMap: ChainMap = { + [baseChainName]: { + type: baseType, + token: baseTokenAddr, + owner, + mailbox: base.mailbox || mergedContractAddresses[baseChainName].mailbox, + interchainSecurityModule: + base.interchainSecurityModule || + mergedContractAddresses[baseChainName].interchainSecurityModule || + mergedContractAddresses[baseChainName].multisigIsm, + interchainGasPaymaster: + base.interchainGasPaymaster || + mergedContractAddresses[baseChainName] + .defaultIsmInterchainGasPaymaster, + foreignDeployment: base.foreignDeployment, + name: base.name, + symbol: base.symbol, + decimals: base.decimals, + }, + }; + this.logger( + `Hyp token config on base chain ${baseChainName}:`, + JSON.stringify(configMap[baseChainName]), + ); + + const baseTokenMetadata = await this.getBaseTokenMetadata(base); + this.logger( + `Using base token metadata: Name: ${baseTokenMetadata.name}, Symbol: ${baseTokenMetadata.symbol}, Decimals: ${baseTokenMetadata.decimals} `, + ); + + for (const synthetic of synthetics) { + const sChainName = synthetic.chainName; + configMap[sChainName] = { + type: TokenType.synthetic, + name: synthetic.name || baseTokenMetadata.name, + symbol: synthetic.symbol || baseTokenMetadata.symbol, + totalSupply: synthetic.totalSupply || 0, + owner, + mailbox: + synthetic.mailbox || mergedContractAddresses[sChainName].mailbox, + interchainSecurityModule: + synthetic.interchainSecurityModule || + mergedContractAddresses[sChainName].interchainSecurityModule || + mergedContractAddresses[sChainName].multisigIsm, + interchainGasPaymaster: + synthetic.interchainGasPaymaster || + mergedContractAddresses[sChainName].defaultIsmInterchainGasPaymaster, + foreignDeployment: synthetic.foreignDeployment, + }; + this.logger( + `Hyp token config on synthetic chain ${sChainName}:`, + JSON.stringify(configMap[sChainName]), + ); + } + return { + configMap, + baseToken: { + type: baseType, + chainName: baseChainName, + address: baseTokenAddr, + metadata: baseTokenMetadata, + isNft, + }, + }; + } + + async getBaseTokenMetadata( + base: WarpBaseTokenConfig, + ): Promise { + // Skip fetching metadata if it's already provided in the config + if (base.name && base.symbol && base.decimals) { + return { + name: base.name, + symbol: base.symbol, + decimals: base.decimals, + }; + } + + if (base.type === TokenType.native) { + return ( + this.multiProvider.getChainMetadata(base.chainName).nativeToken || + chainMetadata.ethereum.nativeToken! + ); + } else if (base.type === TokenType.collateral) { + this.logger( + `Fetching token metadata for ${base.address} on ${base.chainName}}`, + ); + const provider = this.multiProvider.getProvider(base.chainName); + if (base.isNft) { + const erc721Contract = ERC721__factory.connect(base.address, provider); + const [name, symbol] = await Promise.all([ + erc721Contract.name(), + erc721Contract.symbol(), + ]); + return { name, symbol, decimals: 0 }; + } else { + const erc20Contract = ERC20__factory.connect(base.address, provider); + const [name, symbol, decimals] = await Promise.all([ + erc20Contract.name(), + erc20Contract.symbol(), + erc20Contract.decimals(), + ]); + return { name, symbol, decimals }; + } + } else { + throw new Error(`Unsupported token type: ${base}`); + } + } + + writeDeploymentResult( + contracts: HyperlaneContractsMap, + configMap: ChainMap, + baseToken: Awaited< + ReturnType + >['baseToken'], + ) { + this.writeTokenDeploymentArtifacts(contracts, configMap); + this.writeWarpUiTokenList(contracts, baseToken, configMap); + } + + writeTokenDeploymentArtifacts( + contracts: HyperlaneContractsMap, + configMap: ChainMap, + ) { + this.logger( + 'Writing token deployment addresses to artifacts/warp-token-addresses.json', + ); + const artifacts: ChainMap = objMap( + contracts, + (chain, contract) => { + return { + router: contract.router.address, + tokenType: configMap[chain].type, + }; + }, + ); + mergeJSON('./artifacts/', 'warp-token-addresses.json', artifacts); + } + + writeWarpUiTokenList( + contracts: HyperlaneContractsMap, + baseToken: Awaited< + ReturnType + >['baseToken'], + configMap: ChainMap, + ) { + this.logger( + 'Writing warp ui token list to artifacts/warp-ui-token-list.json and artifacts/warp-ui-token-list.ts', + ); + const currentTokenList: WarpUITokenConfig[] = + tryReadJSON('./artifacts/', 'warp-ui-token-list.json') || []; + + const { type, address, chainName, metadata, isNft } = baseToken; + const { name, symbol, decimals } = metadata; + const hypTokenAddr = + contracts[chainName]?.router?.address || + configMap[chainName]?.foreignDeployment; + if (!hypTokenAddr) { + throw Error( + 'No base Hyperlane token address deployed and no foreign deployment specified', + ); + } + const commonFields = { + chainId: this.multiProvider.getChainId(chainName), + name, + symbol, + decimals, + }; + let newToken: WarpUITokenConfig; + if (type === TokenType.collateral) { + newToken = { + ...commonFields, + type: TokenType.collateral, + address, + hypCollateralAddress: hypTokenAddr, + isNft, + }; + } else if (type === TokenType.native) { + newToken = { + ...commonFields, + type: TokenType.native, + hypNativeAddress: hypTokenAddr, + }; + } else { + throw new Error(`Unsupported token type: ${type}`); + } + + currentTokenList.push(newToken); + // Write list as JSON + writeJSON('./artifacts/', 'warp-ui-token-list.json', currentTokenList); + // Also write list as TS + const serializedTokens = currentTokenList + .map((t) => JSON.stringify(t)) + .join(',\n'); + writeFileAtPath( + './artifacts/', + 'warp-ui-token-list.ts', + `export const tokenList = [\n${serializedTokens}\n];`, + ); + } +} diff --git a/typescript/cli/src/warp/config.ts b/typescript/cli/src/warp/config.ts new file mode 100644 index 0000000000..6cfe1b37a2 --- /dev/null +++ b/typescript/cli/src/warp/config.ts @@ -0,0 +1,82 @@ +import { z } from 'zod'; + +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; +import { RouterConfig } from '@hyperlane-xyz/sdk'; + +import { MinimalTokenMetadata } from './types.js'; + +type WarpBaseToken = { + type: TokenType.native | TokenType.collateral; + chainName: string; +} & Partial & + Partial; + +export interface WarpNativeTokenConfig extends WarpBaseToken { + type: TokenType.native; +} + +export interface WarpCollateralTokenConfig extends WarpBaseToken { + type: TokenType.collateral; + address: string; + isNft?: boolean; +} + +export type WarpSyntheticTokenConfig = { + chainName: string; + totalSupply?: number; +} & Partial & + Partial; + +export type WarpBaseTokenConfig = + | WarpNativeTokenConfig + | WarpCollateralTokenConfig; + +export interface WarpRouteConfig { + base: WarpBaseTokenConfig; + synthetics: WarpSyntheticTokenConfig[]; +} + +// Zod schema for Warp Route config validation validation +const ConnectionConfigSchema = { + mailbox: z.string().optional(), + interchainGasPaymaster: z.string().optional(), + interchainSecurityModule: z.string().optional(), +}; + +export const WarpTokenConfigSchema = z.object({ + base: z.object({ + type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)), + chainName: z.string(), + address: z.string().optional(), + isNft: z.boolean().optional(), + ...ConnectionConfigSchema, + }), + synthetics: z + .array( + z.object({ + chainName: z.string(), + name: z.string().optional(), + symbol: z.string().optional(), + totalSupply: z.number().optional(), + ...ConnectionConfigSchema, + }), + ) + .nonempty(), +}); + +export function validateWarpTokenConfig(data: WarpRouteConfig) { + const result = WarpTokenConfigSchema.safeParse(data); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } +} + +export function getWarpConfigChains(config: WarpRouteConfig) { + const { base, synthetics } = config; + return [base, ...synthetics] + .filter((c) => !c.foreignDeployment) + .map((token) => token.chainName); +} diff --git a/typescript/cli/src/warp/types.ts b/typescript/cli/src/warp/types.ts new file mode 100644 index 0000000000..f7f62708f1 --- /dev/null +++ b/typescript/cli/src/warp/types.ts @@ -0,0 +1,27 @@ +import type { ERC20Metadata, TokenType } from '@hyperlane-xyz/hyperlane-token'; +import type { types } from '@hyperlane-xyz/utils'; + +export type MinimalTokenMetadata = Omit; + +// Types below must match the Warp UI token config schema +// It is used to generate the configs for the Warp UI +// https://github.com/hyperlane-xyz/hyperlane-warp-ui-template/blob/main/src/features/tokens/types.ts +interface BaseWarpUITokenConfig extends MinimalTokenMetadata { + type: TokenType.collateral | TokenType.native; + chainId: number; + logoURI?: string; + isNft?: boolean; +} + +interface CollateralTokenConfig extends BaseWarpUITokenConfig { + type: TokenType.collateral; + address: types.Address; + hypCollateralAddress: types.Address; +} + +interface NativeTokenConfig extends BaseWarpUITokenConfig { + type: TokenType.native; + hypNativeAddress: types.Address; +} + +export type WarpUITokenConfig = CollateralTokenConfig | NativeTokenConfig; diff --git a/typescript/cli/tsconfig.json b/typescript/cli/tsconfig.json new file mode 100644 index 0000000000..d9af26bff1 --- /dev/null +++ b/typescript/cli/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./dist/", + "rootDir": "." + }, + "include": ["./cli.ts", "./src/**/*.ts", "./src/*.d.ts", "./examples/**/*.ts",], +} diff --git a/typescript/helloworld/.eslintrc b/typescript/helloworld/.eslintrc index 36c46e35ac..446616f52f 100644 --- a/typescript/helloworld/.eslintrc +++ b/typescript/helloworld/.eslintrc @@ -27,5 +27,13 @@ "@typescript-eslint/no-floating-promises": ["error"], "@typescript-eslint/no-non-null-assertion": ["off"], "@typescript-eslint/no-require-imports": ["warn"], + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] } } diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 4f516a8774..c8cacf42ca 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -10,26 +10,26 @@ "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.3", - "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typechain/ethers-v5": "10.0.0", "@typechain/hardhat": "^6.0.0", "@types/mocha": "^9.1.0", - "@typescript-eslint/eslint-plugin": "^5.27.0", - "@typescript-eslint/parser": "^5.27.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "chai": "^4.3.0", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", "ethereum-waffle": "^3.4.4", "hardhat": "^2.16.1", "hardhat-gas-reporter": "^1.0.9", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.0.0-beta.5", "solhint": "^3.3.2", "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.3", - "ts-node": "^10.8.0", + "ts-node": "^10.9.1", "typechain": "8.0.0", - "typescript": "^4.7.2" + "typescript": "^5.1.6" }, "files": [ "/dist", @@ -47,7 +47,7 @@ "packageManager": "yarn@3.2.0", "repository": { "type": "git", - "url": "https://github.com/hyperlane-xyz/hyperlane-app-template" + "url": "https://github.com/hyperlane-xyz/hyperlane-monorepo" }, "scripts": { "build": "hardhat compile && tsc", diff --git a/typescript/helloworld/tsconfig.json b/typescript/helloworld/tsconfig.json index 2300e544b4..5e1b6c225e 100644 --- a/typescript/helloworld/tsconfig.json +++ b/typescript/helloworld/tsconfig.json @@ -5,7 +5,10 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "incremental": false, - "lib": ["es2015", "es5", "dom"], + "lib": [ + "ES2015", "ES2016", "ES2017", "ES2018", + "ES2019", "ES2020","ES2021", "DOM" + ], "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, @@ -17,7 +20,7 @@ "preserveWatchOutput": true, "pretty": false, "sourceMap": true, - "target": "es6", + "target": "ES2020", "strict": true, "outDir": "./dist", "rootDir": "./", diff --git a/typescript/infra/package.json b/typescript/infra/package.json index cf5710ce74..569783d3a8 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -23,7 +23,7 @@ "dotenv": "^10.0.0", "prom-client": "^14.0.1", "prompts": "^2.4.2", - "yargs": "^17.4.1" + "yargs": "^17.7.2" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.1", @@ -32,14 +32,14 @@ "@types/mocha": "^9.1.0", "@types/node": "^16.9.1", "@types/prompts": "^2.0.14", - "@types/yargs": "^17.0.10", + "@types/yargs": "^17.0.24", "chai": "^4.3.4", "ethereum-waffle": "^3.4.4", "ethers": "^5.7.2", "hardhat": "^2.16.1", - "prettier": "^2.4.1", - "ts-node": "^10.8.0", - "typescript": "^4.7.2" + "prettier": "^2.8.8", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" }, "private": true, "homepage": "https://www.hyperlane.xyz", diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index e52a9c966b..806cbf0819 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -26,7 +26,7 @@ import { LiquidityLayerRelayerConfig } from './middleware'; export const EnvironmentNames = Object.keys(environments); export type DeployEnvironment = keyof typeof environments; export type EnvironmentChain = Extract< - keyof typeof environments[E], + keyof (typeof environments)[E], ChainName >; diff --git a/typescript/sdk/.eslintrc b/typescript/sdk/.eslintrc index a254a40141..eb1e08414f 100644 --- a/typescript/sdk/.eslintrc +++ b/typescript/sdk/.eslintrc @@ -1,13 +1,5 @@ { "rules": { - "@typescript-eslint/explicit-module-boundary-types": ["error"], - "@typescript-eslint/no-unused-vars": [ - "warn", // or "error" - { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_" - } - ], + "@typescript-eslint/explicit-module-boundary-types": ["error"] } } diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index f24607a2f0..0efcdcebd2 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -25,10 +25,10 @@ "fs": "0.0.1-security", "hardhat": "^2.16.1", "mocha": "^9.2.2", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "sinon": "^13.0.2", - "ts-node": "^10.8.0", - "typescript": "^4.7.2" + "ts-node": "^10.9.1", + "typescript": "^5.1.6" }, "files": [ "/dist", diff --git a/typescript/sdk/src/consts/environments/index.ts b/typescript/sdk/src/consts/environments/index.ts index 5abf951a1a..55be095f28 100644 --- a/typescript/sdk/src/consts/environments/index.ts +++ b/typescript/sdk/src/consts/environments/index.ts @@ -10,7 +10,7 @@ export const hyperlaneEnvironments = { test, testnet, mainnet }; export type HyperlaneEnvironment = keyof typeof hyperlaneEnvironments; export type HyperlaneEnvironmentChain = Extract< - keyof typeof hyperlaneEnvironments[E], + keyof (typeof hyperlaneEnvironments)[E], ChainName >; @@ -18,4 +18,7 @@ export type HyperlaneEnvironmentChain = Extract< export const hyperlaneContractAddresses = objMerge( hyperlaneEnvironments.testnet, hyperlaneEnvironments.mainnet, -) as Record; +) as Record< + CoreChainName, + (typeof hyperlaneEnvironments)['mainnet']['ethereum'] +>; diff --git a/typescript/token/.eslintrc b/typescript/token/.eslintrc index 16cac6ceb6..e3f712414b 100644 --- a/typescript/token/.eslintrc +++ b/typescript/token/.eslintrc @@ -1,31 +1,5 @@ { - "env": { - "node": true, - "browser": true, - "es2021": true - }, - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module", - "project": "./tsconfig.json" - }, - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], "rules": { - "no-eval": ["error"], - "no-ex-assign": ["error"], - "no-constant-condition": ["off"], - "@typescript-eslint/ban-ts-comment": ["off"], - "@typescript-eslint/explicit-module-boundary-types": ["off"], - "@typescript-eslint/no-explicit-any": ["off"], - "@typescript-eslint/no-floating-promises": ["off"], - "@typescript-eslint/no-non-null-assertion": ["off"], - "@typescript-eslint/no-require-imports": ["warn"] + "no-console": ["off"] } } diff --git a/typescript/token/package.json b/typescript/token/package.json index 3c6c070ed1..5486ef11cd 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -12,26 +12,26 @@ "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.3", - "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typechain/ethers-v5": "10.0.0", "@typechain/hardhat": "^6.0.0", "@types/mocha": "^9.1.0", - "@typescript-eslint/eslint-plugin": "^5.27.0", - "@typescript-eslint/parser": "^5.27.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "chai": "^4.3.0", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", "ethereum-waffle": "^3.4.4", "hardhat": "^2.16.1", "hardhat-gas-reporter": "^1.0.9", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.0.0-beta.5", "solhint": "^3.3.2", "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.3", - "ts-node": "^10.8.0", + "ts-node": "^10.9.1", "typechain": "8.0.0", - "typescript": "^4.7.2" + "typescript": "^5.1.6" }, "files": [ "/dist", diff --git a/typescript/token/tsconfig.json b/typescript/token/tsconfig.json index 64f481fab0..3df5408c8c 100644 --- a/typescript/token/tsconfig.json +++ b/typescript/token/tsconfig.json @@ -1,27 +1,8 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "declaration": true, - "declarationMap": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "incremental": false, - "lib": ["es2015", "es5", "dom"], - "module": "commonjs", - "moduleResolution": "node", - "noEmitOnError": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "sourceMap": true, - "target": "es6", - "strict": true, - "resolveJsonModule": true, - "outDir": "./dist", - "rootDir": "./src", + "outDir": "./dist/", + "rootDir": "./src/" }, "exclude": [ "./node_modules/", @@ -31,4 +12,4 @@ "./src/types/hardhat.d.ts", "hardhat.config.ts" ], -} +} \ No newline at end of file diff --git a/typescript/utils/package.json b/typescript/utils/package.json index a1521c23c1..880d3c90be 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -7,8 +7,8 @@ }, "devDependencies": { "chai": "^4.3.0", - "prettier": "^2.4.1", - "typescript": "^4.7.2" + "prettier": "^2.8.8", + "typescript": "^5.1.6" }, "homepage": "https://www.hyperlane.xyz", "keywords": [ diff --git a/yarn.lock b/yarn.lock index ffd3cbfd41..482d75e464 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2412,7 +2412,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7": version: 7.16.7 resolution: "@babel/code-frame@npm:7.16.7" dependencies: @@ -2421,87 +2421,52 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.17.10": - version: 7.18.5 - resolution: "@babel/compat-data@npm:7.18.5" - checksum: 1baee39fcf0992402ed12d6be43739f3bfb7f0cacddee8959236692ae926bcc3f4fe5abdd907870f4fc8b9fd798c1e6e2999ae97c9b8aedbd834fe03f2765e73 - languageName: node - linkType: hard - -"@babel/core@npm:7.13.10": - version: 7.13.10 - resolution: "@babel/core@npm:7.13.10" +"@babel/code-frame@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/code-frame@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.12.13 - "@babel/generator": ^7.13.9 - "@babel/helper-compilation-targets": ^7.13.10 - "@babel/helper-module-transforms": ^7.13.0 - "@babel/helpers": ^7.13.10 - "@babel/parser": ^7.13.10 - "@babel/template": ^7.12.13 - "@babel/traverse": ^7.13.0 - "@babel/types": ^7.13.0 - convert-source-map: ^1.7.0 - debug: ^4.1.0 - gensync: ^1.0.0-beta.2 - json5: ^2.1.2 - lodash: ^4.17.19 - semver: ^6.3.0 - source-map: ^0.5.0 - checksum: 9b3362fd02e6a4f3ad642893312ec3d22713c4eeb2571c994d49c31f38d24893a6a18f4b49abb8d56b510e116278608eaddde2ca72ccb39ab29350efa5af39de + "@babel/highlight": ^7.22.5 + checksum: cfe804f518f53faaf9a1d3e0f9f74127ab9a004912c3a16fda07fb6a633393ecb9918a053cb71804204c1b7ec3d49e1699604715e2cfb0c9f7bc4933d324ebb6 languageName: node linkType: hard -"@babel/generator@npm:7.13.9": - version: 7.13.9 - resolution: "@babel/generator@npm:7.13.9" +"@babel/generator@npm:7.17.7": + version: 7.17.7 + resolution: "@babel/generator@npm:7.17.7" dependencies: - "@babel/types": ^7.13.0 + "@babel/types": ^7.17.0 jsesc: ^2.5.1 source-map: ^0.5.0 - checksum: 1b0e9fa1b5ea6656f0abeeedc99ff7bffa455d7bf118f4d17a75d80c439206b4ba3e1071c104b486b7447689512969286cbde505e6169ab38cf437e13ca54225 + checksum: e7344b9b4559115f2754ecc2ae9508412ea6a8f617544cd3d3f17cabc727bd30630765f96c8a4ebc8901ded1492a3a6c23d695a4f1e8f3042f860b30c891985c languageName: node linkType: hard -"@babel/generator@npm:^7.13.0, @babel/generator@npm:^7.13.9, @babel/generator@npm:^7.18.2": - version: 7.18.2 - resolution: "@babel/generator@npm:7.18.2" +"@babel/generator@npm:^7.17.3": + version: 7.22.9 + resolution: "@babel/generator@npm:7.22.9" dependencies: - "@babel/types": ^7.18.2 - "@jridgewell/gen-mapping": ^0.3.0 + "@babel/types": ^7.22.5 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: d0661e95532ddd97566d41fec26355a7b28d1cbc4df95fe80cc084c413342935911b48db20910708db39714844ddd614f61c2ec4cca3fb10181418bdcaa2e7a3 + checksum: 7c9d2c58b8d5ac5e047421a6ab03ec2ff5d9a5ff2c2212130a0055e063ac349e0b19d435537d6886c999771aef394832e4f54cd9fc810100a7f23d982f6af06b languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.10": - version: 7.18.2 - resolution: "@babel/helper-compilation-targets@npm:7.18.2" - dependencies: - "@babel/compat-data": ^7.17.10 - "@babel/helper-validator-option": ^7.16.7 - browserslist: ^4.20.2 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 4f02e79f20c0b3f8db5049ba8c35027c41ccb3fc7884835d04e49886538e0f55702959db1bb75213c94a5708fec2dc81a443047559a4f184abb884c72c0059b4 - languageName: node - linkType: hard - -"@babel/helper-environment-visitor@npm:^7.16.7, @babel/helper-environment-visitor@npm:^7.18.2": +"@babel/helper-environment-visitor@npm:^7.16.7": version: 7.18.2 resolution: "@babel/helper-environment-visitor@npm:7.18.2" checksum: 1a9c8726fad454a082d077952a90f17188e92eabb3de236cb4782c49b39e3f69c327e272b965e9a20ff8abf37d30d03ffa6fd7974625a6c23946f70f7527f5e9 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.12.13, @babel/helper-function-name@npm:^7.17.9": - version: 7.17.9 - resolution: "@babel/helper-function-name@npm:7.17.9" +"@babel/helper-function-name@npm:^7.16.7": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" dependencies: - "@babel/template": ^7.16.7 - "@babel/types": ^7.17.0 - checksum: a59b2e5af56d8f43b9b0019939a43774754beb7cb01a211809ca8031c71890999d07739e955343135ec566c4d8ff725435f1f60fb0af3bb546837c1f9f84f496 + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 6b1f6ce1b1f4e513bf2c8385a557ea0dd7fa37971b9002ad19268ca4384bbe90c09681fe4c076013f33deabc63a53b341ed91e792de741b4b35e01c00238177a languageName: node linkType: hard @@ -2514,41 +2479,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-module-imports@npm:7.16.7" - dependencies: - "@babel/types": ^7.16.7 - checksum: ddd2c4a600a2e9a4fee192ab92bf35a627c5461dbab4af31b903d9ba4d6b6e59e0ff3499fde4e2e9a0eebe24906f00b636f8b4d9bd72ff24d50e6618215c3212 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.13.0": - version: 7.18.0 - resolution: "@babel/helper-module-transforms@npm:7.18.0" - dependencies: - "@babel/helper-environment-visitor": ^7.16.7 - "@babel/helper-module-imports": ^7.16.7 - "@babel/helper-simple-access": ^7.17.7 - "@babel/helper-split-export-declaration": ^7.16.7 - "@babel/helper-validator-identifier": ^7.16.7 - "@babel/template": ^7.16.7 - "@babel/traverse": ^7.18.0 - "@babel/types": ^7.18.0 - checksum: 824c3967c08d75bb36adc18c31dcafebcd495b75b723e2e17c6185e88daf5c6db62a6a75d9f791b5f38618a349e7cb32503e715a1b9a4e8bad4d0f43e3e6b523 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.17.7": - version: 7.18.2 - resolution: "@babel/helper-simple-access@npm:7.18.2" - dependencies: - "@babel/types": ^7.18.2 - checksum: c0862b56db7e120754d89273a039b128c27517389f6a4425ff24e49779791e8fe10061579171fb986be81fa076778acb847c709f6f5e396278d9c5e01360c375 - languageName: node - linkType: hard - -"@babel/helper-split-export-declaration@npm:^7.12.13, @babel/helper-split-export-declaration@npm:^7.16.7": +"@babel/helper-split-export-declaration@npm:^7.16.7": version: 7.16.7 resolution: "@babel/helper-split-export-declaration@npm:7.16.7" dependencies: @@ -2557,28 +2488,24 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.12.11, @babel/helper-validator-identifier@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-validator-identifier@npm:7.16.7" - checksum: dbb3db9d184343152520a209b5684f5e0ed416109cde82b428ca9c759c29b10c7450657785a8b5c5256aa74acc6da491c1f0cf6b784939f7931ef82982051b69 +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.16.7": +"@babel/helper-validator-identifier@npm:^7.16.7": version: 7.16.7 - resolution: "@babel/helper-validator-option@npm:7.16.7" - checksum: c5ccc451911883cc9f12125d47be69434f28094475c1b9d2ada7c3452e6ac98a1ee8ddd364ca9e3f9855fcdee96cdeafa32543ebd9d17fee7a1062c202e80570 + resolution: "@babel/helper-validator-identifier@npm:7.16.7" + checksum: dbb3db9d184343152520a209b5684f5e0ed416109cde82b428ca9c759c29b10c7450657785a8b5c5256aa74acc6da491c1f0cf6b784939f7931ef82982051b69 languageName: node linkType: hard -"@babel/helpers@npm:^7.13.10": - version: 7.18.2 - resolution: "@babel/helpers@npm:7.18.2" - dependencies: - "@babel/template": ^7.16.7 - "@babel/traverse": ^7.18.2 - "@babel/types": ^7.18.2 - checksum: 94620242f23f6d5f9b83a02b1aa1632ffb05b0815e1bb53d3b46d64aa8e771066bba1db8bd267d9091fb00134cfaeda6a8d69d1d4cc2c89658631adfa077ae70 +"@babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 7f0f30113474a28298c12161763b49de5018732290ca4de13cdaefd4fd0d635a6fe3f6686c37a02905fb1e64f21a5ee2b55140cf7b070e729f1bd66866506aea languageName: node linkType: hard @@ -2593,21 +2520,23 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:7.14.6": - version: 7.14.6 - resolution: "@babel/parser@npm:7.14.6" - bin: - parser: ./bin/babel-parser.js - checksum: 104482e07971a78a3d68a0c329b1303981a272f55a510d39c93dac3c293f207ec863329046abc5d8bb86db58c49670cc607654793470a87ccd386544c2ccf298 +"@babel/highlight@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/highlight@npm:7.22.5" + dependencies: + "@babel/helper-validator-identifier": ^7.22.5 + chalk: ^2.0.0 + js-tokens: ^4.0.0 + checksum: f61ae6de6ee0ea8d9b5bcf2a532faec5ab0a1dc0f7c640e5047fc61630a0edb88b18d8c92eb06566d30da7a27db841aca11820ecd3ebe9ce514c9350fbed39c4 languageName: node linkType: hard -"@babel/parser@npm:^7.13.0, @babel/parser@npm:^7.13.10, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.18.5": - version: 7.18.5 - resolution: "@babel/parser@npm:7.18.5" +"@babel/parser@npm:^7.17.3, @babel/parser@npm:^7.22.5, @babel/parser@npm:^7.22.7": + version: 7.22.7 + resolution: "@babel/parser@npm:7.22.7" bin: parser: ./bin/babel-parser.js - checksum: 4976349d8681af215fd5771bd5b74568cc95a2e8bf2afcf354bf46f73f3d6f08d54705f354b1d0012f914dd02a524b7d37c5c1204ccaafccb9db3c37dba96a9b + checksum: 02209ddbd445831ee8bf966fdf7c29d189ed4b14343a68eb2479d940e7e3846340d7cc6bd654a5f3d87d19dc84f49f50a58cf9363bee249dc5409ff3ba3dab54 languageName: node linkType: hard @@ -2620,64 +2549,46 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.12.13, @babel/template@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/template@npm:7.16.7" - dependencies: - "@babel/code-frame": ^7.16.7 - "@babel/parser": ^7.16.7 - "@babel/types": ^7.16.7 - checksum: 10cd112e89276e00f8b11b55a51c8b2f1262c318283a980f4d6cdb0286dc05734b9aaeeb9f3ad3311900b09bc913e02343fcaa9d4a4f413964aaab04eb84ac4a - languageName: node - linkType: hard - -"@babel/traverse@npm:7.13.0": - version: 7.13.0 - resolution: "@babel/traverse@npm:7.13.0" +"@babel/template@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.12.13 - "@babel/generator": ^7.13.0 - "@babel/helper-function-name": ^7.12.13 - "@babel/helper-split-export-declaration": ^7.12.13 - "@babel/parser": ^7.13.0 - "@babel/types": ^7.13.0 - debug: ^4.1.0 - globals: ^11.1.0 - lodash: ^4.17.19 - checksum: 7d584b5541396b02f6973ba8ec8a067f2a6c2fd2e894c663dfae36e86e65a004a6865fbffbfc89ca040c894f9c12134bb971d31f09e7ec32c28e9b18bf737f2a + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 languageName: node linkType: hard -"@babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.18.0, @babel/traverse@npm:^7.18.2": - version: 7.18.5 - resolution: "@babel/traverse@npm:7.18.5" +"@babel/traverse@npm:7.17.3": + version: 7.17.3 + resolution: "@babel/traverse@npm:7.17.3" dependencies: "@babel/code-frame": ^7.16.7 - "@babel/generator": ^7.18.2 - "@babel/helper-environment-visitor": ^7.18.2 - "@babel/helper-function-name": ^7.17.9 + "@babel/generator": ^7.17.3 + "@babel/helper-environment-visitor": ^7.16.7 + "@babel/helper-function-name": ^7.16.7 "@babel/helper-hoist-variables": ^7.16.7 "@babel/helper-split-export-declaration": ^7.16.7 - "@babel/parser": ^7.18.5 - "@babel/types": ^7.18.4 + "@babel/parser": ^7.17.3 + "@babel/types": ^7.17.0 debug: ^4.1.0 globals: ^11.1.0 - checksum: cc0470c880e15a748ca3424665c65836dba450fd0331fb28f9d30aa42acd06387b6321996517ab1761213f781fe8d657e2c3ad67c34afcb766d50653b393810f + checksum: 780d7ecf711758174989794891af08d378f81febdb8932056c0d9979524bf0298e28f8e7708a872d7781151506c28f56c85c63ea3f1f654662c2fcb8a3eb9fdc languageName: node linkType: hard -"@babel/types@npm:7.13.0": - version: 7.13.0 - resolution: "@babel/types@npm:7.13.0" +"@babel/types@npm:7.17.0": + version: 7.17.0 + resolution: "@babel/types@npm:7.17.0" dependencies: - "@babel/helper-validator-identifier": ^7.12.11 - lodash: ^4.17.19 + "@babel/helper-validator-identifier": ^7.16.7 to-fast-properties: ^2.0.0 - checksum: 3dbb08add345325a49e1deebefa8d3774a8ab055c4be675c339a389358f4b3443652ded4bfdb230b342c6af12593a6fd3fb95156564e7ec84081018815896821 + checksum: 12e5a287986fe557188e87b2c5202223f1dc83d9239a196ab936fdb9f8c1eb0be717ff19f934b5fad4e29a75586d5798f74bed209bccea1c20376b9952056f0e languageName: node linkType: hard -"@babel/types@npm:^7.13.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.18.0, @babel/types@npm:^7.18.2, @babel/types@npm:^7.18.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.8.3": version: 7.18.4 resolution: "@babel/types@npm:7.18.4" dependencies: @@ -2687,6 +2598,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/types@npm:7.22.5" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 + to-fast-properties: ^2.0.0 + checksum: c13a9c1dc7d2d1a241a2f8363540cb9af1d66e978e8984b400a20c4f38ba38ca29f06e26a0f2d49a70bad9e57615dac09c35accfddf1bb90d23cd3e0a0bab892 + languageName: node + linkType: hard + "@chainsafe/as-sha256@npm:^0.3.1": version: 0.3.1 resolution: "@chainsafe/as-sha256@npm:0.3.1" @@ -2780,23 +2702,6 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.3.0": - version: 1.3.0 - resolution: "@eslint/eslintrc@npm:1.3.0" - dependencies: - ajv: ^6.12.4 - debug: ^4.3.2 - espree: ^9.3.2 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.2.1 - js-yaml: ^4.1.0 - minimatch: ^3.1.2 - strip-json-comments: ^3.1.1 - checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515 - languageName: node - linkType: hard - "@eslint/eslintrc@npm:^2.0.3": version: 2.0.3 resolution: "@eslint/eslintrc@npm:2.0.3" @@ -3880,17 +3785,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.9.2": - version: 0.9.5 - resolution: "@humanwhocodes/config-array@npm:0.9.5" - dependencies: - "@humanwhocodes/object-schema": ^1.2.1 - debug: ^4.1.1 - minimatch: ^3.0.4 - checksum: 8ba6281bc0590f6c6eadeefc14244b5a3e3f5903445aadd1a32099ed80e753037674026ce1b3c945ab93561bea5eb29e3c5bff67060e230c295595ba517a3492 - languageName: node - linkType: hard - "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -3905,6 +3799,30 @@ __metadata: languageName: node linkType: hard +"@hyperlane-xyz/cli@workspace:typescript/cli": + version: 0.0.0-use.local + resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" + dependencies: + "@hyperlane-xyz/hyperlane-token": 1.4.2 + "@hyperlane-xyz/sdk": 1.4.2 + "@types/node": ^18.14.5 + "@types/yargs": ^17.0.24 + "@typescript-eslint/eslint-plugin": ^5.62.0 + "@typescript-eslint/parser": ^5.62.0 + chalk: ^5.3.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 + ethers: ^5.7.2 + inquirer: ^9.2.8 + prettier: ^2.8.8 + typescript: ^5.1.6 + yargs: ^17.7.2 + zod: ^3.21.2 + bin: + hyperlane: ./dist/cli.js + languageName: unknown + linkType: soft + "@hyperlane-xyz/core@1.4.2, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" @@ -3922,14 +3840,14 @@ __metadata: ethers: ^5.7.2 hardhat: ^2.16.1 hardhat-gas-reporter: ^1.0.9 - prettier: ^2.4.1 + prettier: ^2.8.8 prettier-plugin-solidity: ^1.0.0-beta.5 solhint: ^3.3.2 solhint-plugin-prettier: ^0.0.5 solidity-coverage: ^0.8.3 ts-generator: ^0.1.1 typechain: ^8.1.1 - typescript: ^4.7.2 + typescript: ^5.1.6 languageName: unknown linkType: soft @@ -3941,31 +3859,31 @@ __metadata: "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 - "@trivago/prettier-plugin-sort-imports": ^3.2.0 + "@trivago/prettier-plugin-sort-imports": ^4.2.0 "@typechain/ethers-v5": 10.0.0 "@typechain/hardhat": ^6.0.0 "@types/mocha": ^9.1.0 - "@typescript-eslint/eslint-plugin": ^5.27.0 - "@typescript-eslint/parser": ^5.27.0 + "@typescript-eslint/eslint-plugin": ^5.62.0 + "@typescript-eslint/parser": ^5.62.0 chai: ^4.3.0 - eslint: ^8.16.0 - eslint-config-prettier: ^8.5.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 ethereum-waffle: ^3.4.4 ethers: ^5.7.2 hardhat: ^2.16.1 hardhat-gas-reporter: ^1.0.9 - prettier: ^2.4.1 + prettier: ^2.8.8 prettier-plugin-solidity: ^1.0.0-beta.5 solhint: ^3.3.2 solhint-plugin-prettier: ^0.0.5 solidity-coverage: ^0.8.3 - ts-node: ^10.8.0 + ts-node: ^10.9.1 typechain: 8.0.0 - typescript: ^4.7.2 + typescript: ^5.1.6 languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.4.2, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: @@ -3975,27 +3893,27 @@ __metadata: "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 - "@trivago/prettier-plugin-sort-imports": ^3.2.0 + "@trivago/prettier-plugin-sort-imports": ^4.2.0 "@typechain/ethers-v5": 10.0.0 "@typechain/hardhat": ^6.0.0 "@types/mocha": ^9.1.0 - "@typescript-eslint/eslint-plugin": ^5.27.0 - "@typescript-eslint/parser": ^5.27.0 + "@typescript-eslint/eslint-plugin": ^5.62.0 + "@typescript-eslint/parser": ^5.62.0 chai: ^4.3.0 - eslint: ^8.16.0 - eslint-config-prettier: ^8.5.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 ethereum-waffle: ^3.4.4 ethers: ^5.7.2 hardhat: ^2.16.1 hardhat-gas-reporter: ^1.0.9 - prettier: ^2.4.1 + prettier: ^2.8.8 prettier-plugin-solidity: ^1.0.0-beta.5 solhint: ^3.3.2 solhint-plugin-prettier: ^0.0.5 solidity-coverage: ^0.8.3 - ts-node: ^10.8.0 + ts-node: ^10.9.1 typechain: 8.0.0 - typescript: ^4.7.2 + typescript: ^5.1.6 languageName: unknown linkType: soft @@ -4024,7 +3942,7 @@ __metadata: "@types/mocha": ^9.1.0 "@types/node": ^16.9.1 "@types/prompts": ^2.0.14 - "@types/yargs": ^17.0.10 + "@types/yargs": ^17.0.24 asn1.js: 5.4.1 aws-kms-ethers-signer: ^0.1.3 chai: ^4.3.4 @@ -4032,12 +3950,12 @@ __metadata: ethereum-waffle: ^3.4.4 ethers: ^5.7.2 hardhat: ^2.16.1 - prettier: ^2.4.1 + prettier: ^2.8.8 prom-client: ^14.0.1 prompts: ^2.4.2 - ts-node: ^10.8.0 - typescript: ^4.7.2 - yargs: ^17.4.1 + ts-node: ^10.9.1 + typescript: ^5.1.6 + yargs: ^17.7.2 languageName: unknown linkType: soft @@ -4045,14 +3963,14 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/monorepo@workspace:." dependencies: - "@trivago/prettier-plugin-sort-imports": ^3.2.0 - "@typescript-eslint/eslint-plugin": ^5.27.0 - "@typescript-eslint/parser": ^5.27.0 - eslint: ^8.16.0 - eslint-config-prettier: ^8.5.0 + "@trivago/prettier-plugin-sort-imports": ^4.2.0 + "@typescript-eslint/eslint-plugin": ^5.62.0 + "@typescript-eslint/parser": ^5.62.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 husky: ^8.0.0 lint-staged: ^12.4.3 - prettier: ^2.4.1 + prettier: ^2.8.8 languageName: unknown linkType: soft @@ -4079,10 +3997,10 @@ __metadata: fs: 0.0.1-security hardhat: ^2.16.1 mocha: ^9.2.2 - prettier: ^2.4.1 + prettier: ^2.8.8 sinon: ^13.0.2 - ts-node: ^10.8.0 - typescript: ^4.7.2 + ts-node: ^10.9.1 + typescript: ^5.1.6 zod: ^3.21.2 languageName: unknown linkType: soft @@ -4093,19 +4011,26 @@ __metadata: dependencies: chai: ^4.3.0 ethers: ^5.7.2 - prettier: ^2.4.1 - typescript: ^4.7.2 + prettier: ^2.8.8 + typescript: ^5.1.6 languageName: unknown linkType: soft -"@jridgewell/gen-mapping@npm:^0.3.0": - version: 0.3.1 - resolution: "@jridgewell/gen-mapping@npm:0.3.1" +"@jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" dependencies: - "@jridgewell/set-array": ^1.0.0 + "@jridgewell/set-array": ^1.0.1 "@jridgewell/sourcemap-codec": ^1.4.10 "@jridgewell/trace-mapping": ^0.3.9 - checksum: e9e7bb3335dea9e60872089761d4e8e089597360cdb1af90370e9d53b7d67232c1e0a3ab65fbfef4fc785745193fbc56bff9f3a6cab6c6ce3f15e12b4191f86b + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:3.1.0": + version: 3.1.0 + resolution: "@jridgewell/resolve-uri@npm:3.1.0" + checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard @@ -4116,10 +4041,17 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0": - version: 1.1.1 - resolution: "@jridgewell/set-array@npm:1.1.1" - checksum: cc5d91e0381c347e3edee4ca90b3c292df9e6e55f29acbe0dd97de8651b4730e9ab761406fd572effa79972a0edc55647b627f8c72315e276d959508853d9bf2 +"@jridgewell/set-array@npm:^1.0.1": + version: 1.1.2 + resolution: "@jridgewell/set-array@npm:1.1.2" + checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:1.4.14": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard @@ -4140,6 +4072,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.17": + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" + dependencies: + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.13 resolution: "@jridgewell/trace-mapping@npm:0.3.13" @@ -5168,20 +5110,23 @@ __metadata: languageName: node linkType: hard -"@trivago/prettier-plugin-sort-imports@npm:^3.2.0": - version: 3.2.0 - resolution: "@trivago/prettier-plugin-sort-imports@npm:3.2.0" +"@trivago/prettier-plugin-sort-imports@npm:^4.2.0": + version: 4.2.0 + resolution: "@trivago/prettier-plugin-sort-imports@npm:4.2.0" dependencies: - "@babel/core": 7.13.10 - "@babel/generator": 7.13.9 - "@babel/parser": 7.14.6 - "@babel/traverse": 7.13.0 - "@babel/types": 7.13.0 + "@babel/generator": 7.17.7 + "@babel/parser": ^7.20.5 + "@babel/traverse": 7.17.3 + "@babel/types": 7.17.0 javascript-natural-sort: 0.7.1 - lodash: 4.17.21 + lodash: ^4.17.21 peerDependencies: - prettier: 2.x - checksum: 22461433fa0dc82621713cdfb88f8f527c6c41729e9859bb7f0106ef23c35b0da591ee7fed63516be7e8df5604dc8055f0c7e200fed1ef97f44000c9fe25a890 + "@vue/compiler-sfc": 3.x + prettier: 2.x - 3.x + peerDependenciesMeta: + "@vue/compiler-sfc": + optional: true + checksum: 2081ba9f1a2d33b9a3eeadeb3e713d404ee3d1a5cff3b20a23d94d6d915f0a8ff549616c1e77cd728f1b33733e0d7ab8e4c2512f344a612d81ece40025351160 languageName: node linkType: hard @@ -5463,6 +5408,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.14.5": + version: 18.17.1 + resolution: "@types/node@npm:18.17.1" + checksum: 56201bda9a2d05d68602df63b4e67b0545ac8c6d0280bd5fb31701350a978a577a027501fbf49db99bf177f2242ebd1244896bfd35e89042d5bd7dfebff28d4e + languageName: node + linkType: hard + "@types/node@npm:^8.0.0": version: 8.10.66 resolution: "@types/node@npm:8.10.66" @@ -5537,6 +5489,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.3.12": + version: 7.5.0 + resolution: "@types/semver@npm:7.5.0" + checksum: 0a64b9b9c7424d9a467658b18dd70d1d781c2d6f033096a6e05762d20ebbad23c1b69b0083b0484722aabf35640b78ccc3de26368bcae1129c87e9df028a22e2 + languageName: node + linkType: hard + "@types/sinon-chai@npm:^3.2.3": version: 3.2.8 resolution: "@types/sinon-chai@npm:3.2.8" @@ -5596,26 +5555,27 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.10": - version: 17.0.10 - resolution: "@types/yargs@npm:17.0.10" +"@types/yargs@npm:^17.0.24": + version: 17.0.24 + resolution: "@types/yargs@npm:17.0.24" dependencies: "@types/yargs-parser": "*" - checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8 + checksum: 5f3ac4dc4f6e211c1627340160fbe2fd247ceba002190da6cf9155af1798450501d628c9165a183f30a224fc68fa5e700490d740ff4c73e2cdef95bc4e8ba7bf languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.27.0": - version: 5.27.1 - resolution: "@typescript-eslint/eslint-plugin@npm:5.27.1" +"@typescript-eslint/eslint-plugin@npm:^5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" dependencies: - "@typescript-eslint/scope-manager": 5.27.1 - "@typescript-eslint/type-utils": 5.27.1 - "@typescript-eslint/utils": 5.27.1 + "@eslint-community/regexpp": ^4.4.0 + "@typescript-eslint/scope-manager": 5.62.0 + "@typescript-eslint/type-utils": 5.62.0 + "@typescript-eslint/utils": 5.62.0 debug: ^4.3.4 - functional-red-black-tree: ^1.0.1 + graphemer: ^1.4.0 ignore: ^5.2.0 - regexpp: ^3.2.0 + natural-compare-lite: ^1.4.0 semver: ^7.3.7 tsutils: ^3.21.0 peerDependencies: @@ -5624,42 +5584,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: ee00d8d3a7b395e346801b7bf30209e278f06b5c283ad71c03b34db9e2d68a43ca0e292e315fa7e5bf131a8839ff4a24e0ed76c37811d230f97aae7e123d73ea + checksum: fc104b389c768f9fa7d45a48c86d5c1ad522c1d0512943e782a56b1e3096b2cbcc1eea3fcc590647bf0658eef61aac35120a9c6daf979bf629ad2956deb516a1 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.27.0": - version: 5.27.1 - resolution: "@typescript-eslint/parser@npm:5.27.1" +"@typescript-eslint/parser@npm:^5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" dependencies: - "@typescript-eslint/scope-manager": 5.27.1 - "@typescript-eslint/types": 5.27.1 - "@typescript-eslint/typescript-estree": 5.27.1 + "@typescript-eslint/scope-manager": 5.62.0 + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/typescript-estree": 5.62.0 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 0f1df76142c9d7a6c6dbfc5d19fdee02bbc0e79def6e6df4b126c7eaae1c3a46a3871ad498d4b1fc7ad5cb58d6eb70f020807f600d99c0b9add98441fc12f23b + checksum: d168f4c7f21a7a63f47002e2d319bcbb6173597af5c60c1cf2de046b46c76b4930a093619e69faf2d30214c29ab27b54dcf1efc7046a6a6bd6f37f59a990e752 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/scope-manager@npm:5.27.1" +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" dependencies: - "@typescript-eslint/types": 5.27.1 - "@typescript-eslint/visitor-keys": 5.27.1 - checksum: 401bf2b46de08ddb80ec9f36df7d58bf5de7837185a472b190b670d421d685743aad4c9fa8a6893f65ba933b822c5d7060c640e87cf0756d7aa56abdd25689cc + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/visitor-keys": 5.62.0 + checksum: 6062d6b797fe1ce4d275bb0d17204c827494af59b5eaf09d8a78cdd39dadddb31074dded4297aaf5d0f839016d601032857698b0e4516c86a41207de606e9573 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/type-utils@npm:5.27.1" +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" dependencies: - "@typescript-eslint/utils": 5.27.1 + "@typescript-eslint/typescript-estree": 5.62.0 + "@typescript-eslint/utils": 5.62.0 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -5667,23 +5628,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 43b7da26ea1bd7d249c45d168ec88f971fb71362bbc21ec4748d73b1ecb43f4ca59f5ed338e8dbc74272ae4ebac1cab87a9b62c0fa616c6f9bd833a212dc8a40 + checksum: fc41eece5f315dfda14320be0da78d3a971d650ea41300be7196934b9715f3fe1120a80207551eb71d39568275dbbcf359bde540d1ca1439d8be15e9885d2739 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/types@npm:5.27.1" - checksum: 81faa50256ba67c23221273744c51676774fe6a1583698c3a542f3e2fd21ab34a4399019527c9cf7ab4e5a1577272f091d5848d3af937232ddb2dbf558a7c39a +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 48c87117383d1864766486f24de34086155532b070f6264e09d0e6139449270f8a9559cfef3c56d16e3bcfb52d83d42105d61b36743626399c7c2b5e0ac3b670 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/typescript-estree@npm:5.27.1" +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" dependencies: - "@typescript-eslint/types": 5.27.1 - "@typescript-eslint/visitor-keys": 5.27.1 + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/visitor-keys": 5.62.0 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -5692,33 +5653,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 59d2a0885be7d54bd86472a446d84930cc52d2690ea432d9164075ea437b3b4206dadd49799764ad0fb68f3e4ebb4e36db9717c7a443d0f3c82d5659e41fbd05 + checksum: 3624520abb5807ed8f57b1197e61c7b1ed770c56dfcaca66372d584ff50175225798bccb701f7ef129d62c5989070e1ee3a0aa2d84e56d9524dcf011a2bb1a52 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/utils@npm:5.27.1" +"@typescript-eslint/utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" dependencies: + "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 - "@typescript-eslint/scope-manager": 5.27.1 - "@typescript-eslint/types": 5.27.1 - "@typescript-eslint/typescript-estree": 5.27.1 + "@types/semver": ^7.3.12 + "@typescript-eslint/scope-manager": 5.62.0 + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/typescript-estree": 5.62.0 eslint-scope: ^5.1.1 - eslint-utils: ^3.0.0 + semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 51add038226cddad2b3322225de18d53bc1ed44613f7b3a379eb597114b8830a632990b0f4321e0ddf3502b460d80072d7e789be89135b5e11e8dae167005625 + checksum: ee9398c8c5db6d1da09463ca7bf36ed134361e20131ea354b2da16a5fdb6df9ba70c62a388d19f6eebb421af1786dbbd79ba95ddd6ab287324fc171c3e28d931 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/visitor-keys@npm:5.27.1" +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" dependencies: - "@typescript-eslint/types": 5.27.1 + "@typescript-eslint/types": 5.62.0 eslint-visitor-keys: ^3.3.0 - checksum: 8f104eda321cd6c613daf284fbebbd32b149d4213d137b0ce1caec7a1334c9f46c82ed64aff1243b712ac8c13f67ac344c996cd36d21fbb15032c24d9897a64a + checksum: 976b05d103fe8335bef5c93ad3f76d781e3ce50329c0243ee0f00c0fcfb186c81df50e64bfdd34970148113f8ade90887f53e3c4938183afba830b4ba8e30a35 languageName: node linkType: hard @@ -5876,7 +5839,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.4.1, acorn@npm:^8.7.1": +"acorn@npm:^8.4.1": version: 8.7.1 resolution: "acorn@npm:8.7.1" bin: @@ -6011,7 +5974,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.0": +"ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -7208,7 +7171,7 @@ __metadata: languageName: node linkType: hard -"bl@npm:^4.0.3": +"bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" dependencies: @@ -7440,21 +7403,6 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.20.2": - version: 4.20.4 - resolution: "browserslist@npm:4.20.4" - dependencies: - caniuse-lite: ^1.0.30001349 - electron-to-chromium: ^1.4.147 - escalade: ^3.1.1 - node-releases: ^2.0.5 - picocolors: ^1.0.0 - bin: - browserslist: cli.js - checksum: 0e56c42da765524e5c31bc9a1f08afaa8d5dba085071137cf21e56dc78d0cf0283764143df4c7d1c0cd18c3187fc9494e1d93fa0255004f0be493251a28635f9 - languageName: node - linkType: hard - "bs58@npm:^4.0.0, bs58@npm:^4.0.1": version: 4.0.1 resolution: "bs58@npm:4.0.1" @@ -7737,7 +7685,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30000844, caniuse-lite@npm:^1.0.30001349": +"caniuse-lite@npm:^1.0.30000844": version: 1.0.30001352 resolution: "caniuse-lite@npm:1.0.30001352" checksum: 575ad031349e56224471859decd100d0f90c804325bf1b543789b212d6126f6e18925766b325b1d96f75e48df0036e68f92af26d1fb175803fd6ad935bc807ac @@ -7824,6 +7772,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -7994,6 +7949,13 @@ __metadata: languageName: node linkType: hard +"cli-spinners@npm:^2.5.0": + version: 2.9.0 + resolution: "cli-spinners@npm:2.9.0" + checksum: a9c56e1f44457d4a9f4f535364e729cb8726198efa9e98990cfd9eda9e220dfa4ba12f92808d1be5e29029cdfead781db82dc8549b97b31c907d55f96aa9b0e2 + languageName: node + linkType: hard + "cli-table3@npm:^0.5.0": version: 0.5.1 resolution: "cli-table3@npm:0.5.1" @@ -8035,6 +7997,13 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-width@npm:4.0.0" + checksum: 1ec12311217cc8b2d018646a58b61424d2348def598fb58ba2c32e28f0bcb59a35cef168110311cefe3340abf00e5171b351de6c3e2c084bd1642e6e2a9e144e + languageName: node + linkType: hard + "cliui@npm:^3.2.0": version: 3.2.0 resolution: "cliui@npm:3.2.0" @@ -8068,6 +8037,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "clone-response@npm:^1.0.2": version: 1.0.2 resolution: "clone-response@npm:1.0.2" @@ -8084,6 +8064,13 @@ __metadata: languageName: node linkType: hard +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + "code-point-at@npm:^1.0.0": version: 1.1.0 resolution: "code-point-at@npm:1.1.0" @@ -8304,7 +8291,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.5.1, convert-source-map@npm:^1.7.0": +"convert-source-map@npm:^1.5.1": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" dependencies: @@ -8689,6 +8676,15 @@ __metadata: languageName: node linkType: hard +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + "defer-to-connect@npm:^1.0.1": version: 1.1.3 resolution: "defer-to-connect@npm:1.1.3" @@ -8972,7 +8968,7 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.3.47, electron-to-chromium@npm:^1.4.147": +"electron-to-chromium@npm:^1.3.47": version: 1.4.152 resolution: "electron-to-chromium@npm:1.4.152" checksum: d1e3405adc8a8ddbcf5a91f33739f2f3f5fa7612a4e2b6cc6a85d9ebccc87f59cf9e99d2de93c7959b34aa7c633c6c162d912bf895a0e4b79d0c2ce35594948f @@ -9235,6 +9231,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e + languageName: node + linkType: hard + "escodegen@npm:1.8.x": version: 1.8.1 resolution: "escodegen@npm:1.8.1" @@ -9254,14 +9257,14 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.5.0": - version: 8.5.0 - resolution: "eslint-config-prettier@npm:8.5.0" +"eslint-config-prettier@npm:^8.8.0": + version: 8.8.0 + resolution: "eslint-config-prettier@npm:8.8.0" peerDependencies: eslint: ">=7.0.0" bin: eslint-config-prettier: bin/cli.js - checksum: 0d0f5c32e7a0ad91249467ce71ca92394ccd343178277d318baf32063b79ea90216f4c81d1065d60f96366fdc60f151d4d68ae7811a58bd37228b84c2083f893 + checksum: 1e94c3882c4d5e41e1dcfa2c368dbccbfe3134f6ac7d40101644d3bfbe3eb2f2ffac757f3145910b5eacf20c0e85e02b91293d3126d770cbf3dc390b3564681c languageName: node linkType: hard @@ -9285,16 +9288,6 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" - dependencies: - esrecurse: ^4.3.0 - estraverse: ^5.2.0 - checksum: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e - languageName: node - linkType: hard - "eslint-scope@npm:^7.2.0": version: 7.2.0 resolution: "eslint-scope@npm:7.2.0" @@ -9314,17 +9307,6 @@ __metadata: languageName: node linkType: hard -"eslint-utils@npm:^3.0.0": - version: 3.0.0 - resolution: "eslint-utils@npm:3.0.0" - dependencies: - eslint-visitor-keys: ^2.0.0 - peerDependencies: - eslint: ">=5" - checksum: 0668fe02f5adab2e5a367eee5089f4c39033af20499df88fe4e6aba2015c20720404d8c3d6349b6f716b08fdf91b9da4e5d5481f265049278099c4c836ccb619 - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^1.0.0, eslint-visitor-keys@npm:^1.1.0": version: 1.3.0 resolution: "eslint-visitor-keys@npm:1.3.0" @@ -9332,13 +9314,6 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^2.0.0": - version: 2.1.0 - resolution: "eslint-visitor-keys@npm:2.1.0" - checksum: e3081d7dd2611a35f0388bbdc2f5da60b3a3c5b8b6e928daffff7391146b434d691577aa95064c8b7faad0b8a680266bcda0a42439c18c717b80e6718d7e267d - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^3.3.0": version: 3.3.0 resolution: "eslint-visitor-keys@npm:3.3.0" @@ -9399,51 +9374,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.16.0": - version: 8.17.0 - resolution: "eslint@npm:8.17.0" - dependencies: - "@eslint/eslintrc": ^1.3.0 - "@humanwhocodes/config-array": ^0.9.2 - ajv: ^6.10.0 - chalk: ^4.0.0 - cross-spawn: ^7.0.2 - debug: ^4.3.2 - doctrine: ^3.0.0 - escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.1 - eslint-utils: ^3.0.0 - eslint-visitor-keys: ^3.3.0 - espree: ^9.3.2 - esquery: ^1.4.0 - esutils: ^2.0.2 - fast-deep-equal: ^3.1.3 - file-entry-cache: ^6.0.1 - functional-red-black-tree: ^1.0.1 - glob-parent: ^6.0.1 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.0.0 - imurmurhash: ^0.1.4 - is-glob: ^4.0.0 - js-yaml: ^4.1.0 - json-stable-stringify-without-jsonify: ^1.0.1 - levn: ^0.4.1 - lodash.merge: ^4.6.2 - minimatch: ^3.1.2 - natural-compare: ^1.4.0 - optionator: ^0.9.1 - regexpp: ^3.2.0 - strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 - text-table: ^0.2.0 - v8-compile-cache: ^2.0.3 - bin: - eslint: bin/eslint.js - checksum: b484c96681c6b19f5b437f664623f1cd310d3ee9be88400d8450e086e664cd968a9dc202f0b0678578fd50e7a445b92586efe8c787de5073ff2f83213b00bb7b - languageName: node - linkType: hard - "eslint@npm:^8.43.0": version: 8.43.0 resolution: "eslint@npm:8.43.0" @@ -9504,17 +9434,6 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.3.2": - version: 9.3.2 - resolution: "espree@npm:9.3.2" - dependencies: - acorn: ^8.7.1 - acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30 - languageName: node - linkType: hard - "espree@npm:^9.5.2": version: 9.5.2 resolution: "espree@npm:9.5.2" @@ -9546,7 +9465,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.0.1, esquery@npm:^1.4.0": +"esquery@npm:^1.0.1": version: 1.4.0 resolution: "esquery@npm:1.4.0" dependencies: @@ -10507,6 +10426,16 @@ __metadata: languageName: node linkType: hard +"figures@npm:^5.0.0": + version: 5.0.0 + resolution: "figures@npm:5.0.0" + dependencies: + escape-string-regexp: ^5.0.0 + is-unicode-supported: ^1.2.0 + checksum: e6e8b6d1df2f554d4effae4a5ceff5d796f9449f6d4e912d74dab7d5f25916ecda6c305b9084833157d56485a0c78b37164430ddc5675bcee1330e346710669e + languageName: node + linkType: hard + "file-entry-cache@npm:^5.0.1": version: 5.0.1 resolution: "file-entry-cache@npm:5.0.1" @@ -11055,13 +10984,6 @@ __metadata: languageName: node linkType: hard -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec - languageName: node - linkType: hard - "get-caller-file@npm:^1.0.1": version: 1.0.3 resolution: "get-caller-file@npm:1.0.3" @@ -11187,7 +11109,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^6.0.1, glob-parent@npm:^6.0.2": +"glob-parent@npm:^6.0.2": version: 6.0.2 resolution: "glob-parent@npm:6.0.2" dependencies: @@ -11315,15 +11237,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.15.0 - resolution: "globals@npm:13.15.0" - dependencies: - type-fest: ^0.20.2 - checksum: 383ade0873b2ab29ce6d143466c203ed960491575bc97406395e5c8434026fb02472ab2dfff5bc16689b8460269b18fda1047975295cd0183904385c51258bae - languageName: node - linkType: hard - "globals@npm:^13.19.0": version: 13.20.0 resolution: "globals@npm:13.20.0" @@ -12121,6 +12034,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^9.2.8": + version: 9.2.8 + resolution: "inquirer@npm:9.2.8" + dependencies: + ansi-escapes: ^4.3.2 + chalk: ^5.3.0 + cli-cursor: ^3.1.0 + cli-width: ^4.0.0 + external-editor: ^3.0.3 + figures: ^5.0.0 + lodash: ^4.17.21 + mute-stream: 1.0.0 + ora: ^5.4.1 + run-async: ^3.0.0 + rxjs: ^7.8.1 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + through: ^2.3.6 + wrap-ansi: ^6.0.1 + checksum: 95127000be2bdd195fc2d94f7bedf54fe4889ff0c8cd5265bb08d50e6a9f10a749aa2d80e2d3ce647c2228d78f864ab40d0c768ddc8af62d48b9abd24cfbb6b2 + languageName: node + linkType: hard + "internal-slot@npm:^1.0.3": version: 1.0.3 resolution: "internal-slot@npm:1.0.3" @@ -12446,6 +12382,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -12607,6 +12550,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^1.2.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 20a1fc161afafaf49243551a5ac33b6c4cf0bbcce369fcd8f2951fbdd000c30698ce320de3ee6830497310a8f41880f8066d440aa3eb0a853e2aa4836dd89abc + languageName: node + linkType: hard + "is-url@npm:^1.2.4": version: 1.2.4 resolution: "is-url@npm:1.2.4" @@ -12946,15 +12896,6 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2": - version: 2.2.1 - resolution: "json5@npm:2.2.1" - bin: - json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b - languageName: node - linkType: hard - "jsonfile@npm:^2.1.0": version: 2.4.0 resolution: "jsonfile@npm:2.4.0" @@ -13506,7 +13447,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:4.1.0": +"log-symbols@npm:4.1.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -14404,6 +14345,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "nan@npm:^2.14.0": version: 2.16.0 resolution: "nan@npm:2.16.0" @@ -14471,6 +14419,13 @@ __metadata: languageName: node linkType: hard +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -14648,13 +14603,6 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.5": - version: 2.0.5 - resolution: "node-releases@npm:2.0.5" - checksum: e85d949addd19f8827f32569d2be5751e7812ccf6cc47879d49f79b5234ff4982225e39a3929315f96370823b070640fb04d79fc0ddec8b515a969a03493a42f - languageName: node - linkType: hard - "nofilter@npm:^1.0.4": version: 1.0.4 resolution: "nofilter@npm:1.0.4" @@ -14983,6 +14931,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: ^4.1.0 + chalk: ^4.1.0 + cli-cursor: ^3.1.0 + cli-spinners: ^2.5.0 + is-interactive: ^1.0.0 + is-unicode-supported: ^0.1.0 + log-symbols: ^4.1.0 + strip-ansi: ^6.0.0 + wcwidth: ^1.0.1 + checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 + languageName: node + linkType: hard + "os-homedir@npm:^1.0.0": version: 1.0.2 resolution: "os-homedir@npm:1.0.2" @@ -15360,13 +15325,6 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0": - version: 1.0.0 - resolution: "picocolors@npm:1.0.0" - checksum: a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 - languageName: node - linkType: hard - "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -15544,7 +15502,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.1.2, prettier@npm:^2.3.1, prettier@npm:^2.4.1": +"prettier@npm:^2.1.2, prettier@npm:^2.3.1": version: 2.6.2 resolution: "prettier@npm:2.6.2" bin: @@ -15553,6 +15511,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^2.8.8": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 + languageName: node + linkType: hard + "private@npm:^0.1.6, private@npm:^0.1.8": version: 0.1.8 resolution: "private@npm:0.1.8" @@ -16073,13 +16040,6 @@ __metadata: languageName: node linkType: hard -"regexpp@npm:^3.2.0": - version: 3.2.0 - resolution: "regexpp@npm:3.2.0" - checksum: a78dc5c7158ad9ddcfe01aa9144f46e192ddbfa7b263895a70a5c6c73edd9ce85faf7c0430e59ac38839e1734e275b9c3de5c57ee3ab6edc0e0b1bdebefccef8 - languageName: node - linkType: hard - "regexpu-core@npm:^2.0.0": version: 2.0.0 resolution: "regexpu-core@npm:2.0.0" @@ -16478,6 +16438,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 280c03d5a88603f48103fc6fd69f07fb0c392a1e0d319c34ec96a2516030e07ba06f79231a563c78698b882649c2fc1fda601bc84705f57d50efcd1fa506cfc0 + languageName: node + linkType: hard + "run-parallel-limit@npm:^1.1.0": version: 1.1.0 resolution: "run-parallel-limit@npm:1.1.0" @@ -16521,6 +16488,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: ^2.1.0 + checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 + languageName: node + linkType: hard + "safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -18083,9 +18059,9 @@ __metadata: languageName: node linkType: hard -"ts-node@npm:^10.8.0": - version: 10.8.1 - resolution: "ts-node@npm:10.8.1" +"ts-node@npm:^10.9.1": + version: 10.9.1 + resolution: "ts-node@npm:10.9.1" dependencies: "@cspotcode/source-map-support": ^0.8.0 "@tsconfig/node10": ^1.0.7 @@ -18117,7 +18093,7 @@ __metadata: ts-node-script: dist/bin-script.js ts-node-transpile-only: dist/bin-transpile.js ts-script: dist/bin-script-deprecated.js - checksum: 7d1aa7aa3ae1c0459c4922ed0dbfbade442cfe0c25aebaf620cdf1774f112c8d7a9b14934cb6719274917f35b2c503ba87bcaf5e16a0d39ba0f68ce3e7728363 + checksum: 090adff1302ab20bd3486e6b4799e90f97726ed39e02b39e566f8ab674fd5bd5f727f43615debbfc580d33c6d9d1c6b1b3ce7d8e3cca3e20530a145ffa232c35 languageName: node linkType: hard @@ -18330,23 +18306,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.7.2": - version: 4.7.3 - resolution: "typescript@npm:4.7.3" +"typescript@npm:^5.1.6": + version: 5.1.6 + resolution: "typescript@npm:5.1.6" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: fd13a1ce53790a36bb8350e1f5e5e384b5f6cb9b0635114a6d01d49cb99916abdcfbc13c7521cdae2f2d3f6d8bc4a8ae7625edf645a04ee940588cd5e7597b2f + checksum: b2f2c35096035fe1f5facd1e38922ccb8558996331405eb00a5111cc948b2e733163cc22fab5db46992aba7dd520fff637f2c1df4996ff0e134e77d3249a7350 languageName: node linkType: hard -"typescript@patch:typescript@^4.7.2#~builtin": - version: 4.7.3 - resolution: "typescript@patch:typescript@npm%3A4.7.3#~builtin::version=4.7.3&hash=bda367" +"typescript@patch:typescript@^5.1.6#~builtin": + version: 5.1.6 + resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin::version=5.1.6&hash=bda367" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 8257ce7ecbbf9416da60045a76a99d473698ca9e973fa0ddab7137cacb1587255431176cbbcc801a650938c4dc8109ab88355774829a714fabe56a53a2fe4524 + checksum: 21e88b0a0c0226f9cb9fd25b9626fb05b4c0f3fddac521844a13e1f30beb8f14e90bd409a9ac43c812c5946d714d6e0dee12d5d02dfc1c562c5aacfa1f49b606 languageName: node linkType: hard @@ -18710,13 +18686,6 @@ __metadata: languageName: node linkType: hard -"v8-compile-cache@npm:^2.0.3": - version: 2.3.0 - resolution: "v8-compile-cache@npm:2.3.0" - checksum: adb0a271eaa2297f2f4c536acbfee872d0dd26ec2d76f66921aa7fc437319132773483344207bdbeee169225f4739016d8d2dbf0553913a52bb34da6d0334f8e - languageName: node - linkType: hard - "validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" @@ -18752,6 +18721,15 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c + languageName: node + linkType: hard + "web3-bzz@npm:1.10.0": version: 1.10.0 resolution: "web3-bzz@npm:1.10.0" @@ -19683,7 +19661,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.2.0": +"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" dependencies: @@ -19952,10 +19930,10 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0": - version: 21.0.1 - resolution: "yargs-parser@npm:21.0.1" - checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c languageName: node linkType: hard @@ -20015,18 +19993,18 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.4.1": - version: 17.5.1 - resolution: "yargs@npm:17.5.1" +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: ^7.0.2 + cliui: ^8.0.1 escalade: ^3.1.1 get-caller-file: ^2.0.5 require-directory: ^2.1.1 string-width: ^4.2.3 y18n: ^5.0.5 - yargs-parser: ^21.0.0 - checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard From a2aa1b5b21ad395e0a9604865d29c7ddd6e66ada Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 26 Jul 2023 21:25:28 -0400 Subject: [PATCH 02/82] Progress on deploy commands --- typescript/cli/cli.ts | 12 +- typescript/cli/package.json | 2 +- typescript/cli/src/commands/chains.ts | 40 +++- typescript/cli/src/commands/deploy.ts | 67 ++++++ yarn.lock | 281 ++++++++++++++++---------- 5 files changed, 289 insertions(+), 113 deletions(-) create mode 100644 typescript/cli/src/commands/deploy.ts diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index d021dcc09d..054c640da6 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -3,18 +3,24 @@ import chalk from 'chalk'; import yargs from 'yargs'; import { chainsCommand } from './src/commands/chains.js'; +import { deployCommand } from './src/commands/deploy.js'; import './src/logger.js'; -console.log('⏩', chalk.blue('Hyperlane'), chalk.magentaBright('CLI'), '⏩'); -console.log(chalk.gray('===================')); +console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); try { await yargs(process.argv.slice(2)) .scriptName('hyperlane') .command(chainsCommand) + .command(deployCommand) .demandCommand() + .fail((msg, err) => { + if (err) throw err; // preserve stack + console.error(msg); + process.exit(1); + }) .strict() .help().argv; } catch (error: any) { - console.error(chalk.red('Error:' + error.message)); + console.error(chalk.red('Error: ' + error.message)); } diff --git a/typescript/cli/package.json b/typescript/cli/package.json index f08175fb73..b5248a26c1 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -5,9 +5,9 @@ "dependencies": { "@hyperlane-xyz/hyperlane-token": "1.4.2", "@hyperlane-xyz/sdk": "1.4.2", + "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", - "inquirer": "^9.2.8", "yargs": "^17.7.2", "zod": "^3.21.2" }, diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts index a08dfcef47..8662bf0631 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/chains.ts @@ -1,7 +1,14 @@ import chalk from 'chalk'; import { CommandModule } from 'yargs'; -import { Mainnets, Testnets, chainMetadata } from '@hyperlane-xyz/sdk'; +import { + Chains, + CoreChainName, + Mainnets, + Testnets, + chainMetadata, + hyperlaneContractAddresses, +} from '@hyperlane-xyz/sdk'; /** * Parent command @@ -9,7 +16,8 @@ import { Mainnets, Testnets, chainMetadata } from '@hyperlane-xyz/sdk'; export const chainsCommand: CommandModule = { command: 'chains', describe: 'View information about core Hyperlane chains', - builder: (yargs) => yargs.command(listCommand).demandCommand(), + builder: (yargs) => + yargs.command(listCommand).command(addressesCommand).demandCommand(), handler: () => console.log('Command required'), }; @@ -33,3 +41,31 @@ const listCommand: CommandModule = { ); }, }; + +/** + * Addresses command + */ +const addressesCommand: CommandModule = { + command: 'addresses', + describe: 'Display the addresses of core Hyperlane contracts', + builder: (yargs) => + yargs.options({ + name: { + type: 'string', + description: 'Chain to display addresses for', + choices: Object.values(Chains), + }, + }), + handler: (args) => { + const name = args.name as CoreChainName | undefined; + if (name && hyperlaneContractAddresses[name]) { + console.log(chalk.blue('Hyperlane contract addresses for:', name)); + console.log(chalk.gray('---------------------------------')); + console.log(JSON.stringify(hyperlaneContractAddresses[name], null, 2)); + } else { + console.log(chalk.blue('Hyperlane core contract addresses:')); + console.log(chalk.gray('----------------------------------')); + console.log(JSON.stringify(hyperlaneContractAddresses, null, 2)); + } + }, +}; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts new file mode 100644 index 0000000000..4306b70321 --- /dev/null +++ b/typescript/cli/src/commands/deploy.ts @@ -0,0 +1,67 @@ +import { confirm } from '@inquirer/prompts'; +import chalk from 'chalk'; +import { CommandModule } from 'yargs'; + +/** + * Parent command + */ +export const deployCommand: CommandModule = { + command: 'deploy', + describe: 'Permisionslessly deploy a Hyperlane contracts or extensions', + builder: (yargs) => + yargs.command(coreCommand).command(warpCommand).demandCommand(), + handler: () => console.log('Command required'), +}; + +/** + * Core command + */ +const coreCommand: CommandModule = { + command: 'core', + describe: 'Deploy core Hyperlane contracts', + builder: (yargs) => + yargs.options({ + local: { + type: 'string', + description: 'The chain to deploy to', + demandOption: true, + }, + remotes: { + type: 'string', + array: true, + description: + 'The chains with which local will send and receive messages', + demandOption: true, + }, + key: { + type: 'string', + description: + 'A hexadecimal private key or seed phrase for transaction signing', + demandOption: true, + }, + config: { + type: 'string', + description: + 'A path to a JSON or YAML file with the chain configs. See ./examples/deploy_core_config.yml for an example.', + demandOption: true, + }, + }), + handler: async (_argv) => { + console.log(chalk.blue('Hyperlane permissionless core deployment')); + console.log(chalk.gray('----------------------------------------')); + const confirmation = await confirm({ message: 'Are you sure?' }); + if (!confirmation) throw new Error('Deployment cancelled'); + }, +}; + +/** + * Warp command + */ +const warpCommand: CommandModule = { + command: 'warp', + describe: 'Deploy Warp Route contracts', + builder: (yargs) => yargs.options({}), + handler: (_args) => { + // TODO + }, +}; diff --git a/yarn.lock b/yarn.lock index 482d75e464..fa4575717b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3805,6 +3805,7 @@ __metadata: dependencies: "@hyperlane-xyz/hyperlane-token": 1.4.2 "@hyperlane-xyz/sdk": 1.4.2 + "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 "@typescript-eslint/eslint-plugin": ^5.62.0 @@ -3813,7 +3814,6 @@ __metadata: eslint: ^8.43.0 eslint-config-prettier: ^8.8.0 ethers: ^5.7.2 - inquirer: ^9.2.8 prettier: ^2.8.8 typescript: ^5.1.6 yargs: ^17.7.2 @@ -4016,6 +4016,146 @@ __metadata: languageName: unknown linkType: soft +"@inquirer/checkbox@npm:^1.3.5": + version: 1.3.5 + resolution: "@inquirer/checkbox@npm:1.3.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + ansi-escapes: ^4.3.2 + chalk: ^4.1.2 + figures: ^3.2.0 + checksum: 9359a697d2a98e3110291ae7503d8e817449bef00158624d4827f783d29f2738dc2c33721311de814fe76b642989941b9718c440e828973b12803facd4a9ea09 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^2.0.6": + version: 2.0.6 + resolution: "@inquirer/confirm@npm:2.0.6" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: cbffd0d652ccc25942e12b670c20abaa77c69eaec09ac2d5cc0ae32125abae06ab2d30cd4a49f3041efcce6eab65c77a60c64b9334ed1df2b6acad196779827b + languageName: node + linkType: hard + +"@inquirer/core@npm:^3.0.0": + version: 3.0.0 + resolution: "@inquirer/core@npm:3.0.0" + dependencies: + "@inquirer/type": ^1.1.1 + "@types/mute-stream": ^0.0.1 + "@types/node": ^20.4.2 + "@types/wrap-ansi": ^3.0.0 + ansi-escapes: ^4.3.2 + chalk: ^4.1.2 + cli-spinners: ^2.8.0 + cli-width: ^4.0.0 + figures: ^3.2.0 + mute-stream: ^1.0.0 + run-async: ^3.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wrap-ansi: ^6.0.1 + checksum: d8a47c43c7ae1678cb669b66440ada287563fa6a5c47e4a2f686db3fa4d7e14a277432b137acdc4c49ecd4dc57f0c03352bf7973e3c2ae8e49f02498665372a8 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^1.2.4": + version: 1.2.4 + resolution: "@inquirer/editor@npm:1.2.4" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + external-editor: ^3.0.3 + checksum: 3355ddbf91baab86d5016b35fd09905bfe6f5daee3f8dc844d8f3904161547eb93efd2a8845e172bbf9159148abd68bcf20c1d811c74330064b783c9ab8be67d + languageName: node + linkType: hard + +"@inquirer/expand@npm:^1.1.5": + version: 1.1.5 + resolution: "@inquirer/expand@npm:1.1.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + figures: ^3.2.0 + checksum: 04df9f1713864a965bfaff12358cbd4de97f58363dfb0b625ba4c4b4736bdb1cef38b5a726024ea41b0a55cdbdd1d271fbb6659a8e7fce8927383a8d1bf77562 + languageName: node + linkType: hard + +"@inquirer/input@npm:^1.2.5": + version: 1.2.5 + resolution: "@inquirer/input@npm:1.2.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: a39b7e4ea9140efd1ff41ff2661fbb9b1bbc4dc08e4e645d028eb50925ebc0c9d3d5e31f969ffef4139e5b170e2e6ff536624ec2bbe22aae9bc7e8712f182087 + languageName: node + linkType: hard + +"@inquirer/password@npm:^1.1.5": + version: 1.1.5 + resolution: "@inquirer/password@npm:1.1.5" + dependencies: + "@inquirer/input": ^1.2.5 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: 81aa1a101cfffdcaab6e12487ffc672e5a5cad0c4e2c504f0c56b7bf3119077826d6df5b0a35a2a938c1ff888618d68e656f7acc5103abd5d7b2b56e97aa5ff3 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:^3.0.0": + version: 3.0.0 + resolution: "@inquirer/prompts@npm:3.0.0" + dependencies: + "@inquirer/checkbox": ^1.3.5 + "@inquirer/confirm": ^2.0.6 + "@inquirer/core": ^3.0.0 + "@inquirer/editor": ^1.2.4 + "@inquirer/expand": ^1.1.5 + "@inquirer/input": ^1.2.5 + "@inquirer/password": ^1.1.5 + "@inquirer/rawlist": ^1.2.5 + "@inquirer/select": ^1.2.5 + checksum: f4bfa4da8f8be09ed25c2c13cf7006dfcbbad9ea895562a3de0a3d29a3cac4d9fadb9180c9334de9e41bb2572f73c78e802df383fd5a0d02a7d90d6330e990f0 + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^1.2.5": + version: 1.2.5 + resolution: "@inquirer/rawlist@npm:1.2.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: e94388228305b0d1b49b4f4c6b744c89614c7f413cae2098ff5f7d685ae485b9ad3af9f93796061fad2c8bfa816e7daa9e349ee2499398011744d7c03f6718c3 + languageName: node + linkType: hard + +"@inquirer/select@npm:^1.2.5": + version: 1.2.5 + resolution: "@inquirer/select@npm:1.2.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + ansi-escapes: ^4.3.2 + chalk: ^4.1.2 + figures: ^3.2.0 + checksum: 752b97cc3189e2d119c0a431f2c61ac25029f5edcdf5495da42edabec698c53b08275df6649dbf8678368ffef61e5fe1f9ad8e0e9b3c0f925a48c1eeb549ddd9 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.1.1": + version: 1.1.1 + resolution: "@inquirer/type@npm:1.1.1" + checksum: 1fcce0bd6c92611ed67ee9252deebba0fa9a54aeb4e37fc349ec736c8e1ffaa58f3a02dbe84489ff9a90bebc6b1080a19169ef30c16a18128cf8f42d06a49f51 + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.3 resolution: "@jridgewell/gen-mapping@npm:0.3.3" @@ -5370,6 +5510,15 @@ __metadata: languageName: node linkType: hard +"@types/mute-stream@npm:^0.0.1": + version: 0.0.1 + resolution: "@types/mute-stream@npm:0.0.1" + dependencies: + "@types/node": "*" + checksum: 01bb9f45a77b691538cba7f0c89166a5bfb112993056ae06a8218cd47d417a5f6a5cfc31f0d31293790c647d27564fe6aa39aa9cb2ef08792d42ed40f18de8d5 + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.5.5": version: 2.6.1 resolution: "@types/node-fetch@npm:2.6.1" @@ -5415,6 +5564,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.4.2": + version: 20.4.5 + resolution: "@types/node@npm:20.4.5" + checksum: 36a0304a8dc346a1b2d2edac4c4633eecf70875793d61a5274d0df052d7a7af7a8e34f29884eac4fbd094c4f0201477dcb39c0ecd3307ca141688806538d1138 + languageName: node + linkType: hard + "@types/node@npm:^8.0.0": version: 8.10.66 resolution: "@types/node@npm:8.10.66" @@ -5539,6 +5695,13 @@ __metadata: languageName: node linkType: hard +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 492f0610093b5802f45ca292777679bb9b381f1f32ae939956dd9e00bf81dba7cc99979687620a2817d9a7d8b59928207698166c47a0861c6a2e5c30d4aaf1e9 + languageName: node + linkType: hard + "@types/ws@npm:^7.4.4": version: 7.4.7 resolution: "@types/ws@npm:7.4.7" @@ -7171,7 +7334,7 @@ __metadata: languageName: node linkType: hard -"bl@npm:^4.0.3, bl@npm:^4.1.0": +"bl@npm:^4.0.3": version: 4.1.0 resolution: "bl@npm:4.1.0" dependencies: @@ -7762,7 +7925,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -7949,7 +8112,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.8.0": version: 2.9.0 resolution: "cli-spinners@npm:2.9.0" checksum: a9c56e1f44457d4a9f4f535364e729cb8726198efa9e98990cfd9eda9e220dfa4ba12f92808d1be5e29029cdfead781db82dc8549b97b31c907d55f96aa9b0e2 @@ -8064,13 +8227,6 @@ __metadata: languageName: node linkType: hard -"clone@npm:^1.0.2": - version: 1.0.4 - resolution: "clone@npm:1.0.4" - checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd - languageName: node - linkType: hard - "code-point-at@npm:^1.0.0": version: 1.1.0 resolution: "code-point-at@npm:1.1.0" @@ -8676,15 +8832,6 @@ __metadata: languageName: node linkType: hard -"defaults@npm:^1.0.3": - version: 1.0.4 - resolution: "defaults@npm:1.0.4" - dependencies: - clone: ^1.0.2 - checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a - languageName: node - linkType: hard - "defer-to-connect@npm:^1.0.1": version: 1.1.3 resolution: "defer-to-connect@npm:1.1.3" @@ -9231,13 +9378,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^5.0.0": - version: 5.0.0 - resolution: "escape-string-regexp@npm:5.0.0" - checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e - languageName: node - linkType: hard - "escodegen@npm:1.8.x": version: 1.8.1 resolution: "escodegen@npm:1.8.1" @@ -10426,13 +10566,12 @@ __metadata: languageName: node linkType: hard -"figures@npm:^5.0.0": - version: 5.0.0 - resolution: "figures@npm:5.0.0" +"figures@npm:^3.2.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" dependencies: - escape-string-regexp: ^5.0.0 - is-unicode-supported: ^1.2.0 - checksum: e6e8b6d1df2f554d4effae4a5ceff5d796f9449f6d4e912d74dab7d5f25916ecda6c305b9084833157d56485a0c78b37164430ddc5675bcee1330e346710669e + escape-string-regexp: ^1.0.5 + checksum: 85a6ad29e9aca80b49b817e7c89ecc4716ff14e3779d9835af554db91bac41c0f289c418923519392a1e582b4d10482ad282021330cd045bb7b80c84152f2a2b languageName: node linkType: hard @@ -12034,29 +12173,6 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:^9.2.8": - version: 9.2.8 - resolution: "inquirer@npm:9.2.8" - dependencies: - ansi-escapes: ^4.3.2 - chalk: ^5.3.0 - cli-cursor: ^3.1.0 - cli-width: ^4.0.0 - external-editor: ^3.0.3 - figures: ^5.0.0 - lodash: ^4.17.21 - mute-stream: 1.0.0 - ora: ^5.4.1 - run-async: ^3.0.0 - rxjs: ^7.8.1 - string-width: ^4.2.3 - strip-ansi: ^6.0.1 - through: ^2.3.6 - wrap-ansi: ^6.0.1 - checksum: 95127000be2bdd195fc2d94f7bedf54fe4889ff0c8cd5265bb08d50e6a9f10a749aa2d80e2d3ce647c2228d78f864ab40d0c768ddc8af62d48b9abd24cfbb6b2 - languageName: node - linkType: hard - "internal-slot@npm:^1.0.3": version: 1.0.3 resolution: "internal-slot@npm:1.0.3" @@ -12382,13 +12498,6 @@ __metadata: languageName: node linkType: hard -"is-interactive@npm:^1.0.0": - version: 1.0.0 - resolution: "is-interactive@npm:1.0.0" - checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 - languageName: node - linkType: hard - "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -12550,13 +12659,6 @@ __metadata: languageName: node linkType: hard -"is-unicode-supported@npm:^1.2.0": - version: 1.3.0 - resolution: "is-unicode-supported@npm:1.3.0" - checksum: 20a1fc161afafaf49243551a5ac33b6c4cf0bbcce369fcd8f2951fbdd000c30698ce320de3ee6830497310a8f41880f8066d440aa3eb0a853e2aa4836dd89abc - languageName: node - linkType: hard - "is-url@npm:^1.2.4": version: 1.2.4 resolution: "is-url@npm:1.2.4" @@ -13447,7 +13549,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:4.1.0, log-symbols@npm:^4.1.0": +"log-symbols@npm:4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -14345,7 +14447,7 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:1.0.0": +"mute-stream@npm:^1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 @@ -14931,23 +15033,6 @@ __metadata: languageName: node linkType: hard -"ora@npm:^5.4.1": - version: 5.4.1 - resolution: "ora@npm:5.4.1" - dependencies: - bl: ^4.1.0 - chalk: ^4.1.0 - cli-cursor: ^3.1.0 - cli-spinners: ^2.5.0 - is-interactive: ^1.0.0 - is-unicode-supported: ^0.1.0 - log-symbols: ^4.1.0 - strip-ansi: ^6.0.0 - wcwidth: ^1.0.1 - checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 - languageName: node - linkType: hard - "os-homedir@npm:^1.0.0": version: 1.0.2 resolution: "os-homedir@npm:1.0.2" @@ -16488,15 +16573,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.1 - resolution: "rxjs@npm:7.8.1" - dependencies: - tslib: ^2.1.0 - checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 - languageName: node - linkType: hard - "safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -18721,15 +18797,6 @@ __metadata: languageName: node linkType: hard -"wcwidth@npm:^1.0.1": - version: 1.0.1 - resolution: "wcwidth@npm:1.0.1" - dependencies: - defaults: ^1.0.3 - checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c - languageName: node - linkType: hard - "web3-bzz@npm:1.10.0": version: 1.10.0 resolution: "web3-bzz@npm:1.10.0" From 4aa2fb2b71330abe5a7ab34075d5c270be7410a5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 27 Jul 2023 22:58:33 -0400 Subject: [PATCH 03/82] Finish implementation of core deploy command Add example more example configs Add support for Yaml read/write --- typescript/cli/.gitignore | 6 +- typescript/cli/cli.ts | 22 +- typescript/cli/examples/chain-config.yaml | 45 ++ typescript/cli/examples/chains.ts | 49 -- .../cli/examples/contract-artifacts.yaml | 20 + typescript/cli/examples/multisig-ism.yaml | 13 + typescript/cli/examples/multisig_ism.ts | 21 - typescript/cli/package.json | 1 + typescript/cli/src/chains/config.ts | 43 ++ typescript/cli/src/commands/chains.ts | 41 +- typescript/cli/src/commands/config.ts | 106 ++++ typescript/cli/src/commands/deploy.ts | 44 +- typescript/cli/src/config.ts | 264 -------- typescript/cli/src/consts.ts | 2 + .../core/HyperlanePermissionlessDeployer.ts | 268 -------- .../{core => deploy}/TestRecipientDeployer.ts | 2 +- typescript/cli/src/deploy/core.ts | 563 +++++++++++++++++ typescript/cli/src/json.ts | 55 -- typescript/cli/src/logger.ts | 11 + typescript/cli/src/test/test-messages.ts | 286 ++++----- typescript/cli/src/test/test-warp-transfer.ts | 426 ++++++------- typescript/cli/src/utils/balances.ts | 25 + typescript/cli/src/utils/files.ts | 129 ++++ typescript/cli/src/utils/keys.ts | 16 + typescript/cli/src/utils/providers.ts | 18 + typescript/cli/src/utils/time.ts | 4 + typescript/cli/src/warp/WarpRouteDeployer.ts | 574 +++++++++--------- .../src/consts/agentStartBlocks.ts} | 7 +- typescript/sdk/src/index.ts | 1 + yarn.lock | 8 + 30 files changed, 1716 insertions(+), 1354 deletions(-) create mode 100644 typescript/cli/examples/chain-config.yaml delete mode 100644 typescript/cli/examples/chains.ts create mode 100644 typescript/cli/examples/contract-artifacts.yaml create mode 100644 typescript/cli/examples/multisig-ism.yaml delete mode 100644 typescript/cli/examples/multisig_ism.ts create mode 100644 typescript/cli/src/chains/config.ts create mode 100644 typescript/cli/src/commands/config.ts delete mode 100644 typescript/cli/src/config.ts create mode 100644 typescript/cli/src/consts.ts delete mode 100644 typescript/cli/src/core/HyperlanePermissionlessDeployer.ts rename typescript/cli/src/{core => deploy}/TestRecipientDeployer.ts (95%) create mode 100644 typescript/cli/src/deploy/core.ts delete mode 100644 typescript/cli/src/json.ts create mode 100644 typescript/cli/src/utils/balances.ts create mode 100644 typescript/cli/src/utils/files.ts create mode 100644 typescript/cli/src/utils/keys.ts create mode 100644 typescript/cli/src/utils/providers.ts create mode 100644 typescript/cli/src/utils/time.ts rename typescript/{cli/examples/start_blocks.ts => sdk/src/consts/agentStartBlocks.ts} (71%) diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore index b1ead9840b..df29ed2d70 100644 --- a/typescript/cli/.gitignore +++ b/typescript/cli/.gitignore @@ -1,3 +1,5 @@ .env -dist -cache \ No newline at end of file +/dist +/cache +/configs +/artifacts \ No newline at end of file diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 054c640da6..fdb4f2eab7 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -3,24 +3,32 @@ import chalk from 'chalk'; import yargs from 'yargs'; import { chainsCommand } from './src/commands/chains.js'; +import { configCommand } from './src/commands/config.js'; import { deployCommand } from './src/commands/deploy.js'; import './src/logger.js'; +import { errorRed } from './src/logger.js'; console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); try { await yargs(process.argv.slice(2)) .scriptName('hyperlane') + // TODO get version num from package.json + .version(false) .command(chainsCommand) + .command(configCommand) .command(deployCommand) .demandCommand() - .fail((msg, err) => { - if (err) throw err; // preserve stack - console.error(msg); - process.exit(1); - }) .strict() - .help().argv; + .help() + .fail((msg, err, yargs) => { + if (msg) errorRed('Error: ' + msg); + console.log(''); + yargs.showHelp(); + console.log(''); + if (err) throw err; // preserve stack + else process.exit(1); + }).argv; } catch (error: any) { - console.error(chalk.red('Error: ' + error.message)); + errorRed('Error: ' + error.message); } diff --git a/typescript/cli/examples/chain-config.yaml b/typescript/cli/examples/chain-config.yaml new file mode 100644 index 0000000000..1a27776ab4 --- /dev/null +++ b/typescript/cli/examples/chain-config.yaml @@ -0,0 +1,45 @@ +# Configs for describing chain metadata for use in Hyperlane deployments or apps +# Consists of a map of chain names to metadata +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +--- +mychainname: + # Required fields: + chainId: 1234567890 # Number: Use EIP-155 for EVM chains + domainId: 1234567890 # Number: Recommend matching chainId when possible + name: mychainname # String: Unique identifier for the chain, must match key above + protocol: ethereum # ProtocolType: Ethereum, Sealevel, etc. + rpcUrls: # Array: List of RPC configs + # Only http field is required + - http: https://mychain.com/rpc # String: HTTP URL of the RPC endpoint (preferably HTTPS) + # Others here are optional + pagination: + maxBlockRange: 1000 # Number + maxBlockAge: 1000 # Number + minBlockNumber: 1000 # Number + retry: + maxRequests: 5 # Number + baseRetryMs: 1000 # Number + # Optional fields, not required for Hyperlane deployments but useful for apps: + isTestnet: false # Boolean: Whether the chain is considered a testnet or a mainnet + blockExplorers: # Array: List of BlockExplorer configs + # Required fields: + - name: My Chain Explorer # String: Human-readable name for the explorer + url: https://mychain.com/explorer # String: Base URL for the explorer + apiUrl: https://mychain.com/api # String: Base URL for the explorer API + # Optional fields: + apiKey: myapikey # String: API key for the explorer (optional) + family: etherscan # ExplorerFamily: See ExplorerFamily for valid values + nativeToken: + name: Eth # String + symbol: ETH # String + decimals: 18 # Number + displayName: My Chain Name # String: Human-readable name of the chain + displayNameShort: My Chain # String: A shorter human-readable name + logoURI: https://mychain.com/logo.png # String: URI to a logo image for the chain + blocks: + confirmations: 12 # Number: Blocks to wait before considering a transaction confirmed + reorgPeriod: 100 # Number: Blocks before a transaction has a near-zero chance of reverting + estimateBlockTime: 15 # Number: Rough estimate of time per block in seconds + # transactionOverrides: # Object: Properties to include when forming transaction requests + # Any tx fields are allowed + \ No newline at end of file diff --git a/typescript/cli/examples/chains.ts b/typescript/cli/examples/chains.ts deleted file mode 100644 index 4f8532f2cf..0000000000 --- a/typescript/cli/examples/chains.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ChainMap, ChainMetadata, ProtocolType } from '@hyperlane-xyz/sdk'; - -// import { chainMetadata } from '@hyperlane-xyz/sdk'; -// A map of chain names to ChainMetadata -export const chains: ChainMap = { - // ----------- Add your chains here ----------------- - anvil1: { - name: 'anvil1', - protocol: ProtocolType.Ethereum, - // anvil default chain id - chainId: 31337, - // Used to configure a Warp Route to bridge anvil1 ETH - // to anvil2 in CI tests. - nativeToken: { - name: 'ether', - symbol: 'ETH', - decimals: 18, - }, - rpcUrls: [ - { - http: 'http://127.0.0.1:8545', - }, - ], - // You can set overrides for transaction fields here - // transactionOverrides: { - // gasLimit: 1000000 - // }, - }, - anvil2: { - name: 'anvil2', - protocol: ProtocolType.Ethereum, - chainId: 31338, - rpcUrls: [ - { - http: 'http://127.0.0.1:8555', - }, - ], - }, - // -------------------------------------------------- - // You can also override the default chain metadata (completely) - // ethereum: { - // ...chainMetadata.ethereum, - // publicRpcUrls: [ - // { - // http: 'my.custom.rpc.url', - // } - // ], - // } -}; diff --git a/typescript/cli/examples/contract-artifacts.yaml b/typescript/cli/examples/contract-artifacts.yaml new file mode 100644 index 0000000000..cbd7d6da46 --- /dev/null +++ b/typescript/cli/examples/contract-artifacts.yaml @@ -0,0 +1,20 @@ +# Artifacts representing contract addresses +# Typically not written by hand but generated as JSON by the deploy command +# Consists of a map of chain names to contract names to addresses +--- +mychainname: + storageGasOracle: "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C" + validatorAnnounce: "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" + proxyAdmin: "0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9" + mailbox: "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70" + interchainGasPaymaster: "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918" + defaultIsmInterchainGasPaymaster: "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc" + multisigIsm: "0x9bDE63104EE030d9De419EEd6bA7D14b86D6fE3f" + testRecipient: "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35" + interchainAccountIsm: "0x5e8ee6840caa4f367aff1FF28aA36D5B1b836d35" + aggregationIsmFactory: "0xc864fa3B662613cA5051f41e157d0a997f9a5A87" + routingIsmFactory: "0x1fdfD1486b8339638C6b92f8a96D698D8182D2b1" + interchainQueryRouter: "0xA837e38C3F7D509DF3a7a0fCf65E3814DB6c2618" + interchainAccountRouter: "0x9521291A43ebA3aD3FD24d610F4b7F7543C8d761" + merkleRootMultisigIsmFactory: "0x82140b2ddAd4E4dd7e1D6757Fb5F9485c230B79d" + messageIdMultisigIsmFactory: "0x1079056da3EC7D55521F27e1E094015C0d39Cc65" \ No newline at end of file diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml new file mode 100644 index 0000000000..5a7d79c358 --- /dev/null +++ b/typescript/cli/examples/multisig-ism.yaml @@ -0,0 +1,13 @@ +# A config for a multisig Interchain Security Module (ISM) +# Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts +--- +anvil1: + type: 3 # ModuleType.LEGACY_MULTISIG + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator configs + - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720" +anvil2: + type: 3 + threshold: 1 + validators: + - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720" \ No newline at end of file diff --git a/typescript/cli/examples/multisig_ism.ts b/typescript/cli/examples/multisig_ism.ts deleted file mode 100644 index 208ed5bc6c..0000000000 --- a/typescript/cli/examples/multisig_ism.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ChainMap, ModuleType, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; - -export const multisigIsmConfig: ChainMap = { - // ----------- Your chains here ----------------- - anvil1: { - type: ModuleType.LEGACY_MULTISIG, - threshold: 1, - validators: [ - // Last anvil address - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', - ], - }, - anvil2: { - type: ModuleType.LEGACY_MULTISIG, - threshold: 1, - validators: [ - // Last anvil address - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', - ], - }, -}; diff --git a/typescript/cli/package.json b/typescript/cli/package.json index b5248a26c1..fd70393b0b 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -8,6 +8,7 @@ "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", + "yaml": "^2.3.1", "yargs": "^17.7.2", "zod": "^3.21.2" }, diff --git a/typescript/cli/src/chains/config.ts b/typescript/cli/src/chains/config.ts new file mode 100644 index 0000000000..3a2d341f73 --- /dev/null +++ b/typescript/cli/src/chains/config.ts @@ -0,0 +1,43 @@ +import { + ChainMap, + ChainMetadata, + isValidChainMetadata, +} from '@hyperlane-xyz/sdk'; + +import { errorRed, logGreen } from '../logger.js'; +import { readYamlOrJson } from '../utils/files.js'; +import { getMultiProvider } from '../utils/providers.js'; + +export function readChainConfig(filepath: string) { + console.log(`Reading file configs in ${filepath}`); + const chainToMetadata = readYamlOrJson>(filepath); + + if ( + !chainToMetadata || + typeof chainToMetadata !== 'object' || + !Object.keys(chainToMetadata).length + ) { + errorRed(`No configs found in ${filepath}`); + process.exit(1); + } + + for (const [chain, metadata] of Object.entries(chainToMetadata)) { + if (!isValidChainMetadata(metadata)) { + errorRed(`Chain ${chain} has invalid metadata`); + errorRed( + `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + process.exit(1); + } + if (metadata.name !== chain) { + errorRed(`Chain ${chain} name does not match key`); + process.exit(1); + } + } + + // Ensure multiprovider accepts this metadata + getMultiProvider(chainToMetadata); + + logGreen(`All chain configs in ${filepath} are valid`); + return chainToMetadata; +} diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts index 8662bf0631..2eaccfe935 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/chains.ts @@ -1,4 +1,3 @@ -import chalk from 'chalk'; import { CommandModule } from 'yargs'; import { @@ -10,6 +9,8 @@ import { hyperlaneContractAddresses, } from '@hyperlane-xyz/sdk'; +import { log, logBlue, logGray } from '../logger.js'; + /** * Parent command */ @@ -17,8 +18,12 @@ export const chainsCommand: CommandModule = { command: 'chains', describe: 'View information about core Hyperlane chains', builder: (yargs) => - yargs.command(listCommand).command(addressesCommand).demandCommand(), - handler: () => console.log('Command required'), + yargs + .command(listCommand) + .command(addressesCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), }; /** @@ -28,17 +33,13 @@ const listCommand: CommandModule = { command: 'list', describe: 'List all core chains included in the Hyperlane SDK', handler: () => { - console.log(chalk.blue('Hyperlane core mainnet chains:')); - console.log(chalk.gray('------------------------------')); - console.log( - Mainnets.map((chain) => chainMetadata[chain].displayName).join(', '), - ); - console.log(''); - console.log(chalk.blue('Hyperlane core testnet chains:')); - console.log(chalk.gray('------------------------------')); - console.log( - Testnets.map((chain) => chainMetadata[chain].displayName).join(', '), - ); + logBlue('Hyperlane core mainnet chains:'); + logGray('------------------------------'); + log(Mainnets.map((chain) => chainMetadata[chain].displayName).join(', ')); + log(''); + logBlue('Hyperlane core testnet chains:'); + logGray('------------------------------'); + log(Testnets.map((chain) => chainMetadata[chain].displayName).join(', ')); }, }; @@ -59,13 +60,13 @@ const addressesCommand: CommandModule = { handler: (args) => { const name = args.name as CoreChainName | undefined; if (name && hyperlaneContractAddresses[name]) { - console.log(chalk.blue('Hyperlane contract addresses for:', name)); - console.log(chalk.gray('---------------------------------')); - console.log(JSON.stringify(hyperlaneContractAddresses[name], null, 2)); + logBlue('Hyperlane contract addresses for:', name); + logGray('---------------------------------'); + log(JSON.stringify(hyperlaneContractAddresses[name], null, 2)); } else { - console.log(chalk.blue('Hyperlane core contract addresses:')); - console.log(chalk.gray('----------------------------------')); - console.log(JSON.stringify(hyperlaneContractAddresses, null, 2)); + logBlue('Hyperlane core contract addresses:'); + logGray('----------------------------------'); + log(JSON.stringify(hyperlaneContractAddresses, null, 2)); } }, }; diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts new file mode 100644 index 0000000000..9bbc416ce1 --- /dev/null +++ b/typescript/cli/src/commands/config.ts @@ -0,0 +1,106 @@ +import { input } from '@inquirer/prompts'; +import select from '@inquirer/select'; +import { CommandModule } from 'yargs'; + +import { + ChainMetadata, + ProtocolType, + isValidChainMetadata, +} from '@hyperlane-xyz/sdk'; + +import { readChainConfig } from '../chains/config.js'; +import { errorRed, logBlue, logGreen } from '../logger.js'; +import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; + +/** + * Parent command + */ +export const configCommand: CommandModule = { + command: 'config', + describe: 'Create or validate Hyperlane configs', + builder: (yargs) => + yargs + .command(createCommand) + .command(validateCommand) + .version(false) + .demandCommand(), + handler: () => console.log('Command required'), +}; + +/** + * Create command + */ +const createCommand: CommandModule = { + command: 'create', + describe: 'Create a new, minimal Hyperlane config', + builder: (yargs) => + yargs.options({ + output: { + type: 'string', + alias: 'o', + description: 'Output file path', + }, + format: { + type: 'string', + alias: 'f', + description: 'Output file format', + choices: ['json', 'yaml'], + }, + }), + handler: async (argv: any) => { + const format: FileFormat = argv.format || 'yaml'; + const output: string = argv.output || `./configs/chain-config.${format}`; + logBlue('Creating a new chain config'); + const name = await input({ + message: 'Enter chain name (one word, lower case)', + }); + const chainId = await input({ message: 'Enter chain id (number)' }); + const domainId = await input({ + message: 'Enter domain id (number, often matches chainId)', + }); + const protocol = await select({ + message: 'Select protocol type', + choices: Object.values(ProtocolType).map((protocol) => ({ + name: protocol, + value: protocol, + })), + }); + const rpcUrl = await input({ message: 'Enter http or https rpc url' }); + const metadata: ChainMetadata = { + name, + chainId: parseInt(chainId, 10), + domainId: parseInt(domainId, 10), + protocol, + rpcUrls: [{ http: rpcUrl }], + }; + if (isValidChainMetadata(metadata)) { + logGreen(`Chain config is valid, writing to file ${output}`); + mergeYamlOrJson(output, { [name]: metadata }, format); + } else { + errorRed( + `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + throw new Error('Invalid chain config'); + } + }, +}; + +/** + * Validate command + */ +const validateCommand: CommandModule = { + command: 'validate', + describe: 'Validate the configs in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readChainConfig(path); + }, +}; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 4306b70321..9bc82f17fd 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -1,7 +1,8 @@ -import { confirm } from '@inquirer/prompts'; -import chalk from 'chalk'; import { CommandModule } from 'yargs'; +import { runCoreDeploy } from '../deploy/core.js'; +import { logGray } from '../logger.js'; + /** * Parent command */ @@ -9,7 +10,11 @@ export const deployCommand: CommandModule = { command: 'deploy', describe: 'Permisionslessly deploy a Hyperlane contracts or extensions', builder: (yargs) => - yargs.command(coreCommand).command(warpCommand).demandCommand(), + yargs + .command(coreCommand) + .command(warpCommand) + .version(false) + .demandCommand(), handler: () => console.log('Command required'), }; @@ -21,36 +26,29 @@ const coreCommand: CommandModule = { describe: 'Deploy core Hyperlane contracts', builder: (yargs) => yargs.options({ - local: { - type: 'string', - description: 'The chain to deploy to', - demandOption: true, - }, - remotes: { + key: { type: 'string', - array: true, description: - 'The chains with which local will send and receive messages', - demandOption: true, + 'A hex private key or seed phrase for transaction signing. Or use the HYP_KEY env var', }, - key: { + config: { type: 'string', description: - 'A hexadecimal private key or seed phrase for transaction signing', - demandOption: true, + 'A path to a JSON or YAML file with chain configs. Defaults to ./configs/chain-config.yaml', }, - config: { + out: { type: 'string', description: - 'A path to a JSON or YAML file with the chain configs. See ./examples/deploy_core_config.yml for an example.', - demandOption: true, + 'A folder name output artifacts into. Defaults to ./artifacts', }, }), - handler: async (_argv) => { - console.log(chalk.blue('Hyperlane permissionless core deployment')); - console.log(chalk.gray('----------------------------------------')); - const confirmation = await confirm({ message: 'Are you sure?' }); - if (!confirmation) throw new Error('Deployment cancelled'); + handler: (argv: any) => { + logGray('Hyperlane permissionless core deployment'); + logGray('----------------------------------------'); + const key: string = argv.key || process.env.HYP_KEY; + const configPath: string = argv.config || './configs/chain-config.yaml'; + const outPath: string = argv.out || './artifacts/'; + return runCoreDeploy({ key, configPath, outPath }); }, }; diff --git a/typescript/cli/src/config.ts b/typescript/cli/src/config.ts deleted file mode 100644 index b83e512c78..0000000000 --- a/typescript/cli/src/config.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { ethers } from 'ethers'; - -import { Mailbox__factory, OverheadIgp__factory } from '@hyperlane-xyz/core'; -import { - ChainMap, - ChainName, - CoreConfig, - GasOracleContractType, - HyperlaneAddresses, - HyperlaneAddressesMap, - HyperlaneContractsMap, - HyperlaneDeploymentArtifacts, - ModuleType, - MultiProvider, - MultisigIsmConfig, - OverheadIgpConfig, - RouterConfig, - RoutingIsmConfig, - buildAgentConfigDeprecated, - chainMetadata, - defaultMultisigIsmConfigs, - filterAddressesMap, - multisigIsmVerificationCost, - objFilter, - objMerge, -} from '@hyperlane-xyz/sdk'; -import { hyperlaneEnvironments } from '@hyperlane-xyz/sdk/dist/consts/environments'; -import { types, utils } from '@hyperlane-xyz/utils'; - -import { chains } from '../examples/chains.js'; -import { multisigIsmConfig } from '../examples/multisig_ism.js'; - -import { TestRecipientConfig } from './core/TestRecipientDeployer.js'; -import { tryReadJSON } from './json.js'; - -let multiProvider: MultiProvider; - -export function getMultiProvider() { - if (!multiProvider) { - const chainConfigs = { ...chainMetadata, ...chains }; - multiProvider = new MultiProvider(chainConfigs); - } - return multiProvider; -} -export function assertBytesN(value: string, length: number): string { - const valueWithPrefix = utils.ensure0x(value); - if ( - ethers.utils.isHexString(valueWithPrefix) && - ethers.utils.hexDataLength(valueWithPrefix) == length - ) { - return valueWithPrefix; - } - throw new Error( - `Invalid value ${value}, must be a ${length} byte hex string`, - ); -} - -export function assertBytes32(value: string): string { - return assertBytesN(value, 32); -} - -export function assertBytes20(value: string): string { - return assertBytesN(value, 20); -} - -export function assertUnique( - values: (argv: any) => string[], -): (argv: any) => void { - return (argv: any) => { - const _values = values(argv); - const hasDuplicates = new Set(_values).size !== _values.length; - if (hasDuplicates) { - throw new Error(`Must provide unique values, got ${_values}`); - } - }; -} - -export function assertBalances( - multiProvider: MultiProvider, - chainsFunc: (argv: any) => ChainName[], -): (argv: any) => Promise { - return async (argv: any) => { - const chains = chainsFunc(argv); - const signer = new ethers.Wallet(argv.key); - const address = await signer.getAddress(); - await Promise.all( - chains.map(async (chain: ChainName) => { - const balance = await multiProvider - .getProvider(chain) - .getBalance(address); - if (balance.isZero()) - throw new Error(`${address} has no balance on ${chain}`); - }), - ); - }; -} - -export function coerceAddressToBytes32(value: string): string { - if (ethers.utils.isHexString(value)) { - const length = ethers.utils.hexDataLength(value); - if (length == 32) { - return value; - } else if (length == 20) { - return utils.addressToBytes32(value); - } - } - throw new Error(`Invalid value ${value}, must be a 20 or 32 byte hex string`); -} - -export function buildIsmConfig( - owner: types.Address, - remotes: ChainName[], -): RoutingIsmConfig { - const mergedMultisigIsmConfig: ChainMap = objMerge( - defaultMultisigIsmConfigs, - multisigIsmConfig, - ); - return { - owner, - type: ModuleType.ROUTING, - domains: Object.fromEntries( - remotes.map((remote) => [remote, mergedMultisigIsmConfig[remote]]), - ), - }; -} - -export function buildIsmConfigMap( - owner: types.Address, - chains: ChainName[], - remotes: ChainName[], -): ChainMap { - return Object.fromEntries( - chains.map((chain) => { - const ismConfig = buildIsmConfig( - owner, - remotes.filter((r) => r !== chain), - ); - return [chain, ismConfig]; - }), - ); -} - -export function buildCoreConfigMap( - owner: types.Address, - local: ChainName, - remotes: ChainName[], -): ChainMap { - const configMap: ChainMap = {}; - configMap[local] = { - owner, - defaultIsm: buildIsmConfig(owner, remotes), - }; - return configMap; -} - -export function buildRouterConfigMap( - owner: types.Address, - chains: ChainName[], - addressesMap: HyperlaneAddressesMap, -): ChainMap { - const routerConfigFactories = { - mailbox: new Mailbox__factory(), - defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(), - }; - const filteredAddressesMap = filterAddressesMap( - addressesMap, - routerConfigFactories, - ); - return Object.fromEntries( - chains.map((chain) => { - const routerConfig: RouterConfig = { - owner, - mailbox: filteredAddressesMap[chain].mailbox, - interchainGasPaymaster: - filteredAddressesMap[chain].defaultIsmInterchainGasPaymaster, - }; - return [chain, routerConfig]; - }), - ); -} - -export function buildTestRecipientConfigMap( - chains: ChainName[], - addressesMap: HyperlaneAddressesMap, -): ChainMap { - return Object.fromEntries( - chains.map((chain) => { - const interchainSecurityModule = - addressesMap[chain].interchainSecurityModule ?? - ethers.constants.AddressZero; - return [chain, { interchainSecurityModule }]; - }), - ); -} - -export function buildIgpConfigMap( - owner: types.Address, - deployChains: ChainName[], - allChains: ChainName[], -): ChainMap { - const mergedMultisigIsmConfig: ChainMap = objMerge( - defaultMultisigIsmConfigs, - multisigIsmConfig, - ); - const configMap: ChainMap = {}; - for (const local of deployChains) { - const overhead: ChainMap = {}; - const gasOracleType: ChainMap = {}; - for (const remote of allChains) { - if (local === remote) continue; - overhead[remote] = multisigIsmVerificationCost( - mergedMultisigIsmConfig[remote].threshold, - mergedMultisigIsmConfig[remote].validators.length, - ); - gasOracleType[remote] = GasOracleContractType.StorageGasOracle; - } - configMap[local] = { - owner, - beneficiary: owner, - gasOracleType, - overhead, - oracleKey: 'TODO', - }; - } - return configMap; -} - -export const sdkContractAddressesMap = { - ...hyperlaneEnvironments.testnet, - ...hyperlaneEnvironments.mainnet, -}; - -export function artifactsAddressesMap(): HyperlaneContractsMap { - return ( - tryReadJSON>('./artifacts', 'addresses.json') || - {} - ); -} - -export function buildOverriddenAgentConfig( - chains: ChainName[], - multiProvider: MultiProvider, - startBlocks: ChainMap, -) { - const mergedAddressesMap: HyperlaneAddressesMap = objMerge( - sdkContractAddressesMap, - artifactsAddressesMap(), - ); - const filteredAddressesMap = objFilter( - mergedAddressesMap, - (chain, v): v is HyperlaneAddresses => - chains.includes(chain) && - !!v.mailbox && - !!v.interchainGasPaymaster && - !!v.validatorAnnounce, - ) as unknown as ChainMap; - - return buildAgentConfigDeprecated( - chains, - multiProvider, - filteredAddressesMap, - startBlocks, - ); -} diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts new file mode 100644 index 0000000000..f12a03e400 --- /dev/null +++ b/typescript/cli/src/consts.ts @@ -0,0 +1,2 @@ +export const MINIMUM_CORE_DEPLOY_BALANCE = 0.5; // Half an ETH +export const MINIMUM_WARP = 0.2; // 2/10 an ETH diff --git a/typescript/cli/src/core/HyperlanePermissionlessDeployer.ts b/typescript/cli/src/core/HyperlanePermissionlessDeployer.ts deleted file mode 100644 index 5b33b53d09..0000000000 --- a/typescript/cli/src/core/HyperlanePermissionlessDeployer.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { ethers } from 'ethers'; -import yargs from 'yargs'; - -import { - ChainMap, - ChainName, - DeployedIsm, - HyperlaneAddresses, - HyperlaneAddressesMap, - HyperlaneContractsMap, - HyperlaneCoreDeployer, - HyperlaneIgpDeployer, - HyperlaneIsmFactory, - HyperlaneIsmFactoryDeployer, - MultiProvider, - ProtocolType, - defaultMultisigIsmConfigs, - objFilter, - objMap, - objMerge, - serializeContractsMap, -} from '@hyperlane-xyz/sdk'; - -import { multisigIsmConfig } from '../../examples/multisig_ism.js'; -import { startBlocks } from '../../examples/start_blocks.js'; -import { - artifactsAddressesMap, - assertBalances, - assertBytes32, - assertUnique, - buildCoreConfigMap, - buildIgpConfigMap, - buildIsmConfigMap, - buildOverriddenAgentConfig, - buildTestRecipientConfigMap, - getMultiProvider, - sdkContractAddressesMap, -} from '../config.js'; -import { mergeJSON, writeJSON } from '../json.js'; -import { createLogger } from '../logger.js'; - -import { HyperlaneTestRecipientDeployer } from './TestRecipientDeployer.js'; - -export function getArgs(multiProvider: MultiProvider) { - // For each chain, we need: - // - ChainMetadata for the MultiProvider - // - A MultisigIsmConfig - const { intersection } = multiProvider.intersect( - Object.keys(objMerge(defaultMultisigIsmConfigs, multisigIsmConfig)), - ); - - return yargs(process.argv.slice(2)) - .describe('local', 'The chain to deploy to') - .choices('local', intersection) - .demandOption('local') - .array('remotes') - .describe( - 'remotes', - "The chains with which 'local' will be able to send and receive messages", - ) - .choices('remotes', intersection) - .demandOption('remotes') - .middleware(assertUnique((argv) => argv.remotes.concat(argv.local))) - .describe('key', 'A hexadecimal private key for transaction signing') - .string('key') - .coerce('key', assertBytes32) - .demandOption('key') - .middleware( - assertBalances(multiProvider, (argv) => - argv.remotes - .concat(argv.local) - .filter( - (chain: string) => - multiProvider.getChainMetadata(chain).protocol === - ProtocolType.Ethereum, - ), - ), - ) - .describe('write-agent-config', 'Whether or not to write agent config') - .default('write-agent-config', true) - .boolean('write-agent-config').argv; -} - -export class HyperlanePermissionlessDeployer { - constructor( - public readonly multiProvider: MultiProvider, - public readonly signer: ethers.Signer, - public readonly local: ChainName, - public readonly remotes: ChainName[], - public readonly writeAgentConfig?: boolean, - protected readonly logger = createLogger('HyperlanePermissionlessDeployer'), - ) {} - - static async fromArgs(): Promise { - const multiProvider = getMultiProvider(); - const { local, remotes, key, writeAgentConfig } = await getArgs( - multiProvider, - ); - if (remotes.includes(local)) - throw new Error('Local and remotes must be distinct'); - const signer = new ethers.Wallet(key); - multiProvider.setSharedSigner(signer); - - return new HyperlanePermissionlessDeployer( - multiProvider, - signer, - local, - remotes as unknown as string[], - writeAgentConfig, - ); - } - - skipLocalDeploy(): boolean { - return !this.isDeployableChain(this.local); - } - - remoteDeployableChains(): ChainName[] { - return this.remotes.filter((chain) => this.isDeployableChain(chain)); - } - - deployableChains(): ChainName[] { - return this.remotes - .concat([this.local]) - .filter((chain) => this.isDeployableChain(chain)); - } - - allChains(): ChainName[] { - return this.remotes.concat([this.local]); - } - - async deploy(): Promise { - let addressesMap = artifactsAddressesMap(); - const owner = await this.signer.getAddress(); - - const deployableChains = this.deployableChains(); - const remoteDeployableChains = this.remoteDeployableChains(); - const allChains = this.allChains(); - const skipLocalDeploy = this.skipLocalDeploy(); - - // 1. Deploy ISM factories to all deployable chains that don't have them. - this.logger('Deploying ISM factory contracts'); - const ismDeployer = new HyperlaneIsmFactoryDeployer(this.multiProvider); - ismDeployer.cacheAddressesMap( - objMerge(sdkContractAddressesMap, addressesMap), - ); - const ismFactoryContracts = await ismDeployer.deploy(deployableChains); - addressesMap = this.writeMergedAddresses(addressesMap, ismFactoryContracts); - this.logger(`ISM factory deployment complete`); - - // 2. Deploy IGPs to all deployable chains. - this.logger(`Deploying IGP contracts`); - const igpConfig = buildIgpConfigMap(owner, deployableChains, allChains); - const igpDeployer = new HyperlaneIgpDeployer(this.multiProvider); - igpDeployer.cacheAddressesMap(addressesMap); - const igpContracts = await igpDeployer.deploy(igpConfig); - addressesMap = this.writeMergedAddresses(addressesMap, igpContracts); - this.logger(`IGP deployment complete`); - - // Build an IsmFactory that covers all chains so that we can - // use it later to deploy ISMs to remote chains. - const ismFactory = HyperlaneIsmFactory.fromAddressesMap( - objMerge(sdkContractAddressesMap, addressesMap), - this.multiProvider, - ); - - // 3. Deploy core contracts to local chain - if (!skipLocalDeploy) { - this.logger(`Deploying core contracts to ${this.local}`); - const coreDeployer = new HyperlaneCoreDeployer( - this.multiProvider, - ismFactory, - ); - coreDeployer.cacheAddressesMap(addressesMap); - const coreConfig = buildCoreConfigMap(owner, this.local, this.remotes); - const coreContracts = await coreDeployer.deploy(coreConfig); - addressesMap = this.writeMergedAddresses(addressesMap, coreContracts); - this.logger(`Core deployment complete`); - } else { - this.logger(`Skipping core deployment to local ${this.local}`); - } - - // 4. Deploy ISM contracts to remote deployable chains - this.logger(`Deploying ISMs to ${remoteDeployableChains}`); - const ismConfigs = buildIsmConfigMap( - owner, - remoteDeployableChains, - allChains, - ); - const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = - {}; - for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { - this.logger(`Deploying ISM to ${ismChain}`); - ismContracts[ismChain] = { - interchainSecurityModule: await ismFactory.deploy(ismChain, ismConfig), - }; - } - addressesMap = this.writeMergedAddresses(addressesMap, ismContracts); - this.logger(`ISM deployment complete`); - - // 5. Deploy TestRecipients to all deployable chains - this.logger(`Deploying test recipient contracts`); - const testRecipientConfig = buildTestRecipientConfigMap( - deployableChains, - addressesMap, - ); - const testRecipientDeployer = new HyperlaneTestRecipientDeployer( - this.multiProvider, - ); - testRecipientDeployer.cacheAddressesMap(addressesMap); - const testRecipients = await testRecipientDeployer.deploy( - testRecipientConfig, - ); - addressesMap = this.writeMergedAddresses(addressesMap, testRecipients); - this.logger(`Test recipient deployment complete`); - - if (!skipLocalDeploy) { - startBlocks[this.local] = await this.multiProvider - .getProvider(this.local) - .getBlockNumber(); - } - - if (this.writeAgentConfig) { - const agentConfig = buildOverriddenAgentConfig( - deployableChains, - this.multiProvider, - startBlocks, - ); - - this.logger(`Writing agent config to artifacts/agent_config.json`); - writeJSON('./artifacts/', 'agent_config.json', agentConfig); - } - } - - protected writeMergedAddresses( - aAddresses: HyperlaneAddressesMap, - bContracts: HyperlaneContractsMap, - ): HyperlaneAddressesMap { - // Only write addresses that aren't present in the SDK - const bAddresses = serializeContractsMap(bContracts); - const mergedAddresses = objMerge(aAddresses, bAddresses); - const filteredAddresses = objMap( - mergedAddresses, - (chain: string, addresses) => - objFilter(addresses, (contract, address): address is string => { - // @ts-ignore - const chainAddresses = sdkContractAddressesMap[chain]; - return !chainAddresses || chainAddresses[contract] !== address; - }), - ); - this.logger(`Writing contract addresses to artifacts/addresses.json`); - mergeJSON( - './artifacts/', - 'addresses.json', - objFilter( - filteredAddresses, - (_, value): value is HyperlaneAddresses => !!value, - ), - ); - return mergedAddresses; - } - - isDeployableChain(chain: ChainName): boolean { - return ( - this.multiProvider.getChainMetadata(chain).protocol === - ProtocolType.Ethereum - ); - } -} diff --git a/typescript/cli/src/core/TestRecipientDeployer.ts b/typescript/cli/src/deploy/TestRecipientDeployer.ts similarity index 95% rename from typescript/cli/src/core/TestRecipientDeployer.ts rename to typescript/cli/src/deploy/TestRecipientDeployer.ts index c91b5f94e4..e71e6bd4ad 100644 --- a/typescript/cli/src/core/TestRecipientDeployer.ts +++ b/typescript/cli/src/deploy/TestRecipientDeployer.ts @@ -25,7 +25,7 @@ export const testRecipientFactories = { testRecipient: new TestRecipient__factory(), }; -export class HyperlaneTestRecipientDeployer extends HyperlaneDeployer< +export class TestRecipientDeployer extends HyperlaneDeployer< TestRecipientConfig, typeof testRecipientFactories > { diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts new file mode 100644 index 0000000000..49e9f3e982 --- /dev/null +++ b/typescript/cli/src/deploy/core.ts @@ -0,0 +1,563 @@ +import { Separator, checkbox, confirm, input } from '@inquirer/prompts'; +import select from '@inquirer/select'; +import { log } from 'console'; +import { ethers } from 'ethers'; +import fs from 'fs'; +import path from 'path'; +import { z } from 'zod'; + +import { + ChainMap, + ChainMetadata, + ChainName, + CoreConfig, + DeployedIsm, + GasOracleContractType, + HyperlaneAddresses, + HyperlaneAddressesMap, + HyperlaneContractsMap, + HyperlaneCoreDeployer, + HyperlaneDeploymentArtifacts, + HyperlaneIgpDeployer, + HyperlaneIsmFactory, + HyperlaneIsmFactoryDeployer, + ModuleType, + MultiProvider, + MultisigIsmConfig, + OverheadIgpConfig, + ProtocolType, + RoutingIsmConfig, + agentStartBlocks, + buildAgentConfig, + defaultMultisigIsmConfigs, + hyperlaneEnvironments, + mainnetChainsMetadata, + multisigIsmVerificationCost, + objFilter, + objMerge, + serializeContractsMap, + testnetChainsMetadata, +} from '@hyperlane-xyz/sdk'; +import { types } from '@hyperlane-xyz/utils'; + +import { readChainConfig } from '../chains/config.js'; +import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; +import { logBlue, logGray, logGreen } from '../logger.js'; +import { assertBalances } from '../utils/balances.js'; +import { readYamlOrJson, writeJson } from '../utils/files.js'; +import { assertSigner, keyToSigner } from '../utils/keys.js'; +import { getMultiProvider } from '../utils/providers.js'; +import { getTimestampForFilename } from '../utils/time.js'; + +import { + TestRecipientConfig, + TestRecipientDeployer, +} from './TestRecipientDeployer.js'; + +export const sdkContractAddressesMap = { + ...hyperlaneEnvironments.testnet, + ...hyperlaneEnvironments.mainnet, +}; + +export async function runCoreDeploy({ + key, + configPath, + outPath, +}: { + key: string; + configPath: string; + outPath: string; +}) { + const signer = keyToSigner(key); + const customChains = getCustomChains(configPath); + const multiProvider = getMultiProvider(customChains, signer); + + const { local, remotes, allChains } = await runChainSelectionStep( + customChains, + ); + const artifacts = await runArtifactStep(allChains); + const multisigConfig = await runIsmStep(allChains); + + const deploymentParams = { + local, + remotes, + signer, + multiProvider, + artifacts, + multisigConfig, + outPath, + }; + + await runDeployPlanStep(deploymentParams); + await runPreflightChecks(deploymentParams); + + const isConfirmed = await confirm({ + message: 'All systems ready, captain. Should we deploy?', + }); + if (!isConfirmed) throw new Error('Deployment cancelled'); + await executeDeploy(deploymentParams); +} + +async function runChainSelectionStep(customChains: ChainMap) { + const chainsToChoices = (chains: ChainMetadata[]) => + chains.map((c) => ({ name: c.name, value: c.name })); + const choices: Parameters['0']['choices'] = [ + new Separator('--Custom Chains--'), + ...chainsToChoices(Object.values(customChains)), + { name: '(New custom chain)', value: '__new__' }, + new Separator('--Mainnet Chains--'), + ...chainsToChoices(mainnetChainsMetadata), + new Separator('--Testnet Chains--'), + ...chainsToChoices(testnetChainsMetadata), + ]; + + const local = (await select({ + message: 'Select local chain (the chain to which you will deploy now)', + choices, + pageSize: 20, + })) as string; + handleNewChain([local]); + + const remotes = (await checkbox({ + message: 'Select remote chains the local will send messages to', + choices, + pageSize: 20, + })) as string[]; + handleNewChain(remotes); + if (!remotes?.length) throw new Error('No remote chains selected'); + + const allChains = [local, ...remotes]; + return { local, remotes, allChains }; +} + +async function runArtifactStep(allChains: ChainName[]) { + logBlue( + '\nDeployments 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; + + const artifactsPath = await input({ + message: 'Enter filepath with existing contract artifacts (addresses)', + }); + const artifacts = readDeploymentArtifacts(artifactsPath); + const artifactChains = Object.keys(artifacts).filter((c) => + allChains.includes(c), + ); + log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); + return artifacts; +} + +async function runIsmStep(allChains: ChainName[]) { + logBlue( + '\nHyperlane instances requires an Interchain Security Module (ISM).', + ); + const isMultisig = await confirm({ + message: 'Do you want use a Multisig ISM?', + }); + if (!isMultisig) + throw new Error( + 'Sorry, only multisig ISMs are currently supported in the CLI', + ); + + const defaultConfigChains = Object.keys(defaultMultisigIsmConfigs); + const configRequired = !!allChains.find( + (c) => !defaultConfigChains.includes(c), + ); + if (!configRequired) return; + + logGray( + 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/multisig-ism.yaml', + ); + const multisigConfigPath = await input({ + message: 'Enter filepath for the multisig config', + }); + const configs = readMultisigConfig(multisigConfigPath); + const multisigConfigChains = Object.keys(configs).filter((c) => + allChains.includes(c), + ); + log(`Found configs for chains: ${multisigConfigChains.join(', ')}`); + return configs; +} + +function getCustomChains(configPath: string) { + if (!fs.existsSync(configPath)) { + log('No config file provided, using default chains in SDK'); + return {}; + } else { + return readChainConfig(configPath); + } +} + +function handleNewChain(chainNames: string[]) { + if (chainNames.includes('__new__')) { + logBlue( + 'To choose a new chain, add them to a config file and use the --config flag', + ); + logBlue( + 'Use the "hyperlane config create" command to create new chain configs', + ); + process.exit(0); + } +} + +const GenericDeploymentArtifactsSchema = z + .object({}) + .catchall(z.object({}).catchall(z.string())); + +function readDeploymentArtifacts(filePath: string) { + const artifacts = readYamlOrJson>(filePath); + if (!artifacts) throw new Error(`No artifacts found at ${filePath}`); + const result = GenericDeploymentArtifactsSchema.safeParse(artifacts); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return artifacts; +} + +function readMultisigConfig(filePath: string) { + const config = readYamlOrJson>(filePath); + if (!config) throw new Error(`No multisig config found at ${filePath}`); + // TODO validate multisig config + return config; +} + +interface DeployParams { + local: string; + remotes: string[]; + signer: ethers.Signer; + multiProvider: MultiProvider; + artifacts?: HyperlaneContractsMap; + multiSigConfig?: ChainMap; + outPath: string; +} + +async function runDeployPlanStep({ + local, + remotes, + signer, + artifacts, +}: DeployParams) { + const address = await signer.getAddress(); + logBlue('Deployment plan:'); + logGray('===================:'); + log(`Transaction signer and contract owner will be account ${address}`); + log( + `Deploying Hyperlane to ${local} and connecting it to ${remotes.join( + ', ', + )}`, + ); + if (artifacts) { + log('But contracts with an address in the artifacts file will be skipped'); + for (const chain of [local, ...remotes]) { + const chainArtifacts = artifacts[chain]; + if (!chainArtifacts) continue; + log( + `Skipped contracts for ${chain}: ${Object.keys(chainArtifacts).join( + ', ', + )}`, + ); + } + } + log(`The interchain security module will be a Multisig.`); + const isConfirmed = await confirm({ + message: 'Is this deployment plan correct?', + }); + if (!isConfirmed) throw new Error('Deployment cancelled'); +} + +async function runPreflightChecks({ + local, + remotes, + signer, + multiProvider, +}: DeployParams) { + log('Running pre-flight checks...'); + + if (!local || !remotes?.length) throw new Error('Invalid chain selection'); + if (remotes.includes(local)) + throw new Error('Local and remotes must be distinct'); + for (const chain of [local, ...remotes]) { + const metadata = multiProvider.tryGetChainMetadata(chain); + if (!metadata) throw new Error(`No chain config found for ${chain}`); + if (metadata.protocol !== ProtocolType.Ethereum) + throw new Error('Only Ethereum chains are supported for now'); + } + logGreen('Chains are valid ✅'); + + assertSigner(signer); + logGreen('Signer is valid ✅'); + + await assertBalances( + multiProvider, + signer, + [local, ...remotes], + MINIMUM_CORE_DEPLOY_BALANCE, + ); + logGreen('Balances are sufficient ✅'); +} + +async function executeDeploy({ + local, + remotes, + signer, + multiProvider, + outPath, + artifacts = {}, + multiSigConfig = {}, +}: DeployParams) { + const { contractsFilePath, agentFilePath } = + prepNewArtifactsFilePaths(outPath); + + const owner = await signer.getAddress(); + const allChains = [local, ...remotes]; + + // 1. Deploy ISM factories to all deployable chains that don't have them. + logBlue('Deploying ISM factory contracts'); + const ismDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); + ismDeployer.cacheAddressesMap(objMerge(sdkContractAddressesMap, artifacts)); + const ismFactoryContracts = await ismDeployer.deploy(allChains); + console.log(ismFactoryContracts); + artifacts = writeMergedAddresses( + contractsFilePath, + artifacts, + ismFactoryContracts, + ); + logBlue(`ISM factory deployment complete`); + + // 2. Deploy IGPs to all deployable chains. + logBlue(`Deploying IGP contracts`); + const igpConfig = buildIgpConfigMap( + owner, + allChains, + allChains, + multiSigConfig, + ); + const igpDeployer = new HyperlaneIgpDeployer(multiProvider); + igpDeployer.cacheAddressesMap(artifacts); + const igpContracts = await igpDeployer.deploy(igpConfig); + console.log(igpContracts); + artifacts = writeMergedAddresses(contractsFilePath, artifacts, igpContracts); + logBlue(`IGP deployment complete`); + + // Build an IsmFactory that covers all chains so that we can + // use it later to deploy ISMs to remote chains. + const ismFactory = HyperlaneIsmFactory.fromAddressesMap( + objMerge(sdkContractAddressesMap, artifacts), + multiProvider, + ); + + // 3. Deploy core contracts to local chain + logBlue(`Deploying core contracts to ${local}`); + const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); + coreDeployer.cacheAddressesMap(artifacts); + const coreConfig = buildCoreConfigMap(owner, local, remotes, multiSigConfig); + const coreContracts = await coreDeployer.deploy(coreConfig); + console.log(coreContracts); + artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); + logBlue(`Core deployment complete`); + + // 4. Deploy ISM contracts to remote deployable chains + logBlue(`Deploying ISMs to ${remotes}`); + const ismConfigs = buildIsmConfigMap( + owner, + remotes, + allChains, + multiSigConfig, + ); + const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = {}; + for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { + logBlue(`Deploying ISM to ${ismChain}`); + ismContracts[ismChain] = { + interchainSecurityModule: await ismFactory.deploy(ismChain, ismConfig), + }; + } + artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); + logBlue(`ISM deployment complete`); + + // 5. Deploy TestRecipients to all deployable chains + logBlue(`Deploying test recipient contracts`); + const testRecipientConfig = buildTestRecipientConfigMap(allChains, artifacts); + const testRecipientDeployer = new TestRecipientDeployer(multiProvider); + testRecipientDeployer.cacheAddressesMap(artifacts); + const testRecipients = await testRecipientDeployer.deploy( + testRecipientConfig, + ); + console.log(testRecipients); + artifacts = writeMergedAddresses( + contractsFilePath, + artifacts, + testRecipients, + ); + logBlue(`Test recipient deployment complete`); + + await writeAgentConfig( + agentFilePath, + artifacts, + local, + remotes, + multiProvider, + ); + + logBlue(`Writing agent config to artifacts/agent_config.json`); +} + +function buildIsmConfig( + owner: types.Address, + remotes: ChainName[], + multisigIsmConfigs: ChainMap, +): RoutingIsmConfig { + const mergedMultisigIsmConfig: ChainMap = objMerge( + defaultMultisigIsmConfigs, + multisigIsmConfigs, + ); + return { + owner, + type: ModuleType.ROUTING, + domains: Object.fromEntries( + remotes.map((remote) => [remote, mergedMultisigIsmConfig[remote]]), + ), + }; +} + +function buildIsmConfigMap( + owner: types.Address, + chains: ChainName[], + remotes: ChainName[], + multisigIsmConfigs: ChainMap, +): ChainMap { + return Object.fromEntries( + chains.map((chain) => { + const ismConfig = buildIsmConfig( + owner, + remotes.filter((r) => r !== chain), + multisigIsmConfigs, + ); + return [chain, ismConfig]; + }), + ); +} + +function buildCoreConfigMap( + owner: types.Address, + local: ChainName, + remotes: ChainName[], + multisigIsmConfigs: ChainMap, +): ChainMap { + const configMap: ChainMap = {}; + configMap[local] = { + owner, + defaultIsm: buildIsmConfig(owner, remotes, multisigIsmConfigs), + }; + return configMap; +} + +function buildTestRecipientConfigMap( + chains: ChainName[], + addressesMap: HyperlaneAddressesMap, +): ChainMap { + return Object.fromEntries( + chains.map((chain) => { + const interchainSecurityModule = + addressesMap[chain].interchainSecurityModule ?? + ethers.constants.AddressZero; + return [chain, { interchainSecurityModule }]; + }), + ); +} + +function buildIgpConfigMap( + owner: types.Address, + deployChains: ChainName[], + allChains: ChainName[], + multisigIsmConfigs: ChainMap, +): ChainMap { + const mergedMultisigIsmConfig: ChainMap = objMerge( + defaultMultisigIsmConfigs, + multisigIsmConfigs, + ); + const configMap: ChainMap = {}; + for (const local of deployChains) { + const overhead: ChainMap = {}; + const gasOracleType: ChainMap = {}; + for (const remote of allChains) { + if (local === remote) continue; + overhead[remote] = multisigIsmVerificationCost( + mergedMultisigIsmConfig[remote].threshold, + mergedMultisigIsmConfig[remote].validators.length, + ); + gasOracleType[remote] = GasOracleContractType.StorageGasOracle; + } + configMap[local] = { + owner, + beneficiary: owner, + gasOracleType, + overhead, + oracleKey: 'TODO', + }; + } + return configMap; +} + +function writeMergedAddresses( + filePath: string, + aAddresses: HyperlaneAddressesMap, + bContracts: HyperlaneContractsMap, +): HyperlaneAddressesMap { + const bAddresses = serializeContractsMap(bContracts); + const mergedAddresses = objMerge(aAddresses, bAddresses); + writeJson(filePath, mergedAddresses); + return mergedAddresses; +} + +function prepNewArtifactsFilePaths(outPath: string) { + const timestamp = getTimestampForFilename(); + const contractsFilePath = path.join( + outPath, + `core-deployment-${timestamp}.json`, + ); + const agentFilePath = path.join(outPath, `agent-config-${timestamp}.json`); + // Write an empty object to the file to ensure permissions are okay + writeJson(contractsFilePath, {}); + logBlue(`Contract address artifacts will be written to ${contractsFilePath}`); + logBlue(`Agent configs will be written to ${agentFilePath}`); + return { contractsFilePath, agentFilePath }; +} + +async function writeAgentConfig( + filePath: string, + artifacts: HyperlaneAddressesMap, + local: ChainName, + remotes: ChainName[], + multiProvider: MultiProvider, +) { + const allChains = [local, ...remotes]; + const startBlocks: ChainMap = { ...agentStartBlocks }; + startBlocks[local] = await multiProvider.getProvider(local).getBlockNumber(); + + const mergedAddressesMap: HyperlaneAddressesMap = objMerge( + sdkContractAddressesMap, + artifacts, + ); + const filteredAddressesMap = objFilter( + mergedAddressesMap, + (chain, v): v is HyperlaneAddresses => + allChains.includes(chain) && + !!v.mailbox && + !!v.interchainGasPaymaster && + !!v.validatorAnnounce, + ) as ChainMap; + + const agentConfig = buildAgentConfig( + allChains, + multiProvider, + filteredAddressesMap, + startBlocks, + ); + writeJson(filePath, agentConfig); +} diff --git a/typescript/cli/src/json.ts b/typescript/cli/src/json.ts deleted file mode 100644 index 39dbaaf9db..0000000000 --- a/typescript/cli/src/json.ts +++ /dev/null @@ -1,55 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -import { objMerge } from '@hyperlane-xyz/sdk'; - -export function readFileAtPath(filepath: string) { - if (!fs.existsSync(filepath)) { - throw Error(`file doesn't exist at ${filepath}`); - } - return fs.readFileSync(filepath, 'utf8'); -} - -export function readJSONAtPath(filepath: string): T { - return JSON.parse(readFileAtPath(filepath)) as T; -} - -export function readJSON(directory: string, filename: string): T { - return readJSONAtPath(path.join(directory, filename)); -} - -export function tryReadJSON(directory: string, filename: string): T | null { - try { - return readJSONAtPath(path.join(directory, filename)) as T; - } catch (error) { - return null; - } -} - -export function writeFileAtPath( - directory: string, - filename: string, - value: string, -) { - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }); - } - fs.writeFileSync(path.join(directory, filename), value); -} - -export function writeJSON(directory: string, filename: string, obj: any) { - writeFileAtPath(directory, filename, JSON.stringify(obj, null, 2) + '\n'); -} - -export function mergeJSON>( - directory: string, - filename: string, - obj: T, -) { - if (fs.existsSync(path.join(directory, filename))) { - const previous = readJSON(directory, filename); - writeJSON(directory, filename, objMerge(previous, obj)); - } else { - writeJSON(directory, filename, obj); - } -} diff --git a/typescript/cli/src/logger.ts b/typescript/cli/src/logger.ts index e39d4c68b7..0fcce4c9db 100644 --- a/typescript/cli/src/logger.ts +++ b/typescript/cli/src/logger.ts @@ -1,3 +1,4 @@ +import chalk from 'chalk'; import debug from 'debug'; const HYPERLANE_NS = 'hyperlane'; @@ -19,3 +20,13 @@ const hypNamespaces = `${HYPERLANE_NS},${HYPERLANE_NS}:*`; debug.enable( otherNamespaces ? `${otherNamespaces},${hypNamespaces}` : `${hypNamespaces}`, ); + +// Colored logs directly to console +export const logBlue = (...args: any) => console.log(chalk.blue(...args)); +export const logPink = (...args: any) => + console.log(chalk.magentaBright(...args)); +export const logGray = (...args: any) => console.log(chalk.gray(...args)); +export const logGreen = (...args: any) => console.log(chalk.green(...args)); +export const logRed = (...args: any) => console.log(chalk.red(...args)); +export const errorRed = (...args: any) => console.error(chalk.red(...args)); +export const log = (...args: any) => console.log(...args); diff --git a/typescript/cli/src/test/test-messages.ts b/typescript/cli/src/test/test-messages.ts index 07e48a7131..7c6b391ea5 100644 --- a/typescript/cli/src/test/test-messages.ts +++ b/typescript/cli/src/test/test-messages.ts @@ -1,149 +1,149 @@ -import { ethers } from 'ethers'; -import yargs from 'yargs'; +// import { ethers } from 'ethers'; +// import yargs from 'yargs'; -import { - DispatchedMessage, - HyperlaneCore, - HyperlaneIgp, - MultiProvider, - objMerge, -} from '@hyperlane-xyz/sdk'; -import { utils } from '@hyperlane-xyz/utils'; +// import { +// DispatchedMessage, +// HyperlaneCore, +// HyperlaneIgp, +// MultiProvider, +// objMerge, +// } from '@hyperlane-xyz/sdk'; +// import { utils } from '@hyperlane-xyz/utils'; -import { - artifactsAddressesMap, - assertBalances, - assertBytes32, - assertUnique, - getMultiProvider, - sdkContractAddressesMap, -} from '../config.js'; -import { createLogger } from '../logger.js'; +// import { +// artifactsAddressesMap, +// assertBalances, +// assertBytes32, +// assertUnique, +// getMultiProvider, +// sdkContractAddressesMap, +// } from '../config.js'; +// import { createLogger } from '../logger.js'; -import { run } from './run.js'; +// import { run } from './run.js'; -const logger = createLogger('MessageDeliveryTest'); -const error = createLogger('MessageDeliveryTest', true); -const mergedContractAddresses = objMerge( - sdkContractAddressesMap, - artifactsAddressesMap(), -); +// const logger = createLogger('MessageDeliveryTest'); +// const error = createLogger('MessageDeliveryTest', true); +// const mergedContractAddresses = objMerge( +// sdkContractAddressesMap, +// artifactsAddressesMap(), +// ); -function getArgs(multiProvider: MultiProvider) { - // Only accept chains for which we have both a connection and contract addresses - const { intersection } = multiProvider.intersect( - Object.keys(mergedContractAddresses), - ); - return yargs(process.argv.slice(2)) - .describe('chains', 'chain to send message from') - .choices('chains', intersection) - .demandOption('chains') - .array('chains') - .middleware(assertUnique((argv) => argv.chains)) - .describe('key', 'hexadecimal private key for transaction signing') - .string('key') - .coerce('key', assertBytes32) - .demandOption('key') - .describe('timeout', 'timeout in seconds') - .number('timeout') - .default('timeout', 10 * 60) - .middleware(assertBalances(multiProvider, (argv) => argv.chains)).argv; -} +// function getArgs(multiProvider: MultiProvider) { +// // Only accept chains for which we have both a connection and contract addresses +// const { intersection } = multiProvider.intersect( +// Object.keys(mergedContractAddresses), +// ); +// return yargs(process.argv.slice(2)) +// .describe('chains', 'chain to send message from') +// .choices('chains', intersection) +// .demandOption('chains') +// .array('chains') +// .middleware(assertUnique((argv) => argv.chains)) +// .describe('key', 'hexadecimal private key for transaction signing') +// .string('key') +// .coerce('key', assertBytes32) +// .demandOption('key') +// .describe('timeout', 'timeout in seconds') +// .number('timeout') +// .default('timeout', 10 * 60) +// .middleware(assertBalances(multiProvider, (argv) => argv.chains)).argv; +// } -run('Message delivery test', async () => { - let timedOut = false; - const multiProvider = getMultiProvider(); - const { chains, key, timeout } = await getArgs(multiProvider); - const timeoutId = setTimeout(() => { - timedOut = true; - }, timeout * 1000); - const signer = new ethers.Wallet(key); - multiProvider.setSharedSigner(signer); - const core = HyperlaneCore.fromAddressesMap( - mergedContractAddresses, - multiProvider, - ); - const igp = HyperlaneIgp.fromAddressesMap( - mergedContractAddresses, - multiProvider, - ); - const messages: Set = new Set(); - for (const origin of chains) { - const mailbox = core.getContracts(origin).mailbox; - const defaultIgp = - igp.getContracts(origin).defaultIsmInterchainGasPaymaster; - for (const destination of chains) { - const destinationDomain = multiProvider.getDomainId(destination); - if (origin === destination) continue; - try { - const recipient = mergedContractAddresses[destination] - .testRecipient as string; - if (!recipient) { - throw new Error(`Unable to find TestRecipient for ${destination}`); - } - const messageTx = await mailbox.dispatch( - destinationDomain, - utils.addressToBytes32(recipient), - '0xdeadbeef', - ); - const messageReceipt = await multiProvider.handleTx(origin, messageTx); - const dispatchedMessages = core.getDispatchedMessages(messageReceipt); - if (dispatchedMessages.length !== 1) continue; - const dispatchedMessage = dispatchedMessages[0]; - logger( - `Sent message from ${origin} to ${recipient} on ${destination} with message ID ${dispatchedMessage.id}`, - ); - // Make gas payment... - const gasAmount = 100_000; - const value = await defaultIgp.quoteGasPayment( - destinationDomain, - gasAmount, - ); - const paymentTx = await defaultIgp.payForGas( - dispatchedMessage.id, - destinationDomain, - gasAmount, - await multiProvider.getSignerAddress(origin), - { value }, - ); - await paymentTx.wait(); - messages.add(dispatchedMessage); - } catch (e) { - error( - `Encountered error sending message from ${origin} to ${destination}`, - ); - error(e); - } - } - } - while (messages.size > 0 && !timedOut) { - for (const message of messages.values()) { - const origin = multiProvider.getChainName(message.parsed.origin); - const destination = multiProvider.getChainName( - message.parsed.destination, - ); - const mailbox = core.getContracts(destination).mailbox; - const delivered = await mailbox.delivered(message.id); - if (delivered) { - messages.delete(message); - logger( - `Message from ${origin} to ${destination} with ID ${ - message!.id - } was delivered`, - ); - } else { - logger( - `Message from ${origin} to ${destination} with ID ${ - message!.id - } has not yet been delivered`, - ); - } - await utils.sleep(5000); - } - } - clearTimeout(timeoutId); - if (timedOut) { - error('Timed out waiting for messages to be delivered'); - process.exit(1); - } -}); +// run('Message delivery test', async () => { +// let timedOut = false; +// const multiProvider = getMultiProvider(); +// const { chains, key, timeout } = await getArgs(multiProvider); +// const timeoutId = setTimeout(() => { +// timedOut = true; +// }, timeout * 1000); +// const signer = new ethers.Wallet(key); +// multiProvider.setSharedSigner(signer); +// const core = HyperlaneCore.fromAddressesMap( +// mergedContractAddresses, +// multiProvider, +// ); +// const igp = HyperlaneIgp.fromAddressesMap( +// mergedContractAddresses, +// multiProvider, +// ); +// const messages: Set = new Set(); +// for (const origin of chains) { +// const mailbox = core.getContracts(origin).mailbox; +// const defaultIgp = +// igp.getContracts(origin).defaultIsmInterchainGasPaymaster; +// for (const destination of chains) { +// const destinationDomain = multiProvider.getDomainId(destination); +// if (origin === destination) continue; +// try { +// const recipient = mergedContractAddresses[destination] +// .testRecipient as string; +// if (!recipient) { +// throw new Error(`Unable to find TestRecipient for ${destination}`); +// } +// const messageTx = await mailbox.dispatch( +// destinationDomain, +// utils.addressToBytes32(recipient), +// '0xdeadbeef', +// ); +// const messageReceipt = await multiProvider.handleTx(origin, messageTx); +// const dispatchedMessages = core.getDispatchedMessages(messageReceipt); +// if (dispatchedMessages.length !== 1) continue; +// const dispatchedMessage = dispatchedMessages[0]; +// logger( +// `Sent message from ${origin} to ${recipient} on ${destination} with message ID ${dispatchedMessage.id}`, +// ); +// // Make gas payment... +// const gasAmount = 100_000; +// const value = await defaultIgp.quoteGasPayment( +// destinationDomain, +// gasAmount, +// ); +// const paymentTx = await defaultIgp.payForGas( +// dispatchedMessage.id, +// destinationDomain, +// gasAmount, +// await multiProvider.getSignerAddress(origin), +// { value }, +// ); +// await paymentTx.wait(); +// messages.add(dispatchedMessage); +// } catch (e) { +// error( +// `Encountered error sending message from ${origin} to ${destination}`, +// ); +// error(e); +// } +// } +// } +// while (messages.size > 0 && !timedOut) { +// for (const message of messages.values()) { +// const origin = multiProvider.getChainName(message.parsed.origin); +// const destination = multiProvider.getChainName( +// message.parsed.destination, +// ); +// const mailbox = core.getContracts(destination).mailbox; +// const delivered = await mailbox.delivered(message.id); +// if (delivered) { +// messages.delete(message); +// logger( +// `Message from ${origin} to ${destination} with ID ${ +// message!.id +// } was delivered`, +// ); +// } else { +// logger( +// `Message from ${origin} to ${destination} with ID ${ +// message!.id +// } has not yet been delivered`, +// ); +// } +// await utils.sleep(5000); +// } +// } +// clearTimeout(timeoutId); +// if (timedOut) { +// error('Timed out waiting for messages to be delivered'); +// process.exit(1); +// } +// }); diff --git a/typescript/cli/src/test/test-warp-transfer.ts b/typescript/cli/src/test/test-warp-transfer.ts index 74122a4890..335df3658c 100644 --- a/typescript/cli/src/test/test-warp-transfer.ts +++ b/typescript/cli/src/test/test-warp-transfer.ts @@ -1,228 +1,228 @@ -import assert from 'assert'; -import { BigNumber, ContractReceipt, ethers } from 'ethers'; -import yargs from 'yargs'; +// import assert from 'assert'; +// import { BigNumber, ContractReceipt, ethers } from 'ethers'; +// import yargs from 'yargs'; -import { - ERC20__factory, - HypERC20, - HypERC20App, - HypERC20Collateral, - HypERC20Collateral__factory, - HypERC20__factory, - HypNative, - HypNative__factory, - TokenType, -} from '@hyperlane-xyz/hyperlane-token'; -import { - ChainMap, - HyperlaneCore, - MultiProvider, - objMap, - objMerge, -} from '@hyperlane-xyz/sdk'; -import { utils } from '@hyperlane-xyz/utils'; +// import { +// ERC20__factory, +// HypERC20, +// HypERC20App, +// HypERC20Collateral, +// HypERC20Collateral__factory, +// HypERC20__factory, +// HypNative, +// HypNative__factory, +// TokenType, +// } from '@hyperlane-xyz/hyperlane-token'; +// import { +// ChainMap, +// HyperlaneCore, +// MultiProvider, +// objMap, +// objMerge, +// } from '@hyperlane-xyz/sdk'; +// import { utils } from '@hyperlane-xyz/utils'; -import { - artifactsAddressesMap, - assertBalances, - assertBytes20, - assertBytes32, - getMultiProvider, - sdkContractAddressesMap, -} from '../config.js'; -import { readJSONAtPath } from '../json.js'; -import { createLogger } from '../logger.js'; -import { WarpRouteArtifacts } from '../warp/WarpRouteDeployer.js'; +// import { +// artifactsAddressesMap, +// assertBalances, +// assertBytes20, +// assertBytes32, +// getMultiProvider, +// sdkContractAddressesMap, +// } from '../config.js'; +// import { createLogger } from '../logger.js'; +// import { readJson } from '../utils/files.js'; +// import { WarpRouteArtifacts } from '../warp/WarpRouteDeployer.js'; -import { run } from './run.js'; +// import { run } from './run.js'; -const logger = createLogger('WarpTransferTest'); -const error = createLogger('WarpTransferTest', true); +// const logger = createLogger('WarpTransferTest'); +// const error = createLogger('WarpTransferTest', true); -const mergedContractAddresses = objMerge( - sdkContractAddressesMap, - artifactsAddressesMap(), -); +// const mergedContractAddresses = objMerge( +// sdkContractAddressesMap, +// artifactsAddressesMap(), +// ); -function getArgs(multiProvider: MultiProvider) { - // Only accept chains for which we have both a connection and contract addresses - const { intersection } = multiProvider.intersect( - Object.keys(mergedContractAddresses), - ); - return yargs(process.argv.slice(2)) - .describe('origin', 'chain to send tokens from') - .choices('origin', intersection) - .demandOption('origin') - .string('origin') - .describe('destination', 'chain to send tokens to') - .choices('destination', intersection) - .demandOption('destination') - .string('destination') - .describe('wei', 'amount in wei to send') - .demandOption('wei') - .number('wei') - .describe('key', 'hexadecimal private key for transaction signing') - .string('key') - .coerce('key', assertBytes32) - .demandOption('key') - .describe('recipient', 'token recipient address') - .string('recipient') - .coerce('recipient', assertBytes20) - .demandOption('recipient') - .describe('timeout', 'timeout in seconds') - .number('timeout') - .default('timeout', 10 * 60) - .middleware(assertBalances(multiProvider, (argv) => [argv.origin])).argv; -} +// function getArgs(multiProvider: MultiProvider) { +// // Only accept chains for which we have both a connection and contract addresses +// const { intersection } = multiProvider.intersect( +// Object.keys(mergedContractAddresses), +// ); +// return yargs(process.argv.slice(2)) +// .describe('origin', 'chain to send tokens from') +// .choices('origin', intersection) +// .demandOption('origin') +// .string('origin') +// .describe('destination', 'chain to send tokens to') +// .choices('destination', intersection) +// .demandOption('destination') +// .string('destination') +// .describe('wei', 'amount in wei to send') +// .demandOption('wei') +// .number('wei') +// .describe('key', 'hexadecimal private key for transaction signing') +// .string('key') +// .coerce('key', assertBytes32) +// .demandOption('key') +// .describe('recipient', 'token recipient address') +// .string('recipient') +// .coerce('recipient', assertBytes20) +// .demandOption('recipient') +// .describe('timeout', 'timeout in seconds') +// .number('timeout') +// .default('timeout', 10 * 60) +// .middleware(assertBalances(multiProvider, (argv) => [argv.origin])).argv; +// } -function hypErc20FromAddressesMap( - artifactsMap: ChainMap, - multiProvider: MultiProvider, -): HypERC20App { - const contractsMap = objMap(artifactsMap, (chain, artifacts) => { - const signer = multiProvider.getSigner(chain); - switch (artifacts.tokenType) { - case TokenType.collateral: { - const router = HypERC20Collateral__factory.connect( - artifacts.router, - signer, - ); - return { router }; - } - case TokenType.native: { - const router = HypNative__factory.connect(artifacts.router, signer); - return { router }; - } - case TokenType.synthetic: { - const router = HypERC20__factory.connect(artifacts.router, signer); - return { router }; - } - default: { - throw new Error('Unsupported token type'); - } - } - }); - return new HypERC20App(contractsMap, multiProvider); -} +// function hypErc20FromAddressesMap( +// artifactsMap: ChainMap, +// multiProvider: MultiProvider, +// ): HypERC20App { +// const contractsMap = objMap(artifactsMap, (chain, artifacts) => { +// const signer = multiProvider.getSigner(chain); +// switch (artifacts.tokenType) { +// case TokenType.collateral: { +// const router = HypERC20Collateral__factory.connect( +// artifacts.router, +// signer, +// ); +// return { router }; +// } +// case TokenType.native: { +// const router = HypNative__factory.connect(artifacts.router, signer); +// return { router }; +// } +// case TokenType.synthetic: { +// const router = HypERC20__factory.connect(artifacts.router, signer); +// return { router }; +// } +// default: { +// throw new Error('Unsupported token type'); +// } +// } +// }); +// return new HypERC20App(contractsMap, multiProvider); +// } -run('Warp transfer test', async () => { - let timedOut = false; - const multiProvider = getMultiProvider(); - const { recipient, origin, destination, wei, key, timeout } = await getArgs( - multiProvider, - ); - const timeoutId = setTimeout(() => { - timedOut = true; - }, timeout * 1000); - const signer = new ethers.Wallet(key); - multiProvider.setSharedSigner(signer); - const artifacts: ChainMap = readJSONAtPath( - './artifacts/warp-token-addresses.json', - ); - const app = hypErc20FromAddressesMap(artifacts, multiProvider); +// run('Warp transfer test', async () => { +// let timedOut = false; +// const multiProvider = getMultiProvider(); +// const { recipient, origin, destination, wei, key, timeout } = await getArgs( +// multiProvider, +// ); +// const timeoutId = setTimeout(() => { +// timedOut = true; +// }, timeout * 1000); +// const signer = new ethers.Wallet(key); +// multiProvider.setSharedSigner(signer); +// const artifacts: ChainMap = readJson( +// './artifacts/warp-token-addresses.json', +// ); +// const app = hypErc20FromAddressesMap(artifacts, multiProvider); - const getDestinationBalance = async (): Promise => { - switch (artifacts[destination].tokenType) { - case TokenType.collateral: { - const router = app.getContracts(destination) - .router as HypERC20Collateral; - const tokenAddress = await router.wrappedToken(); - const token = ERC20__factory.connect(tokenAddress, signer); - return token.balanceOf(recipient); - } - case TokenType.native: { - return multiProvider.getProvider(destination).getBalance(recipient); - } - case TokenType.synthetic: { - const router = app.getContracts(destination).router as HypERC20; - return router.balanceOf(recipient); - } - default: { - throw new Error('Unsupported collateral type'); - } - } - }; - const balanceBefore = await getDestinationBalance(); +// const getDestinationBalance = async (): Promise => { +// switch (artifacts[destination].tokenType) { +// case TokenType.collateral: { +// const router = app.getContracts(destination) +// .router as HypERC20Collateral; +// const tokenAddress = await router.wrappedToken(); +// const token = ERC20__factory.connect(tokenAddress, signer); +// return token.balanceOf(recipient); +// } +// case TokenType.native: { +// return multiProvider.getProvider(destination).getBalance(recipient); +// } +// case TokenType.synthetic: { +// const router = app.getContracts(destination).router as HypERC20; +// return router.balanceOf(recipient); +// } +// default: { +// throw new Error('Unsupported collateral type'); +// } +// } +// }; +// const balanceBefore = await getDestinationBalance(); - const core = HyperlaneCore.fromAddressesMap( - mergedContractAddresses, - multiProvider, - ); +// const core = HyperlaneCore.fromAddressesMap( +// mergedContractAddresses, +// multiProvider, +// ); - let receipt: ContractReceipt; - switch (artifacts[origin].tokenType) { - case TokenType.collateral: { - const router = app.getContracts(origin).router as HypERC20Collateral; - const tokenAddress = await router.wrappedToken(); - const token = ERC20__factory.connect(tokenAddress, signer); - const approval = await token.allowance( - await signer.getAddress(), - router.address, - ); - if (approval.lt(wei)) { - await token.approve(router.address, wei); - } - receipt = await app.transfer( - origin, - destination, - utils.addressToBytes32(recipient), - wei, - ); - break; - } - case TokenType.native: { - const destinationDomain = multiProvider.getDomainId(destination); - const router = app.getContracts(origin).router as HypNative; - const gasPayment = await router.quoteGasPayment(destinationDomain); - const value = gasPayment.add(wei); - const tx = await router.transferRemote( - destinationDomain, - utils.addressToBytes32(recipient), - wei, - { value }, - ); - receipt = await tx.wait(); - break; - } - case TokenType.synthetic: { - receipt = await app.transfer( - origin, - destination, - utils.addressToBytes32(recipient), - wei, - ); - break; - } - default: { - throw new Error('Unsupported token type'); - } - } +// let receipt: ContractReceipt; +// switch (artifacts[origin].tokenType) { +// case TokenType.collateral: { +// const router = app.getContracts(origin).router as HypERC20Collateral; +// const tokenAddress = await router.wrappedToken(); +// const token = ERC20__factory.connect(tokenAddress, signer); +// const approval = await token.allowance( +// await signer.getAddress(), +// router.address, +// ); +// if (approval.lt(wei)) { +// await token.approve(router.address, wei); +// } +// receipt = await app.transfer( +// origin, +// destination, +// utils.addressToBytes32(recipient), +// wei, +// ); +// break; +// } +// case TokenType.native: { +// const destinationDomain = multiProvider.getDomainId(destination); +// const router = app.getContracts(origin).router as HypNative; +// const gasPayment = await router.quoteGasPayment(destinationDomain); +// const value = gasPayment.add(wei); +// const tx = await router.transferRemote( +// destinationDomain, +// utils.addressToBytes32(recipient), +// wei, +// { value }, +// ); +// receipt = await tx.wait(); +// break; +// } +// case TokenType.synthetic: { +// receipt = await app.transfer( +// origin, +// destination, +// utils.addressToBytes32(recipient), +// wei, +// ); +// break; +// } +// default: { +// throw new Error('Unsupported token type'); +// } +// } - const messages = await core.getDispatchedMessages(receipt); - const message = messages[0]; - const msgDestination = multiProvider.getChainName(message.parsed.destination); - assert(destination === msgDestination); +// const messages = await core.getDispatchedMessages(receipt); +// const message = messages[0]; +// const msgDestination = multiProvider.getChainName(message.parsed.destination); +// assert(destination === msgDestination); - while ( - !(await core.getContracts(destination).mailbox.delivered(message.id)) && - !timedOut - ) { - logger(`Waiting for message delivery on destination chain`); - await utils.sleep(1000); - } +// while ( +// !(await core.getContracts(destination).mailbox.delivered(message.id)) && +// !timedOut +// ) { +// logger(`Waiting for message delivery on destination chain`); +// await utils.sleep(1000); +// } - if (!timedOut) { - logger(`Message delivered on destination chain!`); - const balanceAfter = await getDestinationBalance(); - if (!balanceAfter.gt(balanceBefore)) { - throw new Error('Destination chain balance did not increase'); - } - logger(`Confirmed balance increase`); - } +// if (!timedOut) { +// logger(`Message delivered on destination chain!`); +// const balanceAfter = await getDestinationBalance(); +// if (!balanceAfter.gt(balanceBefore)) { +// throw new Error('Destination chain balance did not increase'); +// } +// logger(`Confirmed balance increase`); +// } - clearTimeout(timeoutId); - if (timedOut) { - error('Timed out waiting for messages to be delivered'); - process.exit(1); - } -}); +// clearTimeout(timeoutId); +// if (timedOut) { +// error('Timed out waiting for messages to be delivered'); +// process.exit(1); +// } +// }); diff --git a/typescript/cli/src/utils/balances.ts b/typescript/cli/src/utils/balances.ts new file mode 100644 index 0000000000..b9746b11aa --- /dev/null +++ b/typescript/cli/src/utils/balances.ts @@ -0,0 +1,25 @@ +import { ethers } from 'ethers'; + +import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; + +export async function assertBalances( + multiProvider: MultiProvider, + signer: ethers.Signer, + chains: ChainName[], + minBalance = 0, +) { + const address = await signer.getAddress(); + const minBalanceWei = ethers.utils.parseEther(minBalance.toString()); + await Promise.all( + chains.map(async (chain) => { + const balanceWei = await multiProvider + .getProvider(chain) + .getBalance(address); + const balance = ethers.utils.formatEther(balanceWei); + if (balanceWei.lte(minBalanceWei)) + throw new Error( + `${address} has insufficient balance on ${chain}. At least ${minBalance} required but found ${balance.toString()} ETH`, + ); + }), + ); +} diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts new file mode 100644 index 0000000000..e33ee0fe13 --- /dev/null +++ b/typescript/cli/src/utils/files.ts @@ -0,0 +1,129 @@ +import fs from 'fs'; +import path from 'path'; +import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; + +import { objMerge } from '@hyperlane-xyz/sdk'; + +export type FileFormat = 'yaml' | 'json'; + +export function readFileAtPath(filepath: string) { + if (!fs.existsSync(filepath)) { + throw Error(`File doesn't exist at ${filepath}`); + } + return fs.readFileSync(filepath, 'utf8'); +} + +export function writeFileAtPath(filepath: string, value: string) { + const dirname = path.dirname(filepath); + if (!fs.existsSync(dirname)) { + fs.mkdirSync(dirname, { recursive: true }); + } + fs.writeFileSync(filepath, value); +} + +export function readJson(filepath: string): T { + return JSON.parse(readFileAtPath(filepath)) as T; +} + +export function tryReadJson(filepath: string): T | null { + try { + return readJson(filepath) as T; + } catch (error) { + return null; + } +} + +export function writeJson(filepath: string, obj: any) { + writeFileAtPath(filepath, JSON.stringify(obj, null, 2) + '\n'); +} + +export function mergeJson>( + filepath: string, + obj: T, +) { + if (fs.existsSync(filepath)) { + const previous = readJson(filepath); + writeJson(filepath, objMerge(previous, obj)); + } else { + writeJson(filepath, obj); + } +} + +export function readYaml(filepath: string): T { + return yamlParse(readFileAtPath(filepath)) as T; +} + +export function tryReadYamlAtPath(filepath: string): T | null { + try { + return readYaml(filepath); + } catch (error) { + return null; + } +} + +export function writeYaml(filepath: string, obj: any) { + writeFileAtPath(filepath, yamlStringify(obj, null, 2) + '\n'); +} + +export function mergeYaml>( + filepath: string, + obj: T, +) { + if (fs.existsSync(filepath)) { + console.log('MERGING'); + const previous = readYaml(filepath); + console.log('MERGING', previous); + writeYaml(filepath, objMerge(previous, obj)); + } else { + writeYaml(filepath, obj); + } +} + +export function readYamlOrJson(filepath: string, format?: FileFormat): T { + return resolveYamlOrJson(filepath, readJson, readYaml, format); +} + +export function writeYamlOrJson( + filepath: string, + obj: Record, + format?: FileFormat, +) { + return resolveYamlOrJson( + filepath, + (f: string) => writeJson(f, obj), + (f: string) => writeYaml(f, obj), + format, + ); +} + +export function mergeYamlOrJson( + filepath: string, + obj: Record, + format?: FileFormat, +) { + return resolveYamlOrJson( + filepath, + (f: string) => mergeJson(f, obj), + (f: string) => mergeYaml(f, obj), + format, + ); +} + +function resolveYamlOrJson( + filepath: string, + jsonFn: any, + yamlFn: any, + format?: FileFormat, +) { + if (format === 'json' || filepath.endsWith('.json')) { + return jsonFn(filepath); + } else if ( + format === 'yaml' || + filepath.endsWith('.yaml') || + filepath.endsWith('.yml') + ) { + return yamlFn(filepath); + } else { + throw new Error(`Invalid file format for ${filepath}`); + } +} diff --git a/typescript/cli/src/utils/keys.ts b/typescript/cli/src/utils/keys.ts new file mode 100644 index 0000000000..22a6e49e48 --- /dev/null +++ b/typescript/cli/src/utils/keys.ts @@ -0,0 +1,16 @@ +import { ethers } from 'ethers'; + +export function keyToSigner(key: string) { + if (!key) throw new Error('No key provided'); + const formattedKey = key.trim().toLowerCase(); + if (ethers.utils.isHexString(formattedKey)) + return new ethers.Wallet(formattedKey); + else if (formattedKey.split(' ').length >= 6) + return ethers.Wallet.fromMnemonic(formattedKey); + else throw new Error('Invalid key format'); +} + +export function assertSigner(signer: ethers.Signer) { + if (!signer || !ethers.Signer.isSigner(signer)) + throw new Error('Signer is invalid'); +} diff --git a/typescript/cli/src/utils/providers.ts b/typescript/cli/src/utils/providers.ts new file mode 100644 index 0000000000..50bdf0e53a --- /dev/null +++ b/typescript/cli/src/utils/providers.ts @@ -0,0 +1,18 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + ChainMetadata, + MultiProvider, + chainMetadata, +} from '@hyperlane-xyz/sdk'; + +export function getMultiProvider( + customChains: ChainMap, + signer?: ethers.Signer, +) { + const chainConfigs = { ...chainMetadata, ...customChains }; + const mp = new MultiProvider(chainConfigs); + if (signer) mp.setSharedSigner(signer); + return mp; +} diff --git a/typescript/cli/src/utils/time.ts b/typescript/cli/src/utils/time.ts new file mode 100644 index 0000000000..c982a237e3 --- /dev/null +++ b/typescript/cli/src/utils/time.ts @@ -0,0 +1,4 @@ +export function getTimestampForFilename() { + const now = new Date(); + return `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}-${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`; +} diff --git a/typescript/cli/src/warp/WarpRouteDeployer.ts b/typescript/cli/src/warp/WarpRouteDeployer.ts index c02c3aad53..3b61274c44 100644 --- a/typescript/cli/src/warp/WarpRouteDeployer.ts +++ b/typescript/cli/src/warp/WarpRouteDeployer.ts @@ -1,310 +1,314 @@ -import { ethers } from 'ethers'; -import yargs from 'yargs'; +// import { ethers } from 'ethers'; +// import yargs from 'yargs'; -import { - ERC20__factory, - ERC721__factory, - HypERC20Deployer, - HypERC721Deployer, - TokenConfig, - TokenFactories, - TokenType, -} from '@hyperlane-xyz/hyperlane-token'; -import { - ChainMap, - HyperlaneContractsMap, - MultiProvider, - RouterConfig, - chainMetadata, - objMap, - objMerge, -} from '@hyperlane-xyz/sdk'; -import { types } from '@hyperlane-xyz/utils'; +// import { +// ERC20__factory, +// ERC721__factory, +// HypERC20Deployer, +// HypERC721Deployer, +// TokenConfig, +// TokenFactories, +// TokenType, +// } from '@hyperlane-xyz/hyperlane-token'; +// import { +// ChainMap, +// HyperlaneContractsMap, +// MultiProvider, +// RouterConfig, +// chainMetadata, +// objMap, +// objMerge, +// } from '@hyperlane-xyz/sdk'; +// import { types } from '@hyperlane-xyz/utils'; -import { warpRouteConfig } from '../../examples/warp_tokens.js'; -import { - artifactsAddressesMap, - assertBalances, - assertBytes32, - getMultiProvider, - sdkContractAddressesMap, -} from '../config.js'; -import { mergeJSON, tryReadJSON, writeFileAtPath, writeJSON } from '../json.js'; -import { createLogger } from '../logger.js'; +// import { warpRouteConfig } from '../../examples/warp_tokens.js'; +// import { +// artifactsAddressesMap, +// assertBalances, +// assertBytes32, +// getMultiProvider, +// sdkContractAddressesMap, +// } from '../config.js'; +// import { createLogger } from '../logger.js'; +// import { +// mergeJson, +// tryReadJson, +// writeFileAtPath, +// writeJson, +// } from '../utils/files.js'; -import { - WarpBaseTokenConfig, - getWarpConfigChains, - validateWarpTokenConfig, -} from './config.js'; -import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; +// import { +// WarpBaseTokenConfig, +// getWarpConfigChains, +// validateWarpTokenConfig, +// } from './config.js'; +// import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; -export async function getArgs(multiProvider: MultiProvider) { - const args = await yargs(process.argv.slice(2)) - .describe('key', 'A hexadecimal private key for transaction signing') - .string('key') - .coerce('key', assertBytes32) - .demandOption('key') - .middleware( - assertBalances(multiProvider, () => getWarpConfigChains(warpRouteConfig)), - ); - return args.argv; -} +// export async function getArgs(multiProvider: MultiProvider) { +// const args = await yargs(process.argv.slice(2)) +// .describe('key', 'A hexadecimal private key for transaction signing') +// .string('key') +// .coerce('key', assertBytes32) +// .demandOption('key') +// .middleware( +// assertBalances(multiProvider, () => getWarpConfigChains(warpRouteConfig)), +// ); +// return args.argv; +// } -export type WarpRouteArtifacts = { - router: types.Address; - tokenType: TokenType; -}; +// export type WarpRouteArtifacts = { +// router: types.Address; +// tokenType: TokenType; +// }; -export class WarpRouteDeployer { - constructor( - public readonly multiProvider: MultiProvider, - public readonly signer: ethers.Signer, - protected readonly logger = createLogger('WarpRouteDeployer'), - ) {} +// export class WarpRouteDeployer { +// constructor( +// public readonly multiProvider: MultiProvider, +// public readonly signer: ethers.Signer, +// protected readonly logger = createLogger('WarpRouteDeployer'), +// ) {} - static async fromArgs(): Promise { - const multiProvider = getMultiProvider(); - const { key } = await getArgs(multiProvider); - const signer = new ethers.Wallet(key); - multiProvider.setSharedSigner(signer); - return new WarpRouteDeployer(multiProvider, signer); - } +// static async fromArgs(): Promise { +// const multiProvider = getMultiProvider(); +// const { key } = await getArgs(multiProvider); +// const signer = new ethers.Wallet(key); +// multiProvider.setSharedSigner(signer); +// return new WarpRouteDeployer(multiProvider, signer); +// } - async deploy(): Promise { - const { configMap, baseToken } = await this.buildHypTokenConfig(); +// async deploy(): Promise { +// const { configMap, baseToken } = await this.buildHypTokenConfig(); - this.logger('Initiating hyp token deployments'); - const deployer = baseToken.isNft - ? new HypERC721Deployer(this.multiProvider) - : new HypERC20Deployer(this.multiProvider); +// this.logger('Initiating hyp token deployments'); +// const deployer = baseToken.isNft +// ? new HypERC721Deployer(this.multiProvider) +// : new HypERC20Deployer(this.multiProvider); - await deployer.deploy(configMap); - this.logger('Hyp token deployments complete'); +// await deployer.deploy(configMap); +// this.logger('Hyp token deployments complete'); - this.writeDeploymentResult( - deployer.deployedContracts, - configMap, - baseToken, - ); - } +// this.writeDeploymentResult( +// deployer.deployedContracts, +// configMap, +// baseToken, +// ); +// } - async buildHypTokenConfig() { - validateWarpTokenConfig(warpRouteConfig); - const { base, synthetics } = warpRouteConfig; - const { type: baseType, chainName: baseChainName } = base; +// async buildHypTokenConfig() { +// validateWarpTokenConfig(warpRouteConfig); +// const { base, synthetics } = warpRouteConfig; +// const { type: baseType, chainName: baseChainName } = base; - const isCollateral = baseType === TokenType.collateral; - const baseTokenAddr = isCollateral - ? base.address - : ethers.constants.AddressZero; - const isNft = !!(isCollateral && base.isNft); +// const isCollateral = baseType === TokenType.collateral; +// const baseTokenAddr = isCollateral +// ? base.address +// : ethers.constants.AddressZero; +// const isNft = !!(isCollateral && base.isNft); - const owner = await this.signer.getAddress(); +// const owner = await this.signer.getAddress(); - const mergedContractAddresses = objMerge( - sdkContractAddressesMap, - artifactsAddressesMap(), - ); +// const mergedContractAddresses = objMerge( +// sdkContractAddressesMap, +// artifactsAddressesMap(), +// ); - const configMap: ChainMap = { - [baseChainName]: { - type: baseType, - token: baseTokenAddr, - owner, - mailbox: base.mailbox || mergedContractAddresses[baseChainName].mailbox, - interchainSecurityModule: - base.interchainSecurityModule || - mergedContractAddresses[baseChainName].interchainSecurityModule || - mergedContractAddresses[baseChainName].multisigIsm, - interchainGasPaymaster: - base.interchainGasPaymaster || - mergedContractAddresses[baseChainName] - .defaultIsmInterchainGasPaymaster, - foreignDeployment: base.foreignDeployment, - name: base.name, - symbol: base.symbol, - decimals: base.decimals, - }, - }; - this.logger( - `Hyp token config on base chain ${baseChainName}:`, - JSON.stringify(configMap[baseChainName]), - ); +// const configMap: ChainMap = { +// [baseChainName]: { +// type: baseType, +// token: baseTokenAddr, +// owner, +// mailbox: base.mailbox || mergedContractAddresses[baseChainName].mailbox, +// interchainSecurityModule: +// base.interchainSecurityModule || +// mergedContractAddresses[baseChainName].interchainSecurityModule || +// mergedContractAddresses[baseChainName].multisigIsm, +// interchainGasPaymaster: +// base.interchainGasPaymaster || +// mergedContractAddresses[baseChainName] +// .defaultIsmInterchainGasPaymaster, +// foreignDeployment: base.foreignDeployment, +// name: base.name, +// symbol: base.symbol, +// decimals: base.decimals, +// }, +// }; +// this.logger( +// `Hyp token config on base chain ${baseChainName}:`, +// JSON.stringify(configMap[baseChainName]), +// ); - const baseTokenMetadata = await this.getBaseTokenMetadata(base); - this.logger( - `Using base token metadata: Name: ${baseTokenMetadata.name}, Symbol: ${baseTokenMetadata.symbol}, Decimals: ${baseTokenMetadata.decimals} `, - ); +// const baseTokenMetadata = await this.getBaseTokenMetadata(base); +// this.logger( +// `Using base token metadata: Name: ${baseTokenMetadata.name}, Symbol: ${baseTokenMetadata.symbol}, Decimals: ${baseTokenMetadata.decimals} `, +// ); - for (const synthetic of synthetics) { - const sChainName = synthetic.chainName; - configMap[sChainName] = { - type: TokenType.synthetic, - name: synthetic.name || baseTokenMetadata.name, - symbol: synthetic.symbol || baseTokenMetadata.symbol, - totalSupply: synthetic.totalSupply || 0, - owner, - mailbox: - synthetic.mailbox || mergedContractAddresses[sChainName].mailbox, - interchainSecurityModule: - synthetic.interchainSecurityModule || - mergedContractAddresses[sChainName].interchainSecurityModule || - mergedContractAddresses[sChainName].multisigIsm, - interchainGasPaymaster: - synthetic.interchainGasPaymaster || - mergedContractAddresses[sChainName].defaultIsmInterchainGasPaymaster, - foreignDeployment: synthetic.foreignDeployment, - }; - this.logger( - `Hyp token config on synthetic chain ${sChainName}:`, - JSON.stringify(configMap[sChainName]), - ); - } - return { - configMap, - baseToken: { - type: baseType, - chainName: baseChainName, - address: baseTokenAddr, - metadata: baseTokenMetadata, - isNft, - }, - }; - } +// for (const synthetic of synthetics) { +// const sChainName = synthetic.chainName; +// configMap[sChainName] = { +// type: TokenType.synthetic, +// name: synthetic.name || baseTokenMetadata.name, +// symbol: synthetic.symbol || baseTokenMetadata.symbol, +// totalSupply: synthetic.totalSupply || 0, +// owner, +// mailbox: +// synthetic.mailbox || mergedContractAddresses[sChainName].mailbox, +// interchainSecurityModule: +// synthetic.interchainSecurityModule || +// mergedContractAddresses[sChainName].interchainSecurityModule || +// mergedContractAddresses[sChainName].multisigIsm, +// interchainGasPaymaster: +// synthetic.interchainGasPaymaster || +// mergedContractAddresses[sChainName].defaultIsmInterchainGasPaymaster, +// foreignDeployment: synthetic.foreignDeployment, +// }; +// this.logger( +// `Hyp token config on synthetic chain ${sChainName}:`, +// JSON.stringify(configMap[sChainName]), +// ); +// } +// return { +// configMap, +// baseToken: { +// type: baseType, +// chainName: baseChainName, +// address: baseTokenAddr, +// metadata: baseTokenMetadata, +// isNft, +// }, +// }; +// } - async getBaseTokenMetadata( - base: WarpBaseTokenConfig, - ): Promise { - // Skip fetching metadata if it's already provided in the config - if (base.name && base.symbol && base.decimals) { - return { - name: base.name, - symbol: base.symbol, - decimals: base.decimals, - }; - } +// async getBaseTokenMetadata( +// base: WarpBaseTokenConfig, +// ): Promise { +// // Skip fetching metadata if it's already provided in the config +// if (base.name && base.symbol && base.decimals) { +// return { +// name: base.name, +// symbol: base.symbol, +// decimals: base.decimals, +// }; +// } - if (base.type === TokenType.native) { - return ( - this.multiProvider.getChainMetadata(base.chainName).nativeToken || - chainMetadata.ethereum.nativeToken! - ); - } else if (base.type === TokenType.collateral) { - this.logger( - `Fetching token metadata for ${base.address} on ${base.chainName}}`, - ); - const provider = this.multiProvider.getProvider(base.chainName); - if (base.isNft) { - const erc721Contract = ERC721__factory.connect(base.address, provider); - const [name, symbol] = await Promise.all([ - erc721Contract.name(), - erc721Contract.symbol(), - ]); - return { name, symbol, decimals: 0 }; - } else { - const erc20Contract = ERC20__factory.connect(base.address, provider); - const [name, symbol, decimals] = await Promise.all([ - erc20Contract.name(), - erc20Contract.symbol(), - erc20Contract.decimals(), - ]); - return { name, symbol, decimals }; - } - } else { - throw new Error(`Unsupported token type: ${base}`); - } - } +// if (base.type === TokenType.native) { +// return ( +// this.multiProvider.getChainMetadata(base.chainName).nativeToken || +// chainMetadata.ethereum.nativeToken! +// ); +// } else if (base.type === TokenType.collateral) { +// this.logger( +// `Fetching token metadata for ${base.address} on ${base.chainName}}`, +// ); +// const provider = this.multiProvider.getProvider(base.chainName); +// if (base.isNft) { +// const erc721Contract = ERC721__factory.connect(base.address, provider); +// const [name, symbol] = await Promise.all([ +// erc721Contract.name(), +// erc721Contract.symbol(), +// ]); +// return { name, symbol, decimals: 0 }; +// } else { +// const erc20Contract = ERC20__factory.connect(base.address, provider); +// const [name, symbol, decimals] = await Promise.all([ +// erc20Contract.name(), +// erc20Contract.symbol(), +// erc20Contract.decimals(), +// ]); +// return { name, symbol, decimals }; +// } +// } else { +// throw new Error(`Unsupported token type: ${base}`); +// } +// } - writeDeploymentResult( - contracts: HyperlaneContractsMap, - configMap: ChainMap, - baseToken: Awaited< - ReturnType - >['baseToken'], - ) { - this.writeTokenDeploymentArtifacts(contracts, configMap); - this.writeWarpUiTokenList(contracts, baseToken, configMap); - } +// writeDeploymentResult( +// contracts: HyperlaneContractsMap, +// configMap: ChainMap, +// baseToken: Awaited< +// ReturnType +// >['baseToken'], +// ) { +// this.writeTokenDeploymentArtifacts(contracts, configMap); +// this.writeWarpUiTokenList(contracts, baseToken, configMap); +// } - writeTokenDeploymentArtifacts( - contracts: HyperlaneContractsMap, - configMap: ChainMap, - ) { - this.logger( - 'Writing token deployment addresses to artifacts/warp-token-addresses.json', - ); - const artifacts: ChainMap = objMap( - contracts, - (chain, contract) => { - return { - router: contract.router.address, - tokenType: configMap[chain].type, - }; - }, - ); - mergeJSON('./artifacts/', 'warp-token-addresses.json', artifacts); - } +// writeTokenDeploymentArtifacts( +// contracts: HyperlaneContractsMap, +// configMap: ChainMap, +// ) { +// this.logger( +// 'Writing token deployment addresses to artifacts/warp-token-addresses.json', +// ); +// const artifacts: ChainMap = objMap( +// contracts, +// (chain, contract) => { +// return { +// router: contract.router.address, +// tokenType: configMap[chain].type, +// }; +// }, +// ); +// mergeJson('./artifacts/warp-token-addresses.json', artifacts); +// } - writeWarpUiTokenList( - contracts: HyperlaneContractsMap, - baseToken: Awaited< - ReturnType - >['baseToken'], - configMap: ChainMap, - ) { - this.logger( - 'Writing warp ui token list to artifacts/warp-ui-token-list.json and artifacts/warp-ui-token-list.ts', - ); - const currentTokenList: WarpUITokenConfig[] = - tryReadJSON('./artifacts/', 'warp-ui-token-list.json') || []; +// writeWarpUiTokenList( +// contracts: HyperlaneContractsMap, +// baseToken: Awaited< +// ReturnType +// >['baseToken'], +// configMap: ChainMap, +// ) { +// this.logger( +// 'Writing warp ui token list to artifacts/warp-ui-token-list.json and artifacts/warp-ui-token-list.ts', +// ); +// const currentTokenList: WarpUITokenConfig[] = +// tryReadJson('./artifacts/warp-ui-token-list.json') || []; - const { type, address, chainName, metadata, isNft } = baseToken; - const { name, symbol, decimals } = metadata; - const hypTokenAddr = - contracts[chainName]?.router?.address || - configMap[chainName]?.foreignDeployment; - if (!hypTokenAddr) { - throw Error( - 'No base Hyperlane token address deployed and no foreign deployment specified', - ); - } - const commonFields = { - chainId: this.multiProvider.getChainId(chainName), - name, - symbol, - decimals, - }; - let newToken: WarpUITokenConfig; - if (type === TokenType.collateral) { - newToken = { - ...commonFields, - type: TokenType.collateral, - address, - hypCollateralAddress: hypTokenAddr, - isNft, - }; - } else if (type === TokenType.native) { - newToken = { - ...commonFields, - type: TokenType.native, - hypNativeAddress: hypTokenAddr, - }; - } else { - throw new Error(`Unsupported token type: ${type}`); - } +// const { type, address, chainName, metadata, isNft } = baseToken; +// const { name, symbol, decimals } = metadata; +// const hypTokenAddr = +// contracts[chainName]?.router?.address || +// configMap[chainName]?.foreignDeployment; +// if (!hypTokenAddr) { +// throw Error( +// 'No base Hyperlane token address deployed and no foreign deployment specified', +// ); +// } +// const commonFields = { +// chainId: this.multiProvider.getChainId(chainName), +// name, +// symbol, +// decimals, +// }; +// let newToken: WarpUITokenConfig; +// if (type === TokenType.collateral) { +// newToken = { +// ...commonFields, +// type: TokenType.collateral, +// address, +// hypCollateralAddress: hypTokenAddr, +// isNft, +// }; +// } else if (type === TokenType.native) { +// newToken = { +// ...commonFields, +// type: TokenType.native, +// hypNativeAddress: hypTokenAddr, +// }; +// } else { +// throw new Error(`Unsupported token type: ${type}`); +// } - currentTokenList.push(newToken); - // Write list as JSON - writeJSON('./artifacts/', 'warp-ui-token-list.json', currentTokenList); - // Also write list as TS - const serializedTokens = currentTokenList - .map((t) => JSON.stringify(t)) - .join(',\n'); - writeFileAtPath( - './artifacts/', - 'warp-ui-token-list.ts', - `export const tokenList = [\n${serializedTokens}\n];`, - ); - } -} +// currentTokenList.push(newToken); +// // Write list as JSON +// writeJson('./artifacts/warp-ui-token-list.json', currentTokenList); +// // Also write list as TS +// const serializedTokens = currentTokenList +// .map((t) => JSON.stringify(t)) +// .join(',\n'); +// writeFileAtPath( +// './artifacts/warp-ui-token-list.ts', +// `export const tokenList = [\n${serializedTokens}\n];`, +// ); +// } +// } diff --git a/typescript/cli/examples/start_blocks.ts b/typescript/sdk/src/consts/agentStartBlocks.ts similarity index 71% rename from typescript/cli/examples/start_blocks.ts rename to typescript/sdk/src/consts/agentStartBlocks.ts index cedfbb3468..6ac42ad51f 100644 --- a/typescript/cli/examples/start_blocks.ts +++ b/typescript/sdk/src/consts/agentStartBlocks.ts @@ -1,7 +1,8 @@ -import { ChainMap } from '@hyperlane-xyz/sdk'; +import { ChainMap } from '../types'; -// TODO move to SDK -export const startBlocks: ChainMap = { +// TODO this was previously in hyp-deploy, but ideally should be integrated +// into the ChainMetadata type +export const agentStartBlocks: ChainMap = { // --------------- Mainnets --------------------- celo: 16884144, ethereum: 16271503, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index a2ef43d08a..72acfcb8fb 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -1,4 +1,5 @@ export { HyperlaneApp } from './HyperlaneApp'; +export { agentStartBlocks } from './consts/agentStartBlocks'; export { chainIdToMetadata, chainMetadata, diff --git a/yarn.lock b/yarn.lock index fa4575717b..c9f3f4c774 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3816,6 +3816,7 @@ __metadata: ethers: ^5.7.2 prettier: ^2.8.8 typescript: ^5.1.6 + yaml: ^2.3.1 yargs: ^17.7.2 zod: ^3.21.2 bin: @@ -19963,6 +19964,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.3.1": + version: 2.3.1 + resolution: "yaml@npm:2.3.1" + checksum: 2c7bc9a7cd4c9f40d3b0b0a98e370781b68b8b7c4515720869aced2b00d92f5da1762b4ffa947f9e795d6cd6b19f410bd4d15fdd38aca7bd96df59bd9486fb54 + languageName: node + linkType: hard + "yargs-parser@npm:13.1.2, yargs-parser@npm:^13.1.2": version: 13.1.2 resolution: "yargs-parser@npm:13.1.2" From e382ecca6ed400f346e4b3457a69dc07eae313c5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 14:44:01 -0400 Subject: [PATCH 04/82] Implement cli warp deploy command Fix bugs with core deploy command --- typescript/cli/cli.ts | 8 +- typescript/cli/examples/multisig-ism.yaml | 7 + typescript/cli/examples/warp-tokens.yaml | 23 ++ typescript/cli/examples/warp_tokens.ts | 31 -- typescript/cli/src/chains/config.ts | 43 --- typescript/cli/src/commands/config.ts | 2 +- typescript/cli/src/commands/deploy.ts | 57 ++- typescript/cli/src/configs.ts | 142 ++++++++ typescript/cli/src/consts.ts | 2 +- typescript/cli/src/context.ts | 34 ++ .../cli/src/deploy/TestRecipientDeployer.ts | 21 +- typescript/cli/src/deploy/core.ts | 200 +++------- typescript/cli/src/{warp => deploy}/types.ts | 0 typescript/cli/src/deploy/utils.ts | 40 ++ typescript/cli/src/deploy/warp.ts | 342 ++++++++++++++++++ typescript/cli/src/index.ts | 0 typescript/cli/src/utils/files.ts | 22 +- typescript/cli/src/utils/providers.ts | 18 - typescript/cli/src/utils/time.ts | 2 +- typescript/cli/src/warp/WarpRouteDeployer.ts | 314 ---------------- typescript/cli/src/warp/config.ts | 82 ----- typescript/sdk/src/index.ts | 1 + typescript/sdk/src/ism/HyperlaneIsmFactory.ts | 1 + 23 files changed, 729 insertions(+), 663 deletions(-) create mode 100644 typescript/cli/examples/warp-tokens.yaml delete mode 100644 typescript/cli/examples/warp_tokens.ts delete mode 100644 typescript/cli/src/chains/config.ts create mode 100644 typescript/cli/src/configs.ts create mode 100644 typescript/cli/src/context.ts rename typescript/cli/src/{warp => deploy}/types.ts (100%) create mode 100644 typescript/cli/src/deploy/utils.ts create mode 100644 typescript/cli/src/deploy/warp.ts delete mode 100644 typescript/cli/src/index.ts delete mode 100644 typescript/cli/src/utils/providers.ts delete mode 100644 typescript/cli/src/warp/WarpRouteDeployer.ts delete mode 100644 typescript/cli/src/warp/config.ts diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index fdb4f2eab7..34ae2654fd 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -8,6 +8,8 @@ import { deployCommand } from './src/commands/deploy.js'; import './src/logger.js'; import { errorRed } from './src/logger.js'; +const MISSING_PARAMS_ERROR = 'Not enough non-option arguments'; + console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); try { @@ -22,12 +24,12 @@ try { .strict() .help() .fail((msg, err, yargs) => { - if (msg) errorRed('Error: ' + msg); + if (msg && !msg.includes(MISSING_PARAMS_ERROR)) errorRed('Error: ' + msg); console.log(''); yargs.showHelp(); console.log(''); - if (err) throw err; // preserve stack - else process.exit(1); + if (err) errorRed(err.toString()); + process.exit(1); }).argv; } catch (error: any) { errorRed('Error: ' + error.message); diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml index 5a7d79c358..88055577b3 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/multisig-ism.yaml @@ -1,5 +1,12 @@ # A config for a multisig Interchain Security Module (ISM) # Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts +# Module types: +# UNUSED : 0 +# ROUTING : 1 +# AGGREGATION : 2 +# LEGACY_MULTISIG : 3 +# MERKLE_ROOT_MULTISIG : 4 +# MESSAGE_ID_MULTISIG : 5 --- anvil1: type: 3 # ModuleType.LEGACY_MULTISIG diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml new file mode 100644 index 0000000000..f73abf5c82 --- /dev/null +++ b/typescript/cli/examples/warp-tokens.yaml @@ -0,0 +1,23 @@ +# A config for a Warp Route deployment +# Typically used with the 'hyperlane deploy warp' command +# Token Types: +# synthetic +# syntheticUri +# collateral +# collateralUri +# native +--- +base: + chainName: anvil1 + type: native + # address: 0x123... # Required for collateral types) + # isNft: true # If the token is an NFT (ERC721), set to true) + # owner: 0x123 # Optional owner address for synthetic token + # mailbox: 0x123 # Optional mailbox address route + # interchainGasPaymaster: 0x123 # Optional interchainGasPaymaster address +synthetics: + - chainName: anvil2 + # You can optionally set the token metadata, otherwise the base token's will be used + # name: "MySyntheticToken" + # symbol: "MST" + # totalSupply: 10000000 \ No newline at end of file diff --git a/typescript/cli/examples/warp_tokens.ts b/typescript/cli/examples/warp_tokens.ts deleted file mode 100644 index 3b939fbaec..0000000000 --- a/typescript/cli/examples/warp_tokens.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { TokenType } from '@hyperlane-xyz/hyperlane-token'; - -import type { WarpRouteConfig } from '../src/warp/config.js'; - -// A config for deploying Warp Routes to a set of chains -// Not required for Hyperlane core deployments -export const warpRouteConfig: WarpRouteConfig = { - base: { - // Chain name must be in the Hyperlane SDK or in the chains.ts config - chainName: 'anvil1', - type: TokenType.native, // TokenType.native or TokenType.collateral - // If type is collateral, a token address is required: - // address: '0x123...' - // If the token is an NFT (ERC721), set to true: - // isNft: boolean - - // Optionally, specify owner, mailbox, and interchainGasPaymaster addresses - // If not specified, the Permissionless Deployment artifacts or the SDK's defaults will be used - }, - synthetics: [ - { - chainName: 'anvil2', - - // Optionally specify a name, symbol, and totalSupply - // If not specified, the base token's properties will be used - - // Optionally, specify owner, mailbox, and interchainGasPaymaster addresses - // If not specified, the Permissionless Deployment artifacts or the SDK's defaults will be used - }, - ], -}; diff --git a/typescript/cli/src/chains/config.ts b/typescript/cli/src/chains/config.ts deleted file mode 100644 index 3a2d341f73..0000000000 --- a/typescript/cli/src/chains/config.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - ChainMap, - ChainMetadata, - isValidChainMetadata, -} from '@hyperlane-xyz/sdk'; - -import { errorRed, logGreen } from '../logger.js'; -import { readYamlOrJson } from '../utils/files.js'; -import { getMultiProvider } from '../utils/providers.js'; - -export function readChainConfig(filepath: string) { - console.log(`Reading file configs in ${filepath}`); - const chainToMetadata = readYamlOrJson>(filepath); - - if ( - !chainToMetadata || - typeof chainToMetadata !== 'object' || - !Object.keys(chainToMetadata).length - ) { - errorRed(`No configs found in ${filepath}`); - process.exit(1); - } - - for (const [chain, metadata] of Object.entries(chainToMetadata)) { - if (!isValidChainMetadata(metadata)) { - errorRed(`Chain ${chain} has invalid metadata`); - errorRed( - `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, - ); - process.exit(1); - } - if (metadata.name !== chain) { - errorRed(`Chain ${chain} name does not match key`); - process.exit(1); - } - } - - // Ensure multiprovider accepts this metadata - getMultiProvider(chainToMetadata); - - logGreen(`All chain configs in ${filepath} are valid`); - return chainToMetadata; -} diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 9bbc416ce1..15be737c76 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -8,7 +8,7 @@ import { isValidChainMetadata, } from '@hyperlane-xyz/sdk'; -import { readChainConfig } from '../chains/config.js'; +import { readChainConfig } from '../configs.js'; import { errorRed, logBlue, logGreen } from '../logger.js'; import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 9bc82f17fd..414a3d497a 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -1,6 +1,7 @@ -import { CommandModule } from 'yargs'; +import { CommandModule, Options } from 'yargs'; import { runCoreDeploy } from '../deploy/core.js'; +import { runWarpDeploy } from '../deploy/warp.js'; import { logGray } from '../logger.js'; /** @@ -18,6 +19,19 @@ export const deployCommand: CommandModule = { handler: () => console.log('Command required'), }; +const commonOptions: { [key: string]: Options } = { + key: { + type: 'string', + description: + 'A hex private key or seed phrase for transaction signing. Or use the HYP_KEY env var', + }, + out: { + type: 'string', + description: 'A folder name output artifacts into.', + default: './artifacts', + }, +}; + /** * Core command */ @@ -26,28 +40,19 @@ const coreCommand: CommandModule = { describe: 'Deploy core Hyperlane contracts', builder: (yargs) => yargs.options({ - key: { - type: 'string', - description: - 'A hex private key or seed phrase for transaction signing. Or use the HYP_KEY env var', - }, + ...commonOptions, config: { type: 'string', - description: - 'A path to a JSON or YAML file with chain configs. Defaults to ./configs/chain-config.yaml', - }, - out: { - type: 'string', - description: - 'A folder name output artifacts into. Defaults to ./artifacts', + description: 'A path to a JSON or YAML file with chain configs.', + default: './configs/chain-config.yaml', }, }), handler: (argv: any) => { logGray('Hyperlane permissionless core deployment'); logGray('----------------------------------------'); const key: string = argv.key || process.env.HYP_KEY; - const configPath: string = argv.config || './configs/chain-config.yaml'; - const outPath: string = argv.out || './artifacts/'; + const configPath: string = argv.config; + const outPath: string = argv.out; return runCoreDeploy({ key, configPath, outPath }); }, }; @@ -58,8 +63,24 @@ const coreCommand: CommandModule = { const warpCommand: CommandModule = { command: 'warp', describe: 'Deploy Warp Route contracts', - builder: (yargs) => yargs.options({}), - handler: (_args) => { - // TODO + builder: (yargs) => + yargs.options({ + ...commonOptions, + config: { + type: 'string', + description: 'A path to a JSON or YAML file with a warp config.', + default: './configs/warp-tokens.yaml', + }, + core: { + type: 'string', + description: 'File path to core deployment output artifacts', + }, + }), + handler: (argv: any) => { + const key: string = argv.key || process.env.HYP_KEY; + const configPath: string = argv.config; + const corePath: string = argv.core; + const outPath: string = argv.out; + return runWarpDeploy({ key, configPath, corePath, outPath }); }, }; diff --git a/typescript/cli/src/configs.ts b/typescript/cli/src/configs.ts new file mode 100644 index 0000000000..2cb9e3bb57 --- /dev/null +++ b/typescript/cli/src/configs.ts @@ -0,0 +1,142 @@ +import fs from 'fs'; +import { z } from 'zod'; + +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; +import { + ChainMap, + ChainMetadata, + HyperlaneContractsMap, + MultisigIsmConfig, + isValidChainMetadata, +} from '@hyperlane-xyz/sdk'; + +import { getMultiProvider } from './context.js'; +import { errorRed, log, logGreen } from './logger.js'; +import { readYamlOrJson } from './utils/files.js'; + +export function readChainConfig(filepath: string) { + console.log(`Reading file configs in ${filepath}`); + const chainToMetadata = readYamlOrJson>(filepath); + + if ( + !chainToMetadata || + typeof chainToMetadata !== 'object' || + !Object.keys(chainToMetadata).length + ) { + errorRed(`No configs found in ${filepath}`); + process.exit(1); + } + + for (const [chain, metadata] of Object.entries(chainToMetadata)) { + if (!isValidChainMetadata(metadata)) { + errorRed(`Chain ${chain} has invalid metadata`); + errorRed( + `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + process.exit(1); + } + if (metadata.name !== chain) { + errorRed(`Chain ${chain} name does not match key`); + process.exit(1); + } + } + + // Ensure multiprovider accepts this metadata + getMultiProvider(chainToMetadata); + + logGreen(`All chain configs in ${filepath} are valid`); + return chainToMetadata; +} + +export function readChainConfigIfExists(filePath: string) { + if (!fs.existsSync(filePath)) { + log('No chain config file provided'); + return {}; + } else { + return readChainConfig(filePath); + } +} + +const DeploymentArtifactsSchema = z + .object({}) + .catchall(z.object({}).catchall(z.string())); + +export function readDeploymentArtifacts(filePath: string) { + const artifacts = readYamlOrJson>(filePath); + if (!artifacts) throw new Error(`No artifacts found at ${filePath}`); + const result = DeploymentArtifactsSchema.safeParse(artifacts); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return artifacts; +} + +const MultisigConfigSchema = z.object({}).catchall( + z.object({ + type: z.number(), + threshold: z.number(), + validators: z.array(z.string()), + }), +); + +export function readMultisigConfig(filePath: string) { + const config = readYamlOrJson>(filePath); + if (!config) throw new Error(`No multisig config found at ${filePath}`); + const result = MultisigConfigSchema.safeParse(config); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid multisig config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return result.data as ChainMap; +} + +const ConnectionConfigSchema = { + mailbox: z.string().optional(), + interchainGasPaymaster: z.string().optional(), + interchainSecurityModule: z.string().optional(), + foreignDeployment: z.string().optional(), +}; + +export const WarpRouteConfigSchema = z.object({ + base: z.object({ + type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)), + chainName: z.string(), + address: z.string().optional(), + isNft: z.boolean().optional(), + name: z.string().optional(), + symbol: z.string().optional(), + decimals: z.number().optional(), + ...ConnectionConfigSchema, + }), + synthetics: z + .array( + z.object({ + chainName: z.string(), + name: z.string().optional(), + symbol: z.string().optional(), + totalSupply: z.number().optional(), + ...ConnectionConfigSchema, + }), + ) + .nonempty(), +}); + +export type WarpRouteConfig = z.infer; + +export function readWarpRouteConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No warp config found at ${filePath}`); + const result = WarpRouteConfigSchema.safeParse(config); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return result.data; +} diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index f12a03e400..249949b736 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,2 +1,2 @@ export const MINIMUM_CORE_DEPLOY_BALANCE = 0.5; // Half an ETH -export const MINIMUM_WARP = 0.2; // 2/10 an ETH +export const MINIMUM_WARP_DEPLOY_BALANCE = 0.2; // 2/10 an ETH diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts new file mode 100644 index 0000000000..b677d5938d --- /dev/null +++ b/typescript/cli/src/context.ts @@ -0,0 +1,34 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + ChainMetadata, + MultiProvider, + chainMetadata, + hyperlaneEnvironments, +} from '@hyperlane-xyz/sdk'; + +import { readChainConfigIfExists } from './configs.js'; +import { keyToSigner } from './utils/keys.js'; + +export const sdkContractAddressesMap = { + ...hyperlaneEnvironments.testnet, + ...hyperlaneEnvironments.mainnet, +}; + +export function getDeployerContext(key: string, configPath: string) { + const signer = keyToSigner(key); + const customChains = readChainConfigIfExists(configPath); + const multiProvider = getMultiProvider(customChains, signer); + return { signer, customChains, multiProvider }; +} + +export function getMultiProvider( + customChains: ChainMap, + signer?: ethers.Signer, +) { + const chainConfigs = { ...chainMetadata, ...customChains }; + const mp = new MultiProvider(chainConfigs); + if (signer) mp.setSharedSigner(signer); + return mp; +} diff --git a/typescript/cli/src/deploy/TestRecipientDeployer.ts b/typescript/cli/src/deploy/TestRecipientDeployer.ts index e71e6bd4ad..da08e57dfa 100644 --- a/typescript/cli/src/deploy/TestRecipientDeployer.ts +++ b/typescript/cli/src/deploy/TestRecipientDeployer.ts @@ -8,7 +8,6 @@ import { } from '@hyperlane-xyz/sdk'; import { types, utils } from '@hyperlane-xyz/utils'; -// Maps chain name to ISM address export type TestRecipientConfig = { interchainSecurityModule: types.Address; }; @@ -40,12 +39,20 @@ export class TestRecipientDeployer extends HyperlaneDeployer< config: TestRecipientConfig, ): Promise { const testRecipient = await this.deployContract(chain, 'testRecipient', []); - const ism = await testRecipient.interchainSecurityModule(); - if (!utils.eqAddress(ism, config.interchainSecurityModule)) { - const tx = testRecipient.setInterchainSecurityModule( - config.interchainSecurityModule, - ); - await this.multiProvider.handleTx(chain, tx); + try { + this.logger(`Checking ISM ${chain}`); + const ism = await testRecipient.interchainSecurityModule(); + this.logger(`Found ISM for on ${chain}: ${ism}`); + if (!utils.eqAddress(ism, config.interchainSecurityModule)) { + this.logger(`Current ISM does not match config. Updating.`); + const tx = testRecipient.setInterchainSecurityModule( + config.interchainSecurityModule, + ); + await this.multiProvider.handleTx(chain, tx); + } + } catch (error) { + this.logger(`Failed to check/update ISM for ${chain}: ${error}`); + this.logger('Leaving ISM as is and continuing.'); } return { testRecipient, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 49e9f3e982..257d5799d2 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -1,10 +1,7 @@ import { Separator, checkbox, confirm, input } from '@inquirer/prompts'; import select from '@inquirer/select'; -import { log } from 'console'; +import chalk from 'chalk'; import { ethers } from 'ethers'; -import fs from 'fs'; -import path from 'path'; -import { z } from 'zod'; import { ChainMap, @@ -25,12 +22,10 @@ import { MultiProvider, MultisigIsmConfig, OverheadIgpConfig, - ProtocolType, RoutingIsmConfig, agentStartBlocks, buildAgentConfig, defaultMultisigIsmConfigs, - hyperlaneEnvironments, mainnetChainsMetadata, multisigIsmVerificationCost, objFilter, @@ -40,24 +35,17 @@ import { } from '@hyperlane-xyz/sdk'; import { types } from '@hyperlane-xyz/utils'; -import { readChainConfig } from '../chains/config.js'; +import { readDeploymentArtifacts, readMultisigConfig } from '../configs.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; -import { logBlue, logGray, logGreen } from '../logger.js'; -import { assertBalances } from '../utils/balances.js'; -import { readYamlOrJson, writeJson } from '../utils/files.js'; -import { assertSigner, keyToSigner } from '../utils/keys.js'; -import { getMultiProvider } from '../utils/providers.js'; -import { getTimestampForFilename } from '../utils/time.js'; +import { getDeployerContext, sdkContractAddressesMap } from '../context.js'; +import { log, logBlue, logGray, logGreen } from '../logger.js'; +import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; import { TestRecipientConfig, TestRecipientDeployer, } from './TestRecipientDeployer.js'; - -export const sdkContractAddressesMap = { - ...hyperlaneEnvironments.testnet, - ...hyperlaneEnvironments.mainnet, -}; +import { runPreflightChecks } from './utils.js'; export async function runCoreDeploy({ key, @@ -68,9 +56,10 @@ export async function runCoreDeploy({ configPath: string; outPath: string; }) { - const signer = keyToSigner(key); - const customChains = getCustomChains(configPath); - const multiProvider = getMultiProvider(customChains, signer); + const { customChains, multiProvider, signer } = getDeployerContext( + key, + configPath, + ); const { local, remotes, allChains } = await runChainSelectionStep( customChains, @@ -89,12 +78,10 @@ export async function runCoreDeploy({ }; await runDeployPlanStep(deploymentParams); - await runPreflightChecks(deploymentParams); - - const isConfirmed = await confirm({ - message: 'All systems ready, captain. Should we deploy?', + await runPreflightChecks({ + ...deploymentParams, + minBalance: MINIMUM_CORE_DEPLOY_BALANCE, }); - if (!isConfirmed) throw new Error('Deployment cancelled'); await executeDeploy(deploymentParams); } @@ -182,51 +169,20 @@ async function runIsmStep(allChains: ChainName[]) { return configs; } -function getCustomChains(configPath: string) { - if (!fs.existsSync(configPath)) { - log('No config file provided, using default chains in SDK'); - return {}; - } else { - return readChainConfig(configPath); - } -} - function handleNewChain(chainNames: string[]) { if (chainNames.includes('__new__')) { logBlue( - 'To choose a new chain, add them to a config file and use the --config flag', + 'To use a new chain, use the --config argument add them to that file', ); - logBlue( - 'Use the "hyperlane config create" command to create new chain configs', + log( + chalk.blue('Use the'), + chalk.magentaBright('hyperlane config create'), + chalk.blue('command to create new configs'), ); process.exit(0); } } -const GenericDeploymentArtifactsSchema = z - .object({}) - .catchall(z.object({}).catchall(z.string())); - -function readDeploymentArtifacts(filePath: string) { - const artifacts = readYamlOrJson>(filePath); - if (!artifacts) throw new Error(`No artifacts found at ${filePath}`); - const result = GenericDeploymentArtifactsSchema.safeParse(artifacts); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`, - ); - } - return artifacts; -} - -function readMultisigConfig(filePath: string) { - const config = readYamlOrJson>(filePath); - if (!config) throw new Error(`No multisig config found at ${filePath}`); - // TODO validate multisig config - return config; -} - interface DeployParams { local: string; remotes: string[]; @@ -244,64 +200,30 @@ async function runDeployPlanStep({ artifacts, }: DeployParams) { const address = await signer.getAddress(); - logBlue('Deployment plan:'); - logGray('===================:'); - log(`Transaction signer and contract owner will be account ${address}`); - log( - `Deploying Hyperlane to ${local} and connecting it to ${remotes.join( - ', ', - )}`, - ); + logBlue('\nDeployment plan:'); + logGray('===============:'); + log(`Transaction signer and owner of new contracts will be ${address}`); + log(`Deploying to ${local} and connecting it to ${remotes.join(', ')}`); + const numContracts = Object.keys( + Object.values(sdkContractAddressesMap)[0], + ).length; + log(`There are ${numContracts} contracts for each chain`); if (artifacts) { log('But contracts with an address in the artifacts file will be skipped'); for (const chain of [local, ...remotes]) { const chainArtifacts = artifacts[chain]; if (!chainArtifacts) continue; - log( - `Skipped contracts for ${chain}: ${Object.keys(chainArtifacts).join( - ', ', - )}`, - ); + const numRequired = numContracts - Object.keys(chainArtifacts).length; + log(`${chain} will require ${numRequired} of ${numContracts}`); } } - log(`The interchain security module will be a Multisig.`); + log('The interchain security module will be a Multisig.'); const isConfirmed = await confirm({ message: 'Is this deployment plan correct?', }); if (!isConfirmed) throw new Error('Deployment cancelled'); } -async function runPreflightChecks({ - local, - remotes, - signer, - multiProvider, -}: DeployParams) { - log('Running pre-flight checks...'); - - if (!local || !remotes?.length) throw new Error('Invalid chain selection'); - if (remotes.includes(local)) - throw new Error('Local and remotes must be distinct'); - for (const chain of [local, ...remotes]) { - const metadata = multiProvider.tryGetChainMetadata(chain); - if (!metadata) throw new Error(`No chain config found for ${chain}`); - if (metadata.protocol !== ProtocolType.Ethereum) - throw new Error('Only Ethereum chains are supported for now'); - } - logGreen('Chains are valid ✅'); - - assertSigner(signer); - logGreen('Signer is valid ✅'); - - await assertBalances( - multiProvider, - signer, - [local, ...remotes], - MINIMUM_CORE_DEPLOY_BALANCE, - ); - logGreen('Balances are sufficient ✅'); -} - async function executeDeploy({ local, remotes, @@ -311,27 +233,30 @@ async function executeDeploy({ artifacts = {}, multiSigConfig = {}, }: DeployParams) { - const { contractsFilePath, agentFilePath } = - prepNewArtifactsFilePaths(outPath); + logBlue('All systems ready, captain! Beginning deployment...'); + + const [contractsFilePath, agentFilePath] = prepNewArtifactsFiles(outPath, [ + { filename: 'core-deployment', description: 'Contract addresses' }, + { filename: 'agent-config', description: 'Agent configs' }, + ]); const owner = await signer.getAddress(); const allChains = [local, ...remotes]; // 1. Deploy ISM factories to all deployable chains that don't have them. - logBlue('Deploying ISM factory contracts'); + log('Deploying ISM factory contracts'); const ismDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); ismDeployer.cacheAddressesMap(objMerge(sdkContractAddressesMap, artifacts)); const ismFactoryContracts = await ismDeployer.deploy(allChains); - console.log(ismFactoryContracts); artifacts = writeMergedAddresses( contractsFilePath, artifacts, ismFactoryContracts, ); - logBlue(`ISM factory deployment complete`); + logGreen(`ISM factory contracts deployed`); // 2. Deploy IGPs to all deployable chains. - logBlue(`Deploying IGP contracts`); + log(`Deploying IGP contracts`); const igpConfig = buildIgpConfigMap( owner, allChains, @@ -341,9 +266,8 @@ async function executeDeploy({ const igpDeployer = new HyperlaneIgpDeployer(multiProvider); igpDeployer.cacheAddressesMap(artifacts); const igpContracts = await igpDeployer.deploy(igpConfig); - console.log(igpContracts); artifacts = writeMergedAddresses(contractsFilePath, artifacts, igpContracts); - logBlue(`IGP deployment complete`); + logGreen(`IGP contracts deployed`); // Build an IsmFactory that covers all chains so that we can // use it later to deploy ISMs to remote chains. @@ -353,49 +277,52 @@ async function executeDeploy({ ); // 3. Deploy core contracts to local chain - logBlue(`Deploying core contracts to ${local}`); + log(`Deploying core contracts to ${local}`); const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); coreDeployer.cacheAddressesMap(artifacts); const coreConfig = buildCoreConfigMap(owner, local, remotes, multiSigConfig); const coreContracts = await coreDeployer.deploy(coreConfig); - console.log(coreContracts); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); - logBlue(`Core deployment complete`); + logGreen(`Core contracts deployed`); // 4. Deploy ISM contracts to remote deployable chains - logBlue(`Deploying ISMs to ${remotes}`); + log(`Deploying ISMs`); const ismConfigs = buildIsmConfigMap( owner, remotes, allChains, multiSigConfig, ); - const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = {}; + const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { - logBlue(`Deploying ISM to ${ismChain}`); + if (artifacts[ismChain].multisigIsm) { + log(`ISM contract recovered, skipping ISM deployment to ${ismChain}`); + continue; + } + log(`Deploying ISM to ${ismChain}`); ismContracts[ismChain] = { - interchainSecurityModule: await ismFactory.deploy(ismChain, ismConfig), + multisigIsm: await ismFactory.deploy(ismChain, ismConfig), }; } artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); - logBlue(`ISM deployment complete`); + logGreen(`ISM contracts deployed `); // 5. Deploy TestRecipients to all deployable chains - logBlue(`Deploying test recipient contracts`); + log(`Deploying test recipient contracts`); const testRecipientConfig = buildTestRecipientConfigMap(allChains, artifacts); const testRecipientDeployer = new TestRecipientDeployer(multiProvider); testRecipientDeployer.cacheAddressesMap(artifacts); const testRecipients = await testRecipientDeployer.deploy( testRecipientConfig, ); - console.log(testRecipients); artifacts = writeMergedAddresses( contractsFilePath, artifacts, testRecipients, ); - logBlue(`Test recipient deployment complete`); + logGreen(`Test recipient contracts deployed`); + log('Writing agent configs'); await writeAgentConfig( agentFilePath, artifacts, @@ -403,8 +330,11 @@ async function executeDeploy({ remotes, multiProvider, ); + logGreen('Agent configs written'); - logBlue(`Writing agent config to artifacts/agent_config.json`); + logBlue('Deployment is complete!'); + logBlue(`Contract address artifacts are in ${contractsFilePath}`); + logBlue(`Agent configs are in ${agentFilePath}`); } function buildIsmConfig( @@ -498,7 +428,7 @@ function buildIgpConfigMap( beneficiary: owner, gasOracleType, overhead, - oracleKey: 'TODO', + oracleKey: owner, }; } return configMap; @@ -515,20 +445,6 @@ function writeMergedAddresses( return mergedAddresses; } -function prepNewArtifactsFilePaths(outPath: string) { - const timestamp = getTimestampForFilename(); - const contractsFilePath = path.join( - outPath, - `core-deployment-${timestamp}.json`, - ); - const agentFilePath = path.join(outPath, `agent-config-${timestamp}.json`); - // Write an empty object to the file to ensure permissions are okay - writeJson(contractsFilePath, {}); - logBlue(`Contract address artifacts will be written to ${contractsFilePath}`); - logBlue(`Agent configs will be written to ${agentFilePath}`); - return { contractsFilePath, agentFilePath }; -} - async function writeAgentConfig( filePath: string, artifacts: HyperlaneAddressesMap, diff --git a/typescript/cli/src/warp/types.ts b/typescript/cli/src/deploy/types.ts similarity index 100% rename from typescript/cli/src/warp/types.ts rename to typescript/cli/src/deploy/types.ts diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts new file mode 100644 index 0000000000..7558ce87be --- /dev/null +++ b/typescript/cli/src/deploy/utils.ts @@ -0,0 +1,40 @@ +import { ethers } from 'ethers'; + +import { ChainName, MultiProvider, ProtocolType } from '@hyperlane-xyz/sdk'; + +import { log, logGreen } from '../logger.js'; +import { assertBalances } from '../utils/balances.js'; +import { assertSigner } from '../utils/keys.js'; + +export async function runPreflightChecks({ + local, + remotes, + signer, + multiProvider, + minBalance, +}: { + local: ChainName; + remotes: ChainName[]; + signer: ethers.Signer; + multiProvider: MultiProvider; + minBalance: number; +}) { + log('Running pre-flight checks...'); + + if (!local || !remotes?.length) throw new Error('Invalid chain selection'); + if (remotes.includes(local)) + throw new Error('Local and remotes must be distinct'); + for (const chain of [local, ...remotes]) { + const metadata = multiProvider.tryGetChainMetadata(chain); + if (!metadata) throw new Error(`No chain config found for ${chain}`); + if (metadata.protocol !== ProtocolType.Ethereum) + throw new Error('Only Ethereum chains are supported for now'); + } + logGreen('Chains are valid ✅'); + + assertSigner(signer); + logGreen('Signer is valid ✅'); + + await assertBalances(multiProvider, signer, [local, ...remotes], minBalance); + logGreen('Balances are sufficient ✅'); +} diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts new file mode 100644 index 0000000000..4bf895e0b7 --- /dev/null +++ b/typescript/cli/src/deploy/warp.ts @@ -0,0 +1,342 @@ +import { confirm, input } from '@inquirer/prompts'; +import { ethers } from 'ethers'; + +import { + ERC20__factory, + ERC721__factory, + HypERC20Deployer, + HypERC721Deployer, + TokenConfig, + TokenFactories, + TokenType, +} from '@hyperlane-xyz/hyperlane-token'; +import { + ChainMap, + ChainName, + ConnectionClientConfig, + HyperlaneContractsMap, + MultiProvider, + RouterConfig, + chainMetadata as defaultChainMetadata, + objMap, + objMerge, +} from '@hyperlane-xyz/sdk'; +import { types } from '@hyperlane-xyz/utils'; + +import { + WarpRouteConfig, + readDeploymentArtifacts, + readWarpRouteConfig, +} from '../configs.js'; +import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; +import { getDeployerContext, sdkContractAddressesMap } from '../context.js'; +import { log, logBlue, logGray, logGreen } from '../logger.js'; +import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; + +import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; +import { runPreflightChecks } from './utils.js'; + +export async function runWarpDeploy({ + key, + configPath, + corePath, + outPath, +}: { + key: string; + configPath: string; + corePath: string; + outPath: string; +}) { + const { multiProvider, signer } = getDeployerContext(key, configPath); + + const warpRouteConfig = readWarpRouteConfig(configPath); + const artifacts = corePath ? readDeploymentArtifacts(corePath) : undefined; + + const configs = await runBuildConfigStep({ + warpRouteConfig, + artifacts, + multiProvider, + signer, + }); + + const deploymentParams = { + ...configs, + signer, + multiProvider, + outPath, + }; + + await runDeployPlanStep(deploymentParams); + await runPreflightChecks({ + ...deploymentParams, + minBalance: MINIMUM_WARP_DEPLOY_BALANCE, + }); + await executeDeploy(deploymentParams); +} + +async function runBuildConfigStep({ + warpRouteConfig, + multiProvider, + signer, + artifacts, +}: { + warpRouteConfig: WarpRouteConfig; + multiProvider: MultiProvider; + signer: ethers.Signer; + artifacts?: HyperlaneContractsMap; +}) { + log('Assembling token configs'); + const { base, synthetics } = warpRouteConfig; + const { type: baseType, chainName: baseChainName, isNft } = base; + + const owner = await signer.getAddress(); + + const baseMetadata = await fetchBaseTokenMetadata(base, multiProvider); + log( + `Using base token metadata: Name: ${baseMetadata.name}, Symbol: ${baseMetadata.symbol}, Decimals: ${baseMetadata.decimals}`, + ); + + const mergedContractAddrs = objMerge( + sdkContractAddressesMap, + artifacts || {}, + ) as HyperlaneContractsMap; + + // Create configs that coalesce together values from the config file, + // the artifacts, and the SDK as a fallback + const configMap: ChainMap = { + [baseChainName]: { + type: baseType, + token: + baseType === TokenType.collateral + ? base.address! + : ethers.constants.AddressZero, + owner, + mailbox: base.mailbox || mergedContractAddrs[baseChainName].mailbox, + interchainSecurityModule: + base.interchainSecurityModule || + mergedContractAddrs[baseChainName].interchainSecurityModule || + mergedContractAddrs[baseChainName].multisigIsm, + interchainGasPaymaster: + base.interchainGasPaymaster || + mergedContractAddrs[baseChainName].defaultIsmInterchainGasPaymaster, + foreignDeployment: base.foreignDeployment, + name: baseMetadata.name, + symbol: baseMetadata.symbol, + decimals: baseMetadata.decimals, + }, + }; + + for (const synthetic of synthetics) { + const sChainName = synthetic.chainName; + configMap[sChainName] = { + type: TokenType.synthetic, + name: synthetic.name || baseMetadata.name, + symbol: synthetic.symbol || baseMetadata.symbol, + totalSupply: synthetic.totalSupply || 0, + owner, + mailbox: synthetic.mailbox || mergedContractAddrs[sChainName].mailbox, + interchainSecurityModule: + synthetic.interchainSecurityModule || + mergedContractAddrs[sChainName].interchainSecurityModule || + mergedContractAddrs[sChainName].multisigIsm, + interchainGasPaymaster: + synthetic.interchainGasPaymaster || + mergedContractAddrs[sChainName].defaultIsmInterchainGasPaymaster, + foreignDeployment: synthetic.foreignDeployment, + }; + } + + // Request input for any address fields that are missing + const requiredRouterFields: Array = [ + 'mailbox', + 'interchainSecurityModule', + 'interchainGasPaymaster', + ]; + let hasShownInfo = false; + for (const [chain, token] of Object.entries(configMap)) { + for (const field of requiredRouterFields) { + if (token[field]) continue; + if (!hasShownInfo) { + logBlue( + 'Some router fields are missing. Please enter them now, add them to your warp config, or use the --core flag to use deployment artifacts.', + ); + hasShownInfo = true; + } + const value = await input({ + message: `Enter ${field} for ${getTokenName(token)} token on ${chain}`, + }); + if (!value) throw new Error(`Field ${field} required`); + token[field] = value.trim(); + } + } + + log('Token configs ready'); + return { + configMap, + metadata: baseMetadata, + local: baseChainName, + remotes: synthetics.map(({ chainName }) => chainName), + isNft: !!isNft, + }; +} + +interface DeployParams { + configMap: ChainMap; + isNft: boolean; + metadata: MinimalTokenMetadata; + local: ChainName; + remotes: ChainName[]; + signer: ethers.Signer; + multiProvider: MultiProvider; + outPath: string; +} + +async function runDeployPlanStep({ + configMap, + isNft, + local, + remotes, + signer, +}: DeployParams) { + const address = await signer.getAddress(); + const baseToken = configMap[local]; + const baseName = getTokenName(baseToken); + logBlue('\nDeployment plan:'); + logGray('===============:'); + log(`Transaction signer and owner of new contracts will be ${address}`); + log(`Deploying a warp route with a base of ${baseName} token on ${local}`); + log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`); + log(`Using token standard ${isNft ? 'ERC721' : 'ERC20'}`); + + const isConfirmed = await confirm({ + message: 'Is this deployment plan correct?', + }); + if (!isConfirmed) throw new Error('Deployment cancelled'); +} + +async function executeDeploy(params: DeployParams) { + logBlue('All systems ready, captain! Beginning deployment...'); + + const { configMap, isNft, multiProvider, outPath } = params; + + const [contractsFilePath, tokenConfigPath] = prepNewArtifactsFiles(outPath, [ + { filename: 'warp-deployment', description: 'Contract addresses' }, + { filename: 'warp-token-config', description: 'Warp UI token config' }, + ]); + + const deployer = isNft + ? new HypERC721Deployer(multiProvider) + : new HypERC20Deployer(multiProvider); + + const deployedContracts = await deployer.deploy(configMap); + logGreen('Hyp token deployments complete'); + + log('Writing deployment artifacts'); + writeTokenDeploymentArtifacts(contractsFilePath, deployedContracts, params); + writeWarpUiTokenConfig(tokenConfigPath, deployedContracts, params); + + logBlue('Deployment is complete!'); + logBlue(`Contract address artifacts are in ${contractsFilePath}`); + logBlue(`Warp UI token config is in ${tokenConfigPath}`); +} + +async function fetchBaseTokenMetadata( + base: WarpRouteConfig['base'], + multiProvider: MultiProvider, +): Promise { + const { type, name, symbol, chainName, address, decimals, isNft } = base; + + // Skip fetching metadata if it's already provided in the config + if (name && symbol && decimals) { + return { name, symbol, decimals }; + } + + if (type === TokenType.native) { + return ( + multiProvider.getChainMetadata(base.chainName).nativeToken || + defaultChainMetadata.ethereum.nativeToken! + ); + } else if (base.type === TokenType.collateral && address) { + log(`Fetching token metadata for ${address} on ${chainName}}`); + const provider = multiProvider.getProvider(chainName); + if (isNft) { + const erc721Contract = ERC721__factory.connect(address, provider); + const [name, symbol] = await Promise.all([ + erc721Contract.name(), + erc721Contract.symbol(), + ]); + return { name, symbol, decimals: 0 }; + } else { + const erc20Contract = ERC20__factory.connect(address, provider); + const [name, symbol, decimals] = await Promise.all([ + erc20Contract.name(), + erc20Contract.symbol(), + erc20Contract.decimals(), + ]); + return { name, symbol, decimals }; + } + } else { + throw new Error(`Unsupported token: ${base}`); + } +} + +function getTokenName(token: TokenConfig) { + return token.type === TokenType.native ? 'native' : token.name; +} +function writeTokenDeploymentArtifacts( + filePath: string, + contracts: HyperlaneContractsMap, + { configMap }: DeployParams, +) { + const artifacts: ChainMap<{ + router: types.Address; + tokenType: TokenType; + }> = objMap(contracts, (chain, contract) => { + return { + router: contract.router.address, + tokenType: configMap[chain].type, + }; + }); + writeJson(filePath, artifacts); +} + +function writeWarpUiTokenConfig( + filePath: string, + contracts: HyperlaneContractsMap, + { configMap, isNft, metadata, local, multiProvider }: DeployParams, +) { + const baseConfig = configMap[local]; + const hypTokenAddr = + contracts[local]?.router?.address || configMap[local]?.foreignDeployment; + if (!hypTokenAddr) { + throw Error( + 'No base Hyperlane token address deployed and no foreign deployment specified', + ); + } + const commonFields = { + chainId: multiProvider.getChainId(local), + name: metadata.name, + symbol: metadata.symbol, + decimals: metadata.decimals, + }; + let tokenConfig: WarpUITokenConfig; + if (baseConfig.type === TokenType.collateral) { + tokenConfig = { + ...commonFields, + type: TokenType.collateral, + address: baseConfig.token, + hypCollateralAddress: hypTokenAddr, + isNft, + }; + } else if (baseConfig.type === TokenType.native) { + tokenConfig = { + ...commonFields, + type: TokenType.native, + hypNativeAddress: hypTokenAddr, + }; + } else { + throw new Error(`Unsupported token type: ${baseConfig.type}`); + } + + writeJson(filePath, tokenConfig); +} diff --git a/typescript/cli/src/index.ts b/typescript/cli/src/index.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index e33ee0fe13..9fb40d8590 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -4,6 +4,10 @@ import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; import { objMerge } from '@hyperlane-xyz/sdk'; +import { logBlue } from '../logger.js'; + +import { getTimestampForFilename } from './time.js'; + export type FileFormat = 'yaml' | 'json'; export function readFileAtPath(filepath: string) { @@ -70,9 +74,7 @@ export function mergeYaml>( obj: T, ) { if (fs.existsSync(filepath)) { - console.log('MERGING'); const previous = readYaml(filepath); - console.log('MERGING', previous); writeYaml(filepath, objMerge(previous, obj)); } else { writeYaml(filepath, obj); @@ -127,3 +129,19 @@ function resolveYamlOrJson( throw new Error(`Invalid file format for ${filepath}`); } } + +export function prepNewArtifactsFiles( + outPath: string, + files: Array<{ filename: string; description: string }>, +) { + const timestamp = getTimestampForFilename(); + const newPaths: string[] = []; + for (const file of files) { + const filePath = path.join(outPath, `${file.filename}-${timestamp}.json`); + // Write empty object to ensure permissions are okay + writeJson(filePath, {}); + newPaths.push(filePath); + logBlue(`${file.description} will be written to ${filePath}`); + } + return newPaths; +} diff --git a/typescript/cli/src/utils/providers.ts b/typescript/cli/src/utils/providers.ts deleted file mode 100644 index 50bdf0e53a..0000000000 --- a/typescript/cli/src/utils/providers.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - ChainMetadata, - MultiProvider, - chainMetadata, -} from '@hyperlane-xyz/sdk'; - -export function getMultiProvider( - customChains: ChainMap, - signer?: ethers.Signer, -) { - const chainConfigs = { ...chainMetadata, ...customChains }; - const mp = new MultiProvider(chainConfigs); - if (signer) mp.setSharedSigner(signer); - return mp; -} diff --git a/typescript/cli/src/utils/time.ts b/typescript/cli/src/utils/time.ts index c982a237e3..4793d8d35a 100644 --- a/typescript/cli/src/utils/time.ts +++ b/typescript/cli/src/utils/time.ts @@ -1,4 +1,4 @@ export function getTimestampForFilename() { const now = new Date(); - return `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}-${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`; + return `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}-${now.getHours()}-${now.getMinutes()}`; } diff --git a/typescript/cli/src/warp/WarpRouteDeployer.ts b/typescript/cli/src/warp/WarpRouteDeployer.ts deleted file mode 100644 index 3b61274c44..0000000000 --- a/typescript/cli/src/warp/WarpRouteDeployer.ts +++ /dev/null @@ -1,314 +0,0 @@ -// import { ethers } from 'ethers'; -// import yargs from 'yargs'; - -// import { -// ERC20__factory, -// ERC721__factory, -// HypERC20Deployer, -// HypERC721Deployer, -// TokenConfig, -// TokenFactories, -// TokenType, -// } from '@hyperlane-xyz/hyperlane-token'; -// import { -// ChainMap, -// HyperlaneContractsMap, -// MultiProvider, -// RouterConfig, -// chainMetadata, -// objMap, -// objMerge, -// } from '@hyperlane-xyz/sdk'; -// import { types } from '@hyperlane-xyz/utils'; - -// import { warpRouteConfig } from '../../examples/warp_tokens.js'; -// import { -// artifactsAddressesMap, -// assertBalances, -// assertBytes32, -// getMultiProvider, -// sdkContractAddressesMap, -// } from '../config.js'; -// import { createLogger } from '../logger.js'; -// import { -// mergeJson, -// tryReadJson, -// writeFileAtPath, -// writeJson, -// } from '../utils/files.js'; - -// import { -// WarpBaseTokenConfig, -// getWarpConfigChains, -// validateWarpTokenConfig, -// } from './config.js'; -// import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; - -// export async function getArgs(multiProvider: MultiProvider) { -// const args = await yargs(process.argv.slice(2)) -// .describe('key', 'A hexadecimal private key for transaction signing') -// .string('key') -// .coerce('key', assertBytes32) -// .demandOption('key') -// .middleware( -// assertBalances(multiProvider, () => getWarpConfigChains(warpRouteConfig)), -// ); -// return args.argv; -// } - -// export type WarpRouteArtifacts = { -// router: types.Address; -// tokenType: TokenType; -// }; - -// export class WarpRouteDeployer { -// constructor( -// public readonly multiProvider: MultiProvider, -// public readonly signer: ethers.Signer, -// protected readonly logger = createLogger('WarpRouteDeployer'), -// ) {} - -// static async fromArgs(): Promise { -// const multiProvider = getMultiProvider(); -// const { key } = await getArgs(multiProvider); -// const signer = new ethers.Wallet(key); -// multiProvider.setSharedSigner(signer); -// return new WarpRouteDeployer(multiProvider, signer); -// } - -// async deploy(): Promise { -// const { configMap, baseToken } = await this.buildHypTokenConfig(); - -// this.logger('Initiating hyp token deployments'); -// const deployer = baseToken.isNft -// ? new HypERC721Deployer(this.multiProvider) -// : new HypERC20Deployer(this.multiProvider); - -// await deployer.deploy(configMap); -// this.logger('Hyp token deployments complete'); - -// this.writeDeploymentResult( -// deployer.deployedContracts, -// configMap, -// baseToken, -// ); -// } - -// async buildHypTokenConfig() { -// validateWarpTokenConfig(warpRouteConfig); -// const { base, synthetics } = warpRouteConfig; -// const { type: baseType, chainName: baseChainName } = base; - -// const isCollateral = baseType === TokenType.collateral; -// const baseTokenAddr = isCollateral -// ? base.address -// : ethers.constants.AddressZero; -// const isNft = !!(isCollateral && base.isNft); - -// const owner = await this.signer.getAddress(); - -// const mergedContractAddresses = objMerge( -// sdkContractAddressesMap, -// artifactsAddressesMap(), -// ); - -// const configMap: ChainMap = { -// [baseChainName]: { -// type: baseType, -// token: baseTokenAddr, -// owner, -// mailbox: base.mailbox || mergedContractAddresses[baseChainName].mailbox, -// interchainSecurityModule: -// base.interchainSecurityModule || -// mergedContractAddresses[baseChainName].interchainSecurityModule || -// mergedContractAddresses[baseChainName].multisigIsm, -// interchainGasPaymaster: -// base.interchainGasPaymaster || -// mergedContractAddresses[baseChainName] -// .defaultIsmInterchainGasPaymaster, -// foreignDeployment: base.foreignDeployment, -// name: base.name, -// symbol: base.symbol, -// decimals: base.decimals, -// }, -// }; -// this.logger( -// `Hyp token config on base chain ${baseChainName}:`, -// JSON.stringify(configMap[baseChainName]), -// ); - -// const baseTokenMetadata = await this.getBaseTokenMetadata(base); -// this.logger( -// `Using base token metadata: Name: ${baseTokenMetadata.name}, Symbol: ${baseTokenMetadata.symbol}, Decimals: ${baseTokenMetadata.decimals} `, -// ); - -// for (const synthetic of synthetics) { -// const sChainName = synthetic.chainName; -// configMap[sChainName] = { -// type: TokenType.synthetic, -// name: synthetic.name || baseTokenMetadata.name, -// symbol: synthetic.symbol || baseTokenMetadata.symbol, -// totalSupply: synthetic.totalSupply || 0, -// owner, -// mailbox: -// synthetic.mailbox || mergedContractAddresses[sChainName].mailbox, -// interchainSecurityModule: -// synthetic.interchainSecurityModule || -// mergedContractAddresses[sChainName].interchainSecurityModule || -// mergedContractAddresses[sChainName].multisigIsm, -// interchainGasPaymaster: -// synthetic.interchainGasPaymaster || -// mergedContractAddresses[sChainName].defaultIsmInterchainGasPaymaster, -// foreignDeployment: synthetic.foreignDeployment, -// }; -// this.logger( -// `Hyp token config on synthetic chain ${sChainName}:`, -// JSON.stringify(configMap[sChainName]), -// ); -// } -// return { -// configMap, -// baseToken: { -// type: baseType, -// chainName: baseChainName, -// address: baseTokenAddr, -// metadata: baseTokenMetadata, -// isNft, -// }, -// }; -// } - -// async getBaseTokenMetadata( -// base: WarpBaseTokenConfig, -// ): Promise { -// // Skip fetching metadata if it's already provided in the config -// if (base.name && base.symbol && base.decimals) { -// return { -// name: base.name, -// symbol: base.symbol, -// decimals: base.decimals, -// }; -// } - -// if (base.type === TokenType.native) { -// return ( -// this.multiProvider.getChainMetadata(base.chainName).nativeToken || -// chainMetadata.ethereum.nativeToken! -// ); -// } else if (base.type === TokenType.collateral) { -// this.logger( -// `Fetching token metadata for ${base.address} on ${base.chainName}}`, -// ); -// const provider = this.multiProvider.getProvider(base.chainName); -// if (base.isNft) { -// const erc721Contract = ERC721__factory.connect(base.address, provider); -// const [name, symbol] = await Promise.all([ -// erc721Contract.name(), -// erc721Contract.symbol(), -// ]); -// return { name, symbol, decimals: 0 }; -// } else { -// const erc20Contract = ERC20__factory.connect(base.address, provider); -// const [name, symbol, decimals] = await Promise.all([ -// erc20Contract.name(), -// erc20Contract.symbol(), -// erc20Contract.decimals(), -// ]); -// return { name, symbol, decimals }; -// } -// } else { -// throw new Error(`Unsupported token type: ${base}`); -// } -// } - -// writeDeploymentResult( -// contracts: HyperlaneContractsMap, -// configMap: ChainMap, -// baseToken: Awaited< -// ReturnType -// >['baseToken'], -// ) { -// this.writeTokenDeploymentArtifacts(contracts, configMap); -// this.writeWarpUiTokenList(contracts, baseToken, configMap); -// } - -// writeTokenDeploymentArtifacts( -// contracts: HyperlaneContractsMap, -// configMap: ChainMap, -// ) { -// this.logger( -// 'Writing token deployment addresses to artifacts/warp-token-addresses.json', -// ); -// const artifacts: ChainMap = objMap( -// contracts, -// (chain, contract) => { -// return { -// router: contract.router.address, -// tokenType: configMap[chain].type, -// }; -// }, -// ); -// mergeJson('./artifacts/warp-token-addresses.json', artifacts); -// } - -// writeWarpUiTokenList( -// contracts: HyperlaneContractsMap, -// baseToken: Awaited< -// ReturnType -// >['baseToken'], -// configMap: ChainMap, -// ) { -// this.logger( -// 'Writing warp ui token list to artifacts/warp-ui-token-list.json and artifacts/warp-ui-token-list.ts', -// ); -// const currentTokenList: WarpUITokenConfig[] = -// tryReadJson('./artifacts/warp-ui-token-list.json') || []; - -// const { type, address, chainName, metadata, isNft } = baseToken; -// const { name, symbol, decimals } = metadata; -// const hypTokenAddr = -// contracts[chainName]?.router?.address || -// configMap[chainName]?.foreignDeployment; -// if (!hypTokenAddr) { -// throw Error( -// 'No base Hyperlane token address deployed and no foreign deployment specified', -// ); -// } -// const commonFields = { -// chainId: this.multiProvider.getChainId(chainName), -// name, -// symbol, -// decimals, -// }; -// let newToken: WarpUITokenConfig; -// if (type === TokenType.collateral) { -// newToken = { -// ...commonFields, -// type: TokenType.collateral, -// address, -// hypCollateralAddress: hypTokenAddr, -// isNft, -// }; -// } else if (type === TokenType.native) { -// newToken = { -// ...commonFields, -// type: TokenType.native, -// hypNativeAddress: hypTokenAddr, -// }; -// } else { -// throw new Error(`Unsupported token type: ${type}`); -// } - -// currentTokenList.push(newToken); -// // Write list as JSON -// writeJson('./artifacts/warp-ui-token-list.json', currentTokenList); -// // Also write list as TS -// const serializedTokens = currentTokenList -// .map((t) => JSON.stringify(t)) -// .join(',\n'); -// writeFileAtPath( -// './artifacts/warp-ui-token-list.ts', -// `export const tokenList = [\n${serializedTokens}\n];`, -// ); -// } -// } diff --git a/typescript/cli/src/warp/config.ts b/typescript/cli/src/warp/config.ts deleted file mode 100644 index 6cfe1b37a2..0000000000 --- a/typescript/cli/src/warp/config.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { z } from 'zod'; - -import { TokenType } from '@hyperlane-xyz/hyperlane-token'; -import { RouterConfig } from '@hyperlane-xyz/sdk'; - -import { MinimalTokenMetadata } from './types.js'; - -type WarpBaseToken = { - type: TokenType.native | TokenType.collateral; - chainName: string; -} & Partial & - Partial; - -export interface WarpNativeTokenConfig extends WarpBaseToken { - type: TokenType.native; -} - -export interface WarpCollateralTokenConfig extends WarpBaseToken { - type: TokenType.collateral; - address: string; - isNft?: boolean; -} - -export type WarpSyntheticTokenConfig = { - chainName: string; - totalSupply?: number; -} & Partial & - Partial; - -export type WarpBaseTokenConfig = - | WarpNativeTokenConfig - | WarpCollateralTokenConfig; - -export interface WarpRouteConfig { - base: WarpBaseTokenConfig; - synthetics: WarpSyntheticTokenConfig[]; -} - -// Zod schema for Warp Route config validation validation -const ConnectionConfigSchema = { - mailbox: z.string().optional(), - interchainGasPaymaster: z.string().optional(), - interchainSecurityModule: z.string().optional(), -}; - -export const WarpTokenConfigSchema = z.object({ - base: z.object({ - type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)), - chainName: z.string(), - address: z.string().optional(), - isNft: z.boolean().optional(), - ...ConnectionConfigSchema, - }), - synthetics: z - .array( - z.object({ - chainName: z.string(), - name: z.string().optional(), - symbol: z.string().optional(), - totalSupply: z.number().optional(), - ...ConnectionConfigSchema, - }), - ) - .nonempty(), -}); - -export function validateWarpTokenConfig(data: WarpRouteConfig) { - const result = WarpTokenConfigSchema.safeParse(data); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, - ); - } -} - -export function getWarpConfigChains(config: WarpRouteConfig) { - const { base, synthetics } = config; - return [base, ...synthetics] - .filter((c) => !c.foreignDeployment) - .map((token) => token.chainName); -} diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 72acfcb8fb..05727a7c7a 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -161,6 +161,7 @@ export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker'; export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer'; export { GasRouterApp, Router, RouterApp } from './router/RouterApps'; export { + ConnectionClientConfig, ConnectionClientViolation, ConnectionClientViolationType, GasConfig, diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index cd484dde7b..26b7fc7ffa 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -36,6 +36,7 @@ import { RoutingIsmConfig, } from './types'; +// TODO this should handle cached addresses like the other deployers export class HyperlaneIsmFactory extends HyperlaneApp { static fromEnvironment( env: Env, From e81eaf349d1bcf9cdf8e57f7201c3c5b03caadd5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 14:56:23 -0400 Subject: [PATCH 05/82] Update readme --- typescript/cli/README.md | 68 +++++----------------------------------- 1 file changed, 7 insertions(+), 61 deletions(-) diff --git a/typescript/cli/README.md b/typescript/cli/README.md index 77d649174e..8f3af2c9c8 100644 --- a/typescript/cli/README.md +++ b/typescript/cli/README.md @@ -39,71 +39,17 @@ git clone https://github.com/hyperlane-xyz/hyperlane-monorepo.git cd hyperlane-monorepo yarn install && yarn build cd typescript/cli -node ./dist/cli.js +yarn hyperlane ``` -## Deploying Hyperlane +## Common commands -See below for instructions on using the scripts in this repo to deploy a Hyperlane core instance. For more details see the [deploy documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-hyperlane). +View help: `hyperlane --help` -### Deploying core contracts +Create a core deployment config: `hyperlane config create` -If you're deploying to a new chain, ensure there is a corresponding entry `config/chains.ts`, `config/multisig_ism.ts`, and `config/start_blocks.ts`. +Run hyperlane core deployments: `hyperlane deploy core` -This script is used to deploy the following core Hyperlane contracts to a new chain. The Hyperlane protocol expects exactly one instance of these contracts on every supported chain. +Run warp route deployments: `hyperlane deploy warp` -- `Mailbox`: for sending and receiving messages -- `ValidatorAnnounce`: for registering validators - -This script also deploys the following contracts to all chains, new and existing. The Hyperlane protocol supports many instances of these contracts on every supported chains. - -- `ISM (e.g. MultisigISM)`: for verifying inbound messages from remote chains -- `InterchainGasPaymaster`: for paying relayers for message delivery -- `TestRecipient`: used to test that interchain messages can be delivered - -```bash -yarn ts-node scripts/deploy-hyperlane.ts --local anvil \ - --remotes goerli sepolia \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -``` - -### Sending test messages - -This script is used to verify that Hyperlane messages can be sent between specified chains. - -Users should have first deployed `TestRecipient` contracts to each of the specified chains. - -```sh -yarn ts-node scripts/test-messages.ts \ - --chains anvil goerli sepolia \ - --key 0x6f0311f4a0722954c46050bb9f088c4890999e16b64ad02784d24b5fd6d09061 -``` - -## Deploying Warp Routes - -Warp Routes are Hyperlane's unique take on the concept of token bridging, allowing you to permissionlessly bridge any ERC20-like asset to any chain. You can combine Warp Routes with a Hyperlane deployment to create economic trade routes between any chains already connected through Hyperlane. - -See below for instructions on using the scripts in this repo to deploy Hyperlane Warp Routes. For more details see the [warp route documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route). - -### Deploying Warp contracts - -Establishing a warp route requires deployment of `HypERC20` contracts to the desired chains. Ensure there is an entry for all chains in `config/chains.ts`. - -The deployment also require details about the existing (collateral) token and the new synthetics that will be created. Ensure there are entries for them in `config/warp_tokens.ts`. - -```sh -yarn ts-node scripts/deploy-warp-routes.ts \ - --key 0x6f0311f4a0722954c46050bb9f088c4890999e16b64ad02784d24b5fd6d09061 -``` - -### Sending a test transfer - -```sh -yarn ts-node scripts/test-warp-transfer.ts \ - --origin goerli --destination alfajores --wei 100000000000000 \ - --key 0x6f0311f4a0722954c46050bb9f088c4890999e16b64ad02784d24b5fd6d09061 -``` - -### Deploying a Warp UI - -If you'd like to create a web-based user interface for your warp routes, see the [Warp UI documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route/deploy-the-ui-for-your-warp-route) +View SDK contract addresses: `hyperlane chains addresses` From 0cc2473e562ec818cae5843564edd9d87d34094c Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 15:28:32 -0400 Subject: [PATCH 06/82] Create stubs for send commands --- typescript/cli/README.md | 2 + typescript/cli/cli.ts | 2 + typescript/cli/src/commands/deploy.ts | 53 +++++---- typescript/cli/src/commands/options.ts | 20 ++++ typescript/cli/src/commands/send.ts | 105 ++++++++++++++++++ typescript/cli/src/context.ts | 4 +- typescript/cli/src/deploy/core.ts | 6 +- typescript/cli/src/deploy/warp.ts | 10 +- typescript/cli/src/send/message.ts | 21 ++++ .../cli/src/{test => send}/test-messages.ts | 0 .../src/{test => send}/test-warp-transfer.ts | 0 typescript/cli/src/send/transfer.ts | 25 +++++ typescript/cli/src/test/run.ts | 12 -- 13 files changed, 212 insertions(+), 48 deletions(-) create mode 100644 typescript/cli/src/commands/options.ts create mode 100644 typescript/cli/src/commands/send.ts create mode 100644 typescript/cli/src/send/message.ts rename typescript/cli/src/{test => send}/test-messages.ts (100%) rename typescript/cli/src/{test => send}/test-warp-transfer.ts (100%) create mode 100644 typescript/cli/src/send/transfer.ts delete mode 100644 typescript/cli/src/test/run.ts diff --git a/typescript/cli/README.md b/typescript/cli/README.md index 8f3af2c9c8..168c2dba6a 100644 --- a/typescript/cli/README.md +++ b/typescript/cli/README.md @@ -53,3 +53,5 @@ Run hyperlane core deployments: `hyperlane deploy core` Run warp route deployments: `hyperlane deploy warp` View SDK contract addresses: `hyperlane chains addresses` + +Send test message: `hyperlane send message` diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 34ae2654fd..90a585bbfd 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -5,6 +5,7 @@ import yargs from 'yargs'; import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { deployCommand } from './src/commands/deploy.js'; +import { sendCommand } from './src/commands/send.js'; import './src/logger.js'; import { errorRed } from './src/logger.js'; @@ -20,6 +21,7 @@ try { .command(chainsCommand) .command(configCommand) .command(deployCommand) + .command(sendCommand) .demandCommand() .strict() .help() diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 414a3d497a..801130b85f 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -1,9 +1,15 @@ -import { CommandModule, Options } from 'yargs'; +import { CommandModule } from 'yargs'; import { runCoreDeploy } from '../deploy/core.js'; import { runWarpDeploy } from '../deploy/warp.js'; import { logGray } from '../logger.js'; +import { + chainsCommandOption, + keyCommandOption, + outDirCommandOption, +} from './options.js'; + /** * Parent command */ @@ -19,19 +25,6 @@ export const deployCommand: CommandModule = { handler: () => console.log('Command required'), }; -const commonOptions: { [key: string]: Options } = { - key: { - type: 'string', - description: - 'A hex private key or seed phrase for transaction signing. Or use the HYP_KEY env var', - }, - out: { - type: 'string', - description: 'A folder name output artifacts into.', - default: './artifacts', - }, -}; - /** * Core command */ @@ -40,20 +33,17 @@ const coreCommand: CommandModule = { describe: 'Deploy core Hyperlane contracts', builder: (yargs) => yargs.options({ - ...commonOptions, - config: { - type: 'string', - description: 'A path to a JSON or YAML file with chain configs.', - default: './configs/chain-config.yaml', - }, + key: keyCommandOption, + chains: chainsCommandOption, + out: outDirCommandOption, }), - handler: (argv: any) => { + handler: async (argv: any) => { logGray('Hyperlane permissionless core deployment'); logGray('----------------------------------------'); const key: string = argv.key || process.env.HYP_KEY; - const configPath: string = argv.config; + const chainConfigPath: string = argv.chains; const outPath: string = argv.out; - return runCoreDeploy({ key, configPath, outPath }); + await runCoreDeploy({ key, chainConfigPath, outPath }); }, }; @@ -65,7 +55,9 @@ const warpCommand: CommandModule = { describe: 'Deploy Warp Route contracts', builder: (yargs) => yargs.options({ - ...commonOptions, + key: keyCommandOption, + chains: chainsCommandOption, + out: outDirCommandOption, config: { type: 'string', description: 'A path to a JSON or YAML file with a warp config.', @@ -76,11 +68,18 @@ const warpCommand: CommandModule = { description: 'File path to core deployment output artifacts', }, }), - handler: (argv: any) => { + handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; - const configPath: string = argv.config; + const chainConfigPath: string = argv.chains; + const warpConfigPath: string = argv.config; const corePath: string = argv.core; const outPath: string = argv.out; - return runWarpDeploy({ key, configPath, corePath, outPath }); + await runWarpDeploy({ + key, + chainConfigPath, + warpConfigPath, + corePath, + outPath, + }); }, }; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts new file mode 100644 index 0000000000..f86beeb931 --- /dev/null +++ b/typescript/cli/src/commands/options.ts @@ -0,0 +1,20 @@ +// A set of common options +import { Options } from 'yargs'; + +export const keyCommandOption: Options = { + type: 'string', + description: + 'A hex private key or seed phrase for transaction signing. Or use the HYP_KEY env var', +}; + +export const chainsCommandOption: Options = { + type: 'string', + description: 'A path to a JSON or YAML file with chain configs.', + default: './configs/chain-config.yaml', +}; + +export const outDirCommandOption: Options = { + type: 'string', + description: 'A folder name output artifacts into.', + default: './artifacts', +}; diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts new file mode 100644 index 0000000000..95dd52a3bf --- /dev/null +++ b/typescript/cli/src/commands/send.ts @@ -0,0 +1,105 @@ +import { CommandModule, Options } from 'yargs'; + +import { log } from '../logger.js'; +import { sendTestMessage } from '../send/message.js'; +import { sendTestTrasfer } from '../send/transfer.js'; + +import { chainsCommandOption, keyCommandOption } from './options.js'; + +/** + * Parent command + */ +export const sendCommand: CommandModule = { + command: 'send', + describe: 'Send a test message or transfer', + builder: (yargs) => + yargs + .command(messageCommand) + .command(transferCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * Message command + */ +const messageOptions: { [k: string]: Options } = { + key: keyCommandOption, + chains: chainsCommandOption, + origin: { + type: 'string', + description: 'Origin chain to send message from', + demandOption: true, + }, + destination: { + type: 'string', + description: 'Destination chain to send message to', + demandOption: true, + }, + timeout: { + type: 'number', + description: 'Timeout in seconds', + default: 5 * 60, + }, +}; + +const messageCommand: CommandModule = { + command: 'message', + describe: 'Send a test message to a remote chain', + builder: (yargs) => yargs.options(messageOptions), + handler: async (argv: any) => { + const key: string = argv.key || process.env.HYP_KEY; + const chainConfigPath: string = argv.chains; + const origin: string = argv.origin; + const destination: string = argv.destination; + const timeout: number = argv.timeout; + await sendTestMessage({ + key, + chainConfigPath, + origin, + destination, + timeout, + }); + }, +}; + +/** + * Transfer command + */ +const transferCommand: CommandModule = { + command: 'transfer', + describe: 'Send a test token transfer on a warp route', + builder: (yargs) => + yargs.options({ + ...messageOptions, + wei: { + type: 'number', + description: 'Amount in wei to send', + default: 1, + }, + recipient: { + type: 'string', + description: 'Token recipient address (defaults to sender)', + }, + }), + handler: async (argv: any) => { + const key: string = argv.key || process.env.HYP_KEY; + const chainConfigPath: string = argv.chains; + const origin: string = argv.origin; + const destination: string = argv.destination; + const timeout: number = argv.timeout; + const wei: number = argv.wei; + const recipient: string | undefined = argv.recipient; + + await sendTestTrasfer({ + key, + chainConfigPath, + origin, + destination, + wei, + recipient, + timeout, + }); + }, +}; diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index b677d5938d..cc7fca155c 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -16,9 +16,9 @@ export const sdkContractAddressesMap = { ...hyperlaneEnvironments.mainnet, }; -export function getDeployerContext(key: string, configPath: string) { +export function getDeployerContext(key: string, chainConfigPath: string) { const signer = keyToSigner(key); - const customChains = readChainConfigIfExists(configPath); + const customChains = readChainConfigIfExists(chainConfigPath); const multiProvider = getMultiProvider(customChains, signer); return { signer, customChains, multiProvider }; } diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 257d5799d2..0fede8d75d 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -49,16 +49,16 @@ import { runPreflightChecks } from './utils.js'; export async function runCoreDeploy({ key, - configPath, + chainConfigPath, outPath, }: { key: string; - configPath: string; + chainConfigPath: string; outPath: string; }) { const { customChains, multiProvider, signer } = getDeployerContext( key, - configPath, + chainConfigPath, ); const { local, remotes, allChains } = await runChainSelectionStep( diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 4bf895e0b7..0e54aafb07 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -38,18 +38,20 @@ import { runPreflightChecks } from './utils.js'; export async function runWarpDeploy({ key, - configPath, + chainConfigPath, + warpConfigPath, corePath, outPath, }: { key: string; - configPath: string; + chainConfigPath: string; + warpConfigPath: string; corePath: string; outPath: string; }) { - const { multiProvider, signer } = getDeployerContext(key, configPath); + const { multiProvider, signer } = getDeployerContext(key, chainConfigPath); - const warpRouteConfig = readWarpRouteConfig(configPath); + const warpRouteConfig = readWarpRouteConfig(warpConfigPath); const artifacts = corePath ? readDeploymentArtifacts(corePath) : undefined; const configs = await runBuildConfigStep({ diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts new file mode 100644 index 0000000000..74e09df5c8 --- /dev/null +++ b/typescript/cli/src/send/message.ts @@ -0,0 +1,21 @@ +import { ChainName } from '@hyperlane-xyz/sdk'; + +import { getDeployerContext } from '../context.js'; + +export async function sendTestMessage({ + key, + chainConfigPath, + origin, + destination, + timeout, +}: { + key: string; + chainConfigPath: string; + origin: ChainName; + destination: ChainName; + timeout: number; +}) { + getDeployerContext(key, chainConfigPath); + // TODO migrate test-messages.ts here + console.log(origin, destination, timeout); +} diff --git a/typescript/cli/src/test/test-messages.ts b/typescript/cli/src/send/test-messages.ts similarity index 100% rename from typescript/cli/src/test/test-messages.ts rename to typescript/cli/src/send/test-messages.ts diff --git a/typescript/cli/src/test/test-warp-transfer.ts b/typescript/cli/src/send/test-warp-transfer.ts similarity index 100% rename from typescript/cli/src/test/test-warp-transfer.ts rename to typescript/cli/src/send/test-warp-transfer.ts diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts new file mode 100644 index 0000000000..846788f460 --- /dev/null +++ b/typescript/cli/src/send/transfer.ts @@ -0,0 +1,25 @@ +import { ChainName } from '@hyperlane-xyz/sdk'; + +import { getDeployerContext } from '../context.js'; + +export async function sendTestTrasfer({ + key, + chainConfigPath, + origin, + destination, + wei, + recipient, + timeout, +}: { + key: string; + chainConfigPath: string; + origin: ChainName; + destination: ChainName; + wei: number; + recipient?: string; + timeout: number; +}) { + getDeployerContext(key, chainConfigPath); + // TODO migrate test-warp-transfer.ts here + console.log(origin, destination, wei, recipient, timeout); +} diff --git a/typescript/cli/src/test/run.ts b/typescript/cli/src/test/run.ts deleted file mode 100644 index 9e35cb639f..0000000000 --- a/typescript/cli/src/test/run.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { error, logger } from '../logger.js'; - -//TODO remove -export function run(name: string, fn: () => Promise) { - logger(`Beginning ${name} script`); - fn() - .then(() => logger(`${name} completed successfully`)) - .catch((e: any) => { - error(`Error running ${name}`, e); - process.exit(1); - }); -} From 951f1895c530b72f58d2b7c788e144ef22b05ecb Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 18:03:15 -0400 Subject: [PATCH 07/82] Finish implementation of send commands --- typescript/cli/src/commands/deploy.ts | 10 +- typescript/cli/src/commands/options.ts | 5 + typescript/cli/src/commands/send.ts | 22 +- typescript/cli/src/consts.ts | 5 +- typescript/cli/src/context.ts | 11 + typescript/cli/src/deploy/core.ts | 11 +- typescript/cli/src/deploy/warp.ts | 16 +- typescript/cli/src/send/message.ts | 111 ++++++++- typescript/cli/src/send/test-messages.ts | 149 ------------ typescript/cli/src/send/test-warp-transfer.ts | 228 ------------------ typescript/cli/src/send/transfer.ts | 152 +++++++++++- typescript/token/src/app.ts | 13 +- 12 files changed, 314 insertions(+), 419 deletions(-) delete mode 100644 typescript/cli/src/send/test-messages.ts delete mode 100644 typescript/cli/src/send/test-warp-transfer.ts diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 801130b85f..79294e07ab 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -6,6 +6,7 @@ import { logGray } from '../logger.js'; import { chainsCommandOption, + coreArtifactsOption, keyCommandOption, outDirCommandOption, } from './options.js'; @@ -58,27 +59,24 @@ const warpCommand: CommandModule = { key: keyCommandOption, chains: chainsCommandOption, out: outDirCommandOption, + core: coreArtifactsOption, config: { type: 'string', description: 'A path to a JSON or YAML file with a warp config.', default: './configs/warp-tokens.yaml', }, - core: { - type: 'string', - description: 'File path to core deployment output artifacts', - }, }), handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; const warpConfigPath: string = argv.config; - const corePath: string = argv.core; + const coreArtifactsPath: string = argv.core; const outPath: string = argv.out; await runWarpDeploy({ key, chainConfigPath, warpConfigPath, - corePath, + coreArtifactsPath, outPath, }); }, diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index f86beeb931..87c1449045 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -18,3 +18,8 @@ export const outDirCommandOption: Options = { description: 'A folder name output artifacts into.', default: './artifacts', }; + +export const coreArtifactsOption: Options = { + type: 'string', + description: 'File path to core deployment output artifacts', +}; diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 95dd52a3bf..c2756b90bd 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -2,9 +2,13 @@ import { CommandModule, Options } from 'yargs'; import { log } from '../logger.js'; import { sendTestMessage } from '../send/message.js'; -import { sendTestTrasfer } from '../send/transfer.js'; +import { sendTestTransfer } from '../send/transfer.js'; -import { chainsCommandOption, keyCommandOption } from './options.js'; +import { + chainsCommandOption, + coreArtifactsOption, + keyCommandOption, +} from './options.js'; /** * Parent command @@ -27,6 +31,7 @@ export const sendCommand: CommandModule = { const messageOptions: { [k: string]: Options } = { key: keyCommandOption, chains: chainsCommandOption, + core: coreArtifactsOption, origin: { type: 'string', description: 'Origin chain to send message from', @@ -51,12 +56,14 @@ const messageCommand: CommandModule = { handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; + const coreArtifactsPath: string = argv.core; const origin: string = argv.origin; const destination: string = argv.destination; const timeout: number = argv.timeout; await sendTestMessage({ key, chainConfigPath, + coreArtifactsPath, origin, destination, timeout, @@ -73,6 +80,11 @@ const transferCommand: CommandModule = { builder: (yargs) => yargs.options({ ...messageOptions, + router: { + type: 'string', + description: 'The address of the token router contract', + demandOption: true, + }, wei: { type: 'number', description: 'Amount in wei to send', @@ -86,17 +98,21 @@ const transferCommand: CommandModule = { handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; + const coreArtifactsPath: string = argv.core; const origin: string = argv.origin; const destination: string = argv.destination; const timeout: number = argv.timeout; + const routerAddress: string = argv.router; const wei: number = argv.wei; const recipient: string | undefined = argv.recipient; - await sendTestTrasfer({ + await sendTestTransfer({ key, chainConfigPath, + coreArtifactsPath, origin, destination, + routerAddress, wei, recipient, timeout, diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index 249949b736..b313a8c40e 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,2 +1,3 @@ -export const MINIMUM_CORE_DEPLOY_BALANCE = 0.5; // Half an ETH -export const MINIMUM_WARP_DEPLOY_BALANCE = 0.2; // 2/10 an ETH +export const MINIMUM_CORE_DEPLOY_BALANCE = 0.5; +export const MINIMUM_WARP_DEPLOY_BALANCE = 0.2; +export const MINIMUM_TEST_SEND_BALANCE = 0.01; diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index cc7fca155c..53640ef9c5 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -3,9 +3,11 @@ import { ethers } from 'ethers'; import { ChainMap, ChainMetadata, + HyperlaneContractsMap, MultiProvider, chainMetadata, hyperlaneEnvironments, + objMerge, } from '@hyperlane-xyz/sdk'; import { readChainConfigIfExists } from './configs.js'; @@ -16,6 +18,15 @@ export const sdkContractAddressesMap = { ...hyperlaneEnvironments.mainnet, }; +export function getMergedContractAddresses( + artifacts?: HyperlaneContractsMap, +) { + return objMerge( + sdkContractAddressesMap, + artifacts || {}, + ) as HyperlaneContractsMap; +} + export function getDeployerContext(key: string, chainConfigPath: string) { const signer = keyToSigner(key); const customChains = readChainConfigIfExists(chainConfigPath); diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 0fede8d75d..8a06c2f035 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -37,7 +37,11 @@ import { types } from '@hyperlane-xyz/utils'; import { readDeploymentArtifacts, readMultisigConfig } from '../configs.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; -import { getDeployerContext, sdkContractAddressesMap } from '../context.js'; +import { + getDeployerContext, + getMergedContractAddresses, + sdkContractAddressesMap, +} from '../context.js'; import { log, logBlue, logGray, logGreen } from '../logger.js'; import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; @@ -242,11 +246,12 @@ async function executeDeploy({ const owner = await signer.getAddress(); const allChains = [local, ...remotes]; + const mergedContractAddrs = getMergedContractAddresses(artifacts); // 1. Deploy ISM factories to all deployable chains that don't have them. log('Deploying ISM factory contracts'); const ismDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); - ismDeployer.cacheAddressesMap(objMerge(sdkContractAddressesMap, artifacts)); + ismDeployer.cacheAddressesMap(mergedContractAddrs); const ismFactoryContracts = await ismDeployer.deploy(allChains); artifacts = writeMergedAddresses( contractsFilePath, @@ -272,7 +277,7 @@ async function executeDeploy({ // Build an IsmFactory that covers all chains so that we can // use it later to deploy ISMs to remote chains. const ismFactory = HyperlaneIsmFactory.fromAddressesMap( - objMerge(sdkContractAddressesMap, artifacts), + mergedContractAddrs, multiProvider, ); diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 0e54aafb07..1d1fa3d97a 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -19,7 +19,6 @@ import { RouterConfig, chainMetadata as defaultChainMetadata, objMap, - objMerge, } from '@hyperlane-xyz/sdk'; import { types } from '@hyperlane-xyz/utils'; @@ -29,7 +28,7 @@ import { readWarpRouteConfig, } from '../configs.js'; import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; -import { getDeployerContext, sdkContractAddressesMap } from '../context.js'; +import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { log, logBlue, logGray, logGreen } from '../logger.js'; import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; @@ -40,19 +39,21 @@ export async function runWarpDeploy({ key, chainConfigPath, warpConfigPath, - corePath, + coreArtifactsPath, outPath, }: { key: string; chainConfigPath: string; warpConfigPath: string; - corePath: string; + coreArtifactsPath: string; outPath: string; }) { const { multiProvider, signer } = getDeployerContext(key, chainConfigPath); const warpRouteConfig = readWarpRouteConfig(warpConfigPath); - const artifacts = corePath ? readDeploymentArtifacts(corePath) : undefined; + const artifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; const configs = await runBuildConfigStep({ warpRouteConfig, @@ -98,10 +99,7 @@ async function runBuildConfigStep({ `Using base token metadata: Name: ${baseMetadata.name}, Symbol: ${baseMetadata.symbol}, Decimals: ${baseMetadata.decimals}`, ); - const mergedContractAddrs = objMerge( - sdkContractAddressesMap, - artifacts || {}, - ) as HyperlaneContractsMap; + const mergedContractAddrs = getMergedContractAddresses(artifacts); // Create configs that coalesce together values from the config file, // the artifacts, and the SDK as a fallback diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 74e09df5c8..428c6beb57 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -1,21 +1,122 @@ -import { ChainName } from '@hyperlane-xyz/sdk'; +import { + ChainName, + DispatchedMessage, + HyperlaneContractsMap, + HyperlaneCore, + HyperlaneIgp, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { utils } from '@hyperlane-xyz/utils'; -import { getDeployerContext } from '../context.js'; +import { readDeploymentArtifacts } from '../configs.js'; +import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; +import { getDeployerContext, getMergedContractAddresses } from '../context.js'; +import { runPreflightChecks } from '../deploy/utils.js'; +import { errorRed, log, logGreen } from '../logger.js'; +const GAS_AMOUNT = 100_000; + +// TODO improve the UX here by making params optional and +// prompting for missing values export async function sendTestMessage({ key, chainConfigPath, + coreArtifactsPath, origin, destination, timeout, }: { key: string; chainConfigPath: string; + coreArtifactsPath: string; origin: ChainName; destination: ChainName; timeout: number; }) { - getDeployerContext(key, chainConfigPath); - // TODO migrate test-messages.ts here - console.log(origin, destination, timeout); + const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); + const artifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + await runPreflightChecks({ + local: origin, + remotes: [destination], + multiProvider, + signer, + minBalance: MINIMUM_TEST_SEND_BALANCE, + }); + + await utils.timeout( + executeDelivery({ origin, destination, multiProvider, artifacts }), + timeout * 1000, + 'Timed out waiting for messages to be delivered', + ); +} + +async function executeDelivery({ + origin, + destination, + multiProvider, + artifacts, +}: { + origin: ChainName; + destination: ChainName; + multiProvider: MultiProvider; + artifacts?: HyperlaneContractsMap; +}) { + const mergedContractAddrs = getMergedContractAddresses(artifacts); + + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + const igp = HyperlaneIgp.fromAddressesMap(mergedContractAddrs, multiProvider); + const mailbox = core.getContracts(origin).mailbox; + const defaultIgp = igp.getContracts(origin).defaultIsmInterchainGasPaymaster; + const destinationDomain = multiProvider.getDomainId(destination); + let message: DispatchedMessage; + try { + const recipient = mergedContractAddrs[destination].testRecipient; + if (!recipient) { + throw new Error(`Unable to find TestRecipient for ${destination}`); + } + const messageTx = await mailbox.dispatch( + destinationDomain, + utils.addressToBytes32(recipient), + '0xdeadbeef', + ); + const messageReceipt = await multiProvider.handleTx(origin, messageTx); + message = core.getDispatchedMessages(messageReceipt)[0]; + log(`Sent message from ${origin} to ${recipient} on ${destination}.`); + log(`Message ID: ${message.id}`); + + const value = await defaultIgp.quoteGasPayment( + destinationDomain, + GAS_AMOUNT, + ); + const paymentTx = await defaultIgp.payForGas( + message.id, + destinationDomain, + GAS_AMOUNT, + await multiProvider.getSignerAddress(origin), + { value }, + ); + await paymentTx.wait(); + } catch (e) { + errorRed( + `Encountered error sending message from ${origin} to ${destination}`, + ); + throw e; + } + while (true) { + const destination = multiProvider.getChainName(message.parsed.destination); + const mailbox = core.getContracts(destination).mailbox; + const delivered = await mailbox.delivered(message.id); + if (delivered) break; + + log('Waiting for message delivery on destination chain...'); + await utils.sleep(5000); + } + + logGreen('Message was delivered!'); } diff --git a/typescript/cli/src/send/test-messages.ts b/typescript/cli/src/send/test-messages.ts deleted file mode 100644 index 7c6b391ea5..0000000000 --- a/typescript/cli/src/send/test-messages.ts +++ /dev/null @@ -1,149 +0,0 @@ -// import { ethers } from 'ethers'; -// import yargs from 'yargs'; - -// import { -// DispatchedMessage, -// HyperlaneCore, -// HyperlaneIgp, -// MultiProvider, -// objMerge, -// } from '@hyperlane-xyz/sdk'; -// import { utils } from '@hyperlane-xyz/utils'; - -// import { -// artifactsAddressesMap, -// assertBalances, -// assertBytes32, -// assertUnique, -// getMultiProvider, -// sdkContractAddressesMap, -// } from '../config.js'; -// import { createLogger } from '../logger.js'; - -// import { run } from './run.js'; - -// const logger = createLogger('MessageDeliveryTest'); -// const error = createLogger('MessageDeliveryTest', true); -// const mergedContractAddresses = objMerge( -// sdkContractAddressesMap, -// artifactsAddressesMap(), -// ); - -// function getArgs(multiProvider: MultiProvider) { -// // Only accept chains for which we have both a connection and contract addresses -// const { intersection } = multiProvider.intersect( -// Object.keys(mergedContractAddresses), -// ); -// return yargs(process.argv.slice(2)) -// .describe('chains', 'chain to send message from') -// .choices('chains', intersection) -// .demandOption('chains') -// .array('chains') -// .middleware(assertUnique((argv) => argv.chains)) -// .describe('key', 'hexadecimal private key for transaction signing') -// .string('key') -// .coerce('key', assertBytes32) -// .demandOption('key') -// .describe('timeout', 'timeout in seconds') -// .number('timeout') -// .default('timeout', 10 * 60) -// .middleware(assertBalances(multiProvider, (argv) => argv.chains)).argv; -// } - -// run('Message delivery test', async () => { -// let timedOut = false; -// const multiProvider = getMultiProvider(); -// const { chains, key, timeout } = await getArgs(multiProvider); -// const timeoutId = setTimeout(() => { -// timedOut = true; -// }, timeout * 1000); -// const signer = new ethers.Wallet(key); -// multiProvider.setSharedSigner(signer); -// const core = HyperlaneCore.fromAddressesMap( -// mergedContractAddresses, -// multiProvider, -// ); -// const igp = HyperlaneIgp.fromAddressesMap( -// mergedContractAddresses, -// multiProvider, -// ); -// const messages: Set = new Set(); -// for (const origin of chains) { -// const mailbox = core.getContracts(origin).mailbox; -// const defaultIgp = -// igp.getContracts(origin).defaultIsmInterchainGasPaymaster; -// for (const destination of chains) { -// const destinationDomain = multiProvider.getDomainId(destination); -// if (origin === destination) continue; -// try { -// const recipient = mergedContractAddresses[destination] -// .testRecipient as string; -// if (!recipient) { -// throw new Error(`Unable to find TestRecipient for ${destination}`); -// } -// const messageTx = await mailbox.dispatch( -// destinationDomain, -// utils.addressToBytes32(recipient), -// '0xdeadbeef', -// ); -// const messageReceipt = await multiProvider.handleTx(origin, messageTx); -// const dispatchedMessages = core.getDispatchedMessages(messageReceipt); -// if (dispatchedMessages.length !== 1) continue; -// const dispatchedMessage = dispatchedMessages[0]; -// logger( -// `Sent message from ${origin} to ${recipient} on ${destination} with message ID ${dispatchedMessage.id}`, -// ); -// // Make gas payment... -// const gasAmount = 100_000; -// const value = await defaultIgp.quoteGasPayment( -// destinationDomain, -// gasAmount, -// ); -// const paymentTx = await defaultIgp.payForGas( -// dispatchedMessage.id, -// destinationDomain, -// gasAmount, -// await multiProvider.getSignerAddress(origin), -// { value }, -// ); -// await paymentTx.wait(); -// messages.add(dispatchedMessage); -// } catch (e) { -// error( -// `Encountered error sending message from ${origin} to ${destination}`, -// ); -// error(e); -// } -// } -// } -// while (messages.size > 0 && !timedOut) { -// for (const message of messages.values()) { -// const origin = multiProvider.getChainName(message.parsed.origin); -// const destination = multiProvider.getChainName( -// message.parsed.destination, -// ); -// const mailbox = core.getContracts(destination).mailbox; -// const delivered = await mailbox.delivered(message.id); -// if (delivered) { -// messages.delete(message); -// logger( -// `Message from ${origin} to ${destination} with ID ${ -// message!.id -// } was delivered`, -// ); -// } else { -// logger( -// `Message from ${origin} to ${destination} with ID ${ -// message!.id -// } has not yet been delivered`, -// ); -// } -// await utils.sleep(5000); -// } -// } -// clearTimeout(timeoutId); -// if (timedOut) { -// error('Timed out waiting for messages to be delivered'); -// process.exit(1); -// } -// }); diff --git a/typescript/cli/src/send/test-warp-transfer.ts b/typescript/cli/src/send/test-warp-transfer.ts deleted file mode 100644 index 335df3658c..0000000000 --- a/typescript/cli/src/send/test-warp-transfer.ts +++ /dev/null @@ -1,228 +0,0 @@ -// import assert from 'assert'; -// import { BigNumber, ContractReceipt, ethers } from 'ethers'; -// import yargs from 'yargs'; - -// import { -// ERC20__factory, -// HypERC20, -// HypERC20App, -// HypERC20Collateral, -// HypERC20Collateral__factory, -// HypERC20__factory, -// HypNative, -// HypNative__factory, -// TokenType, -// } from '@hyperlane-xyz/hyperlane-token'; -// import { -// ChainMap, -// HyperlaneCore, -// MultiProvider, -// objMap, -// objMerge, -// } from '@hyperlane-xyz/sdk'; -// import { utils } from '@hyperlane-xyz/utils'; - -// import { -// artifactsAddressesMap, -// assertBalances, -// assertBytes20, -// assertBytes32, -// getMultiProvider, -// sdkContractAddressesMap, -// } from '../config.js'; -// import { createLogger } from '../logger.js'; -// import { readJson } from '../utils/files.js'; -// import { WarpRouteArtifacts } from '../warp/WarpRouteDeployer.js'; - -// import { run } from './run.js'; - -// const logger = createLogger('WarpTransferTest'); -// const error = createLogger('WarpTransferTest', true); - -// const mergedContractAddresses = objMerge( -// sdkContractAddressesMap, -// artifactsAddressesMap(), -// ); - -// function getArgs(multiProvider: MultiProvider) { -// // Only accept chains for which we have both a connection and contract addresses -// const { intersection } = multiProvider.intersect( -// Object.keys(mergedContractAddresses), -// ); -// return yargs(process.argv.slice(2)) -// .describe('origin', 'chain to send tokens from') -// .choices('origin', intersection) -// .demandOption('origin') -// .string('origin') -// .describe('destination', 'chain to send tokens to') -// .choices('destination', intersection) -// .demandOption('destination') -// .string('destination') -// .describe('wei', 'amount in wei to send') -// .demandOption('wei') -// .number('wei') -// .describe('key', 'hexadecimal private key for transaction signing') -// .string('key') -// .coerce('key', assertBytes32) -// .demandOption('key') -// .describe('recipient', 'token recipient address') -// .string('recipient') -// .coerce('recipient', assertBytes20) -// .demandOption('recipient') -// .describe('timeout', 'timeout in seconds') -// .number('timeout') -// .default('timeout', 10 * 60) -// .middleware(assertBalances(multiProvider, (argv) => [argv.origin])).argv; -// } - -// function hypErc20FromAddressesMap( -// artifactsMap: ChainMap, -// multiProvider: MultiProvider, -// ): HypERC20App { -// const contractsMap = objMap(artifactsMap, (chain, artifacts) => { -// const signer = multiProvider.getSigner(chain); -// switch (artifacts.tokenType) { -// case TokenType.collateral: { -// const router = HypERC20Collateral__factory.connect( -// artifacts.router, -// signer, -// ); -// return { router }; -// } -// case TokenType.native: { -// const router = HypNative__factory.connect(artifacts.router, signer); -// return { router }; -// } -// case TokenType.synthetic: { -// const router = HypERC20__factory.connect(artifacts.router, signer); -// return { router }; -// } -// default: { -// throw new Error('Unsupported token type'); -// } -// } -// }); -// return new HypERC20App(contractsMap, multiProvider); -// } - -// run('Warp transfer test', async () => { -// let timedOut = false; -// const multiProvider = getMultiProvider(); -// const { recipient, origin, destination, wei, key, timeout } = await getArgs( -// multiProvider, -// ); -// const timeoutId = setTimeout(() => { -// timedOut = true; -// }, timeout * 1000); -// const signer = new ethers.Wallet(key); -// multiProvider.setSharedSigner(signer); -// const artifacts: ChainMap = readJson( -// './artifacts/warp-token-addresses.json', -// ); -// const app = hypErc20FromAddressesMap(artifacts, multiProvider); - -// const getDestinationBalance = async (): Promise => { -// switch (artifacts[destination].tokenType) { -// case TokenType.collateral: { -// const router = app.getContracts(destination) -// .router as HypERC20Collateral; -// const tokenAddress = await router.wrappedToken(); -// const token = ERC20__factory.connect(tokenAddress, signer); -// return token.balanceOf(recipient); -// } -// case TokenType.native: { -// return multiProvider.getProvider(destination).getBalance(recipient); -// } -// case TokenType.synthetic: { -// const router = app.getContracts(destination).router as HypERC20; -// return router.balanceOf(recipient); -// } -// default: { -// throw new Error('Unsupported collateral type'); -// } -// } -// }; -// const balanceBefore = await getDestinationBalance(); - -// const core = HyperlaneCore.fromAddressesMap( -// mergedContractAddresses, -// multiProvider, -// ); - -// let receipt: ContractReceipt; -// switch (artifacts[origin].tokenType) { -// case TokenType.collateral: { -// const router = app.getContracts(origin).router as HypERC20Collateral; -// const tokenAddress = await router.wrappedToken(); -// const token = ERC20__factory.connect(tokenAddress, signer); -// const approval = await token.allowance( -// await signer.getAddress(), -// router.address, -// ); -// if (approval.lt(wei)) { -// await token.approve(router.address, wei); -// } -// receipt = await app.transfer( -// origin, -// destination, -// utils.addressToBytes32(recipient), -// wei, -// ); -// break; -// } -// case TokenType.native: { -// const destinationDomain = multiProvider.getDomainId(destination); -// const router = app.getContracts(origin).router as HypNative; -// const gasPayment = await router.quoteGasPayment(destinationDomain); -// const value = gasPayment.add(wei); -// const tx = await router.transferRemote( -// destinationDomain, -// utils.addressToBytes32(recipient), -// wei, -// { value }, -// ); -// receipt = await tx.wait(); -// break; -// } -// case TokenType.synthetic: { -// receipt = await app.transfer( -// origin, -// destination, -// utils.addressToBytes32(recipient), -// wei, -// ); -// break; -// } -// default: { -// throw new Error('Unsupported token type'); -// } -// } - -// const messages = await core.getDispatchedMessages(receipt); -// const message = messages[0]; -// const msgDestination = multiProvider.getChainName(message.parsed.destination); -// assert(destination === msgDestination); - -// while ( -// !(await core.getContracts(destination).mailbox.delivered(message.id)) && -// !timedOut -// ) { -// logger(`Waiting for message delivery on destination chain`); -// await utils.sleep(1000); -// } - -// if (!timedOut) { -// logger(`Message delivered on destination chain!`); -// const balanceAfter = await getDestinationBalance(); -// if (!balanceAfter.gt(balanceBefore)) { -// throw new Error('Destination chain balance did not increase'); -// } -// logger(`Confirmed balance increase`); -// } - -// clearTimeout(timeoutId); -// if (timedOut) { -// error('Timed out waiting for messages to be delivered'); -// process.exit(1); -// } -// }); diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 846788f460..353c10f6f6 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -1,25 +1,165 @@ -import { ChainName } from '@hyperlane-xyz/sdk'; +import assert from 'assert'; +import { ethers } from 'ethers'; -import { getDeployerContext } from '../context.js'; +import { + ERC20__factory, + HypERC20App, + HypERC20Collateral__factory, +} from '@hyperlane-xyz/hyperlane-token'; +import { + ChainName, + HyperlaneContractsMap, + HyperlaneCore, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { types, utils } from '@hyperlane-xyz/utils'; -export async function sendTestTrasfer({ +import { readDeploymentArtifacts } from '../configs.js'; +import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; +import { getDeployerContext, getMergedContractAddresses } from '../context.js'; +import { runPreflightChecks } from '../deploy/utils.js'; +import { log, logGreen } from '../logger.js'; + +// TODO improve the UX here by making params optional and +// prompting for missing values +export async function sendTestTransfer({ key, chainConfigPath, + coreArtifactsPath, origin, destination, + routerAddress, wei, recipient, timeout, }: { key: string; chainConfigPath: string; + coreArtifactsPath: string; origin: ChainName; destination: ChainName; + routerAddress: types.Address; wei: number; recipient?: string; timeout: number; }) { - getDeployerContext(key, chainConfigPath); - // TODO migrate test-warp-transfer.ts here - console.log(origin, destination, wei, recipient, timeout); + const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); + const artifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + await runPreflightChecks({ + local: origin, + remotes: [destination], + multiProvider, + signer, + minBalance: MINIMUM_TEST_SEND_BALANCE, + }); + + await utils.timeout( + executeDelivery({ + origin, + destination, + routerAddress, + wei, + recipient, + signer, + multiProvider, + artifacts, + }), + timeout * 1000, + 'Timed out waiting for messages to be delivered', + ); +} + +async function executeDelivery({ + origin, + destination, + routerAddress, + wei, + recipient, + multiProvider, + signer, + artifacts, +}: { + origin: ChainName; + destination: ChainName; + routerAddress: types.Address; + wei: number; + recipient?: string; + multiProvider: MultiProvider; + signer: ethers.Signer; + artifacts?: HyperlaneContractsMap; +}) { + const signerAddress = await signer.getAddress(); + recipient ||= signerAddress; + + const mergedContractAddrs = getMergedContractAddresses(artifacts); + + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + + const provider = multiProvider.getProvider(origin); + const connectedSigner = signer.connect(provider); + const wrappedToken = await getWrappedToken(routerAddress, provider); + if (wrappedToken) { + const token = ERC20__factory.connect(wrappedToken, connectedSigner); + const approval = await token.allowance(signerAddress, routerAddress); + if (approval.lt(wei)) { + const approveTx = await token.approve(routerAddress, wei); + await approveTx.wait(); + } + } else { + // TODO finish support for other types + // See code in warp UI for an example + // Requires gas handling + throw new Error( + 'Sorry, only HypERC20Collateral transfers are currently supported in the CLI', + ); + } + + const app = new HypERC20App( + { + [origin]: { + router: HypERC20Collateral__factory.connect( + routerAddress, + connectedSigner, + ), + }, + }, + multiProvider, + ); + + const receipt = await app.transfer(origin, destination, recipient, wei); + const messages = await core.getDispatchedMessages(receipt); + const message = messages[0]; + const msgDestination = multiProvider.getChainName(message.parsed.destination); + assert(destination === msgDestination); + + while (true) { + const mailbox = core.getContracts(destination).mailbox; + const delivered = await mailbox.delivered(message.id); + if (delivered) break; + log('Waiting for message delivery on destination chain...'); + await utils.sleep(5000); + } + + logGreen(`Transfer sent to destination chain!`); +} + +async function getWrappedToken( + address: types.Address, + provider: ethers.providers.Provider, +): Promise { + try { + const contract = HypERC20Collateral__factory.connect(address, provider); + const wrappedToken = await contract.wrappedToken(); + if (ethers.utils.isAddress(wrappedToken)) return wrappedToken; + else return null; + } catch (error) { + // Token isn't a HypERC20Collateral + return null; + } } diff --git a/typescript/token/src/app.ts b/typescript/token/src/app.ts index 366624b145..a7368d885f 100644 --- a/typescript/token/src/app.ts +++ b/typescript/token/src/app.ts @@ -1,7 +1,7 @@ import { BigNumberish } from 'ethers'; import { ChainName, HyperlaneContracts, RouterApp } from '@hyperlane-xyz/sdk'; -import { types } from '@hyperlane-xyz/utils'; +import { types, utils } from '@hyperlane-xyz/utils'; import { HypERC20Factories, @@ -24,16 +24,13 @@ class HyperlaneTokenApp< amountOrId: BigNumberish, ) { const originRouter = this.getContracts(origin).router; - const destProvider = this.multiProvider.getProvider(destination); - const destinationNetwork = await destProvider.getNetwork(); - const gasPayment = await originRouter.quoteGasPayment( - destinationNetwork.chainId, - ); + const destinationDomain = this.multiProvider.getDomainId(destination); + const gasPayment = await originRouter.quoteGasPayment(destinationDomain); return this.multiProvider.handleTx( origin, originRouter.transferRemote( - destinationNetwork.chainId, - recipient, + destinationDomain, + utils.addressToBytes32(recipient), amountOrId, { value: gasPayment, From 4a4fa07626f46bd9698dc99fb015cf05e0449920 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 18:07:50 -0400 Subject: [PATCH 08/82] Run prettier --- typescript/cli/examples/chain-config.yaml | 19 ++++++------ .../cli/examples/contract-artifacts.yaml | 30 +++++++++---------- typescript/cli/examples/multisig-ism.yaml | 8 ++--- typescript/cli/examples/warp-tokens.yaml | 8 ++--- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/typescript/cli/examples/chain-config.yaml b/typescript/cli/examples/chain-config.yaml index 1a27776ab4..40333a44b4 100644 --- a/typescript/cli/examples/chain-config.yaml +++ b/typescript/cli/examples/chain-config.yaml @@ -1,6 +1,6 @@ # Configs for describing chain metadata for use in Hyperlane deployments or apps # Consists of a map of chain names to metadata -# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts --- mychainname: # Required fields: @@ -11,35 +11,34 @@ mychainname: rpcUrls: # Array: List of RPC configs # Only http field is required - http: https://mychain.com/rpc # String: HTTP URL of the RPC endpoint (preferably HTTPS) - # Others here are optional - pagination: + # Others here are optional + pagination: maxBlockRange: 1000 # Number maxBlockAge: 1000 # Number minBlockNumber: 1000 # Number - retry: + retry: maxRequests: 5 # Number baseRetryMs: 1000 # Number # Optional fields, not required for Hyperlane deployments but useful for apps: isTestnet: false # Boolean: Whether the chain is considered a testnet or a mainnet - blockExplorers: # Array: List of BlockExplorer configs + blockExplorers: # Array: List of BlockExplorer configs # Required fields: - name: My Chain Explorer # String: Human-readable name for the explorer url: https://mychain.com/explorer # String: Base URL for the explorer apiUrl: https://mychain.com/api # String: Base URL for the explorer API - # Optional fields: + # Optional fields: apiKey: myapikey # String: API key for the explorer (optional) family: etherscan # ExplorerFamily: See ExplorerFamily for valid values - nativeToken: + nativeToken: name: Eth # String symbol: ETH # String decimals: 18 # Number displayName: My Chain Name # String: Human-readable name of the chain - displayNameShort: My Chain # String: A shorter human-readable name + displayNameShort: My Chain # String: A shorter human-readable name logoURI: https://mychain.com/logo.png # String: URI to a logo image for the chain blocks: confirmations: 12 # Number: Blocks to wait before considering a transaction confirmed reorgPeriod: 100 # Number: Blocks before a transaction has a near-zero chance of reverting estimateBlockTime: 15 # Number: Rough estimate of time per block in seconds # transactionOverrides: # Object: Properties to include when forming transaction requests - # Any tx fields are allowed - \ No newline at end of file + # Any tx fields are allowed diff --git a/typescript/cli/examples/contract-artifacts.yaml b/typescript/cli/examples/contract-artifacts.yaml index cbd7d6da46..4bf398d549 100644 --- a/typescript/cli/examples/contract-artifacts.yaml +++ b/typescript/cli/examples/contract-artifacts.yaml @@ -3,18 +3,18 @@ # Consists of a map of chain names to contract names to addresses --- mychainname: - storageGasOracle: "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C" - validatorAnnounce: "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" - proxyAdmin: "0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9" - mailbox: "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70" - interchainGasPaymaster: "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918" - defaultIsmInterchainGasPaymaster: "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc" - multisigIsm: "0x9bDE63104EE030d9De419EEd6bA7D14b86D6fE3f" - testRecipient: "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35" - interchainAccountIsm: "0x5e8ee6840caa4f367aff1FF28aA36D5B1b836d35" - aggregationIsmFactory: "0xc864fa3B662613cA5051f41e157d0a997f9a5A87" - routingIsmFactory: "0x1fdfD1486b8339638C6b92f8a96D698D8182D2b1" - interchainQueryRouter: "0xA837e38C3F7D509DF3a7a0fCf65E3814DB6c2618" - interchainAccountRouter: "0x9521291A43ebA3aD3FD24d610F4b7F7543C8d761" - merkleRootMultisigIsmFactory: "0x82140b2ddAd4E4dd7e1D6757Fb5F9485c230B79d" - messageIdMultisigIsmFactory: "0x1079056da3EC7D55521F27e1E094015C0d39Cc65" \ No newline at end of file + storageGasOracle: '0xD9A9966E7dA9a7f0032bF449FB12696a638E673C' + validatorAnnounce: '0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524' + proxyAdmin: '0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9' + mailbox: '0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70' + interchainGasPaymaster: '0x6cA0B6D22da47f091B7613223cD4BB03a2d77918' + defaultIsmInterchainGasPaymaster: '0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc' + multisigIsm: '0x9bDE63104EE030d9De419EEd6bA7D14b86D6fE3f' + testRecipient: '0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35' + interchainAccountIsm: '0x5e8ee6840caa4f367aff1FF28aA36D5B1b836d35' + aggregationIsmFactory: '0xc864fa3B662613cA5051f41e157d0a997f9a5A87' + routingIsmFactory: '0x1fdfD1486b8339638C6b92f8a96D698D8182D2b1' + interchainQueryRouter: '0xA837e38C3F7D509DF3a7a0fCf65E3814DB6c2618' + interchainAccountRouter: '0x9521291A43ebA3aD3FD24d610F4b7F7543C8d761' + merkleRootMultisigIsmFactory: '0x82140b2ddAd4E4dd7e1D6757Fb5F9485c230B79d' + messageIdMultisigIsmFactory: '0x1079056da3EC7D55521F27e1E094015C0d39Cc65' diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml index 88055577b3..726617e797 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/multisig-ism.yaml @@ -12,9 +12,9 @@ anvil1: type: 3 # ModuleType.LEGACY_MULTISIG threshold: 1 # Number: Signatures required to approve a message validators: # Array: List of validator configs - - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720" + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' anvil2: - type: 3 + type: 3 threshold: 1 - validators: - - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720" \ No newline at end of file + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml index f73abf5c82..984e703955 100644 --- a/typescript/cli/examples/warp-tokens.yaml +++ b/typescript/cli/examples/warp-tokens.yaml @@ -1,7 +1,7 @@ # A config for a Warp Route deployment # Typically used with the 'hyperlane deploy warp' command # Token Types: -# synthetic +# synthetic # syntheticUri # collateral # collateralUri @@ -9,7 +9,7 @@ --- base: chainName: anvil1 - type: native + type: native # address: 0x123... # Required for collateral types) # isNft: true # If the token is an NFT (ERC721), set to true) # owner: 0x123 # Optional owner address for synthetic token @@ -19,5 +19,5 @@ synthetics: - chainName: anvil2 # You can optionally set the token metadata, otherwise the base token's will be used # name: "MySyntheticToken" - # symbol: "MST" - # totalSupply: 10000000 \ No newline at end of file + # symbol: "MST" + # totalSupply: 10000000 From 369c71a5984d74139a91348f26bee536bffc99dd Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 21:59:12 -0400 Subject: [PATCH 09/82] Update dockerfile for CLI package --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index a6d4a76bf6..3b9b30b71a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ COPY typescript/utils/package.json ./typescript/utils/ COPY typescript/sdk/package.json ./typescript/sdk/ COPY typescript/helloworld/package.json ./typescript/helloworld/ COPY typescript/token/package.json ./typescript/token/ +COPY typescript/cli/package.json ./typescript/cli/ COPY typescript/infra/package.json ./typescript/infra/ COPY solidity/package.json ./solidity/ From 032986d8df244d8e5e1c5bb40d56dbf0db6a1151 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 22:52:04 -0400 Subject: [PATCH 10/82] Improve send message logging --- typescript/cli/src/send/message.ts | 39 +++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 428c6beb57..007f4e7dea 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -1,3 +1,5 @@ +import { BigNumber, ethers } from 'ethers'; + import { ChainName, DispatchedMessage, @@ -12,9 +14,9 @@ import { readDeploymentArtifacts } from '../configs.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; -import { errorRed, log, logGreen } from '../logger.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; -const GAS_AMOUNT = 100_000; +const GAS_AMOUNT = 300_000; // TODO improve the UX here by making params optional and // prompting for missing values @@ -47,7 +49,7 @@ export async function sendTestMessage({ }); await utils.timeout( - executeDelivery({ origin, destination, multiProvider, artifacts }), + executeDelivery({ origin, destination, multiProvider, signer, artifacts }), timeout * 1000, 'Timed out waiting for messages to be delivered', ); @@ -57,48 +59,57 @@ async function executeDelivery({ origin, destination, multiProvider, + signer, artifacts, }: { origin: ChainName; destination: ChainName; multiProvider: MultiProvider; + signer: ethers.Signer; artifacts?: HyperlaneContractsMap; }) { const mergedContractAddrs = getMergedContractAddresses(artifacts); - const core = HyperlaneCore.fromAddressesMap( mergedContractAddrs, multiProvider, ); - const igp = HyperlaneIgp.fromAddressesMap(mergedContractAddrs, multiProvider); const mailbox = core.getContracts(origin).mailbox; - const defaultIgp = igp.getContracts(origin).defaultIsmInterchainGasPaymaster; + const igp = HyperlaneIgp.fromAddressesMap(mergedContractAddrs, multiProvider); + const igpContract = igp.getContracts(origin).defaultIsmInterchainGasPaymaster; + console.log('igpContract', igpContract.address); + const destinationDomain = multiProvider.getDomainId(destination); + const signerAddress = await signer.getAddress(); + let message: DispatchedMessage; try { const recipient = mergedContractAddrs[destination].testRecipient; if (!recipient) { throw new Error(`Unable to find TestRecipient for ${destination}`); } + + log('Dispatching message'); const messageTx = await mailbox.dispatch( destinationDomain, utils.addressToBytes32(recipient), - '0xdeadbeef', + '0x48656c6c6f21', // Hello! ); const messageReceipt = await multiProvider.handleTx(origin, messageTx); message = core.getDispatchedMessages(messageReceipt)[0]; - log(`Sent message from ${origin} to ${recipient} on ${destination}.`); - log(`Message ID: ${message.id}`); + logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); + logBlue(`Message ID: ${message.id}`); - const value = await defaultIgp.quoteGasPayment( - destinationDomain, - GAS_AMOUNT, + const value = await igp.quoteGasPaymentForDefaultIsmIgp( + origin, + destination, + BigNumber.from(GAS_AMOUNT), ); - const paymentTx = await defaultIgp.payForGas( + log(`Paying for gas with ${value} wei`); + const paymentTx = await igpContract.payForGas( message.id, destinationDomain, GAS_AMOUNT, - await multiProvider.getSignerAddress(origin), + signerAddress, { value }, ); await paymentTx.wait(); From b7bd7b8c0fcad1c32967f77b9c1a620185b1c245 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 29 Jul 2023 23:03:03 -0400 Subject: [PATCH 11/82] Remove extra console.log line --- typescript/cli/src/send/message.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 007f4e7dea..cfb3bf3eeb 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -76,7 +76,6 @@ async function executeDelivery({ const mailbox = core.getContracts(origin).mailbox; const igp = HyperlaneIgp.fromAddressesMap(mergedContractAddrs, multiProvider); const igpContract = igp.getContracts(origin).defaultIsmInterchainGasPaymaster; - console.log('igpContract', igpContract.address); const destinationDomain = multiProvider.getDomainId(destination); const signerAddress = await signer.getAddress(); From 46a316a896b37abc80a085a7a9abf1fc00cc481c Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 31 Jul 2023 19:51:59 -0400 Subject: [PATCH 12/82] Improve send commands --- typescript/cli/src/commands/config.ts | 1 + typescript/cli/src/commands/deploy.ts | 2 ++ typescript/cli/src/commands/send.ts | 7 ++++--- typescript/cli/src/consts.ts | 6 +++--- typescript/cli/src/deploy/core.ts | 2 +- typescript/cli/src/deploy/utils.ts | 13 +++++++++---- typescript/cli/src/deploy/warp.ts | 2 +- typescript/cli/src/send/message.ts | 2 +- typescript/cli/src/send/transfer.ts | 22 ++++++++++++++++------ typescript/cli/src/utils/balances.ts | 25 ++++++++++++++++++++++--- 10 files changed, 60 insertions(+), 22 deletions(-) diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 15be737c76..1525df53f3 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -82,6 +82,7 @@ const createCommand: CommandModule = { ); throw new Error('Invalid chain config'); } + process.exit(0); }, }; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 79294e07ab..609cd664d3 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -45,6 +45,7 @@ const coreCommand: CommandModule = { const chainConfigPath: string = argv.chains; const outPath: string = argv.out; await runCoreDeploy({ key, chainConfigPath, outPath }); + process.exit(0); }, }; @@ -79,5 +80,6 @@ const warpCommand: CommandModule = { coreArtifactsPath, outPath, }); + process.exit(0); }, }; diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index c2756b90bd..c2ec702ae0 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -68,6 +68,7 @@ const messageCommand: CommandModule = { destination, timeout, }); + process.exit(0); }, }; @@ -86,7 +87,7 @@ const transferCommand: CommandModule = { demandOption: true, }, wei: { - type: 'number', + type: 'string', description: 'Amount in wei to send', default: 1, }, @@ -103,9 +104,8 @@ const transferCommand: CommandModule = { const destination: string = argv.destination; const timeout: number = argv.timeout; const routerAddress: string = argv.router; - const wei: number = argv.wei; + const wei: string = argv.wei; const recipient: string | undefined = argv.recipient; - await sendTestTransfer({ key, chainConfigPath, @@ -117,5 +117,6 @@ const transferCommand: CommandModule = { recipient, timeout, }); + process.exit(0); }, }; diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index b313a8c40e..c3c7c6ac41 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,3 +1,3 @@ -export const MINIMUM_CORE_DEPLOY_BALANCE = 0.5; -export const MINIMUM_WARP_DEPLOY_BALANCE = 0.2; -export const MINIMUM_TEST_SEND_BALANCE = 0.01; +export const MINIMUM_CORE_DEPLOY_BALANCE = '500000000000000000'; // 0.5 ETH +export const MINIMUM_WARP_DEPLOY_BALANCE = '2000000000000000000'; // 0.2 Eth +export const MINIMUM_TEST_SEND_BALANCE = '10000000000000000'; // 0.01 ETH diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 8a06c2f035..0de8cc9202 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -84,7 +84,7 @@ export async function runCoreDeploy({ await runDeployPlanStep(deploymentParams); await runPreflightChecks({ ...deploymentParams, - minBalance: MINIMUM_CORE_DEPLOY_BALANCE, + minBalanceWei: MINIMUM_CORE_DEPLOY_BALANCE, }); await executeDeploy(deploymentParams); } diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 7558ce87be..9e358fb496 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -3,7 +3,7 @@ import { ethers } from 'ethers'; import { ChainName, MultiProvider, ProtocolType } from '@hyperlane-xyz/sdk'; import { log, logGreen } from '../logger.js'; -import { assertBalances } from '../utils/balances.js'; +import { assertNativeBalances } from '../utils/balances.js'; import { assertSigner } from '../utils/keys.js'; export async function runPreflightChecks({ @@ -11,13 +11,13 @@ export async function runPreflightChecks({ remotes, signer, multiProvider, - minBalance, + minBalanceWei, }: { local: ChainName; remotes: ChainName[]; signer: ethers.Signer; multiProvider: MultiProvider; - minBalance: number; + minBalanceWei: string; }) { log('Running pre-flight checks...'); @@ -35,6 +35,11 @@ export async function runPreflightChecks({ assertSigner(signer); logGreen('Signer is valid ✅'); - await assertBalances(multiProvider, signer, [local, ...remotes], minBalance); + await assertNativeBalances( + multiProvider, + signer, + [local, ...remotes], + minBalanceWei, + ); logGreen('Balances are sufficient ✅'); } diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 1d1fa3d97a..96ea4b6610 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -72,7 +72,7 @@ export async function runWarpDeploy({ await runDeployPlanStep(deploymentParams); await runPreflightChecks({ ...deploymentParams, - minBalance: MINIMUM_WARP_DEPLOY_BALANCE, + minBalanceWei: MINIMUM_WARP_DEPLOY_BALANCE, }); await executeDeploy(deploymentParams); } diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index cfb3bf3eeb..5a5e70c25b 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -45,7 +45,7 @@ export async function sendTestMessage({ remotes: [destination], multiProvider, signer, - minBalance: MINIMUM_TEST_SEND_BALANCE, + minBalanceWei: MINIMUM_TEST_SEND_BALANCE, }); await utils.timeout( diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 353c10f6f6..f16d6c2862 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -18,7 +18,8 @@ import { readDeploymentArtifacts } from '../configs.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; -import { log, logGreen } from '../logger.js'; +import { log, logBlue, logGreen } from '../logger.js'; +import { assertTokenBalance } from '../utils/balances.js'; // TODO improve the UX here by making params optional and // prompting for missing values @@ -39,7 +40,7 @@ export async function sendTestTransfer({ origin: ChainName; destination: ChainName; routerAddress: types.Address; - wei: number; + wei: string; recipient?: string; timeout: number; }) { @@ -48,12 +49,19 @@ export async function sendTestTransfer({ ? readDeploymentArtifacts(coreArtifactsPath) : undefined; + await assertTokenBalance( + multiProvider, + signer, + origin, + routerAddress, + wei.toString(), + ); await runPreflightChecks({ local: origin, remotes: [destination], multiProvider, signer, - minBalance: MINIMUM_TEST_SEND_BALANCE, + minBalanceWei: MINIMUM_TEST_SEND_BALANCE, }); await utils.timeout( @@ -85,7 +93,7 @@ async function executeDelivery({ origin: ChainName; destination: ChainName; routerAddress: types.Address; - wei: number; + wei: string; recipient?: string; multiProvider: MultiProvider; signer: ethers.Signer; @@ -133,8 +141,10 @@ async function executeDelivery({ ); const receipt = await app.transfer(origin, destination, recipient, wei); - const messages = await core.getDispatchedMessages(receipt); - const message = messages[0]; + const message = core.getDispatchedMessages(receipt)[0]; + logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); + logBlue(`Message ID: ${message.id}`); + const msgDestination = multiProvider.getChainName(message.parsed.destination); assert(destination === msgDestination); diff --git a/typescript/cli/src/utils/balances.ts b/typescript/cli/src/utils/balances.ts index b9746b11aa..27e0adfa81 100644 --- a/typescript/cli/src/utils/balances.ts +++ b/typescript/cli/src/utils/balances.ts @@ -1,15 +1,17 @@ import { ethers } from 'ethers'; +import { ERC20__factory } from '@hyperlane-xyz/hyperlane-token'; import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { types } from '@hyperlane-xyz/utils'; -export async function assertBalances( +export async function assertNativeBalances( multiProvider: MultiProvider, signer: ethers.Signer, chains: ChainName[], - minBalance = 0, + minBalanceWei: string, ) { const address = await signer.getAddress(); - const minBalanceWei = ethers.utils.parseEther(minBalance.toString()); + const minBalance = ethers.utils.formatEther(minBalanceWei.toString()); await Promise.all( chains.map(async (chain) => { const balanceWei = await multiProvider @@ -23,3 +25,20 @@ export async function assertBalances( }), ); } + +export async function assertTokenBalance( + multiProvider: MultiProvider, + signer: ethers.Signer, + chain: ChainName, + token: types.Address, + minBalanceWei: string, +) { + const address = await signer.getAddress(); + const provider = multiProvider.getProvider(chain); + const tokenContract = ERC20__factory.connect(token, provider); + const balanceWei = await tokenContract.balanceOf(address); + if (balanceWei.lte(minBalanceWei)) + throw new Error( + `${address} has insufficient balance on ${chain} for token ${token}. At least ${minBalanceWei} wei required but found ${balanceWei.toString()} wei`, + ); +} From 1aafda360cb4a1b372ce5563030a18fc31f2e0d2 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 1 Aug 2023 07:22:08 -0400 Subject: [PATCH 13/82] Minor fix to warp deploy --- typescript/cli/src/consts.ts | 2 +- typescript/cli/src/deploy/warp.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index c3c7c6ac41..d387877801 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,3 +1,3 @@ export const MINIMUM_CORE_DEPLOY_BALANCE = '500000000000000000'; // 0.5 ETH -export const MINIMUM_WARP_DEPLOY_BALANCE = '2000000000000000000'; // 0.2 Eth +export const MINIMUM_WARP_DEPLOY_BALANCE = '200000000000000000'; // 0.2 Eth export const MINIMUM_TEST_SEND_BALANCE = '10000000000000000'; // 0.01 ETH diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 96ea4b6610..540d9c2067 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -221,7 +221,7 @@ async function executeDeploy(params: DeployParams) { const [contractsFilePath, tokenConfigPath] = prepNewArtifactsFiles(outPath, [ { filename: 'warp-deployment', description: 'Contract addresses' }, - { filename: 'warp-token-config', description: 'Warp UI token config' }, + { filename: 'warp-ui-token-config', description: 'Warp UI token config' }, ]); const deployer = isNft From 9ff88dee06562ebcda546f055041cb3bd823170b Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 1 Aug 2023 08:01:25 -0400 Subject: [PATCH 14/82] Update CLI CI test --- typescript/cli/ci-test.sh | 21 ++++++++------------- typescript/cli/examples/anvil-chains.yaml | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 typescript/cli/examples/anvil-chains.yaml diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 5681ad29a0..f4575dc4b4 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -17,16 +17,20 @@ sleep 1 set -e -for i in "anvil1 anvil2 --no-write-agent-config" "anvil2 anvil1 --write-agent-config" +yarn build + +for i in "anvil1 anvil2" "anvil2 anvil1" do set -- $i echo "Deploying contracts to $1" - yarn ts-node scripts/deploy-hyperlane.ts --local $1 --remotes $2 \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 $3 + yarn hyperlane --local $1 --remotes $2 \ + --chains ./examples/anvil-chains.yaml \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 $3 \ done echo "Deploying warp routes" -yarn ts-node scripts/deploy-warp-routes.ts \ +yarn hyperlane --local "anvil1" --remotes "anvil2" \ + --chains ./examples/anvil-chains.yaml \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 kill $ANVIL_1_PID @@ -91,15 +95,6 @@ do gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./relayer & done -echo "Testing message sending" -yarn ts-node scripts/test-messages.ts --chains anvil1 anvil2 \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --timeout 60 - -echo "Sending a test warp transfer" -yarn ts-node scripts/test-warp-transfer.ts \ - --origin anvil1 --destination anvil2 --wei 1 --recipient 0xac0974bec39a17e36ba4a6b4d238ff944bacb4a5 \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --timeout 60 - docker ps -aq | xargs docker stop | xargs docker rm kill $ANVIL_1_PID kill $ANVIL_2_PID diff --git a/typescript/cli/examples/anvil-chains.yaml b/typescript/cli/examples/anvil-chains.yaml new file mode 100644 index 0000000000..d7ad6ce0ea --- /dev/null +++ b/typescript/cli/examples/anvil-chains.yaml @@ -0,0 +1,21 @@ +# Configs for describing chain metadata for use in Hyperlane deployments or apps +# Consists of a map of chain names to metadata +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +--- +anvil1: + # Required fields: + chainId: 31337 # Number: Use EIP-155 for EVM chains + domainId: 31337 # Number: Recommend matching chainId when possible + name: anvil1 # String: Unique identifier for the chain, must match key above + protocol: ethereum # ProtocolType: Ethereum, Sealevel, etc. + rpcUrls: # Array: List of RPC configs + # Only http field is required + - http: http://127.0.0.1:8545 # String: HTTP URL of the RPC endpoint (preferably HTTPS) +anvil2: + # Required fields: + chainId: 31338 + domainId: 31338 + name: anvil2 + protocol: ethereum + rpcUrls: + - http: http://127.0.0.1:8555 From aff04991fb2a19aa7351047989093edbfca67340 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 2 Aug 2023 10:55:31 -0400 Subject: [PATCH 15/82] Bump versions to 1.4.3-beta0 --- solidity/package.json | 7 +++--- typescript/cli/package.json | 9 ++++---- typescript/helloworld/package.json | 7 +++--- typescript/infra/package.json | 8 +++---- typescript/sdk/package.json | 9 ++++---- typescript/token/package.json | 11 +++++----- typescript/utils/package.json | 5 +++-- yarn.lock | 34 +++++++++++++++--------------- 8 files changed, 48 insertions(+), 42 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index d40e32ea61..a6c65b698e 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.4.2", + "version": "1.4.3-beta0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.4.2", + "@hyperlane-xyz/utils": "1.4.3-beta0", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, @@ -57,5 +57,6 @@ "gas-ci": "yarn gas --check --tolerance 2 || (echo 'Manually update gas snapshot' && exit 1)", "slither": "slither ." }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.4.2" } diff --git a/typescript/cli/package.json b/typescript/cli/package.json index fd70393b0b..4e0befae79 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.4.2", + "version": "1.4.3-beta0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/hyperlane-token": "1.4.2", - "@hyperlane-xyz/sdk": "1.4.2", + "@hyperlane-xyz/hyperlane-token": "1.4.3-beta0", + "@hyperlane-xyz/sdk": "1.4.3-beta0", "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", @@ -53,5 +53,6 @@ "Deployment", "Typescript" ], - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "stableVersion": "1.4.2" } diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index c8cacf42ca..5269357baa 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,9 +1,9 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.4.2", + "version": "1.4.3-beta0", "dependencies": { - "@hyperlane-xyz/sdk": "1.4.2", + "@hyperlane-xyz/sdk": "1.4.3-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, @@ -65,5 +65,6 @@ "lodash": "^4.17.21", "async": "^2.6.4", "undici": "^5.11" - } + }, + "stableVersion": "1.4.2" } diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 569783d3a8..b29500279b 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "1.4.1", + "version": "1.4.3-beta0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -11,9 +11,9 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.4.2", - "@hyperlane-xyz/sdk": "1.4.2", - "@hyperlane-xyz/utils": "1.4.2", + "@hyperlane-xyz/helloworld": "1.4.3-beta0", + "@hyperlane-xyz/sdk": "1.4.3-beta0", + "@hyperlane-xyz/utils": "1.4.3-beta0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.1.0", "@safe-global/protocol-kit": "^1.0.1", diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index d3eaf7de34..72b5f307cd 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.4.2", + "version": "1.4.3-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.4.2", - "@hyperlane-xyz/utils": "1.4.2", + "@hyperlane-xyz/core": "1.4.3-beta0", + "@hyperlane-xyz/utils": "1.4.3-beta0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", "@wagmi/chains": "^0.2.6", @@ -54,5 +54,6 @@ "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts'", "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.4.2" } diff --git a/typescript/token/package.json b/typescript/token/package.json index 5486ef11cd..73c96cd342 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.4.2", + "version": "1.4.3-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.4.2", - "@hyperlane-xyz/sdk": "1.4.2", - "@hyperlane-xyz/utils": "1.4.2", + "@hyperlane-xyz/core": "1.4.3-beta0", + "@hyperlane-xyz/sdk": "1.4.3-beta0", + "@hyperlane-xyz/utils": "1.4.3-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, @@ -61,5 +61,6 @@ "test": "hardhat test ./test/*.test.ts", "deploy-warp-route": "DEBUG=* ts-node scripts/deploy" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.4.2" } diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 880d3c90be..dbbe5c7263 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities for the Hyperlane network", - "version": "1.4.2", + "version": "1.4.3-beta0", "dependencies": { "ethers": "^5.7.2" }, @@ -29,5 +29,6 @@ "types": "dist/index.d.ts", "files": [ "/dist" - ] + ], + "stableVersion": "1.4.2" } diff --git a/yarn.lock b/yarn.lock index c9f3f4c774..08f62264ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3803,8 +3803,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/hyperlane-token": 1.4.2 - "@hyperlane-xyz/sdk": 1.4.2 + "@hyperlane-xyz/hyperlane-token": 1.4.3-beta0 + "@hyperlane-xyz/sdk": 1.4.3-beta0 "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 @@ -3824,12 +3824,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@1.4.2, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@1.4.3-beta0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.4.2 + "@hyperlane-xyz/utils": 1.4.3-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -3852,11 +3852,11 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.4.2, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@1.4.3-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/sdk": 1.4.2 + "@hyperlane-xyz/sdk": 1.4.3-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3884,13 +3884,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@1.4.2, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.4.3-beta0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: - "@hyperlane-xyz/core": 1.4.2 - "@hyperlane-xyz/sdk": 1.4.2 - "@hyperlane-xyz/utils": 1.4.2 + "@hyperlane-xyz/core": 1.4.3-beta0 + "@hyperlane-xyz/sdk": 1.4.3-beta0 + "@hyperlane-xyz/utils": 1.4.3-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3930,9 +3930,9 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.4.2 - "@hyperlane-xyz/sdk": 1.4.2 - "@hyperlane-xyz/utils": 1.4.2 + "@hyperlane-xyz/helloworld": 1.4.3-beta0 + "@hyperlane-xyz/sdk": 1.4.3-beta0 + "@hyperlane-xyz/utils": 1.4.3-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -3975,12 +3975,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.4.2, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@1.4.3-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: - "@hyperlane-xyz/core": 1.4.2 - "@hyperlane-xyz/utils": 1.4.2 + "@hyperlane-xyz/core": 1.4.3-beta0 + "@hyperlane-xyz/utils": 1.4.3-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@types/coingecko-api": ^1.0.10 @@ -4006,7 +4006,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.4.2, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@1.4.3-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From ce5fbfcc655c477c02c278fbe277fffa0e688d2e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 6 Aug 2023 11:19:23 -0400 Subject: [PATCH 16/82] Fix build errors from merge --- typescript/cli/src/commands/config.ts | 7 ++----- typescript/cli/src/commands/send.ts | 8 ++++---- typescript/cli/src/context.ts | 2 +- .../cli/src/deploy/TestRecipientDeployer.ts | 8 ++++---- typescript/cli/src/deploy/core.ts | 12 +++++------ typescript/cli/src/deploy/types.ts | 8 ++++---- typescript/cli/src/deploy/utils.ts | 3 ++- typescript/cli/src/deploy/warp.ts | 5 ++--- typescript/cli/src/send/message.ts | 14 ++++++------- typescript/cli/src/send/transfer.ts | 20 +++++++++---------- typescript/cli/src/utils/balances.ts | 4 ++-- typescript/cli/src/utils/files.ts | 2 +- 12 files changed, 44 insertions(+), 49 deletions(-) diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 1525df53f3..628790305e 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -2,11 +2,8 @@ import { input } from '@inquirer/prompts'; import select from '@inquirer/select'; import { CommandModule } from 'yargs'; -import { - ChainMetadata, - ProtocolType, - isValidChainMetadata, -} from '@hyperlane-xyz/sdk'; +import { ChainMetadata, isValidChainMetadata } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { readChainConfig } from '../configs.js'; import { errorRed, logBlue, logGreen } from '../logger.js'; diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index c2ec702ae0..08ad57e916 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -59,14 +59,14 @@ const messageCommand: CommandModule = { const coreArtifactsPath: string = argv.core; const origin: string = argv.origin; const destination: string = argv.destination; - const timeout: number = argv.timeout; + const timeoutSec: number = argv.timeout; await sendTestMessage({ key, chainConfigPath, coreArtifactsPath, origin, destination, - timeout, + timeoutSec, }); process.exit(0); }, @@ -102,7 +102,7 @@ const transferCommand: CommandModule = { const coreArtifactsPath: string = argv.core; const origin: string = argv.origin; const destination: string = argv.destination; - const timeout: number = argv.timeout; + const timeoutSec: number = argv.timeout; const routerAddress: string = argv.router; const wei: string = argv.wei; const recipient: string | undefined = argv.recipient; @@ -115,7 +115,7 @@ const transferCommand: CommandModule = { routerAddress, wei, recipient, - timeout, + timeoutSec, }); process.exit(0); }, diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index 53640ef9c5..67fc0aa4d6 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -7,8 +7,8 @@ import { MultiProvider, chainMetadata, hyperlaneEnvironments, - objMerge, } from '@hyperlane-xyz/sdk'; +import { objMerge } from '@hyperlane-xyz/utils'; import { readChainConfigIfExists } from './configs.js'; import { keyToSigner } from './utils/keys.js'; diff --git a/typescript/cli/src/deploy/TestRecipientDeployer.ts b/typescript/cli/src/deploy/TestRecipientDeployer.ts index da08e57dfa..bd4bacca63 100644 --- a/typescript/cli/src/deploy/TestRecipientDeployer.ts +++ b/typescript/cli/src/deploy/TestRecipientDeployer.ts @@ -6,10 +6,10 @@ import { HyperlaneDeployer, MultiProvider, } from '@hyperlane-xyz/sdk'; -import { types, utils } from '@hyperlane-xyz/utils'; +import { Address, eqAddress } from '@hyperlane-xyz/utils'; export type TestRecipientConfig = { - interchainSecurityModule: types.Address; + interchainSecurityModule: Address; }; export type TestRecipientContracts = { @@ -17,7 +17,7 @@ export type TestRecipientContracts = { }; export type TestRecipientAddresses = { - testRecipient: types.Address; + testRecipient: Address; }; export const testRecipientFactories = { @@ -43,7 +43,7 @@ export class TestRecipientDeployer extends HyperlaneDeployer< this.logger(`Checking ISM ${chain}`); const ism = await testRecipient.interchainSecurityModule(); this.logger(`Found ISM for on ${chain}: ${ism}`); - if (!utils.eqAddress(ism, config.interchainSecurityModule)) { + if (!eqAddress(ism, config.interchainSecurityModule)) { this.logger(`Current ISM does not match config. Updating.`); const tx = testRecipient.setInterchainSecurityModule( config.interchainSecurityModule, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 0de8cc9202..4e6ed1a2ec 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -28,12 +28,10 @@ import { defaultMultisigIsmConfigs, mainnetChainsMetadata, multisigIsmVerificationCost, - objFilter, - objMerge, serializeContractsMap, testnetChainsMetadata, } from '@hyperlane-xyz/sdk'; -import { types } from '@hyperlane-xyz/utils'; +import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; import { readDeploymentArtifacts, readMultisigConfig } from '../configs.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; @@ -343,7 +341,7 @@ async function executeDeploy({ } function buildIsmConfig( - owner: types.Address, + owner: Address, remotes: ChainName[], multisigIsmConfigs: ChainMap, ): RoutingIsmConfig { @@ -361,7 +359,7 @@ function buildIsmConfig( } function buildIsmConfigMap( - owner: types.Address, + owner: Address, chains: ChainName[], remotes: ChainName[], multisigIsmConfigs: ChainMap, @@ -379,7 +377,7 @@ function buildIsmConfigMap( } function buildCoreConfigMap( - owner: types.Address, + owner: Address, local: ChainName, remotes: ChainName[], multisigIsmConfigs: ChainMap, @@ -407,7 +405,7 @@ function buildTestRecipientConfigMap( } function buildIgpConfigMap( - owner: types.Address, + owner: Address, deployChains: ChainName[], allChains: ChainName[], multisigIsmConfigs: ChainMap, diff --git a/typescript/cli/src/deploy/types.ts b/typescript/cli/src/deploy/types.ts index f7f62708f1..85e6e46458 100644 --- a/typescript/cli/src/deploy/types.ts +++ b/typescript/cli/src/deploy/types.ts @@ -1,5 +1,5 @@ import type { ERC20Metadata, TokenType } from '@hyperlane-xyz/hyperlane-token'; -import type { types } from '@hyperlane-xyz/utils'; +import type { Address } from '@hyperlane-xyz/utils'; export type MinimalTokenMetadata = Omit; @@ -15,13 +15,13 @@ interface BaseWarpUITokenConfig extends MinimalTokenMetadata { interface CollateralTokenConfig extends BaseWarpUITokenConfig { type: TokenType.collateral; - address: types.Address; - hypCollateralAddress: types.Address; + address: Address; + hypCollateralAddress: Address; } interface NativeTokenConfig extends BaseWarpUITokenConfig { type: TokenType.native; - hypNativeAddress: types.Address; + hypNativeAddress: Address; } export type WarpUITokenConfig = CollateralTokenConfig | NativeTokenConfig; diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 9e358fb496..74ef840d54 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; -import { ChainName, MultiProvider, ProtocolType } from '@hyperlane-xyz/sdk'; +import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { log, logGreen } from '../logger.js'; import { assertNativeBalances } from '../utils/balances.js'; diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 540d9c2067..b9dd1f03b0 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -18,9 +18,8 @@ import { MultiProvider, RouterConfig, chainMetadata as defaultChainMetadata, - objMap, } from '@hyperlane-xyz/sdk'; -import { types } from '@hyperlane-xyz/utils'; +import { Address, objMap } from '@hyperlane-xyz/utils'; import { WarpRouteConfig, @@ -289,7 +288,7 @@ function writeTokenDeploymentArtifacts( { configMap }: DeployParams, ) { const artifacts: ChainMap<{ - router: types.Address; + router: Address; tokenType: TokenType; }> = objMap(contracts, (chain, contract) => { return { diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 5a5e70c25b..30a38b5b2f 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -8,7 +8,7 @@ import { HyperlaneIgp, MultiProvider, } from '@hyperlane-xyz/sdk'; -import { utils } from '@hyperlane-xyz/utils'; +import { addressToBytes32, sleep, timeout } from '@hyperlane-xyz/utils'; import { readDeploymentArtifacts } from '../configs.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; @@ -26,14 +26,14 @@ export async function sendTestMessage({ coreArtifactsPath, origin, destination, - timeout, + timeoutSec, }: { key: string; chainConfigPath: string; coreArtifactsPath: string; origin: ChainName; destination: ChainName; - timeout: number; + timeoutSec: number; }) { const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); const artifacts = coreArtifactsPath @@ -48,9 +48,9 @@ export async function sendTestMessage({ minBalanceWei: MINIMUM_TEST_SEND_BALANCE, }); - await utils.timeout( + await timeout( executeDelivery({ origin, destination, multiProvider, signer, artifacts }), - timeout * 1000, + timeoutSec * 1000, 'Timed out waiting for messages to be delivered', ); } @@ -90,7 +90,7 @@ async function executeDelivery({ log('Dispatching message'); const messageTx = await mailbox.dispatch( destinationDomain, - utils.addressToBytes32(recipient), + addressToBytes32(recipient), '0x48656c6c6f21', // Hello! ); const messageReceipt = await multiProvider.handleTx(origin, messageTx); @@ -125,7 +125,7 @@ async function executeDelivery({ if (delivered) break; log('Waiting for message delivery on destination chain...'); - await utils.sleep(5000); + await sleep(5000); } logGreen('Message was delivered!'); diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index f16d6c2862..cda07256ed 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -12,7 +12,7 @@ import { HyperlaneCore, MultiProvider, } from '@hyperlane-xyz/sdk'; -import { types, utils } from '@hyperlane-xyz/utils'; +import { Address, sleep, timeout } from '@hyperlane-xyz/utils'; import { readDeploymentArtifacts } from '../configs.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; @@ -32,17 +32,17 @@ export async function sendTestTransfer({ routerAddress, wei, recipient, - timeout, + timeoutSec, }: { key: string; chainConfigPath: string; coreArtifactsPath: string; origin: ChainName; destination: ChainName; - routerAddress: types.Address; + routerAddress: Address; wei: string; recipient?: string; - timeout: number; + timeoutSec: number; }) { const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); const artifacts = coreArtifactsPath @@ -64,7 +64,7 @@ export async function sendTestTransfer({ minBalanceWei: MINIMUM_TEST_SEND_BALANCE, }); - await utils.timeout( + await timeout( executeDelivery({ origin, destination, @@ -75,7 +75,7 @@ export async function sendTestTransfer({ multiProvider, artifacts, }), - timeout * 1000, + timeoutSec * 1000, 'Timed out waiting for messages to be delivered', ); } @@ -92,7 +92,7 @@ async function executeDelivery({ }: { origin: ChainName; destination: ChainName; - routerAddress: types.Address; + routerAddress: Address; wei: string; recipient?: string; multiProvider: MultiProvider; @@ -153,16 +153,16 @@ async function executeDelivery({ const delivered = await mailbox.delivered(message.id); if (delivered) break; log('Waiting for message delivery on destination chain...'); - await utils.sleep(5000); + await sleep(5000); } logGreen(`Transfer sent to destination chain!`); } async function getWrappedToken( - address: types.Address, + address: Address, provider: ethers.providers.Provider, -): Promise { +): Promise
{ try { const contract = HypERC20Collateral__factory.connect(address, provider); const wrappedToken = await contract.wrappedToken(); diff --git a/typescript/cli/src/utils/balances.ts b/typescript/cli/src/utils/balances.ts index 27e0adfa81..02ee9e836d 100644 --- a/typescript/cli/src/utils/balances.ts +++ b/typescript/cli/src/utils/balances.ts @@ -2,7 +2,7 @@ import { ethers } from 'ethers'; import { ERC20__factory } from '@hyperlane-xyz/hyperlane-token'; import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; -import { types } from '@hyperlane-xyz/utils'; +import { Address } from '@hyperlane-xyz/utils'; export async function assertNativeBalances( multiProvider: MultiProvider, @@ -30,7 +30,7 @@ export async function assertTokenBalance( multiProvider: MultiProvider, signer: ethers.Signer, chain: ChainName, - token: types.Address, + token: Address, minBalanceWei: string, ) { const address = await signer.getAddress(); diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 9fb40d8590..de73f3c5ca 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import path from 'path'; import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; -import { objMerge } from '@hyperlane-xyz/sdk'; +import { objMerge } from '@hyperlane-xyz/utils'; import { logBlue } from '../logger.js'; From e914051687e76c3371c8df3788478f4dd0900be6 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 6 Aug 2023 12:54:18 -0400 Subject: [PATCH 17/82] Address feedback from @yorhodes --- typescript/cli/examples/multisig-ism.yaml | 18 ++-- typescript/cli/examples/warp-tokens.yaml | 1 + typescript/cli/src/commands/config.ts | 18 +++- typescript/cli/src/commands/deploy.ts | 4 +- typescript/cli/src/configs.ts | 21 +++- typescript/cli/src/deploy/core.ts | 118 +++++++--------------- typescript/cli/src/deploy/warp.ts | 2 +- typescript/cli/src/send/message.ts | 21 ++-- typescript/cli/src/send/transfer.ts | 18 +--- typescript/cli/src/utils/chains.ts | 89 ++++++++++++++++ typescript/sdk/src/core/HyperlaneCore.ts | 24 +++-- 11 files changed, 194 insertions(+), 140 deletions(-) create mode 100644 typescript/cli/src/utils/chains.ts diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml index 726617e797..2b33eed1a2 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/multisig-ism.yaml @@ -1,20 +1,20 @@ # A config for a multisig Interchain Security Module (ISM) # Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts -# Module types: -# UNUSED : 0 -# ROUTING : 1 -# AGGREGATION : 2 -# LEGACY_MULTISIG : 3 -# MERKLE_ROOT_MULTISIG : 4 -# MESSAGE_ID_MULTISIG : 5 +# +# Valid module types: +# routing +# aggregation +# legacy_multisig +# merkle_root_multisig +# message_id_multisig --- anvil1: - type: 3 # ModuleType.LEGACY_MULTISIG + type: 'legacy_multisig' threshold: 1 # Number: Signatures required to approve a message validators: # Array: List of validator configs - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' anvil2: - type: 3 + type: 'legacy_multisig' threshold: 1 validators: - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml index 984e703955..0d78580f59 100644 --- a/typescript/cli/examples/warp-tokens.yaml +++ b/typescript/cli/examples/warp-tokens.yaml @@ -1,5 +1,6 @@ # A config for a Warp Route deployment # Typically used with the 'hyperlane deploy warp' command +# # Token Types: # synthetic # syntheticUri diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 628790305e..566589bd6e 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -1,4 +1,4 @@ -import { input } from '@inquirer/prompts'; +import { confirm, input } from '@inquirer/prompts'; import select from '@inquirer/select'; import { CommandModule } from 'yargs'; @@ -6,7 +6,7 @@ import { ChainMetadata, isValidChainMetadata } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { readChainConfig } from '../configs.js'; -import { errorRed, logBlue, logGreen } from '../logger.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; /** @@ -21,7 +21,7 @@ export const configCommand: CommandModule = { .command(validateCommand) .version(false) .demandCommand(), - handler: () => console.log('Command required'), + handler: () => log('Command required'), }; /** @@ -52,9 +52,17 @@ const createCommand: CommandModule = { message: 'Enter chain name (one word, lower case)', }); const chainId = await input({ message: 'Enter chain id (number)' }); - const domainId = await input({ - message: 'Enter domain id (number, often matches chainId)', + const skipDomain = await confirm({ + message: 'Will the domainId match the chainId (recommended)?', }); + let domainId: string; + if (skipDomain) { + domainId = chainId; + } else { + domainId = await input({ + message: 'Enter domain id (number, often matches chainId)', + }); + } const protocol = await select({ message: 'Select protocol type', choices: Object.values(ProtocolType).map((protocol) => ({ diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 609cd664d3..5e6dee1403 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -2,7 +2,7 @@ import { CommandModule } from 'yargs'; import { runCoreDeploy } from '../deploy/core.js'; import { runWarpDeploy } from '../deploy/warp.js'; -import { logGray } from '../logger.js'; +import { log, logGray } from '../logger.js'; import { chainsCommandOption, @@ -23,7 +23,7 @@ export const deployCommand: CommandModule = { .command(warpCommand) .version(false) .demandCommand(), - handler: () => console.log('Command required'), + handler: () => log('Command required'), }; /** diff --git a/typescript/cli/src/configs.ts b/typescript/cli/src/configs.ts index 2cb9e3bb57..76f21f89c5 100644 --- a/typescript/cli/src/configs.ts +++ b/typescript/cli/src/configs.ts @@ -6,16 +6,18 @@ import { ChainMap, ChainMetadata, HyperlaneContractsMap, + ModuleType, MultisigIsmConfig, isValidChainMetadata, } from '@hyperlane-xyz/sdk'; +import { objMap } from '@hyperlane-xyz/utils'; import { getMultiProvider } from './context.js'; import { errorRed, log, logGreen } from './logger.js'; import { readYamlOrJson } from './utils/files.js'; export function readChainConfig(filepath: string) { - console.log(`Reading file configs in ${filepath}`); + log(`Reading file configs in ${filepath}`); const chainToMetadata = readYamlOrJson>(filepath); if ( @@ -76,7 +78,7 @@ export function readDeploymentArtifacts(filePath: string) { const MultisigConfigSchema = z.object({}).catchall( z.object({ - type: z.number(), + type: z.string(), threshold: z.number(), validators: z.array(z.string()), }), @@ -92,7 +94,20 @@ export function readMultisigConfig(filePath: string) { `Invalid multisig config: ${firstIssue.path} => ${firstIssue.message}`, ); } - return result.data as ChainMap; + const parsedConfig = result.data; + const formattedConfig = objMap(parsedConfig, (_, config) => ({ + ...config, + type: humanReadableIsmTypeToEnum(config.type), + })); + + return formattedConfig as ChainMap; +} + +function humanReadableIsmTypeToEnum(type: string): ModuleType { + for (const [key, value] of Object.entries(ModuleType)) { + if (key.toLowerCase() === type) return parseInt(value.toString(), 10); + } + throw new Error(`Invalid ISM type ${type}`); } const ConnectionConfigSchema = { diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 4e6ed1a2ec..8997fd8f32 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -1,11 +1,8 @@ -import { Separator, checkbox, confirm, input } from '@inquirer/prompts'; -import select from '@inquirer/select'; -import chalk from 'chalk'; +import { confirm, input } from '@inquirer/prompts'; import { ethers } from 'ethers'; import { ChainMap, - ChainMetadata, ChainName, CoreConfig, DeployedIsm, @@ -26,10 +23,8 @@ import { agentStartBlocks, buildAgentConfig, defaultMultisigIsmConfigs, - mainnetChainsMetadata, multisigIsmVerificationCost, serializeContractsMap, - testnetChainsMetadata, } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; @@ -41,6 +36,7 @@ import { sdkContractAddressesMap, } from '../context.js'; import { log, logBlue, logGray, logGreen } from '../logger.js'; +import { runLocalAndRemotesSelectionStep } from '../utils/chains.js'; import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; import { @@ -63,11 +59,10 @@ export async function runCoreDeploy({ chainConfigPath, ); - const { local, remotes, allChains } = await runChainSelectionStep( - customChains, - ); - const artifacts = await runArtifactStep(allChains); - const multisigConfig = await runIsmStep(allChains); + const { local, remotes, selectedChains } = + await runLocalAndRemotesSelectionStep(customChains); + const artifacts = await runArtifactStep(selectedChains); + const multisigConfig = await runIsmStep(selectedChains); const deploymentParams = { local, @@ -87,41 +82,10 @@ export async function runCoreDeploy({ await executeDeploy(deploymentParams); } -async function runChainSelectionStep(customChains: ChainMap) { - const chainsToChoices = (chains: ChainMetadata[]) => - chains.map((c) => ({ name: c.name, value: c.name })); - const choices: Parameters['0']['choices'] = [ - new Separator('--Custom Chains--'), - ...chainsToChoices(Object.values(customChains)), - { name: '(New custom chain)', value: '__new__' }, - new Separator('--Mainnet Chains--'), - ...chainsToChoices(mainnetChainsMetadata), - new Separator('--Testnet Chains--'), - ...chainsToChoices(testnetChainsMetadata), - ]; - - const local = (await select({ - message: 'Select local chain (the chain to which you will deploy now)', - choices, - pageSize: 20, - })) as string; - handleNewChain([local]); - - const remotes = (await checkbox({ - message: 'Select remote chains the local will send messages to', - choices, - pageSize: 20, - })) as string[]; - handleNewChain(remotes); - if (!remotes?.length) throw new Error('No remote chains selected'); - - const allChains = [local, ...remotes]; - return { local, remotes, allChains }; -} - -async function runArtifactStep(allChains: ChainName[]) { +async function runArtifactStep(selectedChains: ChainName[]) { logBlue( - '\nDeployments can be totally new or can use some existing contract addresses.', + '\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?', @@ -133,15 +97,16 @@ async function runArtifactStep(allChains: ChainName[]) { }); const artifacts = readDeploymentArtifacts(artifactsPath); const artifactChains = Object.keys(artifacts).filter((c) => - allChains.includes(c), + selectedChains.includes(c), ); log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); return artifacts; } -async function runIsmStep(allChains: ChainName[]) { +async function runIsmStep(selectedChains: ChainName[]) { logBlue( - '\nHyperlane instances requires an Interchain Security Module (ISM).', + '\n', + 'Hyperlane instances requires an Interchain Security Module (ISM).', ); const isMultisig = await confirm({ message: 'Do you want use a Multisig ISM?', @@ -152,7 +117,7 @@ async function runIsmStep(allChains: ChainName[]) { ); const defaultConfigChains = Object.keys(defaultMultisigIsmConfigs); - const configRequired = !!allChains.find( + const configRequired = !!selectedChains.find( (c) => !defaultConfigChains.includes(c), ); if (!configRequired) return; @@ -165,26 +130,12 @@ async function runIsmStep(allChains: ChainName[]) { }); const configs = readMultisigConfig(multisigConfigPath); const multisigConfigChains = Object.keys(configs).filter((c) => - allChains.includes(c), + selectedChains.includes(c), ); log(`Found configs for chains: ${multisigConfigChains.join(', ')}`); return configs; } -function handleNewChain(chainNames: string[]) { - if (chainNames.includes('__new__')) { - logBlue( - 'To use a new chain, use the --config argument add them to that file', - ); - log( - chalk.blue('Use the'), - chalk.magentaBright('hyperlane config create'), - chalk.blue('command to create new configs'), - ); - process.exit(0); - } -} - interface DeployParams { local: string; remotes: string[]; @@ -202,7 +153,7 @@ async function runDeployPlanStep({ artifacts, }: DeployParams) { const address = await signer.getAddress(); - logBlue('\nDeployment plan:'); + logBlue('\n', 'Deployment plan:'); logGray('===============:'); log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying to ${local} and connecting it to ${remotes.join(', ')}`); @@ -210,14 +161,12 @@ async function runDeployPlanStep({ Object.values(sdkContractAddressesMap)[0], ).length; log(`There are ${numContracts} contracts for each chain`); - if (artifacts) { + if (artifacts) log('But contracts with an address in the artifacts file will be skipped'); - for (const chain of [local, ...remotes]) { - const chainArtifacts = artifacts[chain]; - if (!chainArtifacts) continue; - const numRequired = numContracts - Object.keys(chainArtifacts).length; - log(`${chain} will require ${numRequired} of ${numContracts}`); - } + for (const chain of [local, ...remotes]) { + const chainArtifacts = artifacts?.[chain] || {}; + const numRequired = numContracts - Object.keys(chainArtifacts).length; + log(`${chain} will require ${numRequired} of ${numContracts}`); } log('The interchain security module will be a Multisig.'); const isConfirmed = await confirm({ @@ -243,14 +192,14 @@ async function executeDeploy({ ]); const owner = await signer.getAddress(); - const allChains = [local, ...remotes]; + const selectedChains = [local, ...remotes]; const mergedContractAddrs = getMergedContractAddresses(artifacts); // 1. Deploy ISM factories to all deployable chains that don't have them. log('Deploying ISM factory contracts'); const ismDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); ismDeployer.cacheAddressesMap(mergedContractAddrs); - const ismFactoryContracts = await ismDeployer.deploy(allChains); + const ismFactoryContracts = await ismDeployer.deploy(selectedChains); artifacts = writeMergedAddresses( contractsFilePath, artifacts, @@ -262,8 +211,8 @@ async function executeDeploy({ log(`Deploying IGP contracts`); const igpConfig = buildIgpConfigMap( owner, - allChains, - allChains, + selectedChains, + selectedChains, multiSigConfig, ); const igpDeployer = new HyperlaneIgpDeployer(multiProvider); @@ -293,7 +242,7 @@ async function executeDeploy({ const ismConfigs = buildIsmConfigMap( owner, remotes, - allChains, + selectedChains, multiSigConfig, ); const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; @@ -312,7 +261,10 @@ async function executeDeploy({ // 5. Deploy TestRecipients to all deployable chains log(`Deploying test recipient contracts`); - const testRecipientConfig = buildTestRecipientConfigMap(allChains, artifacts); + const testRecipientConfig = buildTestRecipientConfigMap( + selectedChains, + artifacts, + ); const testRecipientDeployer = new TestRecipientDeployer(multiProvider); testRecipientDeployer.cacheAddressesMap(artifacts); const testRecipients = await testRecipientDeployer.deploy( @@ -407,7 +359,7 @@ function buildTestRecipientConfigMap( function buildIgpConfigMap( owner: Address, deployChains: ChainName[], - allChains: ChainName[], + selectedChains: ChainName[], multisigIsmConfigs: ChainMap, ): ChainMap { const mergedMultisigIsmConfig: ChainMap = objMerge( @@ -418,7 +370,7 @@ function buildIgpConfigMap( for (const local of deployChains) { const overhead: ChainMap = {}; const gasOracleType: ChainMap = {}; - for (const remote of allChains) { + for (const remote of selectedChains) { if (local === remote) continue; overhead[remote] = multisigIsmVerificationCost( mergedMultisigIsmConfig[remote].threshold, @@ -455,7 +407,7 @@ async function writeAgentConfig( remotes: ChainName[], multiProvider: MultiProvider, ) { - const allChains = [local, ...remotes]; + const selectedChains = [local, ...remotes]; const startBlocks: ChainMap = { ...agentStartBlocks }; startBlocks[local] = await multiProvider.getProvider(local).getBlockNumber(); @@ -466,14 +418,14 @@ async function writeAgentConfig( const filteredAddressesMap = objFilter( mergedAddressesMap, (chain, v): v is HyperlaneAddresses => - allChains.includes(chain) && + selectedChains.includes(chain) && !!v.mailbox && !!v.interchainGasPaymaster && !!v.validatorAnnounce, ) as ChainMap; const agentConfig = buildAgentConfig( - allChains, + selectedChains, multiProvider, filteredAddressesMap, startBlocks, diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index b9dd1f03b0..5fa206c2ea 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -200,7 +200,7 @@ async function runDeployPlanStep({ const address = await signer.getAddress(); const baseToken = configMap[local]; const baseName = getTokenName(baseToken); - logBlue('\nDeployment plan:'); + logBlue('\n', 'Deployment plan:'); logGray('===============:'); log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying a warp route with a base of ${baseName} token on ${local}`); diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 30a38b5b2f..eca03ddb00 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -2,13 +2,12 @@ import { BigNumber, ethers } from 'ethers'; import { ChainName, - DispatchedMessage, HyperlaneContractsMap, HyperlaneCore, HyperlaneIgp, MultiProvider, } from '@hyperlane-xyz/sdk'; -import { addressToBytes32, sleep, timeout } from '@hyperlane-xyz/utils'; +import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; import { readDeploymentArtifacts } from '../configs.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; @@ -80,7 +79,7 @@ async function executeDelivery({ const destinationDomain = multiProvider.getDomainId(destination); const signerAddress = await signer.getAddress(); - let message: DispatchedMessage; + let messageReceipt: ethers.ContractReceipt; try { const recipient = mergedContractAddrs[destination].testRecipient; if (!recipient) { @@ -93,8 +92,8 @@ async function executeDelivery({ addressToBytes32(recipient), '0x48656c6c6f21', // Hello! ); - const messageReceipt = await multiProvider.handleTx(origin, messageTx); - message = core.getDispatchedMessages(messageReceipt)[0]; + messageReceipt = await multiProvider.handleTx(origin, messageTx); + const message = core.getDispatchedMessages(messageReceipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); @@ -118,15 +117,7 @@ async function executeDelivery({ ); throw e; } - while (true) { - const destination = multiProvider.getChainName(message.parsed.destination); - const mailbox = core.getContracts(destination).mailbox; - const delivered = await mailbox.delivered(message.id); - if (delivered) break; - - log('Waiting for message delivery on destination chain...'); - await sleep(5000); - } - + log('Waiting for message delivery on destination chain...'); + await core.waitForMessageProcessed(messageReceipt, 5000); logGreen('Message was delivered!'); } diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index cda07256ed..af11986fd2 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -1,4 +1,3 @@ -import assert from 'assert'; import { ethers } from 'ethers'; import { @@ -12,13 +11,13 @@ import { HyperlaneCore, MultiProvider, } from '@hyperlane-xyz/sdk'; -import { Address, sleep, timeout } from '@hyperlane-xyz/utils'; +import { Address, timeout } from '@hyperlane-xyz/utils'; import { readDeploymentArtifacts } from '../configs.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; -import { log, logBlue, logGreen } from '../logger.js'; +import { logBlue, logGreen } from '../logger.js'; import { assertTokenBalance } from '../utils/balances.js'; // TODO improve the UX here by making params optional and @@ -144,18 +143,7 @@ async function executeDelivery({ const message = core.getDispatchedMessages(receipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); - - const msgDestination = multiProvider.getChainName(message.parsed.destination); - assert(destination === msgDestination); - - while (true) { - const mailbox = core.getContracts(destination).mailbox; - const delivered = await mailbox.delivered(message.id); - if (delivered) break; - log('Waiting for message delivery on destination chain...'); - await sleep(5000); - } - + await core.waitForMessageProcessed(receipt, 5000); logGreen(`Transfer sent to destination chain!`); } diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts new file mode 100644 index 0000000000..7da05cdb5c --- /dev/null +++ b/typescript/cli/src/utils/chains.ts @@ -0,0 +1,89 @@ +import { Separator, checkbox } from '@inquirer/prompts'; +import select from '@inquirer/select'; +import chalk from 'chalk'; + +import { + ChainMap, + ChainMetadata, + mainnetChainsMetadata, + testnetChainsMetadata, +} from '@hyperlane-xyz/sdk'; + +import { log, logBlue } from '../logger.js'; + +// A special value marker to indicate user selected +// a new chain in the list +const NEW_CHAIN_MARKER = '__new__'; + +export async function runLocalAndRemotesSelectionStep( + customChains: ChainMap, +) { + const local = await runSingleChainSelectionStep( + customChains, + 'Select local chain (the chain to which you will deploy now)', + ); + const remotes = await runMultiChainSelectionStep( + customChains, + 'Select remote chains the local will send messages to', + ); + const selectedChains = [local, ...remotes]; + return { local, remotes, selectedChains }; +} + +export async function runSingleChainSelectionStep( + customChains: ChainMap, + message = 'Select chain', +) { + const choices = getChainChoices(customChains); + const local = (await select({ + message, + choices, + pageSize: 20, + })) as string; + handleNewChain([local]); + return local; +} + +export async function runMultiChainSelectionStep( + customChains: ChainMap, + message = 'Select chains', +) { + const choices = getChainChoices(customChains); + const remotes = (await checkbox({ + message, + choices, + pageSize: 20, + })) as string[]; + handleNewChain(remotes); + if (!remotes?.length) throw new Error('No remote chains selected'); + return remotes; +} + +function getChainChoices(customChains: ChainMap) { + const chainsToChoices = (chains: ChainMetadata[]) => + chains.map((c) => ({ name: c.name, value: c.name })); + const choices: Parameters['0']['choices'] = [ + new Separator('--Custom Chains--'), + ...chainsToChoices(Object.values(customChains)), + { name: '(New custom chain)', value: NEW_CHAIN_MARKER }, + new Separator('--Mainnet Chains--'), + ...chainsToChoices(mainnetChainsMetadata), + new Separator('--Testnet Chains--'), + ...chainsToChoices(testnetChainsMetadata), + ]; + return choices; +} + +function handleNewChain(chainNames: string[]) { + if (chainNames.includes(NEW_CHAIN_MARKER)) { + logBlue( + 'To use a new chain, use the --config argument add them to that file', + ); + log( + chalk.blue('Use the'), + chalk.magentaBright('hyperlane config create'), + chalk.blue('command to create new configs'), + ); + process.exit(0); + } +} diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 1f9fd6e280..b3116c0c7b 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -82,15 +82,21 @@ export class HyperlaneCore extends HyperlaneApp { protected async waitForMessageWasProcessed( message: DispatchedMessage, + delay?: number, + maxAttempts?: number, ): Promise { const id = messageId(message.message); const { mailbox } = this.getDestination(message); - await pollAsync(async () => { - const delivered = await mailbox.delivered(id); - if (!delivered) { - throw new Error(`Message ${id} not yet processed`); - } - }); + await pollAsync( + async () => { + const delivered = await mailbox.delivered(id); + if (!delivered) { + throw new Error(`Message ${id} not yet processed`); + } + }, + delay, + maxAttempts, + ); return; } @@ -103,10 +109,14 @@ export class HyperlaneCore extends HyperlaneApp { async waitForMessageProcessed( sourceTx: ethers.ContractReceipt, + delay?: number, + maxAttempts?: number, ): Promise { const messages = HyperlaneCore.getDispatchedMessages(sourceTx); await Promise.all( - messages.map((msg) => this.waitForMessageWasProcessed(msg)), + messages.map((msg) => + this.waitForMessageWasProcessed(msg, delay, maxAttempts), + ), ); } From 0bd1d095841d47ac230b7a38f9528a04dbc38a2a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 6 Aug 2023 12:56:47 -0400 Subject: [PATCH 18/82] Run prettier --- typescript/cli/examples/multisig-ism.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml index 2b33eed1a2..91dc432a7d 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/multisig-ism.yaml @@ -1,6 +1,6 @@ # A config for a multisig Interchain Security Module (ISM) # Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts -# +# # Valid module types: # routing # aggregation From 18f3d9f499837a878ef71162480067ae0b89507b Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 10 Aug 2023 16:47:05 -0400 Subject: [PATCH 19/82] Add new multisig config create command --- typescript/cli/examples/multisig-ism.yaml | 2 +- typescript/cli/examples/warp-tokens.yaml | 4 +- typescript/cli/package.json | 2 +- typescript/cli/src/commands/config.ts | 142 ++++++++++++---------- typescript/cli/src/commands/options.ts | 15 +++ typescript/cli/src/config/chain.ts | 56 +++++++++ typescript/cli/src/config/multisig.ts | 76 ++++++++++++ typescript/cli/src/configs.ts | 22 ++-- 8 files changed, 243 insertions(+), 76 deletions(-) create mode 100644 typescript/cli/src/config/chain.ts create mode 100644 typescript/cli/src/config/multisig.ts diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml index 91dc432a7d..b2adb91f5b 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/multisig-ism.yaml @@ -11,7 +11,7 @@ anvil1: type: 'legacy_multisig' threshold: 1 # Number: Signatures required to approve a message - validators: # Array: List of validator configs + validators: # Array: List of validator addresses - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' anvil2: type: 'legacy_multisig' diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml index 0d78580f59..d77ef2ca3b 100644 --- a/typescript/cli/examples/warp-tokens.yaml +++ b/typescript/cli/examples/warp-tokens.yaml @@ -11,8 +11,8 @@ base: chainName: anvil1 type: native - # address: 0x123... # Required for collateral types) - # isNft: true # If the token is an NFT (ERC721), set to true) + # address: 0x123... # Required for collateral types + # isNft: true # If the token is an NFT (ERC721), set to true # owner: 0x123 # Optional owner address for synthetic token # mailbox: 0x123 # Optional mailbox address route # interchainGasPaymaster: 0x123 # Optional interchainGasPaymaster address diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 4e0befae79..767c950b8b 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.4.3-beta0", + "version": "1.4.3-beta1", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@hyperlane-xyz/hyperlane-token": "1.4.3-beta0", diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 566589bd6e..c73a405b9b 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -1,13 +1,16 @@ -import { confirm, input } from '@inquirer/prompts'; -import select from '@inquirer/select'; import { CommandModule } from 'yargs'; -import { ChainMetadata, isValidChainMetadata } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { createChainConfig } from '../config/chain.js'; +import { createMultisigConfig } from '../config/multisig.js'; +import { readChainConfig, readMultisigConfig } from '../configs.js'; +import { log } from '../logger.js'; +import { FileFormat } from '../utils/files.js'; -import { readChainConfig } from '../configs.js'; -import { errorRed, log, logBlue, logGreen } from '../logger.js'; -import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; +import { + chainsCommandOption, + fileFormatOption, + outputFileOption, +} from './options.js'; /** * Parent command @@ -25,78 +28,72 @@ export const configCommand: CommandModule = { }; /** - * Create command + * Create commands */ const createCommand: CommandModule = { command: 'create', - describe: 'Create a new, minimal Hyperlane config', + describe: 'Create a new Hyperlane config', + builder: (yargs) => + yargs + .command(createChainCommand) + .command(createMultisigCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +const createChainCommand: CommandModule = { + command: 'chain', + describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)', builder: (yargs) => yargs.options({ - output: { - type: 'string', - alias: 'o', - description: 'Output file path', - }, - format: { - type: 'string', - alias: 'f', - description: 'Output file format', - choices: ['json', 'yaml'], - }, + output: outputFileOption('./configs/chain-config.yaml'), + format: fileFormatOption, + }), + handler: async (argv: any) => { + const format: FileFormat = argv.format; + const outPath: string = argv.output; + await createChainConfig({ format, outPath }); + process.exit(0); + }, +}; + +const createMultisigCommand: CommandModule = { + command: 'multisig', + describe: 'Create a new Multisig ISM config', + builder: (yargs) => + yargs.options({ + output: outputFileOption('./configs/multisig-ism.yaml'), + format: fileFormatOption, + chains: chainsCommandOption, }), handler: async (argv: any) => { - const format: FileFormat = argv.format || 'yaml'; - const output: string = argv.output || `./configs/chain-config.${format}`; - logBlue('Creating a new chain config'); - const name = await input({ - message: 'Enter chain name (one word, lower case)', - }); - const chainId = await input({ message: 'Enter chain id (number)' }); - const skipDomain = await confirm({ - message: 'Will the domainId match the chainId (recommended)?', - }); - let domainId: string; - if (skipDomain) { - domainId = chainId; - } else { - domainId = await input({ - message: 'Enter domain id (number, often matches chainId)', - }); - } - const protocol = await select({ - message: 'Select protocol type', - choices: Object.values(ProtocolType).map((protocol) => ({ - name: protocol, - value: protocol, - })), - }); - const rpcUrl = await input({ message: 'Enter http or https rpc url' }); - const metadata: ChainMetadata = { - name, - chainId: parseInt(chainId, 10), - domainId: parseInt(domainId, 10), - protocol, - rpcUrls: [{ http: rpcUrl }], - }; - if (isValidChainMetadata(metadata)) { - logGreen(`Chain config is valid, writing to file ${output}`); - mergeYamlOrJson(output, { [name]: metadata }, format); - } else { - errorRed( - `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, - ); - throw new Error('Invalid chain config'); - } + const format: FileFormat = argv.format; + const outPath: string = argv.output; + const chainConfigPath: string = argv.chains; + await createMultisigConfig({ format, outPath, chainConfigPath }); process.exit(0); }, }; /** - * Validate command + * Validate commands */ const validateCommand: CommandModule = { command: 'validate', - describe: 'Validate the configs in a YAML or JSON file', + describe: 'Validate a config in a YAML or JSON file', + builder: (yargs) => + yargs + .command(validateChainCommand) + .command(validateMultisigCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +const validateChainCommand: CommandModule = { + command: 'chain', + describe: 'Validate a chain config in a YAML or JSON file', builder: (yargs) => yargs.options({ path: { @@ -110,3 +107,20 @@ const validateCommand: CommandModule = { readChainConfig(path); }, }; + +const validateMultisigCommand: CommandModule = { + command: 'multisig', + describe: 'Validate a multisig ism config in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readMultisigConfig(path); + }, +}; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 87c1449045..96714ca14f 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -23,3 +23,18 @@ export const coreArtifactsOption: Options = { type: 'string', description: 'File path to core deployment output artifacts', }; + +export const fileFormatOption: Options = { + type: 'string', + alias: 'f', + description: 'Output file format', + choices: ['json', 'yaml'], + default: 'yaml', +}; + +export const outputFileOption = (defaultPath: string): Options => ({ + type: 'string', + alias: 'o', + description: 'Output file path', + default: defaultPath, +}); diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts new file mode 100644 index 0000000000..356007a385 --- /dev/null +++ b/typescript/cli/src/config/chain.ts @@ -0,0 +1,56 @@ +import { confirm, input, select } from '@inquirer/prompts'; + +import { ChainMetadata, isValidChainMetadata } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { errorRed, logBlue, logGreen } from '../logger.js'; +import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; + +export async function createChainConfig({ + format, + outPath, +}: { + format: FileFormat; + outPath: string; +}) { + logBlue('Creating a new chain config'); + const name = await input({ + message: 'Enter chain name (one word, lower case)', + }); + const chainId = await input({ message: 'Enter chain id (number)' }); + const skipDomain = await confirm({ + message: 'Will the domainId match the chainId (recommended)?', + }); + let domainId: string; + if (skipDomain) { + domainId = chainId; + } else { + domainId = await input({ + message: 'Enter domain id (number, often matches chainId)', + }); + } + const protocol = await select({ + message: 'Select protocol type', + choices: Object.values(ProtocolType).map((protocol) => ({ + name: protocol, + value: protocol, + })), + }); + const rpcUrl = await input({ message: 'Enter http or https rpc url' }); + const metadata: ChainMetadata = { + name, + chainId: parseInt(chainId, 10), + domainId: parseInt(domainId, 10), + protocol, + rpcUrls: [{ http: rpcUrl }], + }; + if (isValidChainMetadata(metadata)) { + logGreen(`Chain config is valid, writing to file ${outPath}`); + mergeYamlOrJson(outPath, { [name]: metadata }, format); + } else { + errorRed( + `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + throw new Error('Invalid chain config'); + } +} diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts new file mode 100644 index 0000000000..24368855bd --- /dev/null +++ b/typescript/cli/src/config/multisig.ts @@ -0,0 +1,76 @@ +import { confirm, input, select } from '@inquirer/prompts'; + +import { + MultisigConfigMap, + isValidMultisigConfig, + readChainConfigIfExists, +} from '../configs.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; + +export async function createMultisigConfig({ + format, + outPath, + chainConfigPath, +}: { + format: FileFormat; + outPath: string; + chainConfigPath: string; +}) { + logBlue('Creating a new multisig config'); + const customChains = readChainConfigIfExists(chainConfigPath); + const chains = await runMultiChainSelectionStep(customChains); + + const result: MultisigConfigMap = {}; + let lastConfig: MultisigConfigMap['string'] | undefined = undefined; + let repeat = false; + for (const chain of chains) { + log(`Setting values for chain ${chain}`); + if (lastConfig && repeat) { + result[chain] = lastConfig; + continue; + } + const moduleType = await select({ + message: 'Select multisig type', + choices: [ + // { value: 'routing, name: 'routing' }, // TODO add support + // { value: 'aggregation, name: 'aggregation' }, // TODO add support + { value: 'legacy_multisig', name: 'legacy multisig' }, + { value: 'merkle_root_multisig', name: 'merkle root multisig' }, + { value: 'message_id_multisig', name: 'message id multisig' }, + ], + pageSize: 5, + }); + + const thresholdInput = await input({ + message: 'Enter threshold of signers (number)', + }); + const threshold = parseInt(thresholdInput, 10); + + const validatorsInput = await input({ + message: 'Enter validator addresses (comma separated list)', + }); + const validators = validatorsInput.split(',').map((v) => v.trim()); + lastConfig = { + type: moduleType, + threshold, + validators, + }; + result[chain] = lastConfig; + + repeat = await confirm({ + message: 'Use this same config for remaining chains?', + }); + } + + if (isValidMultisigConfig(result)) { + logGreen(`Multisig config is valid, writing to file ${outPath}`); + mergeYamlOrJson(outPath, result, format); + } else { + errorRed( + `Multisig config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/multisig-ism.yaml for an example`, + ); + throw new Error('Invalid multisig config'); + } +} diff --git a/typescript/cli/src/configs.ts b/typescript/cli/src/configs.ts index 76f21f89c5..ac06ca7bea 100644 --- a/typescript/cli/src/configs.ts +++ b/typescript/cli/src/configs.ts @@ -16,16 +16,16 @@ import { getMultiProvider } from './context.js'; import { errorRed, log, logGreen } from './logger.js'; import { readYamlOrJson } from './utils/files.js'; -export function readChainConfig(filepath: string) { - log(`Reading file configs in ${filepath}`); - const chainToMetadata = readYamlOrJson>(filepath); +export function readChainConfig(filePath: string) { + log(`Reading file configs in ${filePath}`); + const chainToMetadata = readYamlOrJson>(filePath); if ( !chainToMetadata || typeof chainToMetadata !== 'object' || !Object.keys(chainToMetadata).length ) { - errorRed(`No configs found in ${filepath}`); + errorRed(`No configs found in ${filePath}`); process.exit(1); } @@ -46,7 +46,7 @@ export function readChainConfig(filepath: string) { // Ensure multiprovider accepts this metadata getMultiProvider(chainToMetadata); - logGreen(`All chain configs in ${filepath} are valid`); + logGreen(`All chain configs in ${filePath} are valid`); return chainToMetadata; } @@ -76,18 +76,19 @@ export function readDeploymentArtifacts(filePath: string) { return artifacts; } -const MultisigConfigSchema = z.object({}).catchall( +const MultisigConfigMapSchema = z.object({}).catchall( z.object({ type: z.string(), threshold: z.number(), validators: z.array(z.string()), }), ); +export type MultisigConfigMap = z.infer; export function readMultisigConfig(filePath: string) { - const config = readYamlOrJson>(filePath); + const config = readYamlOrJson(filePath); if (!config) throw new Error(`No multisig config found at ${filePath}`); - const result = MultisigConfigSchema.safeParse(config); + const result = MultisigConfigMapSchema.safeParse(config); if (!result.success) { const firstIssue = result.error.issues[0]; throw new Error( @@ -100,9 +101,14 @@ export function readMultisigConfig(filePath: string) { type: humanReadableIsmTypeToEnum(config.type), })); + logGreen(`All multisig configs in ${filePath} are valid`); return formattedConfig as ChainMap; } +export function isValidMultisigConfig(config: any) { + return MultisigConfigMapSchema.safeParse(config).success; +} + function humanReadableIsmTypeToEnum(type: string): ModuleType { for (const [key, value] of Object.entries(ModuleType)) { if (key.toLowerCase() === type) return parseInt(value.toString(), 10); From 102929978e90a9757514e643f03b912c2171b52e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 11 Aug 2023 19:44:32 -0400 Subject: [PATCH 20/82] Bump versions to 1.4.3-beta2 --- solidity/package.json | 4 ++-- typescript/cli/package.json | 6 +++--- typescript/helloworld/package.json | 4 ++-- typescript/infra/package.json | 8 +++---- typescript/sdk/package.json | 6 +++--- typescript/token/package.json | 8 +++---- typescript/utils/package.json | 2 +- yarn.lock | 34 +++++++++++++++--------------- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index a6c65b698e..fd918e7755 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.4.3-beta0", + "version": "1.4.3-beta2", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.4.3-beta0", + "@hyperlane-xyz/utils": "1.4.3-beta2", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 767c950b8b..0207432a26 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.4.3-beta1", + "version": "1.4.3-beta2", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/hyperlane-token": "1.4.3-beta0", - "@hyperlane-xyz/sdk": "1.4.3-beta0", + "@hyperlane-xyz/hyperlane-token": "1.4.3-beta2", + "@hyperlane-xyz/sdk": "1.4.3-beta2", "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 5269357baa..77129c1884 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,9 +1,9 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.4.3-beta0", + "version": "1.4.3-beta2", "dependencies": { - "@hyperlane-xyz/sdk": "1.4.3-beta0", + "@hyperlane-xyz/sdk": "1.4.3-beta2", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 4dd62ba6e5..42b3971337 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "1.4.3-beta0", + "version": "1.4.3-beta2", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -11,9 +11,9 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.4.3-beta0", - "@hyperlane-xyz/sdk": "1.4.3-beta0", - "@hyperlane-xyz/utils": "1.4.3-beta0", + "@hyperlane-xyz/helloworld": "1.4.3-beta2", + "@hyperlane-xyz/sdk": "1.4.3-beta2", + "@hyperlane-xyz/utils": "1.4.3-beta2", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.1.0", "@safe-global/protocol-kit": "^1.0.1", diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 7472d29fe2..789500e030 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.4.3-beta0", + "version": "1.4.3-beta2", "dependencies": { - "@hyperlane-xyz/core": "1.4.3-beta0", - "@hyperlane-xyz/utils": "1.4.3-beta0", + "@hyperlane-xyz/core": "1.4.3-beta2", + "@hyperlane-xyz/utils": "1.4.3-beta2", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", diff --git a/typescript/token/package.json b/typescript/token/package.json index 73c96cd342..e6d1d5bbb9 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.4.3-beta0", + "version": "1.4.3-beta2", "dependencies": { - "@hyperlane-xyz/core": "1.4.3-beta0", - "@hyperlane-xyz/sdk": "1.4.3-beta0", - "@hyperlane-xyz/utils": "1.4.3-beta0", + "@hyperlane-xyz/core": "1.4.3-beta2", + "@hyperlane-xyz/sdk": "1.4.3-beta2", + "@hyperlane-xyz/utils": "1.4.3-beta2", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 920a8894bf..df3fffea69 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities for the Hyperlane network", - "version": "1.4.3-beta0", + "version": "1.4.3-beta2", "dependencies": { "@solana/web3.js": "^1.78.0", "bignumber.js": "^9.1.1", diff --git a/yarn.lock b/yarn.lock index 28e4556402..281bd71568 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3819,8 +3819,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/hyperlane-token": 1.4.3-beta0 - "@hyperlane-xyz/sdk": 1.4.3-beta0 + "@hyperlane-xyz/hyperlane-token": 1.4.3-beta2 + "@hyperlane-xyz/sdk": 1.4.3-beta2 "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 @@ -3840,12 +3840,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@1.4.3-beta0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@1.4.3-beta2, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.4.3-beta0 + "@hyperlane-xyz/utils": 1.4.3-beta2 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -3868,11 +3868,11 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.4.3-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@1.4.3-beta2, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/sdk": 1.4.3-beta0 + "@hyperlane-xyz/sdk": 1.4.3-beta2 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3900,13 +3900,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@1.4.3-beta0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.4.3-beta2, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: - "@hyperlane-xyz/core": 1.4.3-beta0 - "@hyperlane-xyz/sdk": 1.4.3-beta0 - "@hyperlane-xyz/utils": 1.4.3-beta0 + "@hyperlane-xyz/core": 1.4.3-beta2 + "@hyperlane-xyz/sdk": 1.4.3-beta2 + "@hyperlane-xyz/utils": 1.4.3-beta2 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3946,9 +3946,9 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.4.3-beta0 - "@hyperlane-xyz/sdk": 1.4.3-beta0 - "@hyperlane-xyz/utils": 1.4.3-beta0 + "@hyperlane-xyz/helloworld": 1.4.3-beta2 + "@hyperlane-xyz/sdk": 1.4.3-beta2 + "@hyperlane-xyz/utils": 1.4.3-beta2 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -3991,12 +3991,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.4.3-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@1.4.3-beta2, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: - "@hyperlane-xyz/core": 1.4.3-beta0 - "@hyperlane-xyz/utils": 1.4.3-beta0 + "@hyperlane-xyz/core": 1.4.3-beta2 + "@hyperlane-xyz/utils": 1.4.3-beta2 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/web3.js": ^1.78.0 @@ -4025,7 +4025,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.4.3-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@1.4.3-beta2, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 8c2a620da381d125f57934ca317e9dff620b194d Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 21 Sep 2023 21:37:59 -0400 Subject: [PATCH 21/82] Add create and validate commands for warp configs Update send transfer command to use EvmTokenAdapter --- typescript/cli/examples/warp-tokens.yaml | 4 +- typescript/cli/package.json | 6 +- typescript/cli/src/commands/config.ts | 51 ++++++++++++++++- typescript/cli/src/config/warp.ts | 73 ++++++++++++++++++++++++ typescript/cli/src/configs.ts | 11 +++- typescript/cli/src/send/transfer.ts | 30 ++++++---- yarn.lock | 60 +------------------ 7 files changed, 157 insertions(+), 78 deletions(-) create mode 100644 typescript/cli/src/config/warp.ts diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml index d77ef2ca3b..4a1f5de3ee 100644 --- a/typescript/cli/examples/warp-tokens.yaml +++ b/typescript/cli/examples/warp-tokens.yaml @@ -2,11 +2,11 @@ # Typically used with the 'hyperlane deploy warp' command # # Token Types: +# native +# collateral # synthetic # syntheticUri -# collateral # collateralUri -# native --- base: chainName: anvil1 diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 0207432a26..c7a206d98e 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.4.3-beta2", + "version": "1.5.1-beta0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/hyperlane-token": "1.4.3-beta2", - "@hyperlane-xyz/sdk": "1.4.3-beta2", + "@hyperlane-xyz/hyperlane-token": "1.5.1", + "@hyperlane-xyz/sdk": "1.5.1", "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index c73a405b9b..a3d2084fb3 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -2,8 +2,13 @@ import { CommandModule } from 'yargs'; import { createChainConfig } from '../config/chain.js'; import { createMultisigConfig } from '../config/multisig.js'; -import { readChainConfig, readMultisigConfig } from '../configs.js'; -import { log } from '../logger.js'; +import { createWarpConfig } from '../config/warp.js'; +import { + readChainConfig, + readMultisigConfig, + readWarpRouteConfig, +} from '../configs.js'; +import { log, logGreen } from '../logger.js'; import { FileFormat } from '../utils/files.js'; import { @@ -37,6 +42,7 @@ const createCommand: CommandModule = { yargs .command(createChainCommand) .command(createMultisigCommand) + .command(createWarpCommand) .version(false) .demandCommand(), handler: () => log('Command required'), @@ -76,6 +82,24 @@ const createMultisigCommand: CommandModule = { }, }; +const createWarpCommand: CommandModule = { + command: 'warp', + describe: 'Create a new Warp Route tokens config', + builder: (yargs) => + yargs.options({ + output: outputFileOption('./configs/warp-tokens.yaml'), + format: fileFormatOption, + chains: chainsCommandOption, + }), + handler: async (argv: any) => { + const format: FileFormat = argv.format; + const outPath: string = argv.output; + const chainConfigPath: string = argv.chains; + await createWarpConfig({ format, outPath, chainConfigPath }); + process.exit(0); + }, +}; + /** * Validate commands */ @@ -86,6 +110,7 @@ const validateCommand: CommandModule = { yargs .command(validateChainCommand) .command(validateMultisigCommand) + .command(validateWarpCommand) .version(false) .demandCommand(), handler: () => log('Command required'), @@ -105,6 +130,7 @@ const validateChainCommand: CommandModule = { handler: async (argv) => { const path = argv.path as string; readChainConfig(path); + process.exit(0); }, }; @@ -122,5 +148,26 @@ const validateMultisigCommand: CommandModule = { handler: async (argv) => { const path = argv.path as string; readMultisigConfig(path); + logGreen('Config is valid'); + process.exit(0); + }, +}; + +const validateWarpCommand: CommandModule = { + command: 'warp', + describe: 'Validate a Warp Route config in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readWarpRouteConfig(path); + logGreen('Config is valid'); + process.exit(0); }, }; diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts new file mode 100644 index 0000000000..fcfd2f679a --- /dev/null +++ b/typescript/cli/src/config/warp.ts @@ -0,0 +1,73 @@ +import { confirm, input } from '@inquirer/prompts'; +import { ethers } from 'ethers'; + +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; + +import { + WarpRouteConfig, + isValidWarpRouteConfig, + readChainConfigIfExists, +} from '../configs.js'; +import { errorRed, logBlue, logGreen } from '../logger.js'; +import { + runMultiChainSelectionStep, + runSingleChainSelectionStep, +} from '../utils/chains.js'; +import { FileFormat, writeYamlOrJson } from '../utils/files.js'; + +export async function createWarpConfig({ + format, + outPath, + chainConfigPath, +}: { + format: FileFormat; + outPath: string; + chainConfigPath: string; +}) { + logBlue('Creating a new warp route config'); + const customChains = readChainConfigIfExists(chainConfigPath); + const baseChain = await runSingleChainSelectionStep( + customChains, + 'Select base chain with the original token to warp', + ); + + const isNative = await confirm({ + message: + 'Are you creating a route for the native token of the base chain (e.g. Ether on Ethereum)?', + }); + + const baseType = isNative ? TokenType.native : TokenType.collateral; + const baseAddress = isNative + ? ethers.constants.AddressZero + : await input({ message: 'Enter the token address' }); + const isNft = isNative + ? false + : await confirm({ message: 'Is this an NFT (i.e. ERC-721)?' }); + + const syntheticChains = await runMultiChainSelectionStep( + customChains, + 'Select the chains to which the base token will be connected', + ); + + // TODO add more prompts here to support customizing the token metadata + + const result: WarpRouteConfig = { + base: { + chainName: baseChain, + type: baseType, + address: baseAddress, + isNft, + }, + synthetics: syntheticChains.map((chain) => ({ chainName: chain })), + }; + + if (isValidWarpRouteConfig(result)) { + logGreen(`Warp Route config is valid, writing to file ${outPath}`); + writeYamlOrJson(outPath, result, format); + } else { + errorRed( + `Warp config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-tokens.yaml for an example`, + ); + throw new Error('Invalid multisig config'); + } +} diff --git a/typescript/cli/src/configs.ts b/typescript/cli/src/configs.ts index ac06ca7bea..3e09570754 100644 --- a/typescript/cli/src/configs.ts +++ b/typescript/cli/src/configs.ts @@ -147,7 +147,12 @@ export const WarpRouteConfigSchema = z.object({ .nonempty(), }); -export type WarpRouteConfig = z.infer; +type InferredType = z.infer; +// A workaround for Zod's terrible typing for nonEmpty arrays +export type WarpRouteConfig = { + base: InferredType['base']; + synthetics: Array; +}; export function readWarpRouteConfig(filePath: string) { const config = readYamlOrJson(filePath); @@ -161,3 +166,7 @@ export function readWarpRouteConfig(filePath: string) { } return result.data; } + +export function isValidWarpRouteConfig(config: any) { + return WarpRouteConfigSchema.safeParse(config).success; +} diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index af11986fd2..14ce4072b2 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -2,13 +2,14 @@ import { ethers } from 'ethers'; import { ERC20__factory, - HypERC20App, + EvmHypCollateralAdapter, HypERC20Collateral__factory, } from '@hyperlane-xyz/hyperlane-token'; import { ChainName, HyperlaneContractsMap, HyperlaneCore, + MultiProtocolProvider, MultiProvider, } from '@hyperlane-xyz/sdk'; import { Address, timeout } from '@hyperlane-xyz/utils'; @@ -127,19 +128,24 @@ async function executeDelivery({ ); } - const app = new HypERC20App( - { - [origin]: { - router: HypERC20Collateral__factory.connect( - routerAddress, - connectedSigner, - ), - }, - }, - multiProvider, + // TODO move next section into MultiProtocolTokenApp when it exists + const adapter = new EvmHypCollateralAdapter( + origin, + MultiProtocolProvider.fromMultiProvider(multiProvider), + { token: routerAddress }, ); + const destinationDomain = multiProvider.getDomainId(destination); + const gasPayment = await adapter.quoteGasPayment(destinationDomain); + const transferTx = await adapter.populateTransferRemoteTx({ + weiAmountOrId: wei, + destination: destinationDomain, + recipient, + txValue: gasPayment, + }); + + const txResponse = await connectedSigner.sendTransaction(transferTx); + const receipt = await multiProvider.handleTx(origin, txResponse); - const receipt = await app.transfer(origin, destination, recipient, wei); const message = core.getDispatchedMessages(receipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); diff --git a/yarn.lock b/yarn.lock index 6094589a51..9e3bda9fba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3856,8 +3856,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/hyperlane-token": 1.4.3-beta2 - "@hyperlane-xyz/sdk": 1.4.3-beta2 + "@hyperlane-xyz/hyperlane-token": 1.5.1 + "@hyperlane-xyz/sdk": 1.5.1 "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 @@ -3905,18 +3905,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:1.4.3-beta2": - version: 1.4.3-beta2 - resolution: "@hyperlane-xyz/core@npm:1.4.3-beta2" - dependencies: - "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.4.3-beta2 - "@openzeppelin/contracts": ^4.8.0 - "@openzeppelin/contracts-upgradeable": ^4.8.0 - checksum: 8419ee0aa67822b5c5d4ccda4f9ecf2d56dd9d5d0125b09f6273dbeb456112480c226d50991b07c02c1e816ae2497ee458726c117a34b05e677cc9e93d035ff7 - languageName: node - linkType: hard - "@hyperlane-xyz/helloworld@1.5.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" @@ -3984,19 +3972,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@npm:1.4.3-beta2": - version: 1.4.3-beta2 - resolution: "@hyperlane-xyz/hyperlane-token@npm:1.4.3-beta2" - dependencies: - "@hyperlane-xyz/core": 1.4.3-beta2 - "@hyperlane-xyz/sdk": 1.4.3-beta2 - "@hyperlane-xyz/utils": 1.4.3-beta2 - "@openzeppelin/contracts-upgradeable": ^4.8.0 - ethers: ^5.7.2 - checksum: c968ed3eae8f5d084568cbeb4875bfff3037f724c3e8b70fc8562a166e02b40531924cfd6c32231c0a2dffb51d7429f464894efb0c68ffcb925caded870540f8 - languageName: node - linkType: hard - "@hyperlane-xyz/infra@workspace:typescript/infra": version: 0.0.0-use.local resolution: "@hyperlane-xyz/infra@workspace:typescript/infra" @@ -4089,26 +4064,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:1.4.3-beta2": - version: 1.4.3-beta2 - resolution: "@hyperlane-xyz/sdk@npm:1.4.3-beta2" - dependencies: - "@hyperlane-xyz/core": 1.4.3-beta2 - "@hyperlane-xyz/utils": 1.4.3-beta2 - "@solana/web3.js": ^1.78.0 - "@types/coingecko-api": ^1.0.10 - "@types/debug": ^4.1.7 - "@wagmi/chains": ^0.2.6 - coingecko-api: ^1.0.10 - cross-fetch: ^3.1.5 - debug: ^4.3.4 - ethers: ^5.7.2 - viem: ^1.3.1 - zod: ^3.21.2 - checksum: fb2973403736bdb45b2e9a3cbae56ef8c5e1cfc9c22197cc437cff33663c3a759ee7560718ac2eb0ba9a2192de375cc06e7ce31d4f6aa361ef5e595bfb2fb222 - languageName: node - linkType: hard - "@hyperlane-xyz/utils@1.5.1, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" @@ -4122,17 +4077,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:1.4.3-beta2": - version: 1.4.3-beta2 - resolution: "@hyperlane-xyz/utils@npm:1.4.3-beta2" - dependencies: - "@solana/web3.js": ^1.78.0 - bignumber.js: ^9.1.1 - ethers: ^5.7.2 - checksum: f2ed147758d7f7370bd122a08e584dc05051e0df57da6c623c4192f15cad0956aaa854900b7db5213c95646a6259fa8c2d21a9c84dc5641f97ebe631417c6b9b - languageName: node - linkType: hard - "@inquirer/checkbox@npm:^1.3.5": version: 1.3.5 resolution: "@inquirer/checkbox@npm:1.3.5" From e0ecd6b47925aa6972b2e69d433f46140bf3e1a5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 21 Sep 2023 21:43:45 -0400 Subject: [PATCH 22/82] Bump versions for beta CLI publish --- solidity/package.json | 4 ++-- typescript/cli/package.json | 6 ++--- typescript/helloworld/package.json | 4 ++-- typescript/infra/package.json | 8 +++---- typescript/sdk/package.json | 6 ++--- typescript/token/package.json | 8 +++---- typescript/utils/package.json | 2 +- yarn.lock | 36 +++++++++++++++--------------- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index 17a227771c..d3ef6faf88 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.5.1", + "version": "1.5.2-beta0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/utils": "1.5.2-beta0", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, diff --git a/typescript/cli/package.json b/typescript/cli/package.json index c7a206d98e..d8f2503eb1 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.5.1-beta0", + "version": "1.5.2-beta0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/hyperlane-token": "1.5.1", - "@hyperlane-xyz/sdk": "1.5.1", + "@hyperlane-xyz/hyperlane-token": "1.5.2-beta0", + "@hyperlane-xyz/sdk": "1.5.2-beta0", "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index b1317c8090..c436a5488b 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,9 +1,9 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.5.1", + "version": "1.5.2-beta0", "dependencies": { - "@hyperlane-xyz/sdk": "1.5.1", + "@hyperlane-xyz/sdk": "1.5.2-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 1272fb898a..c55c4c8081 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -11,10 +11,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.5.1", - "@hyperlane-xyz/hyperlane-token": "1.5.1", - "@hyperlane-xyz/sdk": "1.5.1", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/helloworld": "1.5.2-beta0", + "@hyperlane-xyz/hyperlane-token": "1.5.2-beta0", + "@hyperlane-xyz/sdk": "1.5.2-beta0", + "@hyperlane-xyz/utils": "1.5.2-beta0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 9353ea21be..a52aeed990 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.5.1", + "version": "1.5.2-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.1", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/core": "1.5.2-beta0", + "@hyperlane-xyz/utils": "1.5.2-beta0", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", diff --git a/typescript/token/package.json b/typescript/token/package.json index fffc46b1f0..d9a5fe8e58 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.5.1", + "version": "1.5.2-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.1", - "@hyperlane-xyz/sdk": "1.5.1", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/core": "1.5.2-beta0", + "@hyperlane-xyz/sdk": "1.5.2-beta0", + "@hyperlane-xyz/utils": "1.5.2-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "@solana/spl-token": "^0.3.8", "ethers": "^5.7.2" diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 077de2bd39..50bb2a427c 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "1.5.1", + "version": "1.5.2-beta0", "dependencies": { "@solana/web3.js": "^1.78.0", "bignumber.js": "^9.1.1", diff --git a/yarn.lock b/yarn.lock index 9e3bda9fba..1160b6b809 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3856,8 +3856,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/hyperlane-token": 1.5.1 - "@hyperlane-xyz/sdk": 1.5.1 + "@hyperlane-xyz/hyperlane-token": 1.5.2-beta0 + "@hyperlane-xyz/sdk": 1.5.2-beta0 "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 @@ -3877,12 +3877,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@1.5.1, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@1.5.2-beta0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/utils": 1.5.2-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -3905,11 +3905,11 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.5.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@1.5.2-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/sdk": 1.5.1 + "@hyperlane-xyz/sdk": 1.5.2-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3937,13 +3937,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@1.5.1, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.5.2-beta0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: - "@hyperlane-xyz/core": 1.5.1 - "@hyperlane-xyz/sdk": 1.5.1 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/core": 1.5.2-beta0 + "@hyperlane-xyz/sdk": 1.5.2-beta0 + "@hyperlane-xyz/utils": 1.5.2-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3984,10 +3984,10 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.5.1 - "@hyperlane-xyz/hyperlane-token": 1.5.1 - "@hyperlane-xyz/sdk": 1.5.1 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/helloworld": 1.5.2-beta0 + "@hyperlane-xyz/hyperlane-token": 1.5.2-beta0 + "@hyperlane-xyz/sdk": 1.5.2-beta0 + "@hyperlane-xyz/utils": 1.5.2-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -4030,12 +4030,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.5.1, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@1.5.2-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: - "@hyperlane-xyz/core": 1.5.1 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/core": 1.5.2-beta0 + "@hyperlane-xyz/utils": 1.5.2-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/web3.js": ^1.78.0 @@ -4064,7 +4064,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.5.1, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@1.5.2-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 6959b7ff774b3da91daed52a77d631b9cd51e43f Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 21 Sep 2023 21:47:33 -0400 Subject: [PATCH 23/82] Add alias for addresses command --- typescript/cli/src/commands/chains.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts index 2eaccfe935..a8039ecce4 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/chains.ts @@ -55,6 +55,7 @@ const addressesCommand: CommandModule = { type: 'string', description: 'Chain to display addresses for', choices: Object.values(Chains), + alias: 'chain', }, }), handler: (args) => { From 13103dae40d85a6937eea8fd9446a505c4b407a1 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 28 Sep 2023 15:57:54 -0400 Subject: [PATCH 24/82] Address feedback and reorganize cli config code --- README.md | 4 +- typescript/cli/package.json | 3 +- typescript/cli/src/commands/config.ts | 10 +- typescript/cli/src/config/artifacts.ts | 22 +++ typescript/cli/src/config/chain.ts | 60 +++++- typescript/cli/src/config/multisig.ts | 53 +++++- typescript/cli/src/config/warp.ts | 65 ++++++- typescript/cli/src/configs.ts | 172 ------------------ typescript/cli/src/context.ts | 2 +- typescript/cli/src/deploy/core.ts | 3 +- typescript/cli/src/deploy/warp.ts | 7 +- typescript/cli/src/send/message.ts | 2 +- typescript/cli/src/send/transfer.ts | 2 +- .../sdk/src/metadata/ChainMetadataManager.ts | 15 ++ typescript/utils/src/types.ts | 2 +- 15 files changed, 214 insertions(+), 208 deletions(-) create mode 100644 typescript/cli/src/config/artifacts.ts delete mode 100644 typescript/cli/src/configs.ts diff --git a/README.md b/README.md index 68830a86db..4856f5d0f9 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ [codecov-badge]: https://img.shields.io/codecov/c/github/hyperlane-xyz/hyperlane-monorepo [foundry]: https://getfoundry.sh/ [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg -[license]: https://opensource.org/licenses/MIT -[license-badge]: https://img.shields.io/badge/License-MIT-blue.svg +[license]: https://www.apache.org/licenses/LICENSE-2.0 +[license-badge]: https://img.shields.io/badge/License-Apache-blue.svg ## Versioning diff --git a/typescript/cli/package.json b/typescript/cli/package.json index d8f2503eb1..99104e0be4 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -53,6 +53,5 @@ "Deployment", "Typescript" ], - "packageManager": "yarn@3.2.0", - "stableVersion": "1.4.2" + "packageManager": "yarn@3.2.0" } diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index a3d2084fb3..da25fceab1 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -1,13 +1,11 @@ import { CommandModule } from 'yargs'; -import { createChainConfig } from '../config/chain.js'; -import { createMultisigConfig } from '../config/multisig.js'; -import { createWarpConfig } from '../config/warp.js'; +import { createChainConfig, readChainConfig } from '../config/chain.js'; import { - readChainConfig, + createMultisigConfig, readMultisigConfig, - readWarpRouteConfig, -} from '../configs.js'; +} from '../config/multisig.js'; +import { createWarpConfig, readWarpRouteConfig } from '../config/warp.js'; import { log, logGreen } from '../logger.js'; import { FileFormat } from '../utils/files.js'; diff --git a/typescript/cli/src/config/artifacts.ts b/typescript/cli/src/config/artifacts.ts new file mode 100644 index 0000000000..bed0de298c --- /dev/null +++ b/typescript/cli/src/config/artifacts.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +import { HyperlaneContractsMap } from '@hyperlane-xyz/sdk'; + +import { readYamlOrJson } from '../utils/files.js'; + +const DeploymentArtifactsSchema = z + .object({}) + .catchall(z.object({}).catchall(z.string())); + +export function readDeploymentArtifacts(filePath: string) { + const artifacts = readYamlOrJson>(filePath); + if (!artifacts) throw new Error(`No artifacts found at ${filePath}`); + const result = DeploymentArtifactsSchema.safeParse(artifacts); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return artifacts; +} diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 356007a385..fc887196b4 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -1,10 +1,60 @@ import { confirm, input, select } from '@inquirer/prompts'; +import fs from 'fs'; -import { ChainMetadata, isValidChainMetadata } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainMetadata, + ChainMetadataSchema, +} from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { errorRed, logBlue, logGreen } from '../logger.js'; -import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; +import { getMultiProvider } from '../context.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; +import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; + +export function readChainConfig(filePath: string) { + log(`Reading file configs in ${filePath}`); + const chainToMetadata = readYamlOrJson>(filePath); + + if ( + !chainToMetadata || + typeof chainToMetadata !== 'object' || + !Object.keys(chainToMetadata).length + ) { + errorRed(`No configs found in ${filePath}`); + process.exit(1); + } + + for (const [chain, metadata] of Object.entries(chainToMetadata)) { + const parseResult = ChainMetadataSchema.safeParse(metadata); + if (!parseResult.success) { + errorRed( + `Chain config for ${chain} is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + errorRed(JSON.stringify(parseResult.error.errors)); + process.exit(1); + } + if (metadata.name !== chain) { + errorRed(`Chain ${chain} name does not match key`); + process.exit(1); + } + } + + // Ensure multiprovider accepts this metadata + getMultiProvider(chainToMetadata); + + logGreen(`All chain configs in ${filePath} are valid`); + return chainToMetadata; +} + +export function readChainConfigIfExists(filePath: string) { + if (!fs.existsSync(filePath)) { + log('No chain config file provided'); + return {}; + } else { + return readChainConfig(filePath); + } +} export async function createChainConfig({ format, @@ -44,13 +94,15 @@ export async function createChainConfig({ protocol, rpcUrls: [{ http: rpcUrl }], }; - if (isValidChainMetadata(metadata)) { + const parseResult = ChainMetadataSchema.safeParse(metadata); + if (parseResult.success) { logGreen(`Chain config is valid, writing to file ${outPath}`); mergeYamlOrJson(outPath, { [name]: metadata }, format); } else { errorRed( `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, ); + errorRed(JSON.stringify(parseResult.error.errors)); throw new Error('Invalid chain config'); } } diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index 24368855bd..7fe28998aa 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -1,13 +1,54 @@ import { confirm, input, select } from '@inquirer/prompts'; +import { z } from 'zod'; + +import { ChainMap, ModuleType, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; +import { objMap } from '@hyperlane-xyz/utils'; -import { - MultisigConfigMap, - isValidMultisigConfig, - readChainConfigIfExists, -} from '../configs.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { FileFormat, mergeYamlOrJson } from '../utils/files.js'; +import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; + +import { readChainConfigIfExists } from './chain.js'; + +const MultisigConfigMapSchema = z.object({}).catchall( + z.object({ + type: z.string(), + threshold: z.number(), + validators: z.array(z.string()), + }), +); +export type MultisigConfigMap = z.infer; + +export function readMultisigConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No multisig config found at ${filePath}`); + const result = MultisigConfigMapSchema.safeParse(config); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid multisig config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + const parsedConfig = result.data; + const formattedConfig = objMap(parsedConfig, (_, config) => ({ + ...config, + type: humanReadableIsmTypeToEnum(config.type), + })); + + logGreen(`All multisig configs in ${filePath} are valid`); + return formattedConfig as ChainMap; +} + +export function isValidMultisigConfig(config: any) { + return MultisigConfigMapSchema.safeParse(config).success; +} + +function humanReadableIsmTypeToEnum(type: string): ModuleType { + for (const [key, value] of Object.entries(ModuleType)) { + if (key.toLowerCase() === type) return parseInt(value.toString(), 10); + } + throw new Error(`Invalid ISM type ${type}`); +} export async function createMultisigConfig({ format, diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index fcfd2f679a..6c1a58ec5a 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -1,19 +1,72 @@ import { confirm, input } from '@inquirer/prompts'; import { ethers } from 'ethers'; +import { z } from 'zod'; import { TokenType } from '@hyperlane-xyz/hyperlane-token'; -import { - WarpRouteConfig, - isValidWarpRouteConfig, - readChainConfigIfExists, -} from '../configs.js'; import { errorRed, logBlue, logGreen } from '../logger.js'; import { runMultiChainSelectionStep, runSingleChainSelectionStep, } from '../utils/chains.js'; -import { FileFormat, writeYamlOrJson } from '../utils/files.js'; +import { FileFormat, readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; + +import { readChainConfigIfExists } from './chain.js'; + +const ConnectionConfigSchema = { + mailbox: z.string().optional(), + interchainGasPaymaster: z.string().optional(), + interchainSecurityModule: z.string().optional(), + foreignDeployment: z.string().optional(), +}; + +export const WarpRouteConfigSchema = z.object({ + base: z.object({ + type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)), + chainName: z.string(), + address: z.string().optional(), + isNft: z.boolean().optional(), + name: z.string().optional(), + symbol: z.string().optional(), + decimals: z.number().optional(), + ...ConnectionConfigSchema, + }), + synthetics: z + .array( + z.object({ + chainName: z.string(), + name: z.string().optional(), + symbol: z.string().optional(), + totalSupply: z.number().optional(), + ...ConnectionConfigSchema, + }), + ) + .nonempty(), +}); + +type InferredType = z.infer; +// A workaround for Zod's terrible typing for nonEmpty arrays +export type WarpRouteConfig = { + base: InferredType['base']; + synthetics: Array; +}; + +export function readWarpRouteConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No warp config found at ${filePath}`); + const result = WarpRouteConfigSchema.safeParse(config); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return result.data; +} + +export function isValidWarpRouteConfig(config: any) { + return WarpRouteConfigSchema.safeParse(config).success; +} export async function createWarpConfig({ format, diff --git a/typescript/cli/src/configs.ts b/typescript/cli/src/configs.ts deleted file mode 100644 index 3e09570754..0000000000 --- a/typescript/cli/src/configs.ts +++ /dev/null @@ -1,172 +0,0 @@ -import fs from 'fs'; -import { z } from 'zod'; - -import { TokenType } from '@hyperlane-xyz/hyperlane-token'; -import { - ChainMap, - ChainMetadata, - HyperlaneContractsMap, - ModuleType, - MultisigIsmConfig, - isValidChainMetadata, -} from '@hyperlane-xyz/sdk'; -import { objMap } from '@hyperlane-xyz/utils'; - -import { getMultiProvider } from './context.js'; -import { errorRed, log, logGreen } from './logger.js'; -import { readYamlOrJson } from './utils/files.js'; - -export function readChainConfig(filePath: string) { - log(`Reading file configs in ${filePath}`); - const chainToMetadata = readYamlOrJson>(filePath); - - if ( - !chainToMetadata || - typeof chainToMetadata !== 'object' || - !Object.keys(chainToMetadata).length - ) { - errorRed(`No configs found in ${filePath}`); - process.exit(1); - } - - for (const [chain, metadata] of Object.entries(chainToMetadata)) { - if (!isValidChainMetadata(metadata)) { - errorRed(`Chain ${chain} has invalid metadata`); - errorRed( - `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, - ); - process.exit(1); - } - if (metadata.name !== chain) { - errorRed(`Chain ${chain} name does not match key`); - process.exit(1); - } - } - - // Ensure multiprovider accepts this metadata - getMultiProvider(chainToMetadata); - - logGreen(`All chain configs in ${filePath} are valid`); - return chainToMetadata; -} - -export function readChainConfigIfExists(filePath: string) { - if (!fs.existsSync(filePath)) { - log('No chain config file provided'); - return {}; - } else { - return readChainConfig(filePath); - } -} - -const DeploymentArtifactsSchema = z - .object({}) - .catchall(z.object({}).catchall(z.string())); - -export function readDeploymentArtifacts(filePath: string) { - const artifacts = readYamlOrJson>(filePath); - if (!artifacts) throw new Error(`No artifacts found at ${filePath}`); - const result = DeploymentArtifactsSchema.safeParse(artifacts); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`, - ); - } - return artifacts; -} - -const MultisigConfigMapSchema = z.object({}).catchall( - z.object({ - type: z.string(), - threshold: z.number(), - validators: z.array(z.string()), - }), -); -export type MultisigConfigMap = z.infer; - -export function readMultisigConfig(filePath: string) { - const config = readYamlOrJson(filePath); - if (!config) throw new Error(`No multisig config found at ${filePath}`); - const result = MultisigConfigMapSchema.safeParse(config); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid multisig config: ${firstIssue.path} => ${firstIssue.message}`, - ); - } - const parsedConfig = result.data; - const formattedConfig = objMap(parsedConfig, (_, config) => ({ - ...config, - type: humanReadableIsmTypeToEnum(config.type), - })); - - logGreen(`All multisig configs in ${filePath} are valid`); - return formattedConfig as ChainMap; -} - -export function isValidMultisigConfig(config: any) { - return MultisigConfigMapSchema.safeParse(config).success; -} - -function humanReadableIsmTypeToEnum(type: string): ModuleType { - for (const [key, value] of Object.entries(ModuleType)) { - if (key.toLowerCase() === type) return parseInt(value.toString(), 10); - } - throw new Error(`Invalid ISM type ${type}`); -} - -const ConnectionConfigSchema = { - mailbox: z.string().optional(), - interchainGasPaymaster: z.string().optional(), - interchainSecurityModule: z.string().optional(), - foreignDeployment: z.string().optional(), -}; - -export const WarpRouteConfigSchema = z.object({ - base: z.object({ - type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)), - chainName: z.string(), - address: z.string().optional(), - isNft: z.boolean().optional(), - name: z.string().optional(), - symbol: z.string().optional(), - decimals: z.number().optional(), - ...ConnectionConfigSchema, - }), - synthetics: z - .array( - z.object({ - chainName: z.string(), - name: z.string().optional(), - symbol: z.string().optional(), - totalSupply: z.number().optional(), - ...ConnectionConfigSchema, - }), - ) - .nonempty(), -}); - -type InferredType = z.infer; -// A workaround for Zod's terrible typing for nonEmpty arrays -export type WarpRouteConfig = { - base: InferredType['base']; - synthetics: Array; -}; - -export function readWarpRouteConfig(filePath: string) { - const config = readYamlOrJson(filePath); - if (!config) throw new Error(`No warp config found at ${filePath}`); - const result = WarpRouteConfigSchema.safeParse(config); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, - ); - } - return result.data; -} - -export function isValidWarpRouteConfig(config: any) { - return WarpRouteConfigSchema.safeParse(config).success; -} diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index 67fc0aa4d6..ba2f416df2 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -10,7 +10,7 @@ import { } from '@hyperlane-xyz/sdk'; import { objMerge } from '@hyperlane-xyz/utils'; -import { readChainConfigIfExists } from './configs.js'; +import { readChainConfigIfExists } from './config/chain.js'; import { keyToSigner } from './utils/keys.js'; export const sdkContractAddressesMap = { diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 8997fd8f32..b4230dcb7c 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -28,7 +28,8 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; -import { readDeploymentArtifacts, readMultisigConfig } from '../configs.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; import { getDeployerContext, diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 5fa206c2ea..74159e953a 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -21,11 +21,8 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, objMap } from '@hyperlane-xyz/utils'; -import { - WarpRouteConfig, - readDeploymentArtifacts, - readWarpRouteConfig, -} from '../configs.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { log, logBlue, logGray, logGreen } from '../logger.js'; diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index eca03ddb00..8a1589ee29 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -9,7 +9,7 @@ import { } from '@hyperlane-xyz/sdk'; import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; -import { readDeploymentArtifacts } from '../configs.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 14ce4072b2..d268781de9 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -14,7 +14,7 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, timeout } from '@hyperlane-xyz/utils'; -import { readDeploymentArtifacts } from '../configs.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index 9731482686..4c9e57466f 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -165,6 +165,21 @@ export class ChainMetadataManager { return getDomainId(metadata); } + /** + * Get the protocol type for a given chain name, chain id, or domain id + */ + tryGetProtocol(chainNameOrId: ChainName | number): ProtocolType | null { + return this.tryGetChainMetadata(chainNameOrId)?.protocol ?? null; + } + + /** + * Get the protocol type for a given chain name, chain id, or domain id + * @throws if chain's metadata or protocol has not been set + */ + getProtocol(chainNameOrId: ChainName | number): ProtocolType { + return this.getChainMetadata(chainNameOrId).protocol; + } + /** * Get the domain ids for a list of chain names, chain ids, or domain ids * @throws if any chain's metadata has not been set diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 525f26feb2..7135114bf9 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -17,7 +17,7 @@ export const ProtocolSmallestUnit = { export type Domain = number; export type Address = string; export type AddressBytes32 = string; -export type ChainCaip2Id = `${string}:${string}`; // e.g. ethereum:1 or solana:mainnet-beta +export type ChainCaip2Id = `${string}:${string}`; // e.g. ethereum:1 or sealevel:1399811149 export type TokenCaip19Id = `${string}:${string}/${string}:${string}`; // e.g. ethereum:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f export type HexString = string; From 7033a732c71d8caa05b165dbb458f9553f9ccbae Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 28 Sep 2023 16:08:37 -0400 Subject: [PATCH 25/82] Remove stableVersion fields from package.json files --- solidity/package.json | 3 +-- typescript/helloworld/package.json | 3 +-- typescript/sdk/package.json | 3 +-- typescript/token/package.json | 3 +-- typescript/utils/package.json | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index d3ef6faf88..5e2af2b0d8 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -56,6 +56,5 @@ "gas-ci": "yarn gas --check --tolerance 2 || (echo 'Manually update gas snapshot' && exit 1)", "slither": "slither ." }, - "types": "dist/index.d.ts", - "stableVersion": "1.4.2" + "types": "dist/index.d.ts" } diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index c436a5488b..ac1f6799b6 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -65,6 +65,5 @@ "lodash": "^4.17.21", "async": "^2.6.4", "undici": "^5.11" - }, - "stableVersion": "1.4.2" + } } diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index a52aeed990..70601961a8 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -57,6 +57,5 @@ "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts'", "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")" }, - "types": "dist/index.d.ts", - "stableVersion": "1.4.2" + "types": "dist/index.d.ts" } diff --git a/typescript/token/package.json b/typescript/token/package.json index d9a5fe8e58..5d8c6e9c59 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -62,6 +62,5 @@ "test": "hardhat test ./test/*.test.ts", "deploy-warp-route": "DEBUG=* ts-node scripts/deploy" }, - "types": "dist/index.d.ts", - "stableVersion": "1.4.2" + "types": "dist/index.d.ts" } diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 50bb2a427c..51ba971caf 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -32,6 +32,5 @@ "types": "dist/index.d.ts", "files": [ "/dist" - ], - "stableVersion": "1.4.2" + ] } From 4795ca5ad3b9a27faf3379aec51701503261d9b6 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 3 Oct 2023 16:23:07 -0400 Subject: [PATCH 26/82] Fix multisig casing and output file month number --- typescript/cli/src/deploy/core.ts | 12 ++++++------ typescript/cli/src/utils/time.ts | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index b4230dcb7c..ff14b218f7 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -65,7 +65,7 @@ export async function runCoreDeploy({ const artifacts = await runArtifactStep(selectedChains); const multisigConfig = await runIsmStep(selectedChains); - const deploymentParams = { + const deploymentParams: DeployParams = { local, remotes, signer, @@ -143,7 +143,7 @@ interface DeployParams { signer: ethers.Signer; multiProvider: MultiProvider; artifacts?: HyperlaneContractsMap; - multiSigConfig?: ChainMap; + multisigConfig?: ChainMap; outPath: string; } @@ -183,7 +183,7 @@ async function executeDeploy({ multiProvider, outPath, artifacts = {}, - multiSigConfig = {}, + multisigConfig = {}, }: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); @@ -214,7 +214,7 @@ async function executeDeploy({ owner, selectedChains, selectedChains, - multiSigConfig, + multisigConfig, ); const igpDeployer = new HyperlaneIgpDeployer(multiProvider); igpDeployer.cacheAddressesMap(artifacts); @@ -233,7 +233,7 @@ async function executeDeploy({ log(`Deploying core contracts to ${local}`); const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); coreDeployer.cacheAddressesMap(artifacts); - const coreConfig = buildCoreConfigMap(owner, local, remotes, multiSigConfig); + const coreConfig = buildCoreConfigMap(owner, local, remotes, multisigConfig); const coreContracts = await coreDeployer.deploy(coreConfig); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); logGreen(`Core contracts deployed`); @@ -244,7 +244,7 @@ async function executeDeploy({ owner, remotes, selectedChains, - multiSigConfig, + multisigConfig, ); const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { diff --git a/typescript/cli/src/utils/time.ts b/typescript/cli/src/utils/time.ts index 4793d8d35a..7c14f172c1 100644 --- a/typescript/cli/src/utils/time.ts +++ b/typescript/cli/src/utils/time.ts @@ -1,4 +1,6 @@ export function getTimestampForFilename() { const now = new Date(); - return `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}-${now.getHours()}-${now.getMinutes()}`; + return `${now.getFullYear()}-${ + now.getMonth() + 1 + }-${now.getDate()}-${now.getHours()}-${now.getMinutes()}`; } From 384652b8a735e88ff40d4498f8e4b9a684038445 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 3 Oct 2023 16:48:48 -0400 Subject: [PATCH 27/82] Upgrade packages to 1.5.3 --- solidity/package.json | 4 ++-- typescript/cli/package.json | 6 ++--- typescript/helloworld/package.json | 4 ++-- typescript/infra/package.json | 8 +++---- typescript/sdk/package.json | 6 ++--- typescript/token/package.json | 8 +++---- typescript/utils/package.json | 2 +- yarn.lock | 36 +++++++++++++++--------------- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index 5e2af2b0d8..4c11d91f13 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.5.2-beta0", + "version": "1.5.3", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.5.2-beta0", + "@hyperlane-xyz/utils": "1.5.3", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 99104e0be4..e65210e1d5 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.5.2-beta0", + "version": "1.5.3", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/hyperlane-token": "1.5.2-beta0", - "@hyperlane-xyz/sdk": "1.5.2-beta0", + "@hyperlane-xyz/hyperlane-token": "1.5.3", + "@hyperlane-xyz/sdk": "1.5.3", "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index ac1f6799b6..723982c951 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,9 +1,9 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.5.2-beta0", + "version": "1.5.3", "dependencies": { - "@hyperlane-xyz/sdk": "1.5.2-beta0", + "@hyperlane-xyz/sdk": "1.5.3", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index c55c4c8081..f0142b3185 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -11,10 +11,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.5.2-beta0", - "@hyperlane-xyz/hyperlane-token": "1.5.2-beta0", - "@hyperlane-xyz/sdk": "1.5.2-beta0", - "@hyperlane-xyz/utils": "1.5.2-beta0", + "@hyperlane-xyz/helloworld": "1.5.3", + "@hyperlane-xyz/hyperlane-token": "1.5.3", + "@hyperlane-xyz/sdk": "1.5.3", + "@hyperlane-xyz/utils": "1.5.3", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 70601961a8..2ab6159c31 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.5.2-beta0", + "version": "1.5.3", "dependencies": { - "@hyperlane-xyz/core": "1.5.2-beta0", - "@hyperlane-xyz/utils": "1.5.2-beta0", + "@hyperlane-xyz/core": "1.5.3", + "@hyperlane-xyz/utils": "1.5.3", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", diff --git a/typescript/token/package.json b/typescript/token/package.json index 5d8c6e9c59..45fe1d0139 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.5.2-beta0", + "version": "1.5.3", "dependencies": { - "@hyperlane-xyz/core": "1.5.2-beta0", - "@hyperlane-xyz/sdk": "1.5.2-beta0", - "@hyperlane-xyz/utils": "1.5.2-beta0", + "@hyperlane-xyz/core": "1.5.3", + "@hyperlane-xyz/sdk": "1.5.3", + "@hyperlane-xyz/utils": "1.5.3", "@openzeppelin/contracts-upgradeable": "^4.8.0", "@solana/spl-token": "^0.3.8", "ethers": "^5.7.2" diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 51ba971caf..d5e01c5d24 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "1.5.2-beta0", + "version": "1.5.3", "dependencies": { "@solana/web3.js": "^1.78.0", "bignumber.js": "^9.1.1", diff --git a/yarn.lock b/yarn.lock index 1160b6b809..b41b32b9ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3856,8 +3856,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/hyperlane-token": 1.5.2-beta0 - "@hyperlane-xyz/sdk": 1.5.2-beta0 + "@hyperlane-xyz/hyperlane-token": 1.5.3 + "@hyperlane-xyz/sdk": 1.5.3 "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 @@ -3877,12 +3877,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@1.5.2-beta0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@1.5.3, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.5.2-beta0 + "@hyperlane-xyz/utils": 1.5.3 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -3905,11 +3905,11 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.5.2-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@1.5.3, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/sdk": 1.5.2-beta0 + "@hyperlane-xyz/sdk": 1.5.3 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3937,13 +3937,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@1.5.2-beta0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.5.3, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: - "@hyperlane-xyz/core": 1.5.2-beta0 - "@hyperlane-xyz/sdk": 1.5.2-beta0 - "@hyperlane-xyz/utils": 1.5.2-beta0 + "@hyperlane-xyz/core": 1.5.3 + "@hyperlane-xyz/sdk": 1.5.3 + "@hyperlane-xyz/utils": 1.5.3 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3984,10 +3984,10 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.5.2-beta0 - "@hyperlane-xyz/hyperlane-token": 1.5.2-beta0 - "@hyperlane-xyz/sdk": 1.5.2-beta0 - "@hyperlane-xyz/utils": 1.5.2-beta0 + "@hyperlane-xyz/helloworld": 1.5.3 + "@hyperlane-xyz/hyperlane-token": 1.5.3 + "@hyperlane-xyz/sdk": 1.5.3 + "@hyperlane-xyz/utils": 1.5.3 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -4030,12 +4030,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.5.2-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@1.5.3, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: - "@hyperlane-xyz/core": 1.5.2-beta0 - "@hyperlane-xyz/utils": 1.5.2-beta0 + "@hyperlane-xyz/core": 1.5.3 + "@hyperlane-xyz/utils": 1.5.3 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/web3.js": ^1.78.0 @@ -4064,7 +4064,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.5.2-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@1.5.3, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 7ed8bc20a705559a943987a2c890f958d44ad200 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 5 Oct 2023 18:29:35 -0400 Subject: [PATCH 28/82] Add option to skip prompts for core deploy Update CLI CI test Rename local to origin throughout CLI --- .github/workflows/cli.yml | 35 +++--- typescript/cli/ci-test.sh | 150 ++++++++++++------------- typescript/cli/src/commands/deploy.ts | 35 +++++- typescript/cli/src/commands/options.ts | 11 +- typescript/cli/src/deploy/core.ts | 134 ++++++++++++---------- typescript/cli/src/deploy/utils.ts | 14 +-- typescript/cli/src/deploy/warp.ts | 18 +-- typescript/cli/src/send/message.ts | 2 +- typescript/cli/src/send/transfer.ts | 2 +- typescript/cli/src/utils/chains.ts | 17 ++- 10 files changed, 237 insertions(+), 181 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index b7db4f6662..a115c2d884 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -19,6 +19,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive + - name: yarn-cache uses: actions/cache@v3 with: @@ -26,6 +27,7 @@ jobs: **/node_modules .yarn/cache key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + - name: yarn-install run: | yarn install @@ -36,7 +38,7 @@ jobs: exit 1 fi - yarn-build: + build-and-test: runs-on: ubuntu-latest needs: [yarn-install] steps: @@ -46,6 +48,10 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 18 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + - name: yarn-cache uses: actions/cache@v3 with: @@ -53,24 +59,15 @@ jobs: **/node_modules .yarn/cache key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - name: build - run: yarn build - deploy: - needs: [build] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: yarn-cache + - name: build-cache uses: actions/cache@v3 with: - path: | - **/node_modules - .yarn/cache - key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - name: Install Foundry - uses: onbjerg/foundry-toolchain@v1 - with: - version: nightly - - name: deploy - run: ./ci-test.sh \ No newline at end of file + path: ./* + key: ${{ github.sha }} + + - name: build + run: yarn build + + - name: test + run: ./typescript/cli/ci-test.sh \ No newline at end of file diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index f4575dc4b4..a527d91401 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -17,84 +17,84 @@ sleep 1 set -e -yarn build - for i in "anvil1 anvil2" "anvil2 anvil1" do set -- $i echo "Deploying contracts to $1" - yarn hyperlane --local $1 --remotes $2 \ - --chains ./examples/anvil-chains.yaml \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 $3 \ -done - -echo "Deploying warp routes" -yarn hyperlane --local "anvil1" --remotes "anvil2" \ - --chains ./examples/anvil-chains.yaml \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - -kill $ANVIL_1_PID -kill $ANVIL_2_PID - -anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & -ANVIL_1_PID=$! - -anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & -ANVIL_2_PID=$! - -for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" -do - set -- $i - echo "Running validator on $1" - # Won't work on anything but linux due to -net=host - docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ - --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/config/agent_config.json -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ - -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ - -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ - -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ - -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ - -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ - -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ - -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./validator & -done - -sleep 10 - -for i in "anvil1 8545" "anvil2 8555" -do - set -- $i - echo "Announcing validator on $1" - VALIDATOR_ANNOUNCE_ADDRESS=$(cat ./artifacts/addresses.json | jq -r ".$1.validatorAnnounce") - VALIDATOR=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.validator') - STORAGE_LOCATION=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location') - SIGNATURE=$(cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature') - cast send $VALIDATOR_ANNOUNCE_ADDRESS \ - "announce(address, string calldata, bytes calldata)(bool)" \ - $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ - --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba -done - - -for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" -do - set -- $i - echo "Running relayer on $1" - docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ - --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/config/agent_config.json \ - -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ - -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ - -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ - -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ - -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ - -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ - -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./relayer & + yarn hyperlane deploy core \ + --chains ./examples/anvil-chains.yaml \ + --origin $1 --remotes $2 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + --yes done -docker ps -aq | xargs docker stop | xargs docker rm -kill $ANVIL_1_PID -kill $ANVIL_2_PID +# echo "Deploying warp routes" +# yarn hyperlane --local "anvil1" --remotes "anvil2" \ +# --chains ./examples/anvil-chains.yaml \ +# --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +# kill $ANVIL_1_PID +# kill $ANVIL_2_PID + +# anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & +# ANVIL_1_PID=$! + +# anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & +# ANVIL_2_PID=$! + +# for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" +# do +# set -- $i +# echo "Running validator on $1" +# # Won't work on anything but linux due to -net=host +# docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ +# --mount type=bind,source="/tmp",target=/data --net=host \ +# -e CONFIG_FILES=/config/agent_config.json -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ +# -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ +# -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ +# -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ +# -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ +# -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ +# -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ +# -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ +# gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./validator & +# done + +# sleep 10 + +# for i in "anvil1 8545" "anvil2 8555" +# do +# set -- $i +# echo "Announcing validator on $1" +# VALIDATOR_ANNOUNCE_ADDRESS=$(cat ./artifacts/addresses.json | jq -r ".$1.validatorAnnounce") +# VALIDATOR=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.validator') +# STORAGE_LOCATION=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location') +# SIGNATURE=$(cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature') +# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ +# "announce(address, string calldata, bytes calldata)(bool)" \ +# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ +# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +# done + + +# for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" +# do +# set -- $i +# echo "Running relayer on $1" +# docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ +# --mount type=bind,source="/tmp",target=/data --net=host \ +# -e CONFIG_FILES=/config/agent_config.json \ +# -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ +# -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ +# -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ +# -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ +# -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ +# -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ +# -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ +# -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ +# gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./relayer & +# done + +# docker ps -aq | xargs docker stop | xargs docker rm +# kill $ANVIL_1_PID +# kill $ANVIL_2_PID diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 5e6dee1403..56b232273c 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -9,6 +9,7 @@ import { coreArtifactsOption, keyCommandOption, outDirCommandOption, + skipConfirmationOption, } from './options.js'; /** @@ -37,6 +38,22 @@ const coreCommand: CommandModule = { key: keyCommandOption, chains: chainsCommandOption, out: outDirCommandOption, + artifacts: coreArtifactsOption, + ism: { + type: 'string', + description: + 'A path to a JSON or YAML file with ISM configs (e.g. Multisig)', + }, + origin: { + type: 'string', + description: 'Name of chain to which contracts will be deployed', + }, + remotes: { + type: 'string', + description: + 'Comma separated list of chain names to which origin will be connected', + }, + yes: skipConfirmationOption, }), handler: async (argv: any) => { logGray('Hyperlane permissionless core deployment'); @@ -44,7 +61,23 @@ const coreCommand: CommandModule = { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; const outPath: string = argv.out; - await runCoreDeploy({ key, chainConfigPath, outPath }); + const origin: string | undefined = argv.origin; + const remotes: string[] | undefined = argv.remotes + ? argv.remotes.split(',').map((r: string) => r.trim()) + : undefined; + const artifactsPath: string = argv.artifacts; + const ismConfigPath: string = argv.ism; + const skipConfirmation: boolean = argv.yes; + await runCoreDeploy({ + key, + chainConfigPath, + artifactsPath, + ismConfigPath, + outPath, + origin, + remotes, + skipConfirmation, + }); process.exit(0); }, }; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 96714ca14f..8b1aad3fa8 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -9,13 +9,13 @@ export const keyCommandOption: Options = { export const chainsCommandOption: Options = { type: 'string', - description: 'A path to a JSON or YAML file with chain configs.', + description: 'A path to a JSON or YAML file with chain configs', default: './configs/chain-config.yaml', }; export const outDirCommandOption: Options = { type: 'string', - description: 'A folder name output artifacts into.', + description: 'A folder name output artifacts into', default: './artifacts', }; @@ -38,3 +38,10 @@ export const outputFileOption = (defaultPath: string): Options => ({ description: 'Output file path', default: defaultPath, }); + +export const skipConfirmationOption: Options = { + type: 'boolean', + alias: 'y', + description: 'Skip confirmation prompts', + default: false, +}; diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index ff14b218f7..83a42a80fd 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -37,7 +37,7 @@ import { sdkContractAddressesMap, } from '../context.js'; import { log, logBlue, logGray, logGreen } from '../logger.js'; -import { runLocalAndRemotesSelectionStep } from '../utils/chains.js'; +import { runOriginAndRemotesSelectionStep } from '../utils/chains.js'; import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; import { @@ -49,30 +49,45 @@ import { runPreflightChecks } from './utils.js'; export async function runCoreDeploy({ key, chainConfigPath, + ismConfigPath, + artifactsPath, outPath, + origin, + remotes, + skipConfirmation, }: { key: string; chainConfigPath: string; + ismConfigPath: string; + artifactsPath?: string; outPath: string; + origin?: string; + remotes?: string[]; + skipConfirmation: boolean; }) { const { customChains, multiProvider, signer } = getDeployerContext( key, chainConfigPath, ); - const { local, remotes, selectedChains } = - await runLocalAndRemotesSelectionStep(customChains); - const artifacts = await runArtifactStep(selectedChains); - const multisigConfig = await runIsmStep(selectedChains); + if (!origin || !remotes?.length) { + ({ origin, remotes } = await runOriginAndRemotesSelectionStep( + customChains, + )); + } + const selectedChains = [origin, ...remotes]; + const artifacts = await runArtifactStep(selectedChains, artifactsPath); + const multisigConfig = await runIsmStep(selectedChains, ismConfigPath); const deploymentParams: DeployParams = { - local, + origin, remotes, signer, multiProvider, artifacts, multisigConfig, outPath, + skipConfirmation, }; await runDeployPlanStep(deploymentParams); @@ -83,19 +98,24 @@ export async function runCoreDeploy({ await executeDeploy(deploymentParams); } -async function runArtifactStep(selectedChains: ChainName[]) { - 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; - - const artifactsPath = await input({ - message: 'Enter filepath with existing contract artifacts (addresses)', - }); +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 input({ + message: 'Enter filepath with existing contract artifacts (addresses)', + }); + } const artifacts = readDeploymentArtifacts(artifactsPath); const artifactChains = Object.keys(artifacts).filter((c) => selectedChains.includes(c), @@ -104,32 +124,27 @@ async function runArtifactStep(selectedChains: ChainName[]) { return artifacts; } -async function runIsmStep(selectedChains: ChainName[]) { - logBlue( - '\n', - 'Hyperlane instances requires an Interchain Security Module (ISM).', - ); - const isMultisig = await confirm({ - message: 'Do you want use a Multisig ISM?', - }); - if (!isMultisig) - throw new Error( - 'Sorry, only multisig ISMs are currently supported in the CLI', - ); - +async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) { const defaultConfigChains = Object.keys(defaultMultisigIsmConfigs); const configRequired = !!selectedChains.find( (c) => !defaultConfigChains.includes(c), ); if (!configRequired) return; - logGray( - 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/multisig-ism.yaml', - ); - const multisigConfigPath = await input({ - message: 'Enter filepath for the multisig config', - }); - const configs = readMultisigConfig(multisigConfigPath); + if (!ismConfigPath) { + logBlue( + '\n', + 'Hyperlane instances requires an Interchain Security Module (ISM).', + ); + logGray( + 'Note, only Multisig ISM configs are currently supported in the CLI', + 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/multisig-ism.yaml', + ); + ismConfigPath = await input({ + message: 'Enter filepath for the multisig config', + }); + } + const configs = readMultisigConfig(ismConfigPath); const multisigConfigChains = Object.keys(configs).filter((c) => selectedChains.includes(c), ); @@ -138,38 +153,41 @@ async function runIsmStep(selectedChains: ChainName[]) { } interface DeployParams { - local: string; + origin: string; remotes: string[]; signer: ethers.Signer; multiProvider: MultiProvider; artifacts?: HyperlaneContractsMap; multisigConfig?: ChainMap; outPath: string; + skipConfirmation: boolean; } async function runDeployPlanStep({ - local, + origin, remotes, signer, artifacts, + skipConfirmation, }: DeployParams) { const address = await signer.getAddress(); logBlue('\n', 'Deployment plan:'); logGray('===============:'); log(`Transaction signer and owner of new contracts will be ${address}`); - log(`Deploying to ${local} and connecting it to ${remotes.join(', ')}`); + log(`Deploying to ${origin} and connecting it to ${remotes.join(', ')}`); const numContracts = Object.keys( Object.values(sdkContractAddressesMap)[0], ).length; log(`There are ${numContracts} contracts for each chain`); if (artifacts) log('But contracts with an address in the artifacts file will be skipped'); - for (const chain of [local, ...remotes]) { + for (const chain of [origin, ...remotes]) { const chainArtifacts = artifacts?.[chain] || {}; const numRequired = numContracts - Object.keys(chainArtifacts).length; log(`${chain} will require ${numRequired} of ${numContracts}`); } log('The interchain security module will be a Multisig.'); + if (skipConfirmation) return; const isConfirmed = await confirm({ message: 'Is this deployment plan correct?', }); @@ -177,7 +195,7 @@ async function runDeployPlanStep({ } async function executeDeploy({ - local, + origin, remotes, signer, multiProvider, @@ -193,7 +211,7 @@ async function executeDeploy({ ]); const owner = await signer.getAddress(); - const selectedChains = [local, ...remotes]; + const selectedChains = [origin, ...remotes]; const mergedContractAddrs = getMergedContractAddresses(artifacts); // 1. Deploy ISM factories to all deployable chains that don't have them. @@ -229,11 +247,11 @@ async function executeDeploy({ multiProvider, ); - // 3. Deploy core contracts to local chain - log(`Deploying core contracts to ${local}`); + // 3. Deploy core contracts to origin chain + log(`Deploying core contracts to ${origin}`); const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); coreDeployer.cacheAddressesMap(artifacts); - const coreConfig = buildCoreConfigMap(owner, local, remotes, multisigConfig); + const coreConfig = buildCoreConfigMap(owner, origin, remotes, multisigConfig); const coreContracts = await coreDeployer.deploy(coreConfig); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); logGreen(`Core contracts deployed`); @@ -282,7 +300,7 @@ async function executeDeploy({ await writeAgentConfig( agentFilePath, artifacts, - local, + origin, remotes, multiProvider, ); @@ -331,12 +349,12 @@ function buildIsmConfigMap( function buildCoreConfigMap( owner: Address, - local: ChainName, + origin: ChainName, remotes: ChainName[], multisigIsmConfigs: ChainMap, ): ChainMap { const configMap: ChainMap = {}; - configMap[local] = { + configMap[origin] = { owner, defaultIsm: buildIsmConfig(owner, remotes, multisigIsmConfigs), }; @@ -368,18 +386,18 @@ function buildIgpConfigMap( multisigIsmConfigs, ); const configMap: ChainMap = {}; - for (const local of deployChains) { + for (const origin of deployChains) { const overhead: ChainMap = {}; const gasOracleType: ChainMap = {}; for (const remote of selectedChains) { - if (local === remote) continue; + if (origin === remote) continue; overhead[remote] = multisigIsmVerificationCost( mergedMultisigIsmConfig[remote].threshold, mergedMultisigIsmConfig[remote].validators.length, ); gasOracleType[remote] = GasOracleContractType.StorageGasOracle; } - configMap[local] = { + configMap[origin] = { owner, beneficiary: owner, gasOracleType, @@ -404,13 +422,15 @@ function writeMergedAddresses( async function writeAgentConfig( filePath: string, artifacts: HyperlaneAddressesMap, - local: ChainName, + origin: ChainName, remotes: ChainName[], multiProvider: MultiProvider, ) { - const selectedChains = [local, ...remotes]; + const selectedChains = [origin, ...remotes]; const startBlocks: ChainMap = { ...agentStartBlocks }; - startBlocks[local] = await multiProvider.getProvider(local).getBlockNumber(); + startBlocks[origin] = await multiProvider + .getProvider(origin) + .getBlockNumber(); const mergedAddressesMap: HyperlaneAddressesMap = objMerge( sdkContractAddressesMap, diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 74ef840d54..011b642676 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -8,13 +8,13 @@ import { assertNativeBalances } from '../utils/balances.js'; import { assertSigner } from '../utils/keys.js'; export async function runPreflightChecks({ - local, + origin, remotes, signer, multiProvider, minBalanceWei, }: { - local: ChainName; + origin: ChainName; remotes: ChainName[]; signer: ethers.Signer; multiProvider: MultiProvider; @@ -22,10 +22,10 @@ export async function runPreflightChecks({ }) { log('Running pre-flight checks...'); - if (!local || !remotes?.length) throw new Error('Invalid chain selection'); - if (remotes.includes(local)) - throw new Error('Local and remotes must be distinct'); - for (const chain of [local, ...remotes]) { + if (!origin || !remotes?.length) throw new Error('Invalid chain selection'); + if (remotes.includes(origin)) + throw new Error('Origin and remotes must be distinct'); + for (const chain of [origin, ...remotes]) { const metadata = multiProvider.tryGetChainMetadata(chain); if (!metadata) throw new Error(`No chain config found for ${chain}`); if (metadata.protocol !== ProtocolType.Ethereum) @@ -39,7 +39,7 @@ export async function runPreflightChecks({ await assertNativeBalances( multiProvider, signer, - [local, ...remotes], + [origin, ...remotes], minBalanceWei, ); logGreen('Balances are sufficient ✅'); diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 74159e953a..7e2909e9ef 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -170,7 +170,7 @@ async function runBuildConfigStep({ return { configMap, metadata: baseMetadata, - local: baseChainName, + origin: baseChainName, remotes: synthetics.map(({ chainName }) => chainName), isNft: !!isNft, }; @@ -180,7 +180,7 @@ interface DeployParams { configMap: ChainMap; isNft: boolean; metadata: MinimalTokenMetadata; - local: ChainName; + origin: ChainName; remotes: ChainName[]; signer: ethers.Signer; multiProvider: MultiProvider; @@ -190,17 +190,17 @@ interface DeployParams { async function runDeployPlanStep({ configMap, isNft, - local, + origin, remotes, signer, }: DeployParams) { const address = await signer.getAddress(); - const baseToken = configMap[local]; + const baseToken = configMap[origin]; const baseName = getTokenName(baseToken); logBlue('\n', 'Deployment plan:'); logGray('===============:'); log(`Transaction signer and owner of new contracts will be ${address}`); - log(`Deploying a warp route with a base of ${baseName} token on ${local}`); + log(`Deploying a warp route with a base of ${baseName} token on ${origin}`); log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`); log(`Using token standard ${isNft ? 'ERC721' : 'ERC20'}`); @@ -299,18 +299,18 @@ function writeTokenDeploymentArtifacts( function writeWarpUiTokenConfig( filePath: string, contracts: HyperlaneContractsMap, - { configMap, isNft, metadata, local, multiProvider }: DeployParams, + { configMap, isNft, metadata, origin, multiProvider }: DeployParams, ) { - const baseConfig = configMap[local]; + const baseConfig = configMap[origin]; const hypTokenAddr = - contracts[local]?.router?.address || configMap[local]?.foreignDeployment; + contracts[origin]?.router?.address || configMap[origin]?.foreignDeployment; if (!hypTokenAddr) { throw Error( 'No base Hyperlane token address deployed and no foreign deployment specified', ); } const commonFields = { - chainId: multiProvider.getChainId(local), + chainId: multiProvider.getChainId(origin), name: metadata.name, symbol: metadata.symbol, decimals: metadata.decimals, diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 8a1589ee29..2b6a6892d1 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -40,7 +40,7 @@ export async function sendTestMessage({ : undefined; await runPreflightChecks({ - local: origin, + origin, remotes: [destination], multiProvider, signer, diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index d268781de9..510edda78a 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -57,7 +57,7 @@ export async function sendTestTransfer({ wei.toString(), ); await runPreflightChecks({ - local: origin, + origin, remotes: [destination], multiProvider, signer, diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 7da05cdb5c..c8b50fefd8 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -15,19 +15,18 @@ import { log, logBlue } from '../logger.js'; // a new chain in the list const NEW_CHAIN_MARKER = '__new__'; -export async function runLocalAndRemotesSelectionStep( +export async function runOriginAndRemotesSelectionStep( customChains: ChainMap, ) { - const local = await runSingleChainSelectionStep( + const origin = await runSingleChainSelectionStep( customChains, - 'Select local chain (the chain to which you will deploy now)', + 'Select origin chain (the chain to which you will deploy now)', ); const remotes = await runMultiChainSelectionStep( customChains, - 'Select remote chains the local will send messages to', + 'Select remote chains the origin will send messages to', ); - const selectedChains = [local, ...remotes]; - return { local, remotes, selectedChains }; + return { origin, remotes }; } export async function runSingleChainSelectionStep( @@ -35,13 +34,13 @@ export async function runSingleChainSelectionStep( message = 'Select chain', ) { const choices = getChainChoices(customChains); - const local = (await select({ + const origin = (await select({ message, choices, pageSize: 20, })) as string; - handleNewChain([local]); - return local; + handleNewChain([origin]); + return origin; } export async function runMultiChainSelectionStep( From 7b86a4a024504261266b53dcdb879c59d6d2ea6a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 5 Oct 2023 18:59:04 -0400 Subject: [PATCH 29/82] Move CLI CI into node.yml --- .github/workflows/cli.yml | 73 -------------------------------------- .github/workflows/node.yml | 19 ++++++++++ typescript/cli/ci-test.sh | 2 +- 3 files changed, 20 insertions(+), 74 deletions(-) delete mode 100644 .github/workflows/cli.yml diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml deleted file mode 100644 index a115c2d884..0000000000 --- a/.github/workflows/cli.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: CLI - -on: - push: - branches: [main] - pull_request: - branches: [main] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -env: - DEBUG: 'hyperlane:*' - -jobs: - yarn-install: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: yarn-cache - uses: actions/cache@v3 - with: - path: | - **/node_modules - .yarn/cache - key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - - name: yarn-install - run: | - yarn install - CHANGES=$(git status -s --ignore-submodules) - if [[ ! -z $CHANGES ]]; then - echo "Changes found: $CHANGES" - git diff - exit 1 - fi - - build-and-test: - runs-on: ubuntu-latest - needs: [yarn-install] - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: actions/setup-node@v3 - with: - node-version: 18 - - - name: Install Foundry - uses: onbjerg/foundry-toolchain@v1 - - - name: yarn-cache - uses: actions/cache@v3 - with: - path: | - **/node_modules - .yarn/cache - key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - - name: build-cache - uses: actions/cache@v3 - with: - path: ./* - key: ${{ github.sha }} - - - name: build - run: yarn build - - - name: test - run: ./typescript/cli/ci-test.sh \ No newline at end of file diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 2bb0aaa208..744fad90d3 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -122,6 +122,25 @@ jobs: - name: infra run: yarn workspace @hyperlane-xyz/infra run test + test-cli: + runs-on: ubuntu-latest + needs: [yarn-build] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + + - uses: actions/cache@v3 + with: + path: ./* + key: ${{ github.sha }} + + - name: test + run: ./typescript/cli/ci-test.sh + test-env: runs-on: ubuntu-latest needs: [yarn-build] diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index a527d91401..71221e10b0 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -21,7 +21,7 @@ for i in "anvil1 anvil2" "anvil2 anvil1" do set -- $i echo "Deploying contracts to $1" - yarn hyperlane deploy core \ + node ./dist/cli.js deploy core \ --chains ./examples/anvil-chains.yaml \ --origin $1 --remotes $2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 From eed11f56c3b616f99a99e022049c9a0017706a7b Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 6 Oct 2023 11:01:01 -0400 Subject: [PATCH 30/82] Fix yarn command in ci-test --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 71221e10b0..a6d313902c 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -21,7 +21,7 @@ for i in "anvil1 anvil2" "anvil2 anvil1" do set -- $i echo "Deploying contracts to $1" - node ./dist/cli.js deploy core \ + yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ --origin $1 --remotes $2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 From b592245d3b73de9211fc43b629ca456de581c0a3 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 6 Oct 2023 11:30:56 -0400 Subject: [PATCH 31/82] Set artifacts path in deploy command --- typescript/cli/ci-test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index a6d313902c..7cb1ed8d97 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -23,6 +23,7 @@ do echo "Deploying contracts to $1" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ + --artifacts /examples/contract-artifacts.yaml \ --origin $1 --remotes $2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --yes From 8e44f62072176268f3c643bf51f0f5b67ceb3cce Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 6 Oct 2023 17:51:38 -0400 Subject: [PATCH 32/82] Derp, fix artifact file path --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 7cb1ed8d97..3cadf831d5 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -23,7 +23,7 @@ do echo "Deploying contracts to $1" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ - --artifacts /examples/contract-artifacts.yaml \ + --artifacts ./examples/contract-artifacts.yaml \ --origin $1 --remotes $2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --yes From f9cffa3c09e513cc1423a66d1c2a78787960e5e3 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 6 Oct 2023 18:25:44 -0400 Subject: [PATCH 33/82] Use example multisig config for ci test --- typescript/cli/ci-test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 3cadf831d5..49a9274628 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -24,6 +24,7 @@ do yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ --artifacts ./examples/contract-artifacts.yaml \ + --ism ./examples/multisig-ism.yaml \ --origin $1 --remotes $2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --yes From 42de37c4a97d0e36f75ad6f3894fc24bf5031d25 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 6 Oct 2023 18:30:46 -0400 Subject: [PATCH 34/82] Fix missing yes param --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 49a9274628..a9cb1df0b1 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -26,7 +26,7 @@ do --artifacts ./examples/contract-artifacts.yaml \ --ism ./examples/multisig-ism.yaml \ --origin $1 --remotes $2 \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes done From ccad5265575b65b3b2c4ded152d1e1130fbf9c3a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 7 Oct 2023 10:45:59 -0400 Subject: [PATCH 35/82] More fixes for ci-test --- typescript/cli/.gitignore | 3 +- typescript/cli/ci-test.sh | 36 ++++++++++++------- typescript/cli/empty-artifacts.json | 1 + .../cli/examples/contract-artifacts.yaml | 2 +- typescript/cli/src/deploy/core.ts | 4 +-- typescript/cli/src/utils/time.ts | 2 +- 6 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 typescript/cli/empty-artifacts.json diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore index df29ed2d70..21c42b8ba6 100644 --- a/typescript/cli/.gitignore +++ b/typescript/cli/.gitignore @@ -2,4 +2,5 @@ /dist /cache /configs -/artifacts \ No newline at end of file +/artifacts +empty-artifacts.yaml \ No newline at end of file diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index a9cb1df0b1..0e80c51d69 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -17,18 +17,30 @@ sleep 1 set -e -for i in "anvil1 anvil2" "anvil2 anvil1" -do - set -- $i - echo "Deploying contracts to $1" - yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ - --chains ./examples/anvil-chains.yaml \ - --artifacts ./examples/contract-artifacts.yaml \ - --ism ./examples/multisig-ism.yaml \ - --origin $1 --remotes $2 \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - --yes -done +echo "{}" > ./empty-artifacts.json + +echo "Deploying contracts to anvil1" +yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + --chains ./examples/anvil-chains.yaml \ + --artifacts ./empty-artifacts.yaml \ + --ism ./examples/multisig-ism.yaml \ + --origin anvil1 --remotes anvil2 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --yes + +ARTIFACT_FILE=`find ./artifacts/core-deployment* -type f -exec ls -t1 {} + | head -1` + +echo "Deploying contracts to anvil2" +yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + --chains ./examples/anvil-chains.yaml \ + --artifacts $ARTIFACT_FILE \ + --ism ./examples/multisig-ism.yaml \ + --origin anvil2 --remotes anvil1 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --yes + +kill $ANVIL_1_PID +kill $ANVIL_2_PID # echo "Deploying warp routes" # yarn hyperlane --local "anvil1" --remotes "anvil2" \ diff --git a/typescript/cli/empty-artifacts.json b/typescript/cli/empty-artifacts.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/typescript/cli/empty-artifacts.json @@ -0,0 +1 @@ +{} diff --git a/typescript/cli/examples/contract-artifacts.yaml b/typescript/cli/examples/contract-artifacts.yaml index 4bf398d549..04f690045c 100644 --- a/typescript/cli/examples/contract-artifacts.yaml +++ b/typescript/cli/examples/contract-artifacts.yaml @@ -2,7 +2,7 @@ # Typically not written by hand but generated as JSON by the deploy command # Consists of a map of chain names to contract names to addresses --- -mychainname: +anvil1: storageGasOracle: '0xD9A9966E7dA9a7f0032bF449FB12696a638E673C' validatorAnnounce: '0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524' proxyAdmin: '0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9' diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 83a42a80fd..8371900b0a 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -171,7 +171,7 @@ async function runDeployPlanStep({ skipConfirmation, }: DeployParams) { const address = await signer.getAddress(); - logBlue('\n', 'Deployment plan:'); + logBlue('\nDeployment plan:'); logGray('===============:'); log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying to ${origin} and connecting it to ${remotes.join(', ')}`); @@ -446,7 +446,7 @@ async function writeAgentConfig( ) as ChainMap; const agentConfig = buildAgentConfig( - selectedChains, + [origin], multiProvider, filteredAddressesMap, startBlocks, diff --git a/typescript/cli/src/utils/time.ts b/typescript/cli/src/utils/time.ts index 7c14f172c1..a490684cc3 100644 --- a/typescript/cli/src/utils/time.ts +++ b/typescript/cli/src/utils/time.ts @@ -2,5 +2,5 @@ export function getTimestampForFilename() { const now = new Date(); return `${now.getFullYear()}-${ now.getMonth() + 1 - }-${now.getDate()}-${now.getHours()}-${now.getMinutes()}`; + }-${now.getDate()}-${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`; } From 74715300a5c6a203dc867a25b12acaf2bc55c6d7 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 7 Oct 2023 11:07:41 -0400 Subject: [PATCH 36/82] Fix Ism cofigs and TestRecipient ISM --- typescript/cli/src/deploy/core.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 8371900b0a..db138cc527 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -260,8 +260,8 @@ async function executeDeploy({ log(`Deploying ISMs`); const ismConfigs = buildIsmConfigMap( owner, - remotes, selectedChains, + remotes, multisigConfig, ); const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; @@ -368,6 +368,8 @@ function buildTestRecipientConfigMap( return Object.fromEntries( chains.map((chain) => { const interchainSecurityModule = + // TODO revisit assumption that multisigIsm is always the ISM + addressesMap[chain].multisigIsm ?? addressesMap[chain].interchainSecurityModule ?? ethers.constants.AddressZero; return [chain, { interchainSecurityModule }]; From 528b5548ede541e5bba8a87532653a669b1ab5ce Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 7 Oct 2023 11:13:42 -0400 Subject: [PATCH 37/82] Remove empty-artifacts file from git --- typescript/cli/.gitignore | 3 +-- typescript/cli/ci-test.sh | 2 +- typescript/cli/empty-artifacts.json | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 typescript/cli/empty-artifacts.json diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore index 21c42b8ba6..df29ed2d70 100644 --- a/typescript/cli/.gitignore +++ b/typescript/cli/.gitignore @@ -2,5 +2,4 @@ /dist /cache /configs -/artifacts -empty-artifacts.yaml \ No newline at end of file +/artifacts \ No newline at end of file diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 0e80c51d69..6fddaed53c 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -22,7 +22,7 @@ echo "{}" > ./empty-artifacts.json echo "Deploying contracts to anvil1" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ - --artifacts ./empty-artifacts.yaml \ + --artifacts ./empty-artifacts.json \ --ism ./examples/multisig-ism.yaml \ --origin anvil1 --remotes anvil2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ diff --git a/typescript/cli/empty-artifacts.json b/typescript/cli/empty-artifacts.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/typescript/cli/empty-artifacts.json +++ /dev/null @@ -1 +0,0 @@ -{} From 6692342482b02da5c4aa7a0994a3efc18c0d90aa Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 7 Oct 2023 11:19:49 -0400 Subject: [PATCH 38/82] Attempt to use tmp for empty-artifacts --- typescript/cli/ci-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 6fddaed53c..e7e15263fa 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -17,12 +17,12 @@ sleep 1 set -e -echo "{}" > ./empty-artifacts.json +echo "{}" > /tmp/empty-artifacts.json echo "Deploying contracts to anvil1" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ - --artifacts ./empty-artifacts.json \ + --artifacts /tmp/empty-artifacts.json \ --ism ./examples/multisig-ism.yaml \ --origin anvil1 --remotes anvil2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ From 3033188290c94583a44603ed90e473e184ec8c33 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 7 Oct 2023 11:25:54 -0400 Subject: [PATCH 39/82] Also use tmp for artifacts out --- typescript/cli/ci-test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index e7e15263fa..1bce2cf3ac 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -23,17 +23,19 @@ echo "Deploying contracts to anvil1" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ --artifacts /tmp/empty-artifacts.json \ + --out /tmp \ --ism ./examples/multisig-ism.yaml \ --origin anvil1 --remotes anvil2 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -ARTIFACT_FILE=`find ./artifacts/core-deployment* -type f -exec ls -t1 {} + | head -1` +ARTIFACT_FILE=`find /tmp/artifacts/core-deployment* -type f -exec ls -t1 {} + | head -1` echo "Deploying contracts to anvil2" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ --artifacts $ARTIFACT_FILE \ + --out /tmp \ --ism ./examples/multisig-ism.yaml \ --origin anvil2 --remotes anvil1 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ From 176905343c2de699cb33be6b00ca35fa7c50a205 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 7 Oct 2023 11:32:13 -0400 Subject: [PATCH 40/82] Derp, path again --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 1bce2cf3ac..e7802f127a 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -29,7 +29,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -ARTIFACT_FILE=`find /tmp/artifacts/core-deployment* -type f -exec ls -t1 {} + | head -1` +ARTIFACT_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` echo "Deploying contracts to anvil2" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ From 1d9a994df2f75dfcf02cc82a9cc01579c8a48a6c Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 8 Oct 2023 15:26:53 -0400 Subject: [PATCH 41/82] Hack workaround for bigint console warn Accept keys with or without 0x --- typescript/cli/cli.ts | 4 ++-- typescript/cli/{src => }/logger.ts | 15 +++++++++++++++ typescript/cli/src/commands/chains.ts | 2 +- typescript/cli/src/commands/config.ts | 2 +- typescript/cli/src/commands/deploy.ts | 2 +- typescript/cli/src/commands/send.ts | 2 +- typescript/cli/src/config/chain.ts | 2 +- typescript/cli/src/config/multisig.ts | 2 +- typescript/cli/src/config/warp.ts | 2 +- typescript/cli/src/deploy/core.ts | 2 +- typescript/cli/src/deploy/utils.ts | 2 +- typescript/cli/src/deploy/warp.ts | 2 +- typescript/cli/src/send/message.ts | 2 +- typescript/cli/src/send/transfer.ts | 2 +- typescript/cli/src/utils/chains.ts | 2 +- typescript/cli/src/utils/files.ts | 2 +- typescript/cli/src/utils/keys.ts | 6 ++++-- typescript/cli/tsconfig.json | 2 +- 18 files changed, 36 insertions(+), 19 deletions(-) rename typescript/cli/{src => }/logger.ts (69%) diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 90a585bbfd..f1bcdfc7d2 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -2,13 +2,13 @@ import chalk from 'chalk'; import yargs from 'yargs'; +import { errorRed } from './logger.js'; import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { deployCommand } from './src/commands/deploy.js'; import { sendCommand } from './src/commands/send.js'; -import './src/logger.js'; -import { errorRed } from './src/logger.js'; +// From yargs code: const MISSING_PARAMS_ERROR = 'Not enough non-option arguments'; console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); diff --git a/typescript/cli/src/logger.ts b/typescript/cli/logger.ts similarity index 69% rename from typescript/cli/src/logger.ts rename to typescript/cli/logger.ts index 0fcce4c9db..e142534db2 100644 --- a/typescript/cli/src/logger.ts +++ b/typescript/cli/logger.ts @@ -1,6 +1,21 @@ +// This file isn't in the src dir so it it's imported before others after import sort +// See bigint hack below and https://github.com/trivago/prettier-plugin-sort-imports/issues/112 import chalk from 'chalk'; import debug from 'debug'; +// Workaround for bug in bigint-buffer which solana-web3.js depends on +// https://github.com/no2chem/bigint-buffer/issues/31#issuecomment-1752134062 +const defaultWarn = console.warn; +console.warn = (...args: any) => { + if ( + args && + typeof args[0] === 'string' && + args[0]?.includes('bigint: Failed to load bindings') + ) + return; + defaultWarn(...args); +}; + const HYPERLANE_NS = 'hyperlane'; // Default root logger for use in utils/scripts diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts index a8039ecce4..8565a6d20d 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/chains.ts @@ -9,7 +9,7 @@ import { hyperlaneContractAddresses, } from '@hyperlane-xyz/sdk'; -import { log, logBlue, logGray } from '../logger.js'; +import { log, logBlue, logGray } from '../../logger.js'; /** * Parent command diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index da25fceab1..7e9a76ffbc 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -1,12 +1,12 @@ import { CommandModule } from 'yargs'; +import { log, logGreen } from '../../logger.js'; import { createChainConfig, readChainConfig } from '../config/chain.js'; import { createMultisigConfig, readMultisigConfig, } from '../config/multisig.js'; import { createWarpConfig, readWarpRouteConfig } from '../config/warp.js'; -import { log, logGreen } from '../logger.js'; import { FileFormat } from '../utils/files.js'; import { diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 56b232273c..424782efc8 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -1,8 +1,8 @@ import { CommandModule } from 'yargs'; +import { log, logGray } from '../../logger.js'; import { runCoreDeploy } from '../deploy/core.js'; import { runWarpDeploy } from '../deploy/warp.js'; -import { log, logGray } from '../logger.js'; import { chainsCommandOption, diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 08ad57e916..4a350c88aa 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -1,6 +1,6 @@ import { CommandModule, Options } from 'yargs'; -import { log } from '../logger.js'; +import { log } from '../../logger.js'; import { sendTestMessage } from '../send/message.js'; import { sendTestTransfer } from '../send/transfer.js'; diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index fc887196b4..e8621e6086 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -8,8 +8,8 @@ import { } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; import { getMultiProvider } from '../context.js'; -import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; export function readChainConfig(filePath: string) { diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index 7fe28998aa..9f71bbc3f0 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; import { ChainMap, ModuleType, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; import { objMap } from '@hyperlane-xyz/utils'; -import { errorRed, log, logBlue, logGreen } from '../logger.js'; +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 6c1a58ec5a..744c0dc215 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; import { TokenType } from '@hyperlane-xyz/hyperlane-token'; -import { errorRed, logBlue, logGreen } from '../logger.js'; +import { errorRed, logBlue, logGreen } from '../../logger.js'; import { runMultiChainSelectionStep, runSingleChainSelectionStep, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index db138cc527..8466bd03ce 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -28,6 +28,7 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; +import { log, logBlue, logGray, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; @@ -36,7 +37,6 @@ import { getMergedContractAddresses, sdkContractAddressesMap, } from '../context.js'; -import { log, logBlue, logGray, logGreen } from '../logger.js'; import { runOriginAndRemotesSelectionStep } from '../utils/chains.js'; import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 011b642676..ac5b565855 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -3,7 +3,7 @@ import { ethers } from 'ethers'; import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { log, logGreen } from '../logger.js'; +import { log, logGreen } from '../../logger.js'; import { assertNativeBalances } from '../utils/balances.js'; import { assertSigner } from '../utils/keys.js'; diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 7e2909e9ef..323d1ce26a 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -21,11 +21,11 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, objMap } from '@hyperlane-xyz/utils'; +import { log, logBlue, logGray, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; -import { log, logBlue, logGray, logGreen } from '../logger.js'; import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 2b6a6892d1..8bee3a18fc 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -9,11 +9,11 @@ import { } from '@hyperlane-xyz/sdk'; import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; -import { errorRed, log, logBlue, logGreen } from '../logger.js'; const GAS_AMOUNT = 300_000; diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 510edda78a..113bcb77c5 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -14,11 +14,11 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, timeout } from '@hyperlane-xyz/utils'; +import { logBlue, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; -import { logBlue, logGreen } from '../logger.js'; import { assertTokenBalance } from '../utils/balances.js'; // TODO improve the UX here by making params optional and diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index c8b50fefd8..50143bb63c 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -9,7 +9,7 @@ import { testnetChainsMetadata, } from '@hyperlane-xyz/sdk'; -import { log, logBlue } from '../logger.js'; +import { log, logBlue } from '../../logger.js'; // A special value marker to indicate user selected // a new chain in the list diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index de73f3c5ca..d694d9d049 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -4,7 +4,7 @@ import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; import { objMerge } from '@hyperlane-xyz/utils'; -import { logBlue } from '../logger.js'; +import { logBlue } from '../../logger.js'; import { getTimestampForFilename } from './time.js'; diff --git a/typescript/cli/src/utils/keys.ts b/typescript/cli/src/utils/keys.ts index 22a6e49e48..39d01b1c13 100644 --- a/typescript/cli/src/utils/keys.ts +++ b/typescript/cli/src/utils/keys.ts @@ -1,10 +1,12 @@ import { ethers } from 'ethers'; +import { ensure0x } from '@hyperlane-xyz/utils'; + export function keyToSigner(key: string) { if (!key) throw new Error('No key provided'); const formattedKey = key.trim().toLowerCase(); - if (ethers.utils.isHexString(formattedKey)) - return new ethers.Wallet(formattedKey); + if (ethers.utils.isHexString(ensure0x(formattedKey))) + return new ethers.Wallet(ensure0x(formattedKey)); else if (formattedKey.split(' ').length >= 6) return ethers.Wallet.fromMnemonic(formattedKey); else throw new Error('Invalid key format'); diff --git a/typescript/cli/tsconfig.json b/typescript/cli/tsconfig.json index d9af26bff1..cfeac3badc 100644 --- a/typescript/cli/tsconfig.json +++ b/typescript/cli/tsconfig.json @@ -6,5 +6,5 @@ "outDir": "./dist/", "rootDir": "." }, - "include": ["./cli.ts", "./src/**/*.ts", "./src/*.d.ts", "./examples/**/*.ts",], + "include": ["./cli.ts", "./logger.ts", "./src/**/*.ts", "./src/*.d.ts", "./examples/**/*.ts"], } From 7f4fdeabb583ed451b8be382a828680b0062803e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 8 Oct 2023 16:12:21 -0400 Subject: [PATCH 42/82] Add filename select option for artifact and ism steps --- typescript/cli/src/deploy/core.ts | 26 +++++++++++++++++--------- typescript/cli/src/utils/files.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 8466bd03ce..501a5920fd 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -1,4 +1,4 @@ -import { confirm, input } from '@inquirer/prompts'; +import { confirm } from '@inquirer/prompts'; import { ethers } from 'ethers'; import { @@ -38,7 +38,11 @@ import { sdkContractAddressesMap, } from '../context.js'; import { runOriginAndRemotesSelectionStep } from '../utils/chains.js'; -import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; +import { + prepNewArtifactsFiles, + runFileSelectionStep, + writeJson, +} from '../utils/files.js'; import { TestRecipientConfig, @@ -112,9 +116,11 @@ async function runArtifactStep( }); if (!isResume) return undefined; - artifactsPath = await input({ - message: 'Enter filepath with existing contract artifacts (addresses)', - }); + artifactsPath = await runFileSelectionStep( + './artifacts', + 'contract artifacts', + 'core-deployment', + ); } const artifacts = readDeploymentArtifacts(artifactsPath); const artifactChains = Object.keys(artifacts).filter((c) => @@ -140,9 +146,11 @@ async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) { 'Note, only Multisig ISM configs are currently supported in the CLI', 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/multisig-ism.yaml', ); - ismConfigPath = await input({ - message: 'Enter filepath for the multisig config', - }); + ismConfigPath = await runFileSelectionStep( + './configs', + 'ISM config', + 'ism', + ); } const configs = readMultisigConfig(ismConfigPath); const multisigConfigChains = Object.keys(configs).filter((c) => @@ -186,7 +194,7 @@ async function runDeployPlanStep({ const numRequired = numContracts - Object.keys(chainArtifacts).length; log(`${chain} will require ${numRequired} of ${numContracts}`); } - log('The interchain security module will be a Multisig.'); + log('The default interchain security module will be a Multisig.'); if (skipConfirmation) return; const isConfirmed = await confirm({ message: 'Is this deployment plan correct?', diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index d694d9d049..7d57dec276 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -1,3 +1,5 @@ +import { input } from '@inquirer/prompts'; +import select from '@inquirer/select'; import fs from 'fs'; import path from 'path'; import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; @@ -145,3 +147,32 @@ export function prepNewArtifactsFiles( } return newPaths; } + +export async function runFileSelectionStep( + folderPath: string, + description: string, + pattern?: string, +) { + let filenames = fs.readdirSync(folderPath); + if (pattern) { + filenames = filenames.filter((f) => f.includes(pattern)); + } + + let filename = (await select({ + message: `Select ${description} file`, + choices: [ + ...filenames.map((f) => ({ name: f, value: f })), + { name: '(Other file)', value: null }, + ], + pageSize: 20, + })) as string; + + if (filename) return path.join(folderPath, filename); + + filename = await input({ + message: `Enter ${description} filepath`, + }); + + if (filename) return filename; + else throw new Error(`No filepath entered ${description}`); +} From 7fe4301692ff41a32f3c3d94e475a58272a68d92 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 8 Oct 2023 21:39:38 -0400 Subject: [PATCH 43/82] Bump versions ot 1.5.4-beta0 --- solidity/package.json | 7 +++--- typescript/cli/package.json | 9 ++++---- typescript/helloworld/package.json | 7 +++--- typescript/infra/package.json | 8 +++---- typescript/sdk/package.json | 9 ++++---- typescript/token/package.json | 11 ++++----- typescript/utils/package.json | 5 +++-- yarn.lock | 36 +++++++++++++++--------------- 8 files changed, 49 insertions(+), 43 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index 4c11d91f13..9dd5bc2800 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.5.3", + "version": "1.5.4-beta0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.5.3", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, @@ -56,5 +56,6 @@ "gas-ci": "yarn gas --check --tolerance 2 || (echo 'Manually update gas snapshot' && exit 1)", "slither": "slither ." }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.5.3" } diff --git a/typescript/cli/package.json b/typescript/cli/package.json index e65210e1d5..415d83c875 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "1.5.3", + "version": "1.5.4-beta0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/hyperlane-token": "1.5.3", - "@hyperlane-xyz/sdk": "1.5.3", + "@hyperlane-xyz/hyperlane-token": "1.5.4-beta0", + "@hyperlane-xyz/sdk": "1.5.4-beta0", "@inquirer/prompts": "^3.0.0", "chalk": "^5.3.0", "ethers": "^5.7.2", @@ -53,5 +53,6 @@ "Deployment", "Typescript" ], - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "stableVersion": "1.5.3" } diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 723982c951..9bcbed5ec6 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,9 +1,9 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.5.3", + "version": "1.5.4-beta0", "dependencies": { - "@hyperlane-xyz/sdk": "1.5.3", + "@hyperlane-xyz/sdk": "1.5.4-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, @@ -65,5 +65,6 @@ "lodash": "^4.17.21", "async": "^2.6.4", "undici": "^5.11" - } + }, + "stableVersion": "1.5.3" } diff --git a/typescript/infra/package.json b/typescript/infra/package.json index f0142b3185..a03b8af044 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -11,10 +11,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.5.3", - "@hyperlane-xyz/hyperlane-token": "1.5.3", - "@hyperlane-xyz/sdk": "1.5.3", - "@hyperlane-xyz/utils": "1.5.3", + "@hyperlane-xyz/helloworld": "1.5.4-beta0", + "@hyperlane-xyz/hyperlane-token": "1.5.4-beta0", + "@hyperlane-xyz/sdk": "1.5.4-beta0", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 2ab6159c31..c2bca6b4d3 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.5.3", + "version": "1.5.4-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.3", - "@hyperlane-xyz/utils": "1.5.3", + "@hyperlane-xyz/core": "1.5.4-beta0", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", @@ -57,5 +57,6 @@ "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts'", "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.5.3" } diff --git a/typescript/token/package.json b/typescript/token/package.json index 45fe1d0139..092ffcca50 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.5.3", + "version": "1.5.4-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.3", - "@hyperlane-xyz/sdk": "1.5.3", - "@hyperlane-xyz/utils": "1.5.3", + "@hyperlane-xyz/core": "1.5.4-beta0", + "@hyperlane-xyz/sdk": "1.5.4-beta0", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "@solana/spl-token": "^0.3.8", "ethers": "^5.7.2" @@ -62,5 +62,6 @@ "test": "hardhat test ./test/*.test.ts", "deploy-warp-route": "DEBUG=* ts-node scripts/deploy" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.5.3" } diff --git a/typescript/utils/package.json b/typescript/utils/package.json index d5e01c5d24..baaa9ec843 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "1.5.3", + "version": "1.5.4-beta0", "dependencies": { "@solana/web3.js": "^1.78.0", "bignumber.js": "^9.1.1", @@ -32,5 +32,6 @@ "types": "dist/index.d.ts", "files": [ "/dist" - ] + ], + "stableVersion": "1.5.3" } diff --git a/yarn.lock b/yarn.lock index b41b32b9ea..69f772de4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3856,8 +3856,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/hyperlane-token": 1.5.3 - "@hyperlane-xyz/sdk": 1.5.3 + "@hyperlane-xyz/hyperlane-token": 1.5.4-beta0 + "@hyperlane-xyz/sdk": 1.5.4-beta0 "@inquirer/prompts": ^3.0.0 "@types/node": ^18.14.5 "@types/yargs": ^17.0.24 @@ -3877,12 +3877,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@1.5.3, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@1.5.4-beta0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.5.3 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -3905,11 +3905,11 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.5.3, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@1.5.4-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/sdk": 1.5.3 + "@hyperlane-xyz/sdk": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3937,13 +3937,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@1.5.3, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.5.4-beta0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: - "@hyperlane-xyz/core": 1.5.3 - "@hyperlane-xyz/sdk": 1.5.3 - "@hyperlane-xyz/utils": 1.5.3 + "@hyperlane-xyz/core": 1.5.4-beta0 + "@hyperlane-xyz/sdk": 1.5.4-beta0 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -3984,10 +3984,10 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.5.3 - "@hyperlane-xyz/hyperlane-token": 1.5.3 - "@hyperlane-xyz/sdk": 1.5.3 - "@hyperlane-xyz/utils": 1.5.3 + "@hyperlane-xyz/helloworld": 1.5.4-beta0 + "@hyperlane-xyz/hyperlane-token": 1.5.4-beta0 + "@hyperlane-xyz/sdk": 1.5.4-beta0 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -4030,12 +4030,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.5.3, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@1.5.4-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: - "@hyperlane-xyz/core": 1.5.3 - "@hyperlane-xyz/utils": 1.5.3 + "@hyperlane-xyz/core": 1.5.4-beta0 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/web3.js": ^1.78.0 @@ -4064,7 +4064,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.5.3, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@1.5.4-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From ba8761d0d964ed854ae362ceddf3f06e5290634e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 8 Oct 2023 21:51:05 -0400 Subject: [PATCH 44/82] Add CI test coverage for warp deploy --- typescript/cli/ci-test.sh | 19 +++++++++------- typescript/cli/examples/anvil-chains.yaml | 19 ++++++++-------- typescript/cli/src/commands/deploy.ts | 8 ++++--- typescript/cli/src/deploy/warp.ts | 27 +++++++++++++++++++---- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index e7802f127a..69150e4700 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -41,16 +41,19 @@ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -kill $ANVIL_1_PID -kill $ANVIL_2_PID +ARTIFACT_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` -# echo "Deploying warp routes" -# yarn hyperlane --local "anvil1" --remotes "anvil2" \ -# --chains ./examples/anvil-chains.yaml \ -# --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +echo "Deploying warp routes" +yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ + --chains ./examples/anvil-chains.yaml \ + --core $ARTIFACT_FILE \ + --config ./examples/warp-tokens.yaml \ + --out /tmp \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --yes -# kill $ANVIL_1_PID -# kill $ANVIL_2_PID +kill $ANVIL_1_PID +kill $ANVIL_2_PID # anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & # ANVIL_1_PID=$! diff --git a/typescript/cli/examples/anvil-chains.yaml b/typescript/cli/examples/anvil-chains.yaml index d7ad6ce0ea..b7b7d1bdcb 100644 --- a/typescript/cli/examples/anvil-chains.yaml +++ b/typescript/cli/examples/anvil-chains.yaml @@ -3,16 +3,17 @@ # Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts --- anvil1: - # Required fields: - chainId: 31337 # Number: Use EIP-155 for EVM chains - domainId: 31337 # Number: Recommend matching chainId when possible - name: anvil1 # String: Unique identifier for the chain, must match key above - protocol: ethereum # ProtocolType: Ethereum, Sealevel, etc. - rpcUrls: # Array: List of RPC configs - # Only http field is required - - http: http://127.0.0.1:8545 # String: HTTP URL of the RPC endpoint (preferably HTTPS) + chainId: 31337 + domainId: 31337 + name: anvil1 + protocol: ethereum + rpcUrls: + - http: http://127.0.0.1:8545 + nativeToken: + name: Ether + symbol: ETH + decimals: 18 anvil2: - # Required fields: chainId: 31338 domainId: 31338 name: anvil2 diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 424782efc8..34d591c33b 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -97,21 +97,23 @@ const warpCommand: CommandModule = { config: { type: 'string', description: 'A path to a JSON or YAML file with a warp config.', - default: './configs/warp-tokens.yaml', }, + yes: skipConfirmationOption, }), handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; - const warpConfigPath: string = argv.config; - const coreArtifactsPath: string = argv.core; + const warpConfigPath: string | undefined = argv.config; + const coreArtifactsPath: string | undefined = argv.core; const outPath: string = argv.out; + const skipConfirmation: boolean = argv.yes; await runWarpDeploy({ key, chainConfigPath, warpConfigPath, coreArtifactsPath, outPath, + skipConfirmation, }); process.exit(0); }, diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 323d1ce26a..461c4f0359 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -26,7 +26,11 @@ import { readDeploymentArtifacts } from '../config/artifacts.js'; import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; -import { prepNewArtifactsFiles, writeJson } from '../utils/files.js'; +import { + prepNewArtifactsFiles, + runFileSelectionStep, + writeJson, +} from '../utils/files.js'; import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; import { runPreflightChecks } from './utils.js'; @@ -37,16 +41,26 @@ export async function runWarpDeploy({ warpConfigPath, coreArtifactsPath, outPath, + skipConfirmation, }: { key: string; chainConfigPath: string; - warpConfigPath: string; - coreArtifactsPath: string; + warpConfigPath?: string; + coreArtifactsPath?: string; outPath: string; + skipConfirmation: boolean; }) { const { multiProvider, signer } = getDeployerContext(key, chainConfigPath); + if (!warpConfigPath) { + warpConfigPath = await runFileSelectionStep( + './configs', + 'Warp config', + 'warp', + ); + } const warpRouteConfig = readWarpRouteConfig(warpConfigPath); + const artifacts = coreArtifactsPath ? readDeploymentArtifacts(coreArtifactsPath) : undefined; @@ -63,6 +77,7 @@ export async function runWarpDeploy({ signer, multiProvider, outPath, + skipConfirmation, }; await runDeployPlanStep(deploymentParams); @@ -185,6 +200,7 @@ interface DeployParams { signer: ethers.Signer; multiProvider: MultiProvider; outPath: string; + skipConfirmation: boolean; } async function runDeployPlanStep({ @@ -193,17 +209,20 @@ async function runDeployPlanStep({ origin, remotes, signer, + skipConfirmation, }: DeployParams) { const address = await signer.getAddress(); const baseToken = configMap[origin]; const baseName = getTokenName(baseToken); - logBlue('\n', 'Deployment plan:'); + logBlue('\nDeployment plan:'); logGray('===============:'); log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying a warp route with a base of ${baseName} token on ${origin}`); log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`); log(`Using token standard ${isNft ? 'ERC721' : 'ERC20'}`); + if (skipConfirmation) return; + const isConfirmed = await confirm({ message: 'Is this deployment plan correct?', }); From 84f13bf336daca3024f1edcca748e64d46bc52e0 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 8 Oct 2023 23:24:24 -0400 Subject: [PATCH 45/82] Fix delivery check bug in HyperlaneCore --- typescript/cli/.gitignore | 2 +- typescript/cli/src/send/message.ts | 9 +++++---- typescript/cli/src/send/transfer.ts | 7 ++++--- typescript/sdk/src/app/HyperlaneApp.ts | 5 ++++- typescript/sdk/src/core/HyperlaneCore.ts | 7 ++++--- typescript/sdk/src/core/MultiProtocolCore.ts | 7 ++++--- typescript/sdk/src/core/adapters/EvmCoreAdapter.ts | 2 +- typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts | 4 +++- typescript/sdk/src/core/adapters/types.ts | 2 +- 9 files changed, 27 insertions(+), 18 deletions(-) diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore index df29ed2d70..8ee1783630 100644 --- a/typescript/cli/.gitignore +++ b/typescript/cli/.gitignore @@ -1,4 +1,4 @@ -.env +.env* /dist /cache /configs diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 8bee3a18fc..adb58521e1 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -79,7 +79,7 @@ async function executeDelivery({ const destinationDomain = multiProvider.getDomainId(destination); const signerAddress = await signer.getAddress(); - let messageReceipt: ethers.ContractReceipt; + let txReceipt: ethers.ContractReceipt; try { const recipient = mergedContractAddrs[destination].testRecipient; if (!recipient) { @@ -92,8 +92,8 @@ async function executeDelivery({ addressToBytes32(recipient), '0x48656c6c6f21', // Hello! ); - messageReceipt = await multiProvider.handleTx(origin, messageTx); - const message = core.getDispatchedMessages(messageReceipt)[0]; + txReceipt = await multiProvider.handleTx(origin, messageTx); + const message = core.getDispatchedMessages(txReceipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); @@ -118,6 +118,7 @@ async function executeDelivery({ throw e; } log('Waiting for message delivery on destination chain...'); - await core.waitForMessageProcessed(messageReceipt, 5000); + // Max wait 10 minutes + await core.waitForMessageProcessed(txReceipt, 10000, 60); logGreen('Message was delivered!'); } diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 113bcb77c5..feb1521c1b 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -144,12 +144,13 @@ async function executeDelivery({ }); const txResponse = await connectedSigner.sendTransaction(transferTx); - const receipt = await multiProvider.handleTx(origin, txResponse); + const txReceipt = await multiProvider.handleTx(origin, txResponse); - const message = core.getDispatchedMessages(receipt)[0]; + const message = core.getDispatchedMessages(txReceipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); - await core.waitForMessageProcessed(receipt, 5000); + // Max wait 10 minutes + await core.waitForMessageProcessed(txReceipt, 10000, 60); logGreen(`Transfer sent to destination chain!`); } diff --git a/typescript/sdk/src/app/HyperlaneApp.ts b/typescript/sdk/src/app/HyperlaneApp.ts index 66fb2c0703..99329db1ad 100644 --- a/typescript/sdk/src/app/HyperlaneApp.ts +++ b/typescript/sdk/src/app/HyperlaneApp.ts @@ -16,8 +16,10 @@ import { MultiGeneric } from '../utils/MultiGeneric'; export class HyperlaneApp< Factories extends HyperlaneFactories, > extends MultiGeneric> { + public readonly contractsMap: HyperlaneContractsMap; + constructor( - public readonly contractsMap: HyperlaneContractsMap, + contractsMap: HyperlaneContractsMap, public readonly multiProvider: MultiProvider, public readonly logger = debug('hyperlane:App'), ) { @@ -25,6 +27,7 @@ export class HyperlaneApp< connectContracts(contracts, multiProvider.getSignerOrProvider(chain)), ); super(connectedContractsMap); + this.contractsMap = connectedContractsMap; } getContracts(chain: ChainName): HyperlaneContracts { diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 8f123bb888..771e7c312e 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -69,7 +69,7 @@ export class HyperlaneCore extends HyperlaneApp { destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { await pollAsync( async () => { this.logger(`Checking if message ${messageId} was processed`); @@ -77,7 +77,7 @@ export class HyperlaneCore extends HyperlaneApp { const delivered = await mailbox.delivered(messageId); if (delivered) { this.logger(`Message ${messageId} was processed`); - return; + return true; } else { throw new Error(`Message ${messageId} not yet processed`); } @@ -85,7 +85,7 @@ export class HyperlaneCore extends HyperlaneApp { delayMs, maxAttempts, ); - return; + return true; } waitForMessageProcessing( @@ -112,6 +112,7 @@ export class HyperlaneCore extends HyperlaneApp { ), ), ); + this.logger(`All messages processed for tx ${sourceTx.transactionHash}`); } // Redundant with static method but keeping for backwards compatibility diff --git a/typescript/sdk/src/core/MultiProtocolCore.ts b/typescript/sdk/src/core/MultiProtocolCore.ts index 971394f54d..ddcdc3a3e8 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.ts @@ -57,15 +57,15 @@ export class MultiProtocolCore extends MultiProtocolApp< throw new Error(`No adapter for protocol ${protocol}`); } - waitForMessagesProcessed( + async waitForMessagesProcessed( origin: ChainName, destination: ChainName, sourceTx: TypedTransactionReceipt, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { const messages = this.adapter(origin).extractMessageIds(sourceTx); - return Promise.all( + await Promise.all( messages.map((msg) => this.adapter(destination).waitForMessageProcessed( msg.messageId, @@ -75,5 +75,6 @@ export class MultiProtocolCore extends MultiProtocolApp< ), ), ); + return true; } } diff --git a/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts b/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts index 4eb00a3801..a89da035bf 100644 --- a/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts +++ b/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts @@ -60,7 +60,7 @@ export class EvmCoreAdapter extends BaseEvmAdapter implements ICoreAdapter { destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { return this.core.waitForMessageIdProcessed( messageId, destination, diff --git a/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts b/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts index 1709d9f0f6..bd5adce441 100644 --- a/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts +++ b/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts @@ -57,7 +57,7 @@ export class SealevelCoreAdapter destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { const pda = SealevelCoreAdapter.deriveMailboxMessageProcessedPda( this.addresses.mailbox, messageId, @@ -75,6 +75,8 @@ export class SealevelCoreAdapter delayMs, maxAttempts, ); + + return true; } static parseMessageDispatchLogs( diff --git a/typescript/sdk/src/core/adapters/types.ts b/typescript/sdk/src/core/adapters/types.ts index da97bc36dc..7d2268922f 100644 --- a/typescript/sdk/src/core/adapters/types.ts +++ b/typescript/sdk/src/core/adapters/types.ts @@ -13,5 +13,5 @@ export interface ICoreAdapter extends BaseAppAdapter { destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise; + ): Promise; } From 4c9c04a5b03604e4d67f3e0501db93e90a80cc13 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 9 Oct 2023 22:33:26 -0400 Subject: [PATCH 46/82] Add CI coverage for send commands Add support for non-collateral transfers First attempt at fixing docker commands for validator and relayer --- typescript/cli/ci-test.sh | 158 +++++++++++++--------- typescript/cli/examples/anvil-chains.yaml | 4 +- typescript/cli/src/commands/send.ts | 19 +++ typescript/cli/src/deploy/core.ts | 2 +- typescript/cli/src/send/message.ts | 24 +++- typescript/cli/src/send/transfer.ts | 71 ++++++---- 6 files changed, 179 insertions(+), 99 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 69150e4700..e9718e44e8 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -29,91 +29,115 @@ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -ARTIFACT_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` +CORE_ARTIFACTS_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` echo "Deploying contracts to anvil2" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ - --artifacts $ARTIFACT_FILE \ - --out /tmp \ + --artifacts $CORE_ARTIFACTS_FILE \ + --out /tmp \ --ism ./examples/multisig-ism.yaml \ --origin anvil2 --remotes anvil1 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -ARTIFACT_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` +CORE_ARTIFACTS_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` echo "Deploying warp routes" yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ --chains ./examples/anvil-chains.yaml \ - --core $ARTIFACT_FILE \ + --core $CORE_ARTIFACTS_FILE \ --config ./examples/warp-tokens.yaml \ --out /tmp \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes +echo "Sending test message" +yarn workspace @hyperlane-xyz/cli run hyperlane send message \ + --origin anvil1 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_FILE \ + --quick \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + +WARP_ARTIFACTS_FILE=`find /tmp/warp-deployment* -type f -exec ls -t1 {} + | head -1` +ANVIL1_ROUTER=`cat $WARP_ARTIFACTS_FILE | jq -r ".anvil1.router"` + +echo "Sending test warp transfer" +yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ + --origin anvil1 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_FILE \ + --router $ANVIL1_ROUTER \ + --type native \ + --quick \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + kill $ANVIL_1_PID kill $ANVIL_2_PID -# anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & -# ANVIL_1_PID=$! - -# anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & -# ANVIL_2_PID=$! - -# for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" -# do -# set -- $i -# echo "Running validator on $1" -# # Won't work on anything but linux due to -net=host -# docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ -# --mount type=bind,source="/tmp",target=/data --net=host \ -# -e CONFIG_FILES=/config/agent_config.json -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ -# -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ -# -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ -# -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ -# -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ -# -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -# -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -# -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ -# gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./validator & -# done - -# sleep 10 - -# for i in "anvil1 8545" "anvil2 8555" -# do -# set -- $i -# echo "Announcing validator on $1" -# VALIDATOR_ANNOUNCE_ADDRESS=$(cat ./artifacts/addresses.json | jq -r ".$1.validatorAnnounce") -# VALIDATOR=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.validator') -# STORAGE_LOCATION=$(cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location') -# SIGNATURE=$(cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature') -# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ -# "announce(address, string calldata, bytes calldata)(bool)" \ -# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ -# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba -# done - - -# for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" -# do -# set -- $i -# echo "Running relayer on $1" -# docker run --mount type=bind,source="$(pwd)/artifacts",target=/config \ -# --mount type=bind,source="/tmp",target=/data --net=host \ -# -e CONFIG_FILES=/config/agent_config.json \ -# -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ -# -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ -# -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ -# -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ -# -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ -# -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ -# -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ -# -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ -# gcr.io/abacus-labs-dev/hyperlane-agent:40cc4a6-20230420-080111 ./relayer & -# done - -# docker ps -aq | xargs docker stop | xargs docker rm -# kill $ANVIL_1_PID -# kill $ANVIL_2_PID +anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & +ANVIL_1_PID=$! + +anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & +ANVIL_2_PID=$! + +AGENT_CONFIG_FILE=`find /tmp/agent-config* -type f -exec ls -t1 {} + | head -1` + +for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" +do + set -- $i + echo "Running validator on $1" + # Won't work on anything but linux due to -net=host + docker run \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ + -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ + -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ + -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ + -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ + -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ + -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ + -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & +done + +sleep 10 + +for i in "anvil1 8545" "anvil2 8555" +do + set -- $i + echo "Announcing validator on $1" + VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".$1.validatorAnnounce"` + VALIDATOR=`cat /tmp/$1/validator/announcement.json | jq -r '.value.validator'` + STORAGE_LOCATION=`cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location'` + SIGNATURE=`cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature'` + cast send $VALIDATOR_ANNOUNCE_ADDRESS \ + "announce(address, string calldata, bytes calldata)(bool)" \ + $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ + --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +done + +for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" +do + set -- $i + echo "Running relayer on $1" + docker run \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} \ + -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ + -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ + -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ + -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ + -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ + -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ + -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ + -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & +done + +docker ps -aq | xargs docker stop | xargs docker rm +kill $ANVIL_1_PID +kill $ANVIL_2_PID diff --git a/typescript/cli/examples/anvil-chains.yaml b/typescript/cli/examples/anvil-chains.yaml index b7b7d1bdcb..be2f4a9f2a 100644 --- a/typescript/cli/examples/anvil-chains.yaml +++ b/typescript/cli/examples/anvil-chains.yaml @@ -6,8 +6,8 @@ anvil1: chainId: 31337 domainId: 31337 name: anvil1 - protocol: ethereum - rpcUrls: + protocol: ethereum + rpcUrls: - http: http://127.0.0.1:8545 nativeToken: name: Ether diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 4a350c88aa..f39fd36817 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -1,5 +1,7 @@ import { CommandModule, Options } from 'yargs'; +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; + import { log } from '../../logger.js'; import { sendTestMessage } from '../send/message.js'; import { sendTestTransfer } from '../send/transfer.js'; @@ -47,6 +49,11 @@ const messageOptions: { [k: string]: Options } = { description: 'Timeout in seconds', default: 5 * 60, }, + quick: { + type: 'boolean', + description: 'Skip wait for message to be delivered', + default: false, + }, }; const messageCommand: CommandModule = { @@ -60,6 +67,7 @@ const messageCommand: CommandModule = { const origin: string = argv.origin; const destination: string = argv.destination; const timeoutSec: number = argv.timeout; + const skipWaitForDelivery: boolean = argv.quick; await sendTestMessage({ key, chainConfigPath, @@ -67,6 +75,7 @@ const messageCommand: CommandModule = { origin, destination, timeoutSec, + skipWaitForDelivery, }); process.exit(0); }, @@ -86,6 +95,12 @@ const transferCommand: CommandModule = { description: 'The address of the token router contract', demandOption: true, }, + type: { + type: 'string', + description: 'Warp token type (native of collateral)', + default: TokenType.collateral, + choices: [TokenType.collateral, TokenType.native], + }, wei: { type: 'string', description: 'Amount in wei to send', @@ -104,8 +119,10 @@ const transferCommand: CommandModule = { const destination: string = argv.destination; const timeoutSec: number = argv.timeout; const routerAddress: string = argv.router; + const tokenType: TokenType = argv.type; const wei: string = argv.wei; const recipient: string | undefined = argv.recipient; + const skipWaitForDelivery: boolean = argv.quick; await sendTestTransfer({ key, chainConfigPath, @@ -113,9 +130,11 @@ const transferCommand: CommandModule = { origin, destination, routerAddress, + tokenType, wei, recipient, timeoutSec, + skipWaitForDelivery, }); process.exit(0); }, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 501a5920fd..655cb12936 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -456,7 +456,7 @@ async function writeAgentConfig( ) as ChainMap; const agentConfig = buildAgentConfig( - [origin], + Object.keys(filteredAddressesMap), multiProvider, filteredAddressesMap, startBlocks, diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index adb58521e1..6ccf86d7af 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -26,6 +26,7 @@ export async function sendTestMessage({ origin, destination, timeoutSec, + skipWaitForDelivery, }: { key: string; chainConfigPath: string; @@ -33,9 +34,10 @@ export async function sendTestMessage({ origin: ChainName; destination: ChainName; timeoutSec: number; + skipWaitForDelivery: boolean; }) { const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); - const artifacts = coreArtifactsPath + const coreArtifacts = coreArtifactsPath ? readDeploymentArtifacts(coreArtifactsPath) : undefined; @@ -48,7 +50,14 @@ export async function sendTestMessage({ }); await timeout( - executeDelivery({ origin, destination, multiProvider, signer, artifacts }), + executeDelivery({ + origin, + destination, + multiProvider, + signer, + coreArtifacts, + skipWaitForDelivery, + }), timeoutSec * 1000, 'Timed out waiting for messages to be delivered', ); @@ -59,15 +68,17 @@ async function executeDelivery({ destination, multiProvider, signer, - artifacts, + coreArtifacts, + skipWaitForDelivery, }: { origin: ChainName; destination: ChainName; multiProvider: MultiProvider; signer: ethers.Signer; - artifacts?: HyperlaneContractsMap; + coreArtifacts?: HyperlaneContractsMap; + skipWaitForDelivery: boolean; }) { - const mergedContractAddrs = getMergedContractAddresses(artifacts); + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const core = HyperlaneCore.fromAddressesMap( mergedContractAddrs, multiProvider, @@ -117,6 +128,9 @@ async function executeDelivery({ ); throw e; } + + if (skipWaitForDelivery) return; + log('Waiting for message delivery on destination chain...'); // Max wait 10 minutes await core.waitForMessageProcessed(txReceipt, 10000, 60); diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index feb1521c1b..f5a3316d63 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -1,9 +1,10 @@ -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; import { ERC20__factory, EvmHypCollateralAdapter, HypERC20Collateral__factory, + TokenType, } from '@hyperlane-xyz/hyperlane-token'; import { ChainName, @@ -14,12 +15,12 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, timeout } from '@hyperlane-xyz/utils'; -import { logBlue, logGreen } from '../../logger.js'; +import { log, logBlue, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; import { getDeployerContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; -import { assertTokenBalance } from '../utils/balances.js'; +import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; // TODO improve the UX here by making params optional and // prompting for missing values @@ -30,9 +31,11 @@ export async function sendTestTransfer({ origin, destination, routerAddress, + tokenType, wei, recipient, timeoutSec, + skipWaitForDelivery, }: { key: string; chainConfigPath: string; @@ -40,22 +43,33 @@ export async function sendTestTransfer({ origin: ChainName; destination: ChainName; routerAddress: Address; + tokenType: TokenType; wei: string; recipient?: string; timeoutSec: number; + skipWaitForDelivery: boolean; }) { const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); const artifacts = coreArtifactsPath ? readDeploymentArtifacts(coreArtifactsPath) : undefined; - await assertTokenBalance( - multiProvider, - signer, - origin, - routerAddress, - wei.toString(), - ); + if (tokenType === TokenType.collateral) { + await assertTokenBalance( + multiProvider, + signer, + origin, + routerAddress, + wei.toString(), + ); + } else if (tokenType === TokenType.native) { + await assertNativeBalances(multiProvider, signer, [origin], wei.toString()); + } else { + throw new Error( + 'Only collateral and native token types are currently supported in the CLI. For synthetic transfers, try the Warp UI.', + ); + } + await runPreflightChecks({ origin, remotes: [destination], @@ -69,11 +83,13 @@ export async function sendTestTransfer({ origin, destination, routerAddress, + tokenType, wei, recipient, signer, multiProvider, artifacts, + skipWaitForDelivery, }), timeoutSec * 1000, 'Timed out waiting for messages to be delivered', @@ -84,20 +100,24 @@ async function executeDelivery({ origin, destination, routerAddress, + tokenType, wei, recipient, multiProvider, signer, artifacts, + skipWaitForDelivery, }: { origin: ChainName; destination: ChainName; routerAddress: Address; + tokenType: TokenType; wei: string; recipient?: string; multiProvider: MultiProvider; signer: ethers.Signer; artifacts?: HyperlaneContractsMap; + skipWaitForDelivery: boolean; }) { const signerAddress = await signer.getAddress(); recipient ||= signerAddress; @@ -111,21 +131,15 @@ async function executeDelivery({ const provider = multiProvider.getProvider(origin); const connectedSigner = signer.connect(provider); - const wrappedToken = await getWrappedToken(routerAddress, provider); - if (wrappedToken) { + + if (tokenType === TokenType.collateral) { + const wrappedToken = await getWrappedToken(routerAddress, provider); const token = ERC20__factory.connect(wrappedToken, connectedSigner); const approval = await token.allowance(signerAddress, routerAddress); if (approval.lt(wei)) { const approveTx = await token.approve(routerAddress, wei); await approveTx.wait(); } - } else { - // TODO finish support for other types - // See code in warp UI for an example - // Requires gas handling - throw new Error( - 'Sorry, only HypERC20Collateral transfers are currently supported in the CLI', - ); } // TODO move next section into MultiProtocolTokenApp when it exists @@ -136,11 +150,15 @@ async function executeDelivery({ ); const destinationDomain = multiProvider.getDomainId(destination); const gasPayment = await adapter.quoteGasPayment(destinationDomain); + const txValue = + tokenType === TokenType.native + ? BigNumber.from(gasPayment).add(wei).toString() + : gasPayment; const transferTx = await adapter.populateTransferRemoteTx({ weiAmountOrId: wei, destination: destinationDomain, recipient, - txValue: gasPayment, + txValue, }); const txResponse = await connectedSigner.sendTransaction(transferTx); @@ -149,6 +167,9 @@ async function executeDelivery({ const message = core.getDispatchedMessages(txReceipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); + + if (skipWaitForDelivery) return; + // Max wait 10 minutes await core.waitForMessageProcessed(txReceipt, 10000, 60); logGreen(`Transfer sent to destination chain!`); @@ -157,14 +178,16 @@ async function executeDelivery({ async function getWrappedToken( address: Address, provider: ethers.providers.Provider, -): Promise
{ +): Promise
{ try { const contract = HypERC20Collateral__factory.connect(address, provider); const wrappedToken = await contract.wrappedToken(); if (ethers.utils.isAddress(wrappedToken)) return wrappedToken; - else return null; + else throw new Error('Invalid wrapped token address'); } catch (error) { - // Token isn't a HypERC20Collateral - return null; + log('Error getting wrapped token', error); + throw new Error( + `Could not get wrapped token from router address ${address}`, + ); } } From b04487067ff9a93fe792948ae56ef0d5ea4cd544 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 9 Oct 2023 22:50:34 -0400 Subject: [PATCH 47/82] Increase timeout on some SDK unit tests --- typescript/sdk/src/core/MultiProtocolCore.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/sdk/src/core/MultiProtocolCore.test.ts b/typescript/sdk/src/core/MultiProtocolCore.test.ts index 5c21e842d7..ece1aedfdb 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.test.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.test.ts @@ -62,7 +62,7 @@ describe('MultiProtocolCore', () => { type: ProviderType.EthersV5, receipt, }); - }); + }).timeout(10000); it('to Sealevel', async () => { const multiProvider = new MultiProtocolProvider(); @@ -88,6 +88,6 @@ describe('MultiProtocolCore', () => { type: ProviderType.EthersV5, receipt, }); - }); + }).timeout(10000); }); }); From 287727c7b87d3594ddb587e776cfce832c04d4dd Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 11:40:23 -0400 Subject: [PATCH 48/82] Make SDK + CLI logging more consistent --- typescript/cli/logger.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/typescript/cli/logger.ts b/typescript/cli/logger.ts index e142534db2..860c3c6ace 100644 --- a/typescript/cli/logger.ts +++ b/typescript/cli/logger.ts @@ -36,6 +36,13 @@ debug.enable( otherNamespaces ? `${otherNamespaces},${hypNamespaces}` : `${hypNamespaces}`, ); +// Change Debug's output format to remove prefixes + postfixes +function formatArgs(this: debug.Debugger, args: any[]) { + args.push(debug.humanize(this.diff)); + args.pop(); +} +debug.formatArgs = formatArgs; + // Colored logs directly to console export const logBlue = (...args: any) => console.log(chalk.blue(...args)); export const logPink = (...args: any) => From 3d95734d7f782c9679c4aa310080e4a5cda3993e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 12:06:45 -0400 Subject: [PATCH 49/82] Print tmp filenames to debug announcement --- typescript/cli/ci-test.sh | 3 +++ typescript/cli/src/deploy/core.ts | 4 ++-- typescript/cli/src/deploy/warp.ts | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index e9718e44e8..aa0a24ec0a 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -106,6 +106,9 @@ done sleep 10 +# TODO remove, print names of all files +find /tmp/. + for i in "anvil1 8545" "anvil2 8555" do set -- $i diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 655cb12936..348e1d0ea0 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -179,8 +179,8 @@ async function runDeployPlanStep({ skipConfirmation, }: DeployParams) { const address = await signer.getAddress(); - logBlue('\nDeployment plan:'); - logGray('===============:'); + logBlue('\nDeployment plan'); + logGray('==============='); log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying to ${origin} and connecting it to ${remotes.join(', ')}`); const numContracts = Object.keys( diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 461c4f0359..eb7da0572a 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -214,8 +214,8 @@ async function runDeployPlanStep({ const address = await signer.getAddress(); const baseToken = configMap[origin]; const baseName = getTokenName(baseToken); - logBlue('\nDeployment plan:'); - logGray('===============:'); + logBlue('\nDeployment plan'); + logGray('==============='); log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying a warp route with a base of ${baseName} token on ${origin}`); log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`); From 31842f4b55be1218fb66e4b26e966537edad15d5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 12:21:35 -0400 Subject: [PATCH 50/82] Install libssl3 before cli test --- .github/workflows/node.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 744fad90d3..e79e8e593b 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -133,6 +133,9 @@ jobs: - name: Install Foundry uses: onbjerg/foundry-toolchain@v1 + - name: Install libssl + run: sudo apt-get install -y libssl3 + - uses: actions/cache@v3 with: path: ./* From 5622d6d5b13da657771afd9c8bc80c283a7f005b Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 12:29:17 -0400 Subject: [PATCH 51/82] Attempt to configure libssl --- .github/workflows/node.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index e79e8e593b..5af1d300a1 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -132,9 +132,12 @@ jobs: - name: Install Foundry uses: onbjerg/foundry-toolchain@v1 - + - name: Install libssl - run: sudo apt-get install -y libssl3 + run: sudo apt-get install -y libssl3-dev + + - name: Configure libssl + run: ln -s libssl.so.3 libssl.so && sudo ldconfig - uses: actions/cache@v3 with: From 4d986484551c21f4e053543b8d38b2f5c7c5f9ea Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 12:36:15 -0400 Subject: [PATCH 52/82] Attempt2 --- .github/workflows/node.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 5af1d300a1..2e2a1c2815 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -132,12 +132,9 @@ jobs: - name: Install Foundry uses: onbjerg/foundry-toolchain@v1 - - - name: Install libssl - run: sudo apt-get install -y libssl3-dev - name: Configure libssl - run: ln -s libssl.so.3 libssl.so && sudo ldconfig + run: ln -s /usr/lib/x86_64-linux-gnu/libssl.so.3 /usr/local/lib64/libssl.so.3 && sudo ldconfig - uses: actions/cache@v3 with: From b0384796cdb15570eda0b88b9dfd3953eeb6dab4 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 12:50:13 -0400 Subject: [PATCH 53/82] Attempt to add libssl3 to agent dockerfile --- .github/workflows/node.yml | 3 --- rust/Dockerfile | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 2e2a1c2815..744fad90d3 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -133,9 +133,6 @@ jobs: - name: Install Foundry uses: onbjerg/foundry-toolchain@v1 - - name: Configure libssl - run: ln -s /usr/lib/x86_64-linux-gnu/libssl.so.3 /usr/local/lib64/libssl.so.3 && sudo ldconfig - - uses: actions/cache@v3 with: path: ./* diff --git a/rust/Dockerfile b/rust/Dockerfile index 3ffc062de3..a0026db642 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -38,6 +38,7 @@ FROM ubuntu:20.04 RUN apt-get update && \ apt-get install -y \ openssl \ + libssl3 \ ca-certificates \ tini && \ rm -rf /var/lib/apt/lists/* From d6dabde09d849f63ba7f1a07ee11e0bc1a9424d3 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 13:03:46 -0400 Subject: [PATCH 54/82] Update dockerfile again --- rust/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/Dockerfile b/rust/Dockerfile index a0026db642..63bb4f1272 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -38,10 +38,10 @@ FROM ubuntu:20.04 RUN apt-get update && \ apt-get install -y \ openssl \ - libssl3 \ ca-certificates \ - tini && \ - rm -rf /var/lib/apt/lists/* + tini +RUN apt-get install -y libssl3 +RUN rm -rf /var/lib/apt/lists/* WORKDIR /app COPY config ./config From 392e47486e9dc626d341d557205020b730da781c Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 17:44:18 -0400 Subject: [PATCH 55/82] Revert dockerfile changes --- rust/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/Dockerfile b/rust/Dockerfile index 63bb4f1272..3ffc062de3 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -39,9 +39,8 @@ RUN apt-get update && \ apt-get install -y \ openssl \ ca-certificates \ - tini -RUN apt-get install -y libssl3 -RUN rm -rf /var/lib/apt/lists/* + tini && \ + rm -rf /var/lib/apt/lists/* WORKDIR /app COPY config ./config From 531c98f4d1f1c3cfd2e1b84b090966e4af933ae8 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 10 Oct 2023 17:59:58 -0400 Subject: [PATCH 56/82] Fix agent config file path --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index aa0a24ec0a..431928982e 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -84,7 +84,7 @@ ANVIL_1_PID=$! anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & ANVIL_2_PID=$! -AGENT_CONFIG_FILE=`find /tmp/agent-config* -type f -exec ls -t1 {} + | head -1` +AGENT_CONFIG_FILE=`ls -t1 /tmp | grep agent-config | head -1` for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" do From e15ccdfb62543fb3cb7c23c8a78adf8fbe524a35 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 08:56:44 -0400 Subject: [PATCH 57/82] Increase validator startup sleep time --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 431928982e..62394f0df8 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -104,7 +104,7 @@ do gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & done -sleep 10 +sleep 20 # TODO remove, print names of all files find /tmp/. From cfc8a567389cfcc76871a2ac24c3b61f6b8b8b01 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 09:23:49 -0400 Subject: [PATCH 58/82] Add more logging --- typescript/cli/ci-test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 62394f0df8..22eccd8c3c 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -104,7 +104,9 @@ do gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & done -sleep 20 +echo "Validator running, sleeping to let it sync" +sleep 15 +echo "Done sleeping" # TODO remove, print names of all files find /tmp/. From 688f61c2dbc545b1ca7f5813b6596feae554c0e4 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 09:48:32 -0400 Subject: [PATCH 59/82] Try removing announce loop --- typescript/cli/ci-test.sh | 50 +++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 22eccd8c3c..49ade9e4ea 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -109,21 +109,41 @@ sleep 15 echo "Done sleeping" # TODO remove, print names of all files -find /tmp/. - -for i in "anvil1 8545" "anvil2 8555" -do - set -- $i - echo "Announcing validator on $1" - VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".$1.validatorAnnounce"` - VALIDATOR=`cat /tmp/$1/validator/announcement.json | jq -r '.value.validator'` - STORAGE_LOCATION=`cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location'` - SIGNATURE=`cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature'` - cast send $VALIDATOR_ANNOUNCE_ADDRESS \ - "announce(address, string calldata, bytes calldata)(bool)" \ - $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ - --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba -done +# find /tmp/. + +echo "Announcing validator on anvil1" +VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil1.validatorAnnounce"` +VALIDATOR=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.validator'` +STORAGE_LOCATION=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.storage_location'` +SIGNATURE=`cat /tmp/anvil1/validator/announcement.json | jq -r '.serialized_signature'` +cast send $VALIDATOR_ANNOUNCE_ADDRESS \ + "announce(address, string calldata, bytes calldata)(bool)" \ + $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8545 \ + --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba + +echo "Announcing validator on anvil2" +VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil2.validatorAnnounce"` +VALIDATOR=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.validator'` +STORAGE_LOCATION=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.storage_location'` +SIGNATURE=`cat /tmp/anvil2/validator/announcement.json | jq -r '.serialized_signature'` +cast send $VALIDATOR_ANNOUNCE_ADDRESS \ + "announce(address, string calldata, bytes calldata)(bool)" \ + $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8555 \ + --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba + +# for i in "anvil1 8545" "anvil2 8555" +# do +# set -- $i +# echo "Announcing validator on $1" +# VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".$1.validatorAnnounce"` +# VALIDATOR=`cat /tmp/$1/validator/announcement.json | jq -r '.value.validator'` +# STORAGE_LOCATION=`cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location'` +# SIGNATURE=`cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature'` +# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ +# "announce(address, string calldata, bytes calldata)(bool)" \ +# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ +# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +# done for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" do From c57f98675e79d8f35344516ca58775f15455fe9d Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 10:02:04 -0400 Subject: [PATCH 60/82] Add more logging --- typescript/cli/ci-test.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 49ade9e4ea..5f5f5d22c9 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -109,13 +109,23 @@ sleep 15 echo "Done sleeping" # TODO remove, print names of all files -# find /tmp/. +find /tmp/. + +echo "===Core artifacts:" +cat $CORE_ARTIFACTS_FILE + +echo "===announcement:" +cat /tmp/anvil1/validator/announcement.json echo "Announcing validator on anvil1" VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil1.validatorAnnounce"` +echo "Validator announce address: $VALIDATOR_ANNOUNCE_ADDRESS" VALIDATOR=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.validator'` +echo "Validator: $VALIDATOR" STORAGE_LOCATION=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.storage_location'` +echo "Storage location: $STORAGE_LOCATION" SIGNATURE=`cat /tmp/anvil1/validator/announcement.json | jq -r '.serialized_signature'` +echo "Signature: $SIGNATURE" cast send $VALIDATOR_ANNOUNCE_ADDRESS \ "announce(address, string calldata, bytes calldata)(bool)" \ $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8545 \ From 54180b6d82487b1c19eea3d0b79c7729bce0e21a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 10:39:25 -0400 Subject: [PATCH 61/82] Disable find cmd again --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 5f5f5d22c9..85ace70668 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -109,7 +109,7 @@ sleep 15 echo "Done sleeping" # TODO remove, print names of all files -find /tmp/. +# find /tmp/. echo "===Core artifacts:" cat $CORE_ARTIFACTS_FILE From 7cb395b23cf967c6c1ad2f8b6e9485897bcf86cb Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 11:00:38 -0400 Subject: [PATCH 62/82] Try to disable super noisy validator logs --- typescript/cli/ci-test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 85ace70668..86efd19b63 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -101,7 +101,8 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator \ + --log-driver=none & done echo "Validator running, sleeping to let it sync" From 862f418d9b99e84621c23fc63fa5e354b261d35d Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 11:03:29 -0400 Subject: [PATCH 63/82] Skip announce tx entirely --- typescript/cli/ci-test.sh | 46 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 86efd19b63..1746ab7084 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -118,29 +118,29 @@ cat $CORE_ARTIFACTS_FILE echo "===announcement:" cat /tmp/anvil1/validator/announcement.json -echo "Announcing validator on anvil1" -VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil1.validatorAnnounce"` -echo "Validator announce address: $VALIDATOR_ANNOUNCE_ADDRESS" -VALIDATOR=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.validator'` -echo "Validator: $VALIDATOR" -STORAGE_LOCATION=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.storage_location'` -echo "Storage location: $STORAGE_LOCATION" -SIGNATURE=`cat /tmp/anvil1/validator/announcement.json | jq -r '.serialized_signature'` -echo "Signature: $SIGNATURE" -cast send $VALIDATOR_ANNOUNCE_ADDRESS \ - "announce(address, string calldata, bytes calldata)(bool)" \ - $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8545 \ - --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba - -echo "Announcing validator on anvil2" -VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil2.validatorAnnounce"` -VALIDATOR=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.validator'` -STORAGE_LOCATION=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.storage_location'` -SIGNATURE=`cat /tmp/anvil2/validator/announcement.json | jq -r '.serialized_signature'` -cast send $VALIDATOR_ANNOUNCE_ADDRESS \ - "announce(address, string calldata, bytes calldata)(bool)" \ - $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8555 \ - --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +# echo "Announcing validator on anvil1" +# VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil1.validatorAnnounce"` +# echo "Validator announce address: $VALIDATOR_ANNOUNCE_ADDRESS" +# VALIDATOR=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.validator'` +# echo "Validator: $VALIDATOR" +# STORAGE_LOCATION=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.storage_location'` +# echo "Storage location: $STORAGE_LOCATION" +# SIGNATURE=`cat /tmp/anvil1/validator/announcement.json | jq -r '.serialized_signature'` +# echo "Signature: $SIGNATURE" +# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ +# "announce(address, string calldata, bytes calldata)(bool)" \ +# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8545 \ +# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba + +# echo "Announcing validator on anvil2" +# VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil2.validatorAnnounce"` +# VALIDATOR=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.validator'` +# STORAGE_LOCATION=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.storage_location'` +# SIGNATURE=`cat /tmp/anvil2/validator/announcement.json | jq -r '.serialized_signature'` +# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ +# "announce(address, string calldata, bytes calldata)(bool)" \ +# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8555 \ +# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba # for i in "anvil1 8545" "anvil2 8555" # do From 17c98a4813169d94dd146cd364f410c6c9980e34 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 11:23:56 -0400 Subject: [PATCH 64/82] Try killing validator before running relayer --- typescript/cli/ci-test.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 1746ab7084..a3764348eb 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -112,12 +112,15 @@ echo "Done sleeping" # TODO remove, print names of all files # find /tmp/. +docker ps -aq | xargs docker stop | xargs docker rm + echo "===Core artifacts:" cat $CORE_ARTIFACTS_FILE echo "===announcement:" cat /tmp/anvil1/validator/announcement.json + # echo "Announcing validator on anvil1" # VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil1.validatorAnnounce"` # echo "Validator announce address: $VALIDATOR_ANNOUNCE_ADDRESS" @@ -174,6 +177,9 @@ do gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & done +sleep 5 +echo "Done running relayer + docker ps -aq | xargs docker stop | xargs docker rm kill $ANVIL_1_PID kill $ANVIL_2_PID From ebad637874d8376ba771347ec7a23725b8a3075d Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 11:34:42 -0400 Subject: [PATCH 65/82] Fix missing quotation mark --- typescript/cli/ci-test.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index a3764348eb..0951ea4b8e 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -101,8 +101,7 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator \ - --log-driver=none & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator > /dev/null & done echo "Validator running, sleeping to let it sync" @@ -178,7 +177,7 @@ do done sleep 5 -echo "Done running relayer +echo "Done running relayer" docker ps -aq | xargs docker stop | xargs docker rm kill $ANVIL_1_PID From 1d8add97adf45b6df461204934cd07744523ca3b Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 11:46:47 -0400 Subject: [PATCH 66/82] Attempt to silence noisy logs --- typescript/cli/ci-test.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 0951ea4b8e..0cab81db13 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -101,7 +101,8 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator > /dev/null & + -log-driver none --detach \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator done echo "Validator running, sleeping to let it sync" @@ -111,6 +112,7 @@ echo "Done sleeping" # TODO remove, print names of all files # find /tmp/. +echo "Killing validator docker containers" docker ps -aq | xargs docker stop | xargs docker rm echo "===Core artifacts:" @@ -173,7 +175,8 @@ do -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & + -log-driver none --detach \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer done sleep 5 From 86a074394d9d3cd90e68a53066bd77e526440831 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 12:00:20 -0400 Subject: [PATCH 67/82] Keep trying --- typescript/cli/ci-test.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 0cab81db13..d6f0153290 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -101,8 +101,9 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - -log-driver none --detach \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator + -log-driver none \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator \ + &> /dev/null & done echo "Validator running, sleeping to let it sync" @@ -175,8 +176,9 @@ do -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - -log-driver none --detach \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer + -log-driver none \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer \ + &> /dev/null & done sleep 5 From 885ffdac849c7febfbd270fe38ba2f2e968c18c2 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 12:05:52 -0400 Subject: [PATCH 68/82] Remove first docker kill command --- typescript/cli/ci-test.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index d6f0153290..acbe282204 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -113,8 +113,8 @@ echo "Done sleeping" # TODO remove, print names of all files # find /tmp/. -echo "Killing validator docker containers" -docker ps -aq | xargs docker stop | xargs docker rm +# echo "Killing validator docker containers" +# docker ps -aq | xargs docker stop | xargs docker rm echo "===Core artifacts:" cat $CORE_ARTIFACTS_FILE @@ -177,8 +177,7 @@ do -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ -log-driver none \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer \ - &> /dev/null & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & done sleep 5 From 85934403516f45daffbd7cd9cbefa0327511c77c Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 12:25:26 -0400 Subject: [PATCH 69/82] Extract message IDs --- typescript/cli/ci-test.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index acbe282204..89b1e406ba 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -60,6 +60,10 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send message \ --core $CORE_ARTIFACTS_FILE \ --quick \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + | tee /tmp/message1 + +MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -oP '0x[0-9a-f]+'` +echo "Message 1 ID: $MESSAGE1_ID" WARP_ARTIFACTS_FILE=`find /tmp/warp-deployment* -type f -exec ls -t1 {} + | head -1` ANVIL1_ROUTER=`cat $WARP_ARTIFACTS_FILE | jq -r ".anvil1.router"` @@ -73,7 +77,11 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ --router $ANVIL1_ROUTER \ --type native \ --quick \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + | tee /tmp/message2 + +MESSAGE2_ID=`cat /tmp/message2 | grep "Message ID" | grep -oP '0x[0-9a-f]+'` +echo "Message 2 ID: $MESSAGE2_ID" kill $ANVIL_1_PID kill $ANVIL_2_PID @@ -102,8 +110,7 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ -log-driver none \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator \ - &> /dev/null & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator > /dev/null 2>&1 & done echo "Validator running, sleeping to let it sync" From e6b4262db05a4f970d86d3ae6b9edc9ec6ad7a23 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 22:47:51 -0400 Subject: [PATCH 70/82] Let validator be noisy? --- typescript/cli/ci-test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 89b1e406ba..8cac96a06e 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -109,8 +109,7 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ - -log-driver none \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator > /dev/null 2>&1 & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & done echo "Validator running, sleeping to let it sync" From 44fb1d7e1775b09589f6021da2a93f62afc1892e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 23:42:31 -0400 Subject: [PATCH 71/82] Add message status command --- typescript/cli/cli.ts | 2 ++ typescript/cli/src/commands/status.ts | 38 +++++++++++++++++++++++++++ typescript/cli/src/context.ts | 8 +++++- typescript/cli/src/deploy/core.ts | 4 +-- typescript/cli/src/deploy/warp.ts | 7 +++-- typescript/cli/src/send/message.ts | 7 +++-- typescript/cli/src/send/transfer.ts | 7 +++-- typescript/cli/src/status/message.ts | 36 +++++++++++++++++++++++++ 8 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 typescript/cli/src/commands/status.ts create mode 100644 typescript/cli/src/status/message.ts diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index f1bcdfc7d2..a25672e7ea 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -7,6 +7,7 @@ import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { deployCommand } from './src/commands/deploy.js'; import { sendCommand } from './src/commands/send.js'; +import { statusCommand } from './src/commands/status.js'; // From yargs code: const MISSING_PARAMS_ERROR = 'Not enough non-option arguments'; @@ -22,6 +23,7 @@ try { .command(configCommand) .command(deployCommand) .command(sendCommand) + .command(statusCommand) .demandCommand() .strict() .help() diff --git a/typescript/cli/src/commands/status.ts b/typescript/cli/src/commands/status.ts new file mode 100644 index 0000000000..ab0a39b20a --- /dev/null +++ b/typescript/cli/src/commands/status.ts @@ -0,0 +1,38 @@ +import { CommandModule } from 'yargs'; + +import { checkMessageStatus } from '../status/message.js'; + +import { chainsCommandOption, coreArtifactsOption } from './options.js'; + +export const statusCommand: CommandModule = { + command: 'status', + describe: 'Check status of a message', + builder: (yargs) => + yargs.options({ + chains: chainsCommandOption, + core: coreArtifactsOption, + id: { + type: 'string', + description: 'Message ID', + demandOption: true, + }, + destination: { + type: 'string', + description: 'Destination chain name', + demandOption: true, + }, + }), + handler: async (argv: any) => { + const chainConfigPath: string = argv.chains; + const coreArtifactsPath: string = argv.core; + const messageId: string = argv.id; + const destination: string = argv.destination; + await checkMessageStatus({ + chainConfigPath, + coreArtifactsPath, + messageId, + destination, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index ba2f416df2..710e63b72e 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -27,7 +27,13 @@ export function getMergedContractAddresses( ) as HyperlaneContractsMap; } -export function getDeployerContext(key: string, chainConfigPath: string) { +export function getContext(chainConfigPath: string) { + const customChains = readChainConfigIfExists(chainConfigPath); + const multiProvider = getMultiProvider(customChains); + return { customChains, multiProvider }; +} + +export function getContextWithSigner(key: string, chainConfigPath: string) { const signer = keyToSigner(key); const customChains = readChainConfigIfExists(chainConfigPath); const multiProvider = getMultiProvider(customChains, signer); diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 348e1d0ea0..82a9d194cd 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -33,7 +33,7 @@ import { readDeploymentArtifacts } from '../config/artifacts.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; import { - getDeployerContext, + getContextWithSigner, getMergedContractAddresses, sdkContractAddressesMap, } from '../context.js'; @@ -69,7 +69,7 @@ export async function runCoreDeploy({ remotes?: string[]; skipConfirmation: boolean; }) { - const { customChains, multiProvider, signer } = getDeployerContext( + const { customChains, multiProvider, signer } = getContextWithSigner( key, chainConfigPath, ); diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index eb7da0572a..059aabb34f 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -25,7 +25,10 @@ import { log, logBlue, logGray, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; -import { getDeployerContext, getMergedContractAddresses } from '../context.js'; +import { + getContextWithSigner, + getMergedContractAddresses, +} from '../context.js'; import { prepNewArtifactsFiles, runFileSelectionStep, @@ -50,7 +53,7 @@ export async function runWarpDeploy({ outPath: string; skipConfirmation: boolean; }) { - const { multiProvider, signer } = getDeployerContext(key, chainConfigPath); + const { multiProvider, signer } = getContextWithSigner(key, chainConfigPath); if (!warpConfigPath) { warpConfigPath = await runFileSelectionStep( diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 6ccf86d7af..f83ac593bf 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -12,7 +12,10 @@ import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; import { errorRed, log, logBlue, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; -import { getDeployerContext, getMergedContractAddresses } from '../context.js'; +import { + getContextWithSigner, + getMergedContractAddresses, +} from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; const GAS_AMOUNT = 300_000; @@ -36,7 +39,7 @@ export async function sendTestMessage({ timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); + const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); const coreArtifacts = coreArtifactsPath ? readDeploymentArtifacts(coreArtifactsPath) : undefined; diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index f5a3316d63..c699d57a71 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -18,7 +18,10 @@ import { Address, timeout } from '@hyperlane-xyz/utils'; import { log, logBlue, logGreen } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; -import { getDeployerContext, getMergedContractAddresses } from '../context.js'; +import { + getContextWithSigner, + getMergedContractAddresses, +} from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; @@ -49,7 +52,7 @@ export async function sendTestTransfer({ timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider } = getDeployerContext(key, chainConfigPath); + const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); const artifacts = coreArtifactsPath ? readDeploymentArtifacts(coreArtifactsPath) : undefined; diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts new file mode 100644 index 0000000000..933b75cea2 --- /dev/null +++ b/typescript/cli/src/status/message.ts @@ -0,0 +1,36 @@ +import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; + +import { log, logBlue, logGreen } from '../../logger.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; + +export async function checkMessageStatus({ + chainConfigPath, + coreArtifactsPath, + messageId, + destination, +}: { + chainConfigPath: string; + coreArtifactsPath: string; + messageId: string; + destination: ChainName; +}) { + const { multiProvider } = getContext(chainConfigPath); + const coreArtifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + const mailbox = core.getContracts(destination).mailbox; + log(`Checking status of message ${messageId} on ${destination}`); + const delivered = await mailbox.delivered(messageId); + if (delivered) { + logGreen(`Message ${messageId} was delivered`); + } else { + logBlue(`Message ${messageId} was not yet delivered`); + } +} From 3b16d8e4ff365952868314af50409bcd219dce77 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 11 Oct 2023 23:52:40 -0400 Subject: [PATCH 72/82] Add delivery checking to ci test --- typescript/cli/ci-test.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 8cac96a06e..37411f0251 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -187,7 +187,25 @@ do done sleep 5 -echo "Done running relayer" +echo "Done running relayer, checking message delivery statuses" + +for i in "1 $MESSAGE1_ID" "2 $MESSAGE2_ID" +do + set -- $i + echo "Checking delivery status of $1" + yarn workspace @hyperlane-xyz/cli run hyperlane status \ + --id $2 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_FILE \ + | tee /tmp/message-status-$1 + if ! grep -q "$1 was delivered" /tmp/message-status-$1; then + echo "ERROR: Message $1 was not delivered" + exit 1 + else + echo "Message $1 was delivered!" + fi +done docker ps -aq | xargs docker stop | xargs docker rm kill $ANVIL_1_PID From 1780b6a6d67f6ce37193fb3c4113c3adc11a36ca Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 12 Oct 2023 00:07:05 -0400 Subject: [PATCH 73/82] Attempt to reduce logging again --- typescript/cli/ci-test.sh | 53 +++------------------------------------ 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 37411f0251..f2655c95be 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -108,7 +108,7 @@ do -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ - -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ + -e HYP_BASE_TRACING_LEVEL=warn -e HYP_BASE_TRACING_FMT=pretty \ gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & done @@ -116,57 +116,12 @@ echo "Validator running, sleeping to let it sync" sleep 15 echo "Done sleeping" -# TODO remove, print names of all files -# find /tmp/. - -# echo "Killing validator docker containers" -# docker ps -aq | xargs docker stop | xargs docker rm - -echo "===Core artifacts:" +echo "Core artifacts:" cat $CORE_ARTIFACTS_FILE -echo "===announcement:" +echo "Validator Announcement:" cat /tmp/anvil1/validator/announcement.json - -# echo "Announcing validator on anvil1" -# VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil1.validatorAnnounce"` -# echo "Validator announce address: $VALIDATOR_ANNOUNCE_ADDRESS" -# VALIDATOR=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.validator'` -# echo "Validator: $VALIDATOR" -# STORAGE_LOCATION=`cat /tmp/anvil1/validator/announcement.json | jq -r '.value.storage_location'` -# echo "Storage location: $STORAGE_LOCATION" -# SIGNATURE=`cat /tmp/anvil1/validator/announcement.json | jq -r '.serialized_signature'` -# echo "Signature: $SIGNATURE" -# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ -# "announce(address, string calldata, bytes calldata)(bool)" \ -# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8545 \ -# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba - -# echo "Announcing validator on anvil2" -# VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".anvil2.validatorAnnounce"` -# VALIDATOR=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.validator'` -# STORAGE_LOCATION=`cat /tmp/anvil2/validator/announcement.json | jq -r '.value.storage_location'` -# SIGNATURE=`cat /tmp/anvil2/validator/announcement.json | jq -r '.serialized_signature'` -# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ -# "announce(address, string calldata, bytes calldata)(bool)" \ -# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:8555 \ -# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba - -# for i in "anvil1 8545" "anvil2 8555" -# do -# set -- $i -# echo "Announcing validator on $1" -# VALIDATOR_ANNOUNCE_ADDRESS=`cat $CORE_ARTIFACTS_FILE | jq -r ".$1.validatorAnnounce"` -# VALIDATOR=`cat /tmp/$1/validator/announcement.json | jq -r '.value.validator'` -# STORAGE_LOCATION=`cat /tmp/$1/validator/announcement.json | jq -r '.value.storage_location'` -# SIGNATURE=`cat /tmp/$1/validator/announcement.json | jq -r '.serialized_signature'` -# cast send $VALIDATOR_ANNOUNCE_ADDRESS \ -# "announce(address, string calldata, bytes calldata)(bool)" \ -# $VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ -# --private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba -# done - for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" do set -- $i @@ -176,7 +131,7 @@ do -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} \ -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ - -e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ + -e HYP_BASE_TRACING_LEVEL=warn -e HYP_BASE_TRACING_FMT=pretty \ -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ From b88f7fd8c8da53a67ddd94988721ec021fd7af49 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 12 Oct 2023 00:10:33 -0400 Subject: [PATCH 74/82] Fix message id check --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index f2655c95be..dd4352ce7c 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -154,7 +154,7 @@ do --chains ./examples/anvil-chains.yaml \ --core $CORE_ARTIFACTS_FILE \ | tee /tmp/message-status-$1 - if ! grep -q "$1 was delivered" /tmp/message-status-$1; then + if ! grep -q "$2 was delivered" /tmp/message-status-$1; then echo "ERROR: Message $1 was not delivered" exit 1 else From d5b4a545c184b35e116f6c8edd116247e022e518 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 12 Oct 2023 00:22:44 -0400 Subject: [PATCH 75/82] Print message id to confirm --- typescript/cli/ci-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index dd4352ce7c..f3aebd16d1 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -147,7 +147,7 @@ echo "Done running relayer, checking message delivery statuses" for i in "1 $MESSAGE1_ID" "2 $MESSAGE2_ID" do set -- $i - echo "Checking delivery status of $1" + echo "Checking delivery status of $1: $2" yarn workspace @hyperlane-xyz/cli run hyperlane status \ --id $2 \ --destination anvil2 \ From 4e18c27d642055267ba5348f1e3e9d037cded62a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 12 Oct 2023 18:49:36 -0400 Subject: [PATCH 76/82] Deploy ISMs before core --- .github/CODEOWNERS | 2 +- typescript/cli/ci-test.sh | 4 +- .../cli/src/deploy/TestRecipientDeployer.ts | 4 +- typescript/cli/src/deploy/core.ts | 68 ++++++++++--------- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 50ba6c590f..fc48baf0bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,7 +23,7 @@ typescript/token @yorhodes @jmrossy @tkporter @aroralanuk typescript/helloworld @yorhodes @nambrot ## CLI -typescript/cli @jmrossy @asaj +typescript/cli @jmrossy @yorhodes ## Infra typescript/infra @tkporter @nambrot diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index f3aebd16d1..694d43d7f3 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -62,7 +62,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send message \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ | tee /tmp/message1 -MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -oP '0x[0-9a-f]+'` +MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` echo "Message 1 ID: $MESSAGE1_ID" WARP_ARTIFACTS_FILE=`find /tmp/warp-deployment* -type f -exec ls -t1 {} + | head -1` @@ -80,7 +80,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ | tee /tmp/message2 -MESSAGE2_ID=`cat /tmp/message2 | grep "Message ID" | grep -oP '0x[0-9a-f]+'` +MESSAGE2_ID=`cat /tmp/message2 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` echo "Message 2 ID: $MESSAGE2_ID" kill $ANVIL_1_PID diff --git a/typescript/cli/src/deploy/TestRecipientDeployer.ts b/typescript/cli/src/deploy/TestRecipientDeployer.ts index bd4bacca63..ecbd2af290 100644 --- a/typescript/cli/src/deploy/TestRecipientDeployer.ts +++ b/typescript/cli/src/deploy/TestRecipientDeployer.ts @@ -44,7 +44,9 @@ export class TestRecipientDeployer extends HyperlaneDeployer< const ism = await testRecipient.interchainSecurityModule(); this.logger(`Found ISM for on ${chain}: ${ism}`); if (!eqAddress(ism, config.interchainSecurityModule)) { - this.logger(`Current ISM does not match config. Updating.`); + this.logger( + `Current ISM does not match config. Updating to ${config.interchainSecurityModule}`, + ); const tx = testRecipient.setInterchainSecurityModule( config.interchainSecurityModule, ); diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 82a9d194cd..0912de5cf5 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -28,7 +28,7 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; -import { log, logBlue, logGray, logGreen } from '../../logger.js'; +import { log, logBlue, logGray, logGreen, logRed } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; @@ -224,9 +224,9 @@ async function executeDeploy({ // 1. Deploy ISM factories to all deployable chains that don't have them. log('Deploying ISM factory contracts'); - const ismDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); - ismDeployer.cacheAddressesMap(mergedContractAddrs); - const ismFactoryContracts = await ismDeployer.deploy(selectedChains); + const ismFactoryDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); + ismFactoryDeployer.cacheAddressesMap(mergedContractAddrs); + const ismFactoryContracts = await ismFactoryDeployer.deploy(selectedChains); artifacts = writeMergedAddresses( contractsFilePath, artifacts, @@ -249,23 +249,14 @@ async function executeDeploy({ logGreen(`IGP contracts deployed`); // Build an IsmFactory that covers all chains so that we can - // use it later to deploy ISMs to remote chains. + // use it to deploy ISMs to remote chains. const ismFactory = HyperlaneIsmFactory.fromAddressesMap( mergedContractAddrs, multiProvider, ); - // 3. Deploy core contracts to origin chain - log(`Deploying core contracts to ${origin}`); - const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); - coreDeployer.cacheAddressesMap(artifacts); - const coreConfig = buildCoreConfigMap(owner, origin, remotes, multisigConfig); - const coreContracts = await coreDeployer.deploy(coreConfig); - artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); - logGreen(`Core contracts deployed`); - - // 4. Deploy ISM contracts to remote deployable chains - log(`Deploying ISMs`); + // 3. Deploy ISM contracts to remote deployable chains + log('Deploying ISMs'); const ismConfigs = buildIsmConfigMap( owner, selectedChains, @@ -273,21 +264,34 @@ async function executeDeploy({ multisigConfig, ); const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; + const defaultIsms: ChainMap
= {}; for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { if (artifacts[ismChain].multisigIsm) { log(`ISM contract recovered, skipping ISM deployment to ${ismChain}`); + defaultIsms[ismChain] = artifacts[ismChain].multisigIsm; continue; } log(`Deploying ISM to ${ismChain}`); ismContracts[ismChain] = { multisigIsm: await ismFactory.deploy(ismChain, ismConfig), }; + defaultIsms[ismChain] = ismContracts[ismChain].multisigIsm.address; } artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); - logGreen(`ISM contracts deployed `); + logGreen('ISM contracts deployed'); + + // 4. Deploy core contracts to origin chain + log(`Deploying core contracts to ${origin}`); + const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); + coreDeployer.cacheAddressesMap(artifacts); + const coreConfig = buildCoreConfigMap(owner, origin, defaultIsms); + console.log('===coreConfig', coreConfig); + const coreContracts = await coreDeployer.deploy(coreConfig); + artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); + logGreen('Core contracts deployed'); // 5. Deploy TestRecipients to all deployable chains - log(`Deploying test recipient contracts`); + log('Deploying test recipient contracts'); const testRecipientConfig = buildTestRecipientConfigMap( selectedChains, artifacts, @@ -343,28 +347,25 @@ function buildIsmConfigMap( remotes: ChainName[], multisigIsmConfigs: ChainMap, ): ChainMap { - return Object.fromEntries( - chains.map((chain) => { - const ismConfig = buildIsmConfig( - owner, - remotes.filter((r) => r !== chain), - multisigIsmConfigs, - ); - return [chain, ismConfig]; - }), - ); + return chains.reduce>((config, chain) => { + config[chain] = buildIsmConfig( + owner, + remotes.filter((r) => r !== chain), + multisigIsmConfigs, + ); + return config; + }, {}); } function buildCoreConfigMap( owner: Address, origin: ChainName, - remotes: ChainName[], - multisigIsmConfigs: ChainMap, + defaultIsms: ChainMap
, ): ChainMap { const configMap: ChainMap = {}; configMap[origin] = { owner, - defaultIsm: buildIsmConfig(owner, remotes, multisigIsmConfigs), + defaultIsm: defaultIsms[origin], }; return configMap; } @@ -380,6 +381,11 @@ function buildTestRecipientConfigMap( addressesMap[chain].multisigIsm ?? addressesMap[chain].interchainSecurityModule ?? ethers.constants.AddressZero; + if (interchainSecurityModule === ethers.constants.AddressZero) { + logRed( + 'Error: No ISM provided to the TestRecipient, deploying with zero address', + ); + } return [chain, { interchainSecurityModule }]; }), ); From b39eaa09b33ef43b76789633061fd5bec923889a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 12 Oct 2023 19:35:27 -0400 Subject: [PATCH 77/82] Remove console log line --- typescript/cli/src/deploy/core.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 0912de5cf5..b62d623305 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -285,7 +285,6 @@ async function executeDeploy({ const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); coreDeployer.cacheAddressesMap(artifacts); const coreConfig = buildCoreConfigMap(owner, origin, defaultIsms); - console.log('===coreConfig', coreConfig); const coreContracts = await coreDeployer.deploy(coreConfig); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); logGreen('Core contracts deployed'); From b45c9e40d7eb8c577cc58d5d843c0f4a3d2f18e9 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Thu, 12 Oct 2023 21:52:05 -0400 Subject: [PATCH 78/82] Run relayer properly --- typescript/cli/ci-test.sh | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 694d43d7f3..540514a6f2 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -122,24 +122,22 @@ cat $CORE_ARTIFACTS_FILE echo "Validator Announcement:" cat /tmp/anvil1/validator/announcement.json -for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" -do - set -- $i - echo "Running relayer on $1" - docker run \ - --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} \ - -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ - -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ - -e HYP_BASE_TRACING_LEVEL=warn -e HYP_BASE_TRACING_FMT=pretty \ - -e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ - -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/$1/relayer \ - -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ - -e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ - -e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - -log-driver none \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & -done + +echo "Running relayer on $1" +docker run \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} \ + -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ + -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ + -e HYP_BASE_TRACING_LEVEL=warn -e HYP_BASE_TRACING_FMT=pretty \ + -e HYP_RELAYER_RELAYCHAINS=anvil1,anvil2 \ + -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/relayer \ + -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ + -e HYP_BASE_CHAINS_ANVIL1_SIGNER_TYPE=hexKey \ + -e HYP_BASE_CHAINS_ANVIL1_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ + -e HYP_BASE_CHAINS_ANVIL2_SIGNER_TYPE=hexKey \ + -e HYP_BASE_CHAINS_ANVIL2_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & sleep 5 echo "Done running relayer, checking message delivery statuses" From 18586b656f563102f166deb02d989e9e4d62a625 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 13 Oct 2023 18:55:10 -0400 Subject: [PATCH 79/82] Fix missing ISM domain for secondary PI chains --- typescript/cli/src/deploy/core.ts | 88 +++++++++++-------------------- 1 file changed, 31 insertions(+), 57 deletions(-) diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index b62d623305..01db852787 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -232,21 +232,16 @@ async function executeDeploy({ artifacts, ismFactoryContracts, ); - logGreen(`ISM factory contracts deployed`); + logGreen('ISM factory contracts deployed'); // 2. Deploy IGPs to all deployable chains. - log(`Deploying IGP contracts`); - const igpConfig = buildIgpConfigMap( - owner, - selectedChains, - selectedChains, - multisigConfig, - ); + log('Deploying IGP contracts'); + const igpConfig = buildIgpConfigMap(owner, selectedChains, multisigConfig); const igpDeployer = new HyperlaneIgpDeployer(multiProvider); igpDeployer.cacheAddressesMap(artifacts); const igpContracts = await igpDeployer.deploy(igpConfig); artifacts = writeMergedAddresses(contractsFilePath, artifacts, igpContracts); - logGreen(`IGP contracts deployed`); + logGreen('IGP contracts deployed'); // Build an IsmFactory that covers all chains so that we can // use it to deploy ISMs to remote chains. @@ -257,25 +252,24 @@ async function executeDeploy({ // 3. Deploy ISM contracts to remote deployable chains log('Deploying ISMs'); - const ismConfigs = buildIsmConfigMap( - owner, - selectedChains, - remotes, - multisigConfig, - ); const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; const defaultIsms: ChainMap
= {}; - for (const [ismChain, ismConfig] of Object.entries(ismConfigs)) { - if (artifacts[ismChain].multisigIsm) { - log(`ISM contract recovered, skipping ISM deployment to ${ismChain}`); - defaultIsms[ismChain] = artifacts[ismChain].multisigIsm; + for (const ismOrigin of selectedChains) { + if (artifacts[ismOrigin].multisigIsm) { + log(`ISM contract recovered, skipping ISM deployment to ${ismOrigin}`); + defaultIsms[ismOrigin] = artifacts[ismOrigin].multisigIsm; continue; } - log(`Deploying ISM to ${ismChain}`); - ismContracts[ismChain] = { - multisigIsm: await ismFactory.deploy(ismChain, ismConfig), + log(`Deploying ISM to ${ismOrigin}`); + const ismConfig = buildIsmConfig( + owner, + selectedChains.filter((r) => r !== ismOrigin), + multisigConfig, + ); + ismContracts[ismOrigin] = { + multisigIsm: await ismFactory.deploy(ismOrigin, ismConfig), }; - defaultIsms[ismChain] = ismContracts[ismChain].multisigIsm.address; + defaultIsms[ismOrigin] = ismContracts[ismOrigin].multisigIsm.address; } artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); logGreen('ISM contracts deployed'); @@ -305,7 +299,7 @@ async function executeDeploy({ artifacts, testRecipients, ); - logGreen(`Test recipient contracts deployed`); + logGreen('Test recipient contracts deployed'); log('Writing agent configs'); await writeAgentConfig( @@ -340,22 +334,6 @@ function buildIsmConfig( }; } -function buildIsmConfigMap( - owner: Address, - chains: ChainName[], - remotes: ChainName[], - multisigIsmConfigs: ChainMap, -): ChainMap { - return chains.reduce>((config, chain) => { - config[chain] = buildIsmConfig( - owner, - remotes.filter((r) => r !== chain), - multisigIsmConfigs, - ); - return config; - }, {}); -} - function buildCoreConfigMap( owner: Address, origin: ChainName, @@ -373,26 +351,22 @@ function buildTestRecipientConfigMap( chains: ChainName[], addressesMap: HyperlaneAddressesMap, ): ChainMap { - return Object.fromEntries( - chains.map((chain) => { - const interchainSecurityModule = - // TODO revisit assumption that multisigIsm is always the ISM - addressesMap[chain].multisigIsm ?? - addressesMap[chain].interchainSecurityModule ?? - ethers.constants.AddressZero; - if (interchainSecurityModule === ethers.constants.AddressZero) { - logRed( - 'Error: No ISM provided to the TestRecipient, deploying with zero address', - ); - } - return [chain, { interchainSecurityModule }]; - }), - ); + return chains.reduce>((config, chain) => { + const interchainSecurityModule = + // TODO revisit assumption that multisigIsm is always the ISM + addressesMap[chain].multisigIsm ?? + addressesMap[chain].interchainSecurityModule ?? + ethers.constants.AddressZero; + if (interchainSecurityModule === ethers.constants.AddressZero) { + logRed('Error: No ISM for TestRecipient, deploying with zero address'); + } + config[chain] = { interchainSecurityModule }; + return config; + }, {}); } function buildIgpConfigMap( owner: Address, - deployChains: ChainName[], selectedChains: ChainName[], multisigIsmConfigs: ChainMap, ): ChainMap { @@ -401,7 +375,7 @@ function buildIgpConfigMap( multisigIsmConfigs, ); const configMap: ChainMap = {}; - for (const origin of deployChains) { + for (const origin of selectedChains) { const overhead: ChainMap = {}; const gasOracleType: ChainMap = {}; for (const remote of selectedChains) { From 5120610fdfeca62b1b2d9c6d4d3cdd7eca6f3564 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 14 Oct 2023 01:25:54 -0400 Subject: [PATCH 80/82] Improvements and fixes to ci script --- typescript/cli/ci-test.sh | 84 +++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 540514a6f2..a2fef1e837 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -1,18 +1,20 @@ +#!/usr/bin/env bash + +# Optional cleanup for previous runs, useful when running locally +pkill -f anvil +docker ps -aq | xargs docker stop | xargs docker rm +rm -rf /tmp/anvil* +rm -rf /tmp/relayer + +# Setup directories for anvil chains for CHAIN in anvil1 anvil2 do - mkdir /tmp/$CHAIN \ - /tmp/$CHAIN/state \ - /tmp/$CHAIN/validator \ - /tmp/$CHAIN/relayer && \ - chmod 777 /tmp/$CHAIN -R + mkdir -p /tmp/$CHAIN /tmp/$CHAIN/state /tmp/$CHAIN/validator /tmp/relayer + chmod -R 777 /tmp/relayer /tmp/$CHAIN done -anvil --chain-id 31337 -p 8545 --state /tmp/anvil1/state > /dev/null & -ANVIL_1_PID=$! - -anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state > /dev/null & -ANVIL_2_PID=$! - +anvil --chain-id 31337 -p 8545 --state /tmp/anvil1/state --block-time 1 > /dev/null & +anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state --block-time 1 > /dev/null & sleep 1 set -e @@ -29,24 +31,28 @@ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -CORE_ARTIFACTS_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` +CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` echo "Deploying contracts to anvil2" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ - --artifacts $CORE_ARTIFACTS_FILE \ + --artifacts $CORE_ARTIFACTS_PATH \ --out /tmp \ --ism ./examples/multisig-ism.yaml \ --origin anvil2 --remotes anvil1 \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes -CORE_ARTIFACTS_FILE=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` +CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` +echo "Core artifacts:" +cat $CORE_ARTIFACTS_PATH + +AGENT_CONFIG_FILENAME=`ls -t1 /tmp | grep agent-config | head -1` echo "Deploying warp routes" yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ --chains ./examples/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_FILE \ + --core $CORE_ARTIFACTS_PATH \ --config ./examples/warp-tokens.yaml \ --out /tmp \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ @@ -57,7 +63,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send message \ --origin anvil1 \ --destination anvil2 \ --chains ./examples/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_FILE \ + --core $CORE_ARTIFACTS_PATH \ --quick \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ | tee /tmp/message1 @@ -73,7 +79,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ --origin anvil1 \ --destination anvil2 \ --chains ./examples/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_FILE \ + --core $CORE_ARTIFACTS_PATH \ --router $ANVIL1_ROUTER \ --type native \ --quick \ @@ -83,32 +89,27 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ MESSAGE2_ID=`cat /tmp/message2 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` echo "Message 2 ID: $MESSAGE2_ID" -kill $ANVIL_1_PID -kill $ANVIL_2_PID - -anvil --chain-id 31337 -p 8545 --block-time 1 --state /tmp/anvil1/state > /dev/null & -ANVIL_1_PID=$! - -anvil --chain-id 31338 -p 8555 --block-time 1 --state /tmp/anvil2/state > /dev/null & -ANVIL_2_PID=$! - -AGENT_CONFIG_FILE=`ls -t1 /tmp | grep agent-config | head -1` +if [[ $OSTYPE == 'darwin'* ]]; then + # Required because the -net=host driver only works on linux + DOCKER_CONNECTION_URL="http://host.docker.internal" +else + DOCKER_CONNECTION_URL="http://127.0.0.1" +fi for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" do set -- $i echo "Running validator on $1" - # Won't work on anything but linux due to -net=host docker run \ --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILENAME} -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ - -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ + -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=${DOCKER_CONNECTION_URL}:${2} \ -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ - -e HYP_BASE_TRACING_LEVEL=warn -e HYP_BASE_TRACING_FMT=pretty \ + -e HYP_BASE_TRACING_LEVEL=debug -e HYP_BASE_TRACING_FMT=compact \ gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & done @@ -116,20 +117,18 @@ echo "Validator running, sleeping to let it sync" sleep 15 echo "Done sleeping" -echo "Core artifacts:" -cat $CORE_ARTIFACTS_FILE - echo "Validator Announcement:" cat /tmp/anvil1/validator/announcement.json - -echo "Running relayer on $1" +echo "Running relayer" +# Won't work on anything but linux due to -net=host +# Replace CONNECTION_URL with host.docker.internal on mac docker run \ --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/data/${AGENT_CONFIG_FILE} \ - -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ - -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ - -e HYP_BASE_TRACING_LEVEL=warn -e HYP_BASE_TRACING_FMT=pretty \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILENAME} \ + -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=${DOCKER_CONNECTION_URL}:8545 \ + -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=${DOCKER_CONNECTION_URL}:8555 \ + -e HYP_BASE_TRACING_LEVEL=debug -e HYP_BASE_TRACING_FMT=compact \ -e HYP_RELAYER_RELAYCHAINS=anvil1,anvil2 \ -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/relayer \ -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ @@ -150,7 +149,7 @@ do --id $2 \ --destination anvil2 \ --chains ./examples/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_FILE \ + --core $CORE_ARTIFACTS_PATH \ | tee /tmp/message-status-$1 if ! grep -q "$2 was delivered" /tmp/message-status-$1; then echo "ERROR: Message $1 was not delivered" @@ -161,5 +160,4 @@ do done docker ps -aq | xargs docker stop | xargs docker rm -kill $ANVIL_1_PID -kill $ANVIL_2_PID +pkill -f anvil From 8c6d6f0f20de698691dd4a0685bffa6119b8ccdc Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 14 Oct 2023 01:37:32 -0400 Subject: [PATCH 81/82] Route validator and relayer logs to file --- typescript/cli/ci-test.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index a2fef1e837..996dd42e8e 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -13,6 +13,7 @@ do chmod -R 777 /tmp/relayer /tmp/$CHAIN done +# Optional: remove the --block-time 1 to speedup tests for local runs anvil --chain-id 31337 -p 8545 --state /tmp/anvil1/state --block-time 1 > /dev/null & anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state --block-time 1 > /dev/null & sleep 1 @@ -110,7 +111,7 @@ do -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ -e HYP_BASE_TRACING_LEVEL=debug -e HYP_BASE_TRACING_FMT=compact \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator > /tmp/${1}/validator-logs.txt & done echo "Validator running, sleeping to let it sync" @@ -136,7 +137,7 @@ docker run \ -e HYP_BASE_CHAINS_ANVIL1_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ -e HYP_BASE_CHAINS_ANVIL2_SIGNER_TYPE=hexKey \ -e HYP_BASE_CHAINS_ANVIL2_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer & + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer > /tmp/relayer/relayer-logs.txt & sleep 5 echo "Done running relayer, checking message delivery statuses" From 80086bfd8ca646bdea7081837f75beb635206aa0 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 14 Oct 2023 16:56:48 -0400 Subject: [PATCH 82/82] Add comments for things to address during v3 updates --- typescript/cli/examples/multisig-ism.yaml | 4 ++-- typescript/cli/examples/warp-tokens.yaml | 4 +++- typescript/cli/logger.ts | 2 +- typescript/cli/src/config/multisig.ts | 3 +++ typescript/cli/src/consts.ts | 1 + typescript/cli/src/deploy/TestRecipientDeployer.ts | 1 + typescript/cli/src/deploy/warp.ts | 1 + typescript/cli/src/send/message.ts | 1 + 8 files changed, 13 insertions(+), 4 deletions(-) diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml index b2adb91f5b..1eca411dff 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/multisig-ism.yaml @@ -9,12 +9,12 @@ # message_id_multisig --- anvil1: - type: 'legacy_multisig' + type: 'legacy_multisig' # TODO: update for v3 threshold: 1 # Number: Signatures required to approve a message validators: # Array: List of validator addresses - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' anvil2: - type: 'legacy_multisig' + type: 'legacy_multisig' # TODO: update for v3 threshold: 1 validators: - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml index 4a1f5de3ee..24c31a7562 100644 --- a/typescript/cli/examples/warp-tokens.yaml +++ b/typescript/cli/examples/warp-tokens.yaml @@ -5,8 +5,10 @@ # native # collateral # synthetic -# syntheticUri # collateralUri +# syntheticUri +# fastCollateral +# fastSynthetic --- base: chainName: anvil1 diff --git a/typescript/cli/logger.ts b/typescript/cli/logger.ts index 860c3c6ace..f40f6822c4 100644 --- a/typescript/cli/logger.ts +++ b/typescript/cli/logger.ts @@ -26,7 +26,7 @@ export function createLogger(namespace: string, isError = false) { return isError ? error.extend(namespace) : logger.extend(namespace); } -// Ensure hyperlane logging is enabled +// Ensure hyperlane logging is enabled by forcing inclusion of hyperlane namespace const activeNamespaces = debug.disable(); const otherNamespaces = activeNamespaces .split(',') diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index 9f71bbc3f0..a65280c405 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -72,6 +72,9 @@ export async function createMultisigConfig({ result[chain] = lastConfig; continue; } + // TODO consider using default and not offering options here + // legacy_multisig is being deprecated in v3 + // Default should probably be aggregation(message_id, merkle_root) const moduleType = await select({ message: 'Select multisig type', choices: [ diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index d387877801..2b93a4eb55 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,3 +1,4 @@ +// TODO revisit these rough balance requirements with more precise measurements export const MINIMUM_CORE_DEPLOY_BALANCE = '500000000000000000'; // 0.5 ETH export const MINIMUM_WARP_DEPLOY_BALANCE = '200000000000000000'; // 0.2 Eth export const MINIMUM_TEST_SEND_BALANCE = '10000000000000000'; // 0.01 ETH diff --git a/typescript/cli/src/deploy/TestRecipientDeployer.ts b/typescript/cli/src/deploy/TestRecipientDeployer.ts index ecbd2af290..a62505bdd9 100644 --- a/typescript/cli/src/deploy/TestRecipientDeployer.ts +++ b/typescript/cli/src/deploy/TestRecipientDeployer.ts @@ -24,6 +24,7 @@ export const testRecipientFactories = { testRecipient: new TestRecipient__factory(), }; +// TODO move this and related configs to the SDK export class TestRecipientDeployer extends HyperlaneDeployer< TestRecipientConfig, typeof testRecipientFactories diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 059aabb34f..e2ffc81d12 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -258,6 +258,7 @@ async function executeDeploy(params: DeployParams) { logBlue(`Warp UI token config is in ${tokenConfigPath}`); } +// TODO move into token classes in the SDK async function fetchBaseTokenMetadata( base: WarpRouteConfig['base'], multiProvider: MultiProvider, diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index f83ac593bf..b37291f7f0 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -111,6 +111,7 @@ async function executeDelivery({ logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); + // TODO requires update for v3 const value = await igp.quoteGasPaymentForDefaultIsmIgp( origin, destination,