diff --git a/.changeset/cuddly-fishes-repair.md b/.changeset/cuddly-fishes-repair.md new file mode 100644 index 0000000000..196871cf5a --- /dev/null +++ b/.changeset/cuddly-fishes-repair.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Support configuring non-EVM IGP destinations diff --git a/.changeset/funny-pans-return.md b/.changeset/funny-pans-return.md new file mode 100644 index 0000000000..d6aa05e5f3 --- /dev/null +++ b/.changeset/funny-pans-return.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/infra': patch +'@hyperlane-xyz/sdk': patch +--- + +Removed basegoerli and moonbasealpha testnets diff --git a/.changeset/green-pans-unite.md b/.changeset/green-pans-unite.md new file mode 100644 index 0000000000..71791ee776 --- /dev/null +++ b/.changeset/green-pans-unite.md @@ -0,0 +1,13 @@ +--- +'@hyperlane-xyz/helloworld': minor +'@hyperlane-xyz/infra': minor +'@hyperlane-xyz/sdk': minor +'@hyperlane-xyz/core': minor +--- + +Enabled verification of contracts as part of the deployment flow. + +- Solidity build artifact is now included as part of the `@hyperlane-xyz/core` package. +- Updated the `HyperlaneDeployer` to perform contract verification immediately after deploying a contract. A default verifier is instantiated using the core build artifact. +- Updated the `HyperlaneIsmFactory` to re-use the `HyperlaneDeployer` for deployment where possible. +- Minor logging improvements throughout deployers. diff --git a/.changeset/lazy-guests-enjoy.md b/.changeset/lazy-guests-enjoy.md new file mode 100644 index 0000000000..2632f04385 --- /dev/null +++ b/.changeset/lazy-guests-enjoy.md @@ -0,0 +1,8 @@ +--- +'@hyperlane-xyz/utils': minor +'@hyperlane-xyz/sdk': minor +--- + +Add `WarpCore`, `Token`, and `TokenAmount` classes for interacting with Warp Route instances. + +_Breaking change_: The params to the `IHypTokenAdapter` `populateTransferRemoteTx` method have changed. `txValue` has been replaced with `interchainGas`. diff --git a/.changeset/modern-adults-sell.md b/.changeset/modern-adults-sell.md new file mode 100644 index 0000000000..b498a83f9c --- /dev/null +++ b/.changeset/modern-adults-sell.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/infra': patch +'@hyperlane-xyz/sdk': patch +--- + +Add logos for plume to SDK diff --git a/.changeset/modern-dryers-jump.md b/.changeset/modern-dryers-jump.md new file mode 100644 index 0000000000..0d2ba2e495 --- /dev/null +++ b/.changeset/modern-dryers-jump.md @@ -0,0 +1,7 @@ +--- +'@hyperlane-xyz/infra': patch +'@hyperlane-xyz/cli': patch +'@hyperlane-xyz/sdk': patch +--- + +TestRecipient as part of core deployer diff --git a/.changeset/olive-starfishes-exercise.md b/.changeset/olive-starfishes-exercise.md new file mode 100644 index 0000000000..f28e22c811 --- /dev/null +++ b/.changeset/olive-starfishes-exercise.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/infra': patch +'@hyperlane-xyz/sdk': patch +--- + +Update viction validator set diff --git a/.changeset/orange-kangaroos-allow.md b/.changeset/orange-kangaroos-allow.md new file mode 100644 index 0000000000..636a000e94 --- /dev/null +++ b/.changeset/orange-kangaroos-allow.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Minor fixes for SDK cosmos logos diff --git a/.eslintrc b/.eslintrc index c1ce429fda..0855bdf61b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,7 @@ "no-extra-boolean-cast": ["error"], "no-ex-assign": ["error"], "no-constant-condition": ["off"], + "guard-for-in": ["error"], "@typescript-eslint/ban-ts-comment": ["off"], "@typescript-eslint/explicit-module-boundary-types": ["off"], "@typescript-eslint/no-explicit-any": ["off"], diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..bd0d1d21a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,28 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' +--- + +## Problem + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Additional context** +Add any other context or screenshots about the feature request here. + +## Solution + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +## Nice to Have + +**Describe non-essential extensions to the solution** +Additional features which should be implemented if they are easy to accommodate but otherwise can be skipped diff --git a/.github/workflows/agent-release-artifacts.yml b/.github/workflows/agent-release-artifacts.yml index 79c421f22a..2827120989 100644 --- a/.github/workflows/agent-release-artifacts.yml +++ b/.github/workflows/agent-release-artifacts.yml @@ -49,7 +49,7 @@ jobs: run: | sudo apt-get update -qq sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf - + # some additional configuration for cross-compilation on linux cat >>~/.cargo/config < layout.diff + + - name: Comment PR with layout diff + uses: yorhodes/actions-comment-pull-request@v2.4.6 + with: + header: Storage Layout Diff + filePath: layout.diff + mdLanguage: diff + comment_tag: storagelayoutdiff diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..868b037dbc --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,364 @@ +name: test + +on: + # Triggers the workflow on push or pull request against main + push: + branches: [main] + pull_request: + branches: [main] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'main' }} + +env: + DEBUG: 'hyperlane:*' + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +jobs: + yarn-install: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + 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: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + fetch-depth: 0 + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: build + run: yarn build + + lint-prettier: + runs-on: ubuntu-latest + needs: [yarn-install] + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + # check out full history + fetch-depth: 0 + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: lint + run: yarn lint + + - name: prettier + run: | + yarn prettier + CHANGES=$(git status -s) + if [[ ! -z $CHANGES ]]; then + echo "Changes found: $CHANGES" + exit 1 + fi + + yarn-test: + runs-on: ubuntu-latest + needs: [yarn-build] + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + fetch-depth: 0 + + - name: foundry-install + uses: onbjerg/foundry-toolchain@v1 + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Unit Tests + run: yarn test:ci + + e2e-matrix: + runs-on: larger-runner + needs: [yarn-build] + strategy: + matrix: + e2e-type: [cosmwasm, non-cosmwasm] + steps: + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + + - name: foundry-install + uses: onbjerg/foundry-toolchain@v1 + + - name: setup rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + + - name: Free disk space + run: | + # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - name: Install mold linker + uses: rui314/setup-mold@v1 + with: + mold-version: 2.0.0 + make-default: true + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: cargo-cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo + key: ${{ runner.os }}-cargo-cache-${{ hashFiles('./rust/Cargo.lock') }} + + - name: agent tests with CosmWasm + run: cargo test --release --package run-locally --bin run-locally --features cosmos -- cosmos::test --nocapture + if: matrix.e2e-type == 'cosmwasm' + working-directory: ./rust + env: + RUST_BACKTRACE: 1 + + - name: agent tests excluding CosmWasm + run: cargo run --release --bin run-locally + if: matrix.e2e-type == 'non-cosmwasm' + working-directory: ./rust + env: + E2E_CI_MODE: 'true' + E2E_CI_TIMEOUT_SEC: '600' + E2E_KATHY_MESSAGES: '20' + + e2e: + runs-on: ubuntu-latest + needs: [e2e-matrix] + if: always() # This ensures that the job runs even if the e2e jobs fail + steps: + - name: Report Matrix Result + run: | + echo "All e2e-matrix jobs have completed." + # You can add additional commands here to report the result as needed + + cli-e2e: + runs-on: larger-runner + needs: [yarn-build] + strategy: + matrix: + include: + - hook: preset_hook_enabled + - hook: configure_hook_enabled + steps: + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + + - name: foundry-install + uses: onbjerg/foundry-toolchain@v1 + + - name: setup rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + + - name: Free disk space + run: | + # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - name: Install mold linker + uses: rui314/setup-mold@v1 + with: + mold-version: 2.0.0 + make-default: true + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: cargo-cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo + key: ${{ runner.os }}-cargo-cache-${{ hashFiles('./rust/Cargo.lock') }} + + - name: cli e2e tests with preset hook config + run: ./typescript/cli/ci-test.sh false + if: matrix.hook == 'preset_hook_enabled' + + - name: cli e2e tests with configured hook config + run: ./typescript/cli/ci-test.sh true + if: matrix.hook == 'configure_hook_enabled' + + env-test: + runs-on: ubuntu-latest + needs: [yarn-build] + strategy: + matrix: + environment: [mainnet3] + chain: [ethereum, arbitrum, optimism, inevm, viction] + module: [core, igp] + include: + - environment: testnet4 + chain: sepolia + module: core + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: foundry-install + uses: onbjerg/foundry-toolchain@v1 + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Fork test ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} deployment + run: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} + + coverage: + runs-on: ubuntu-latest + needs: [yarn-test] + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: foundry-install + uses: onbjerg/foundry-toolchain@v1 + + - name: Run tests with coverage + run: yarn coverage + env: + NODE_OPTIONS: --max_old_space_size=4096 + + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/Dockerfile b/Dockerfile index a3e6049616..4463402489 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:18-alpine WORKDIR /hyperlane-monorepo -RUN apk add --update --no-cache git g++ make py3-pip +RUN apk add --update --no-cache git g++ make py3-pip jq RUN yarn set version 4.0.1 diff --git a/README.md b/README.md index 927ea7cda2..de21d97f42 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,24 @@ To read more about interchain applications, how the protocol works, and how to i ## Working on Hyperlane +### Foundry + +First ensure you have Foundry installed on your machine. + +Run the following to install `foundryup`: + +```bash +curl -L https://foundry.paradigm.xyz | bash +``` + +Then run `foundryup` to install `forge`, `cast`, `anvil` and `chisel`. + +```bash +foundryup +``` + +Check out the [Foundry Book](https://book.getfoundry.sh/getting-started/installation) for more information. + ### Workspaces This monorepo uses [Yarn Workspaces](https://yarnpkg.com/features/workspaces). Installing dependencies, building, testing, and running prettier for all packages can be done from the root directory of the repository. diff --git a/package.json b/package.json index fa69684d30..38f5720097 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,11 @@ "build": "yarn workspaces foreach --all --parallel --topological run build", "clean": "yarn workspaces foreach --all --parallel run clean", "prettier": "yarn workspaces foreach --since --parallel run prettier", - "lint": "yarn workspaces foreach --since --parallel run lint", - "test": "yarn workspaces foreach --since --parallel run test", - "coverage": "yarn workspaces foreach --since --parallel run coverage", - "version:prepare": "yarn changeset version && yarn install --no-immutable", + "lint": "yarn workspaces foreach --all --parallel run lint", + "test": "yarn workspaces foreach --all --parallel run test", + "test:ci": "yarn workspaces foreach --all --parallel run test:ci", + "coverage": "yarn workspaces foreach --all --parallel run coverage", + "version:prepare": "yarn changeset version && yarn workspaces foreach --all --parallel run version:update && yarn install --no-immutable", "version:check": "yarn changeset status", "release": "yarn build && yarn changeset publish", "postinstall": "husky install" diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8d1b909db0..a963320d9c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -108,16 +108,16 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "once_cell", @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" dependencies = [ "anstyle", "anstyle-parse", @@ -207,37 +207,37 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "arrayref" @@ -279,8 +279,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", "synstructure", ] @@ -291,8 +291,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -325,6 +325,16 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-rwlock" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c" +dependencies = [ + "async-mutex", + "event-listener", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -342,20 +352,20 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -380,9 +390,9 @@ dependencies = [ [[package]] name = "atomic-polyfill" -version = "0.1.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" dependencies = [ "critical-section", ] @@ -405,8 +415,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -417,8 +427,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -450,7 +460,11 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", "sync_wrapper", + "tokio", "tower", "tower-layer", "tower-service", @@ -504,8 +518,8 @@ checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -551,9 +565,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -584,6 +598,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "bigdecimal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06619be423ea5bb86c95f087d5707942791a08a85530df0db2209a3ecfb8bc9" +dependencies = [ + "autocfg", + "libm", + "num-bigint 0.4.4", + "num-integer", + "num-traits", +] + [[package]] name = "bincode" version = "1.3.3" @@ -606,12 +633,12 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "regex", "rustc-hash", "shlex", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -728,9 +755,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bnum" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" +checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" [[package]] name = "borrown" @@ -750,11 +777,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf617fabf5cdbdc92f774bfe5062d870f228b80056d41180797abf48bed4056e" +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" dependencies = [ - "borsh-derive 1.2.0", + "borsh-derive 1.3.1", "cfg_aliases", ] @@ -767,21 +794,21 @@ dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.69", + "proc-macro2 1.0.76", "syn 1.0.109", ] [[package]] name = "borsh-derive" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f404657a7ea7b5249e36808dff544bc88a28f26e0ac40009f674b7a009d14be3" +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" dependencies = [ "once_cell", - "proc-macro-crate 2.0.0", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro-crate 3.0.0", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", "syn_derive", ] @@ -791,8 +818,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -802,8 +829,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -888,8 +915,8 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -908,9 +935,9 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -970,9 +997,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" dependencies = [ "serde", ] @@ -1067,13 +1094,13 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.1", ] [[package]] @@ -1110,9 +1137,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.8" +version = "4.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253" dependencies = [ "clap_builder", "clap_derive 4.4.7", @@ -1120,9 +1147,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa" dependencies = [ "anstream", "anstyle", @@ -1138,8 +1165,8 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -1150,9 +1177,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -1170,6 +1197,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "cobs" version = "0.2.3" @@ -1186,7 +1222,7 @@ dependencies = [ "bs58 0.4.0", "coins-core", "digest 0.10.7", - "getrandom 0.2.11", + "getrandom 0.2.12", "hmac 0.12.1", "k256 0.11.6", "lazy_static", @@ -1203,7 +1239,7 @@ checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" dependencies = [ "bitvec 0.17.4", "coins-bip32", - "getrandom 0.2.11", + "getrandom 0.2.12", "hex 0.4.3", "hmac 0.12.1", "pbkdf2 0.11.0", @@ -1281,9 +1317,9 @@ dependencies = [ [[package]] name = "config" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" dependencies = [ "async-trait", "json5", @@ -1300,15 +1336,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -1339,9 +1375,9 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" @@ -1394,9 +1430,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -1404,9 +1440,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cosmos-sdk-proto" @@ -1414,10 +1450,10 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" dependencies = [ - "prost", + "prost 0.11.9", "prost-types", "tendermint-proto 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tonic", + "tonic 0.9.2", ] [[package]] @@ -1429,8 +1465,8 @@ dependencies = [ "cosmos-sdk-proto", "ecdsa 0.16.9", "eyre", - "getrandom 0.2.11", - "k256 0.13.2", + "getrandom 0.2.12", + "k256 0.13.3", "rand_core 0.6.4", "serde", "serde_json", @@ -1443,32 +1479,32 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" +checksum = "8ed6aa9f904de106fa16443ad14ec2abe75e94ba003bb61c681c0e43d4c58d2a" dependencies = [ "digest 0.10.7", "ecdsa 0.16.9", "ed25519-zebra", - "k256 0.13.2", + "k256 0.13.3", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" +checksum = "40abec852f3d4abec6d44ead9a58b78325021a1ead1e7229c3471414e57b2e49" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" +checksum = "b166215fbfe93dc5575bae062aa57ae7bb41121cffe53bac33b033257949d2a9" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -1479,22 +1515,22 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43609e92ce1b9368aa951b334dd354a2d0dd4d484931a5f83ae10e12a26c8ba9" +checksum = "8bf12f8e20bb29d1db66b7ca590bc2f670b548d21e9be92499bc0f9022a994a8" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" +checksum = "ad011ae7447188e26e4a7dbca2fcd0fc186aa21ae5c86df0503ea44c78f9e469" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bech32 0.9.1", "bnum", "cosmwasm-crypto", @@ -1506,15 +1542,15 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.8", - "static_assertions", + "static_assertions 1.1.0", "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2b4ae72a03e8f56c85df59d172d51d2d7dc9cec6e2bc811e3fb60c588032a4" +checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" dependencies = [ "cosmwasm-std", "serde", @@ -1531,9 +1567,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -1555,56 +1591,46 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -1708,12 +1734,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" dependencies = [ "nix 0.27.1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1755,9 +1781,9 @@ dependencies = [ [[package]] name = "cw-utils" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9f351a4e4d81ef7c890e44d903f8c0bdcdc00f094fd3a181eaf70c0eec7a3a" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1770,23 +1796,24 @@ dependencies = [ [[package]] name = "cw2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9431d14f64f49e41c6ef5561ed11a5391c417d0cb16455dea8cdcb9037a8d197" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", + "semver", "serde", "thiserror", ] [[package]] name = "cw20" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786e9da5e937f473cecd2463e81384c1af65d0f6398bbd851be7655487c55492" +checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1797,14 +1824,13 @@ dependencies = [ [[package]] name = "cw20-base" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09558f87fd3d5e4a479761051b3f98ee2fa723d9e484b5679b6058ad0eadf8f1" +checksum = "17ad79e86ea3707229bf78df94e08732e8f713207b4a77b2699755596725e7d9" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "cw-utils", "cw2", "cw20", "schemars", @@ -1823,7 +1849,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "static_assertions", + "static_assertions 1.1.0", "thiserror", ] @@ -1837,8 +1863,8 @@ dependencies = [ "darling 0.13.4", "graphql-parser", "once_cell", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "strsim 0.10.0", "syn 1.0.109", ] @@ -1881,8 +1907,8 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "strsim 0.10.0", "syn 1.0.109", ] @@ -1895,8 +1921,8 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "strsim 0.10.0", "syn 1.0.109", ] @@ -1908,7 +1934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core 0.13.4", - "quote 1.0.33", + "quote 1.0.35", "syn 1.0.109", ] @@ -1919,7 +1945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", - "quote 1.0.33", + "quote 1.0.35", "syn 1.0.109", ] @@ -1936,9 +1962,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "der" @@ -1955,7 +1981,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ - "const-oid 0.9.5", + "const-oid 0.9.6", "zeroize", ] @@ -1965,7 +1991,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ - "const-oid 0.9.5", + "const-oid 0.9.6", "zeroize", ] @@ -1985,9 +2011,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -2005,8 +2031,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2016,8 +2042,8 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2037,8 +2063,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2059,8 +2085,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustc_version", "syn 1.0.109", ] @@ -2108,16 +2134,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.5", + "const-oid 0.9.6", "crypto-common", "subtle", ] [[package]] name = "dir-diff" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b" +checksum = "a7ad16bf5f84253b50d6557681c58c3ab67c47c77d39fed9aeb56e947290bd10" dependencies = [ "walkdir", ] @@ -2169,9 +2195,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -2256,14 +2282,14 @@ dependencies = [ "elliptic-curve 0.13.8", "rfc6979 0.4.0", "signature 2.2.0", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] name = "ecdsa-signature" version = "0.1.0" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "hyperlane-core", "solana-program", "thiserror", @@ -2348,8 +2374,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" dependencies = [ "enum-ordinalize", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2449,8 +2475,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8958699f9359f0b04e691a13850d48b7de329138023876d07cbd024c2c820598" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2462,9 +2488,9 @@ checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" dependencies = [ "num-bigint 0.4.4", "num-traits", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -2474,9 +2500,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e" dependencies = [ "once_cell", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -2513,12 +2539,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2571,7 +2597,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "hex 0.4.3", "once_cell", "regex", @@ -2579,7 +2605,20 @@ dependencies = [ "serde_json", "sha3 0.10.8", "thiserror", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "ethbloom" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3932e82d64d347a045208924002930dc105a138995ccdc1479d0f05f0359f17c" +dependencies = [ + "crunchy", + "fixed-hash 0.3.2", + "impl-rlp 0.2.1", + "impl-serde 0.2.3", + "tiny-keccak 1.5.0", ] [[package]] @@ -2594,7 +2633,21 @@ dependencies = [ "impl-rlp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "impl-serde 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "scale-info", - "tiny-keccak", + "tiny-keccak 2.0.2", +] + +[[package]] +name = "ethereum-types" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b054df51e53f253837ea422681215b42823c02824bde982699d0dceecf6165a1" +dependencies = [ + "crunchy", + "ethbloom 0.6.4", + "ethereum-types-serialize", + "fixed-hash 0.3.2", + "serde", + "uint 0.5.0", ] [[package]] @@ -2603,20 +2656,29 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ - "ethbloom", + "ethbloom 0.13.0", "fixed-hash 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "impl-codec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "impl-rlp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "impl-serde 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "primitive-types", "scale-info", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "ethereum-types-serialize" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1873d77b32bc1891a79dad925f2acbc318ee942b38b9110f9dbc5fbeffcea350" +dependencies = [ + "serde", ] [[package]] name = "ethers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2630,7 +2692,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "ethers-core", "once_cell", @@ -2641,7 +2703,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -2659,17 +2721,17 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "Inflector", "cfg-if", "dunce", "ethers-core", "eyre", - "getrandom 0.2.11", + "getrandom 0.2.12", "hex 0.4.3", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "regex", "reqwest", "serde", @@ -2683,13 +2745,13 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "ethers-contract-abigen", "ethers-core", "hex 0.4.3", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "serde_json", "syn 1.0.109", ] @@ -2697,7 +2759,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "arrayvec", "bytes", @@ -2711,26 +2773,26 @@ dependencies = [ "k256 0.11.6", "once_cell", "open-fastrlp", - "proc-macro2 1.0.69", + "proc-macro2 1.0.76", "rand 0.8.5", - "rlp", + "rlp 0.5.2", "rlp-derive", "serde", "serde_json", "strum 0.24.1", "syn 1.0.109", "thiserror", - "tiny-keccak", + "tiny-keccak 2.0.2", "unicode-xid 0.2.4", ] [[package]] name = "ethers-etherscan" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "ethers-core", - "getrandom 0.2.11", + "getrandom 0.2.12", "reqwest", "semver", "serde", @@ -2743,7 +2805,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "async-trait", "auto_impl 0.5.0", @@ -2774,7 +2836,9 @@ dependencies = [ "derive-new", "derive_builder", "ethers", + "ethers-core", "futures", + "hyperlane-core", "log", "maplit", "parking_lot 0.12.1", @@ -2782,14 +2846,14 @@ dependencies = [ "prometheus", "serde", "serde_json", - "static_assertions", + "static_assertions 1.1.0", "tokio", ] [[package]] name = "ethers-providers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "async-trait", "auto_impl 1.1.0", @@ -2799,7 +2863,7 @@ dependencies = [ "futures-core", "futures-timer", "futures-util", - "getrandom 0.2.11", + "getrandom 0.2.12", "hashers", "hex 0.4.3", "http", @@ -2825,7 +2889,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-29-02#c9ced035628da59376c369be035facda1648577a" dependencies = [ "async-trait", "coins-bip32", @@ -2866,9 +2930,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", @@ -2914,14 +2978,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", ] [[package]] @@ -2930,6 +2994,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "fixed-hash" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a683d1234507e4f3bf2736eeddf0de1dc65996dc0164d57eba0a74bcf29489" +dependencies = [ + "byteorder", + "heapsize", + "rand 0.5.6", + "rustc-hex", + "static_assertions 0.2.5", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2939,7 +3016,7 @@ dependencies = [ "byteorder", "rand 0.8.5", "rustc-hex", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -2950,7 +3027,7 @@ dependencies = [ "byteorder", "rand 0.8.5", "rustc-hex", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -3005,9 +3082,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -3024,6 +3101,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "fuel-abi-types" version = "0.2.1" @@ -3127,7 +3210,7 @@ dependencies = [ "coins-bip32", "coins-bip39", "fuel-types", - "getrandom 0.2.11", + "getrandom 0.2.12", "lazy_static", "rand 0.8.5", "secp256k1", @@ -3233,8 +3316,8 @@ dependencies = [ "fuel-abi-types", "itertools 0.10.5", "lazy_static", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "regex", "serde_json", "syn 1.0.109", @@ -3266,8 +3349,8 @@ dependencies = [ "fuels-code-gen", "itertools 0.10.5", "lazy_static", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rand 0.8.5", "regex", "serde_json", @@ -3291,7 +3374,7 @@ dependencies = [ "futures", "hex 0.4.3", "itertools 0.10.5", - "proc-macro2 1.0.69", + "proc-macro2 1.0.76", "rand 0.8.5", "regex", "serde", @@ -3375,7 +3458,7 @@ dependencies = [ "hex 0.4.3", "itertools 0.10.5", "lazy_static", - "proc-macro2 1.0.69", + "proc-macro2 1.0.76", "regex", "serde", "serde_json", @@ -3392,9 +3475,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -3407,9 +3490,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -3417,15 +3500,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -3445,9 +3528,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-locks" @@ -3461,26 +3544,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -3490,9 +3573,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -3561,9 +3644,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", @@ -3574,9 +3657,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -3629,9 +3712,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" dependencies = [ "bytes", "fnv", @@ -3679,16 +3762,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.7", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.7", "allocator-api2", ] @@ -3707,7 +3790,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -3716,7 +3799,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "headers-core", "http", @@ -3736,9 +3819,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", @@ -3748,6 +3831,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapsize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" +dependencies = [ + "winapi", +] + [[package]] name = "heck" version = "0.3.3" @@ -3805,9 +3897,9 @@ checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac 0.12.1", ] @@ -3854,33 +3946,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "hpl-interface" -version = "0.0.6-rc3" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9124bd88d88aba044ae790007bcbf25493b93424156e8759523c9c5d02229b0c" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "bech32 0.9.1", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus", - "cw2", - "cw20", - "cw20-base", - "ripemd", - "schemars", - "serde", - "sha2 0.10.8", - "sha3 0.10.8", - "thiserror", + "windows-sys 0.52.0", ] [[package]] @@ -3896,9 +3966,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -3923,11 +3993,21 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -3940,7 +4020,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -3993,7 +4073,9 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.9", + "log", + "rustls 0.21.10", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] @@ -4028,6 +4110,7 @@ name = "hyperlane-base" version = "0.1.0" dependencies = [ "async-trait", + "axum", "backtrace", "backtrace-oneline", "bs58 0.5.0", @@ -4035,11 +4118,13 @@ dependencies = [ "config", "convert_case 0.6.0", "derive-new", + "derive_builder", "ed25519-dalek", "ethers", "ethers-prometheus", "eyre", "fuels", + "futures", "futures-util", "hyperlane-core", "hyperlane-cosmos", @@ -4047,9 +4132,11 @@ dependencies = [ "hyperlane-fuel", "hyperlane-sealevel", "hyperlane-test", - "itertools 0.11.0", + "itertools 0.12.0", + "maplit", "paste", "prometheus", + "reqwest", "rocksdb", "rusoto_core", "rusoto_kms", @@ -4058,7 +4145,7 @@ dependencies = [ "serde", "serde_json", "solana-sdk", - "static_assertions", + "static_assertions 1.1.0", "tempfile", "thiserror", "tokio", @@ -4066,17 +4153,21 @@ dependencies = [ "tracing-error", "tracing-futures", "tracing-subscriber", + "tracing-test", "url", "walkdir", "warp", + "ya-gcp", ] [[package]] name = "hyperlane-core" version = "0.1.0" dependencies = [ + "async-rwlock", "async-trait", "auto_impl 1.1.0", + "bigdecimal 0.4.2", "borsh 0.9.3", "bs58 0.5.0", "bytes", @@ -4089,9 +4180,9 @@ dependencies = [ "ethers-providers", "eyre", "fixed-hash 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "getrandom 0.2.11", + "getrandom 0.2.12", "hex 0.4.3", - "itertools 0.11.0", + "itertools 0.12.0", "num 0.4.1", "num-derive 0.4.1", "num-traits", @@ -4102,9 +4193,10 @@ dependencies = [ "solana-sdk", "strum 0.25.0", "thiserror", - "tiny-keccak", + "tiny-keccak 2.0.2", "tokio", - "uint", + "tracing", + "uint 0.9.5", ] [[package]] @@ -4112,16 +4204,21 @@ name = "hyperlane-cosmos" version = "0.1.0" dependencies = [ "async-trait", - "base64 0.21.5", + "base64 0.21.7", "bech32 0.9.1", "cosmrs", "derive-new", "hex 0.4.3", - "hpl-interface", + "http", "hyper", "hyper-tls", "hyperlane-core", + "hyperlane-cosmwasm-interface", + "injective-protobuf", + "injective-std", + "itertools 0.12.0", "once_cell", + "protobuf", "ripemd", "serde", "serde_json", @@ -4131,12 +4228,34 @@ dependencies = [ "tendermint-rpc", "thiserror", "tokio", - "tonic", + "tonic 0.9.2", "tracing", "tracing-futures", "url", ] +[[package]] +name = "hyperlane-cosmwasm-interface" +version = "0.0.6-rc6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e622014ab94f1e7f0acbe71df7c1384224261e2c76115807aaf24215970942" +dependencies = [ + "bech32 0.9.1", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw2", + "cw20", + "cw20-base", + "ripemd", + "schemars", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", + "thiserror", +] + [[package]] name = "hyperlane-ethereum" version = "0.1.0" @@ -4187,7 +4306,7 @@ dependencies = [ "account-utils", "anyhow", "async-trait", - "base64 0.21.5", + "base64 0.21.7", "borsh 0.9.3", "derive-new", "hyperlane-core", @@ -4220,7 +4339,8 @@ dependencies = [ "bincode", "borsh 0.9.3", "bs58 0.5.0", - "clap 4.4.8", + "clap 4.4.17", + "ethers", "hex 0.4.3", "hyperlane-core", "hyperlane-sealevel-connection-client", @@ -4283,7 +4403,7 @@ dependencies = [ "access-control", "account-utils", "borsh 0.9.3", - "getrandom 0.2.11", + "getrandom 0.2.12", "hyperlane-core", "num-derive 0.4.1", "num-traits", @@ -4324,14 +4444,14 @@ version = "0.1.0" dependencies = [ "access-control", "account-utils", - "base64 0.21.5", + "base64 0.21.7", "blake3", "borsh 0.9.3", - "getrandom 0.2.11", + "getrandom 0.2.12", "hyperlane-core", "hyperlane-sealevel-interchain-security-module-interface", "hyperlane-sealevel-message-recipient-interface", - "itertools 0.11.0", + "itertools 0.12.0", "log", "num-derive 0.4.1", "num-traits", @@ -4348,7 +4468,7 @@ version = "0.1.0" dependencies = [ "access-control", "account-utils", - "base64 0.21.5", + "base64 0.21.7", "borsh 0.9.3", "hyperlane-core", "hyperlane-sealevel-interchain-security-module-interface", @@ -4357,7 +4477,7 @@ dependencies = [ "hyperlane-sealevel-test-ism", "hyperlane-sealevel-test-send-receiver", "hyperlane-test-utils", - "itertools 0.11.0", + "itertools 0.12.0", "log", "num-derive 0.4.1", "num-traits", @@ -4374,7 +4494,7 @@ name = "hyperlane-sealevel-message-recipient-interface" version = "0.1.0" dependencies = [ "borsh 0.9.3", - "getrandom 0.2.11", + "getrandom 0.2.12", "hyperlane-core", "solana-program", "spl-type-length-value", @@ -4397,6 +4517,7 @@ dependencies = [ "multisig-ism", "num-derive 0.4.1", "num-traits", + "rand 0.8.5", "serializable-account-meta", "solana-program", "solana-program-test", @@ -4596,9 +4717,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -4646,9 +4767,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -4687,13 +4808,22 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f7a72f11830b52333f36e3b09a288333888bf54380fd0ac0790a3c31ab0f3c5" +dependencies = [ + "rlp 0.4.6", +] + [[package]] name = "impl-rlp" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" dependencies = [ - "rlp", + "rlp 0.5.2", ] [[package]] @@ -4701,7 +4831,16 @@ name = "impl-rlp" version = "0.3.0" source = "git+https://github.com/hyperlane-xyz/parity-common.git?branch=hyperlane#3c2a89084ccfc27b82fda29007b4e27215a75cb1" dependencies = [ - "rlp", + "rlp 0.5.2", +] + +[[package]] +name = "impl-serde" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e3cae7e99c7ff5a995da2cf78dd0a5383740eda71d98cf7b1910c301ac69b8" +dependencies = [ + "serde", ] [[package]] @@ -4727,8 +4866,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -4761,7 +4900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -4777,15 +4916,47 @@ dependencies = [ ] [[package]] -name = "inout" -version = "0.1.3" +name = "injective-protobuf" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "a52219a08aba8c17846fd23d472d1d69c817fe5b427d135273e4c7311edd6972" dependencies = [ - "generic-array 0.14.7", -] - -[[package]] + "cosmwasm-std", + "ethereum-types 0.5.2", + "num 0.4.1", + "protobuf", + "protobuf-codegen-pure", + "schemars", + "serde", + "subtle-encoding", +] + +[[package]] +name = "injective-std" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7a5b52d19dca05823c7e4b481d41b49c04a0e56f66a5c92396a6fdd3314710" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive", + "prost 0.11.9", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4805,13 +4976,13 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi 0.3.3", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4825,33 +4996,33 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -4897,9 +5068,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa 0.16.9", @@ -4911,9 +5082,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -4932,9 +5103,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -4946,6 +5117,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.0.1" @@ -5023,9 +5210,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "295c17e837573c8c821dbaeb3cceb3d745ad082f7572191409e69cbc1b3fd050" dependencies = [ "cc", "pkg-config", @@ -5040,9 +5227,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -5155,9 +5342,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -5177,15 +5364,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "merlin" version = "3.0.0" @@ -5244,9 +5422,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -5275,8 +5453,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -5287,7 +5465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" dependencies = [ "modular-bitfield-impl", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -5296,8 +5474,8 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -5359,7 +5537,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -5487,8 +5665,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -5498,9 +5676,9 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -5593,8 +5771,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate 1.2.1", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -5605,9 +5783,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate 1.2.1", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", ] [[package]] @@ -5618,9 +5805,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -5636,9 +5823,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -5661,7 +5848,7 @@ dependencies = [ "arrayvec", "auto_impl 1.1.0", "bytes", - "ethereum-types", + "ethereum-types 0.14.1", "open-fastrlp-derive", ] @@ -5672,16 +5859,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" dependencies = [ "bytes", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -5698,9 +5885,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -5711,9 +5898,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -5756,6 +5943,18 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "osmosis-std-derive" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4d482a16be198ee04e0f94e10dd9b8d02332dcf33bc5ea4b255e7e25eedc5df" +dependencies = [ + "itertools 0.10.5", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 1.0.109", +] + [[package]] name = "ouroboros" version = "0.15.6" @@ -5774,8 +5973,8 @@ checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -5793,9 +5992,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parity-scale-codec" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" dependencies = [ "arrayvec", "bitvec 1.0.1", @@ -5807,13 +6006,13 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro-crate 2.0.0", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -5956,8 +6155,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" dependencies = [ "peg-runtime", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", ] [[package]] @@ -5977,9 +6176,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "percentage" @@ -5992,9 +6191,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" dependencies = [ "memchr", "thiserror", @@ -6003,9 +6202,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" dependencies = [ "pest", "pest_generator", @@ -6013,22 +6212,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] name = "pest_meta" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" dependencies = [ "once_cell", "pest", @@ -6060,9 +6259,9 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -6105,14 +6304,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der 0.7.8", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "plain" @@ -6207,12 +6406,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ - "proc-macro2 1.0.69", - "syn 2.0.39", + "proc-macro2 1.0.76", + "syn 2.0.48", ] [[package]] @@ -6225,7 +6424,7 @@ dependencies = [ "impl-rlp 0.3.0 (git+https://github.com/hyperlane-xyz/parity-common.git?branch=hyperlane)", "impl-serde 0.4.0 (git+https://github.com/hyperlane-xyz/parity-common.git?branch=hyperlane)", "scale-info", - "uint", + "uint 0.9.5", ] [[package]] @@ -6257,6 +6456,15 @@ dependencies = [ "toml_edit 0.20.7", ] +[[package]] +name = "proc-macro-crate" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b2685dd208a3771337d8d386a89840f0f43cd68be8dae90a5f8c2384effc9cd" +dependencies = [ + "toml_edit 0.21.0", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -6264,8 +6472,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", "version_check", ] @@ -6276,8 +6484,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "version_check", ] @@ -6292,9 +6500,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -6324,6 +6532,15 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", +] + [[package]] name = "prost-derive" version = "0.11.9" @@ -6332,8 +6549,8 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -6343,7 +6560,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost", + "prost 0.11.9", ] [[package]] @@ -6351,6 +6568,28 @@ name = "protobuf" version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +dependencies = [ + "bytes", +] + +[[package]] +name = "protobuf-codegen" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" +dependencies = [ + "protobuf", +] + +[[package]] +name = "protobuf-codegen-pure" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a29399fc94bcd3eeaa951c715f7bea69409b2445356b00519740bcd6ddd865" +dependencies = [ + "protobuf", + "protobuf-codegen", +] [[package]] name = "psl-types" @@ -6373,8 +6612,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -6461,11 +6700,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.76", ] [[package]] @@ -6480,6 +6719,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "winapi", +] + [[package]] name = "rand" version = "0.7.3" @@ -6524,6 +6776,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -6539,7 +6806,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", ] [[package]] @@ -6601,15 +6868,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -6625,7 +6883,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "libredox", "thiserror", ] @@ -6687,14 +6945,16 @@ dependencies = [ "ethers", "ethers-contract", "eyre", + "futures", "futures-util", "hyperlane-base", "hyperlane-core", "hyperlane-ethereum", "hyperlane-test", - "itertools 0.11.0", + "itertools 0.12.0", "num-derive 0.4.1", "num-traits", + "once_cell", "prometheus", "regex", "reqwest", @@ -6719,12 +6979,12 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "async-compression", - "base64 0.21.5", + "base64 0.21.7", "bytes", "cookie", "cookie_store", @@ -6745,7 +7005,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.9", + "rustls 0.21.10", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -6760,7 +7020,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", "winreg", ] @@ -6802,12 +7062,12 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom 0.2.11", + "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -6825,12 +7085,13 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.42" +version = "0.7.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" dependencies = [ "bitvec 1.0.1", "bytecheck", + "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -6842,15 +7103,24 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.42" +version = "0.7.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] +[[package]] +name = "rlp" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1190dcc8c3a512f1eef5d09bb8c84c7f39e1054e174d1795482e18f5272f2e73" +dependencies = [ + "rustc-hex", +] + [[package]] name = "rlp" version = "0.5.2" @@ -6866,8 +7136,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -6912,9 +7182,11 @@ dependencies = [ "ctrlc", "eyre", "hex 0.4.3", - "hpl-interface", "hyperlane-core", - "k256 0.13.2", + "hyperlane-cosmos", + "hyperlane-cosmwasm-interface", + "jobserver", + "k256 0.13.3", "macro_rules_attribute", "maplit", "nix 0.26.4", @@ -7057,7 +7329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4" dependencies = [ "arrayvec", - "borsh 1.2.0", + "borsh 1.3.1", "bytes", "num-traits", "rand 0.8.5", @@ -7104,15 +7376,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7142,16 +7414,30 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.5", - "rustls-webpki", + "ring 0.17.7", + "rustls-webpki 0.101.7", "sct 0.7.1", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.1", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.5.0" @@ -7191,16 +7477,33 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", + "ring 0.17.7", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +dependencies = [ + "ring 0.17.7", + "rustls-pki-types", "untrusted 0.9.0", ] @@ -7212,9 +7515,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "salsa20" @@ -7262,18 +7565,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" dependencies = [ "proc-macro-crate 1.2.1", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7294,8 +7597,8 @@ version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "serde_derive_internals", "syn 1.0.109", ] @@ -7326,9 +7629,10 @@ dependencies = [ "hyperlane-base", "hyperlane-core", "hyperlane-test", - "itertools 0.11.0", + "itertools 0.12.0", "migration", "num-bigint 0.4.4", + "num-traits", "prometheus", "sea-orm", "serde", @@ -7356,9 +7660,9 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -7403,7 +7707,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.5", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -7415,7 +7719,7 @@ checksum = "fade86e8d41fd1a4721f84cb834f4ca2783f973cc30e6212b7fafc134f169214" dependencies = [ "async-stream", "async-trait", - "bigdecimal", + "bigdecimal 0.3.1", "chrono", "futures", "log", @@ -7459,8 +7763,8 @@ checksum = "28936f26d62234ff0be16f80115dbdeb3237fe9c25cf18fbcd1e3b3592360f20" dependencies = [ "bae", "heck 0.3.3", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -7487,7 +7791,7 @@ version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbab99b8cd878ab7786157b7eb8df96333a6807cc6e45e8888c85b51534b401a" dependencies = [ - "bigdecimal", + "bigdecimal 0.3.1", "chrono", "rust_decimal", "sea-query-derive", @@ -7502,7 +7806,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cea85029985b40dfbf18318d85fe985c04db7c1b4e5e8e0a0a0cdff5f1e30f9" dependencies = [ - "bigdecimal", + "bigdecimal 0.3.1", "chrono", "rust_decimal", "sea-query", @@ -7519,8 +7823,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63f62030c60f3a691f5fe251713b4e220b306e50a71e1d6f9cce1f24bb781978" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", "thiserror", ] @@ -7543,8 +7847,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56821b7076f5096b8f726e2791ad255a99c82498e08ec477a65a96c461ff1927" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -7564,8 +7868,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", "syn 1.0.109", ] @@ -7657,9 +7961,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" dependencies = [ "serde", ] @@ -7672,23 +7976,32 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde-aux" -version = "4.2.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3dfe1b7eb6f9dcf011bd6fad169cdeaae75eda0d61b1a99a3f015b41b0cae39" +checksum = "a86348501c129f3ad50c2f4635a01971f76974cd8a3f335988a0f1581c082765" dependencies = [ "serde", "serde_json", ] +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.5.1" @@ -7700,22 +8013,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -7724,31 +8037,41 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -7781,8 +8104,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling 0.13.4", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -7866,9 +8189,9 @@ dependencies = [ [[package]] name = "sha256" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" dependencies = [ "async-trait", "bytes", @@ -7976,9 +8299,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" [[package]] name = "socket2" @@ -8289,8 +8612,8 @@ name = "solana-frozen-abi-macro" version = "1.14.13" source = "git+https://github.com/hyperlane-xyz/solana.git?tag=hyperlane-1.14.13-2023-07-04#62a6421cab862c77b9ac7a8d93f54f8b5b223af7" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustc_version", "syn 1.0.109", ] @@ -8392,14 +8715,14 @@ dependencies = [ "console_error_panic_hook", "console_log", "curve25519-dalek", - "getrandom 0.2.11", + "getrandom 0.2.12", "itertools 0.10.5", "js-sys", "lazy_static", "libc", "libsecp256k1", "log", - "memoffset 0.6.5", + "memoffset", "num-derive 0.3.3", "num-traits", "parking_lot 0.12.1", @@ -8433,7 +8756,7 @@ dependencies = [ "enum-iterator", "itertools 0.10.5", "libc", - "libloading", + "libloading 0.7.4", "log", "num-derive 0.3.3", "num-traits", @@ -8615,8 +8938,8 @@ version = "1.14.13" source = "git+https://github.com/hyperlane-xyz/solana.git?tag=hyperlane-1.14.13-2023-07-04#62a6421cab862c77b9ac7a8d93f54f8b5b223af7" dependencies = [ "bs58 0.4.0", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", "syn 1.0.109", ] @@ -8847,9 +9170,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der 0.7.8", @@ -8942,11 +9265,11 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools 0.11.0", + "itertools 0.12.0", "nom", "unicode_categories", ] @@ -8970,7 +9293,7 @@ dependencies = [ "ahash 0.7.7", "atoi", "base64 0.13.1", - "bigdecimal", + "bigdecimal 0.3.1", "bitflags 1.3.2", "byteorder", "bytes", @@ -9026,8 +9349,8 @@ dependencies = [ "either", "heck 0.4.1", "once_cell", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "serde_json", "sqlx-core", "sqlx-rt", @@ -9053,6 +9376,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" + [[package]] name = "static_assertions" version = "1.1.0" @@ -9113,8 +9442,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck 0.3.3", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -9125,8 +9454,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", "syn 1.0.109", ] @@ -9138,10 +9467,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -9188,19 +9517,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.39" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "unicode-ident", ] @@ -9211,9 +9540,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -9228,8 +9557,8 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", "unicode-xid 0.2.4", ] @@ -9264,6 +9593,24 @@ dependencies = [ "serde", ] +[[package]] +name = "tame-gcs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20ec2d6525a66afebdff9e1d8ef143c9deae9a3b040c61d3cfa9ae6fda80060" +dependencies = [ + "base64 0.13.1", + "bytes", + "chrono", + "http", + "percent-encoding", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "url", +] + [[package]] name = "tap" version = "1.0.1" @@ -9295,7 +9642,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "serde", - "static_assertions", + "static_assertions 1.1.0", "tarpc-plugins", "thiserror", "tokio", @@ -9311,22 +9658,22 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee42b4e559f17bce0385ebf511a7beb67d5cc33c12c96b7f4e9789919d9c10f" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.4.1", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -9340,10 +9687,10 @@ dependencies = [ "ed25519-consensus", "flex-error", "futures", - "k256 0.13.2", + "k256 0.13.3", "num-traits", "once_cell", - "prost", + "prost 0.11.9", "prost-types", "ripemd", "serde", @@ -9382,7 +9729,7 @@ dependencies = [ "flex-error", "num-derive 0.3.3", "num-traits", - "prost", + "prost 0.11.9", "prost-types", "serde", "serde_bytes", @@ -9399,7 +9746,7 @@ dependencies = [ "flex-error", "num-derive 0.3.3", "num-traits", - "prost", + "prost 0.11.9", "prost-types", "serde", "serde_bytes", @@ -9416,7 +9763,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.11", + "getrandom 0.2.12", "http", "hyper", "hyper-proxy", @@ -9443,9 +9790,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -9473,22 +9820,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -9503,12 +9850,14 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", + "libc", + "num_threads", "powerfmt", "serde", "time-core", @@ -9523,9 +9872,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -9549,6 +9898,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -9575,9 +9933,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -9608,9 +9966,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -9651,7 +10009,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.9", + "rustls 0.21.10", "tokio", ] @@ -9789,15 +10147,27 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ + "async-stream", "async-trait", "axum", - "base64 0.21.5", + "base64 0.21.7", "bytes", "futures-core", "futures-util", @@ -9808,8 +10178,42 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost", + "prost 0.11.9", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", + "tokio", + "tokio-rustls 0.24.1", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.12.3", + "rustls 0.21.10", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", "tokio", + "tokio-rustls 0.24.1", "tokio-stream", "tower", "tower-layer", @@ -9867,9 +10271,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -9902,6 +10306,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-opentelemetry" version = "0.17.4" @@ -9938,17 +10353,42 @@ dependencies = [ "serde", "serde_json", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", "tracing-serde", ] [[package]] -name = "try-lock" +name = "tracing-test" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a2c0ff408fe918a94c428a3f2ad04e4afd5c95bbc08fcf868eff750c15728a4" +dependencies = [ + "lazy_static", + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08" +dependencies = [ + "lazy_static", + "quote 1.0.35", + "syn 1.0.109", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" @@ -10003,6 +10443,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "uint" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082df6964410f6aa929a61ddfafc997e4f32c62c22490e439ac351cec827f436" +dependencies = [ + "byteorder", + "crunchy", + "heapsize", + "rustc-hex", +] + [[package]] name = "uint" version = "0.9.5" @@ -10012,7 +10464,7 @@ dependencies = [ "byteorder", "crunchy", "hex 0.4.3", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -10026,9 +10478,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -10108,11 +10560,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7830e33f6e25723d41a63f77e434159dad02919f18f55a512b5f16f3b1d77138" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "log", "once_cell", "url", @@ -10130,12 +10582,12 @@ dependencies = [ [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", ] @@ -10157,7 +10609,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "serde", ] @@ -10175,18 +10627,22 @@ name = "validator" version = "0.1.0" dependencies = [ "async-trait", + "axum", "config", + "derive-new", "derive_more", "ethers", "eyre", + "futures", "futures-util", "hyperlane-base", "hyperlane-core", "hyperlane-cosmos", "hyperlane-ethereum", "hyperlane-test", - "k256 0.13.2", + "k256 0.13.3", "prometheus", + "reqwest", "serde", "serde_json", "thiserror", @@ -10290,9 +10746,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -10300,24 +10756,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -10327,32 +10783,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ - "quote 1.0.33", + "quote 1.0.35", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-timer" @@ -10371,9 +10827,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -10395,7 +10851,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.5", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -10419,9 +10875,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "which" @@ -10478,20 +10934,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.0", ] [[package]] @@ -10504,18 +10951,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.0", ] [[package]] @@ -10534,10 +10975,19 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] [[package]] name = "windows_aarch64_gnullvm" @@ -10546,10 +10996,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +name = "windows_aarch64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -10558,10 +11008,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "windows_i686_gnu" -version = "0.42.2" +name = "windows_aarch64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -10570,10 +11020,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -10582,10 +11032,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +name = "windows_i686_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -10594,10 +11044,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +name = "windows_x86_64_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -10606,10 +11056,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "windows_x86_64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -10617,11 +11067,17 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] @@ -10684,11 +11140,13 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys", + "rustix", ] [[package]] @@ -10697,6 +11155,32 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +[[package]] +name = "ya-gcp" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186a4237c7bddb8a13c1056f45bdfb5c548020327203a66878d74fa7cf31286e" +dependencies = [ + "cfg-if", + "futures", + "http", + "humantime-serde", + "hyper", + "hyper-rustls 0.24.2", + "paste", + "rand 0.8.5", + "rustls 0.21.10", + "rustls-native-certs 0.6.3", + "serde", + "tame-gcs", + "thiserror", + "tokio", + "tonic 0.10.2", + "tower", + "tracing", + "yup-oauth2", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -10715,24 +11199,51 @@ dependencies = [ "time", ] +[[package]] +name = "yup-oauth2" +version = "8.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b61da40aeb0907a65f7fb5c1de83c5a224d6a9ebb83bf918588a2bb744d636b8" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.21.7", + "futures", + "http", + "hyper", + "hyper-rustls 0.24.2", + "itertools 0.12.0", + "log", + "percent-encoding", + "rustls 0.22.2", + "rustls-pemfile 1.0.4", + "seahash", + "serde", + "serde_json", + "time", + "tokio", + "tower-service", + "url", +] + [[package]] name = "zerocopy" -version = "0.7.26" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.26" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -10750,9 +11261,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c1ee840430..ffdd2f624f 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -52,9 +52,12 @@ version = "0.1.0" Inflector = "0.11.4" anyhow = "1.0" async-trait = "0.1" +async-rwlock = "1.3" auto_impl = "1.0" +axum = "0.6.1" backtrace = "0.3" base64 = "0.21.2" +bigdecimal = "0.4.2" bincode = "1.3" borsh = "0.9" bs58 = "0.5.0" @@ -77,7 +80,7 @@ derive_builder = "0.12" derive_more = "0.99" ed25519-dalek = "~1.0" enum_dispatch = "0.3" -eyre = "0.6" +eyre = "=0.6.8" fixed-hash = "0.8.0" fuels = "0.38" fuels-code-gen = "0.38" @@ -89,10 +92,14 @@ bech32 = "0.9.1" elliptic-curve = "0.12.3" getrandom = { version = "0.2", features = ["js"] } hex = "0.4.3" -hpl-interface = "=0.0.6-rc3" +http = "*" hyper = "0.14" hyper-tls = "0.5.0" -itertools = "0.11.0" +hyperlane-cosmwasm-interface = "=0.0.6-rc6" +injective-protobuf = "0.2.2" +injective-std = "0.1.5" +itertools = "*" +jobserver = "=0.1.26" jsonrpc-core = "18.0" k256 = { version = "0.13.1", features = ["std", "ecdsa"] } log = "0.4" @@ -110,6 +117,7 @@ paste = "1.0" pretty_env_logger = "0.5.0" primitive-types = "=0.12.1" prometheus = "0.13" +protobuf = "*" regex = "1.5" reqwest = "0.11" ripemd = "0.1.3" @@ -170,12 +178,14 @@ tracing = { version = "0.1", features = ["release_max_level_debug"] } tracing-error = "0.2" tracing-futures = "0.2" tracing-subscriber = { version = "0.3", default-features = false } +tracing-test = "0.2.2" uint = "0.9.5" ureq = { version = "2.4", default-features = false } url = "2.3" walkdir = "2" warp = "0.3" which = "4.3" +ya-gcp = { version = "0.11.1", features = ["storage"] } ## TODO: remove this cosmwasm-schema = "1.2.7" @@ -183,27 +193,27 @@ cosmwasm-schema = "1.2.7" [workspace.dependencies.ethers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-29-02" [workspace.dependencies.ethers-contract] features = ["legacy"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-29-02" [workspace.dependencies.ethers-core] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-29-02" [workspace.dependencies.ethers-providers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-29-02" [workspace.dependencies.ethers-signers] features = ["aws"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-29-02" [patch.crates-io.curve25519-dalek] branch = "v3.2.2-relax-zeroize" diff --git a/rust/README.md b/rust/README.md index ae73e24187..f2b26247df 100644 --- a/rust/README.md +++ b/rust/README.md @@ -15,6 +15,14 @@ info: This is the version for the rustup toolchain manager, not the rustc compil info: The currently active `rustc` version is `rustc 1.72.1 (d5c2e9c34 2023-09-13)` ``` +#### Apple Silicon + +If your device has an Apple Silicon processor, you may need to install Rosetta 2: + +```bash +softwareupdate --install-rosetta --agree-to-license +``` + ### Running Locally To run the validator, run: @@ -43,7 +51,7 @@ cargo build --release --bin relayer ./target/release/relayer ``` -### Running local binary against cloud resources (AWS KMS, S3, Postgresql etc) +### Running local binary against cloud resources (AWS KMS, S3, Postgresql, Google Cloud Storage, etc) Building the docker image and upgrading the pod is a **slow** process. To speed up the development cycle, you can run a local binary against cloud resources. This workflow is useful for testing local changes against cloud resources. It is also useful for debugging issues in production. @@ -66,6 +74,9 @@ Configure additional env variables appropriately: HYP_DB=/tmp/fuji-validator-db CONFIG_FILES=./config/testnet_config.json HYP_TRACING_FMT=pretty +GCS_USER_SECRET=./path/to/file +# or if service account used +GCS_SERVICE_ACCOUNT_KEY=./path/to/file DATABASE_URL= # for scraper ``` @@ -86,6 +97,12 @@ cargo run --release --bin run-locally This will automatically build the agents, start a local node, build and deploy the contracts, and run a relayer and validator. By default, this test will run indefinitely, but can be stopped with `ctrl-c`. +To run the tests for a specific VM, use the `--features` flag. + +```bash +cargo test --release --package run-locally --bin run-locally --features cosmos -- cosmos::test --nocapture +``` + ### Building Agent Docker Images There exists a docker build for the agent binaries. These docker images are used for deploying the agents in a diff --git a/rust/agents/relayer/Cargo.toml b/rust/agents/relayer/Cargo.toml index 7eb5b83fe6..1cb2a1d329 100644 --- a/rust/agents/relayer/Cargo.toml +++ b/rust/agents/relayer/Cargo.toml @@ -19,6 +19,7 @@ enum_dispatch.workspace = true ethers-contract.workspace = true ethers.workspace = true eyre.workspace = true +futures.workspace = true futures-util.workspace = true itertools.workspace = true num-derive.workspace = true @@ -34,11 +35,12 @@ tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } tracing-futures.workspace = true tracing.workspace = true -hyperlane-core = { path = "../../hyperlane-core", features = ["agent"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "fallback-provider"] } hyperlane-base = { path = "../../hyperlane-base" } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } [dev-dependencies] +once_cell.workspace = true tokio-test.workspace = true hyperlane-test = { path = "../../hyperlane-test" } hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] } diff --git a/rust/agents/relayer/src/merkle_tree/processor.rs b/rust/agents/relayer/src/merkle_tree/processor.rs index 873c52ab97..251e8e7c6b 100644 --- a/rust/agents/relayer/src/merkle_tree/processor.rs +++ b/rust/agents/relayer/src/merkle_tree/processor.rs @@ -11,7 +11,7 @@ use hyperlane_base::db::HyperlaneRocksDB; use hyperlane_core::{HyperlaneDomain, MerkleTreeInsertion}; use prometheus::IntGauge; use tokio::sync::RwLock; -use tracing::debug; +use tracing::trace; use crate::processor::ProcessorExt; @@ -76,7 +76,7 @@ impl MerkleTreeProcessor { .set(insertion.index() as i64); Some(insertion) } else { - debug!(leaf_index=?self.leaf_index, "No message found in DB for leaf index"); + trace!(leaf_index=?self.leaf_index, "No merkle tree insertion found in DB for leaf index, waiting for it to be indexed"); None }; Ok(leaf) diff --git a/rust/agents/relayer/src/msg/gas_payment/mod.rs b/rust/agents/relayer/src/msg/gas_payment/mod.rs index 50f010d244..cd9dd61c06 100644 --- a/rust/agents/relayer/src/msg/gas_payment/mod.rs +++ b/rust/agents/relayer/src/msg/gas_payment/mod.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use eyre::Result; use hyperlane_base::db::HyperlaneRocksDB; use hyperlane_core::{ - GasPaymentKey, HyperlaneMessage, InterchainGasExpenditure, InterchainGasPayment, - TxCostEstimate, TxOutcome, U256, + FixedPointNumber, GasPaymentKey, HyperlaneMessage, InterchainGasExpenditure, + InterchainGasPayment, TxCostEstimate, TxOutcome, U256, }; use tracing::{debug, error, trace}; @@ -135,7 +135,8 @@ impl GasPaymentEnforcer { self.db.process_gas_expenditure(InterchainGasExpenditure { message_id: message.id(), gas_used: outcome.gas_used, - tokens_used: outcome.gas_used * outcome.gas_price, + tokens_used: (FixedPointNumber::try_from(outcome.gas_used)? * outcome.gas_price) + .try_into()?, })?; Ok(()) } diff --git a/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs b/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs index 23f329d09a..ea2c4d23d1 100644 --- a/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs +++ b/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs @@ -59,7 +59,7 @@ async fn test_gas_payment_policy_minimum() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100000u32), + gas_price: U256::from(100000u32).try_into().unwrap(), l2_gas_limit: None, }, ) @@ -83,7 +83,7 @@ async fn test_gas_payment_policy_minimum() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: None, }, ) @@ -101,7 +101,7 @@ async fn test_gas_payment_policy_minimum() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: Some(U256::from(22222u32)), }, ) diff --git a/rust/agents/relayer/src/msg/gas_payment/policies/none.rs b/rust/agents/relayer/src/msg/gas_payment/policies/none.rs index 9ce88a39c7..4d15359f49 100644 --- a/rust/agents/relayer/src/msg/gas_payment/policies/none.rs +++ b/rust/agents/relayer/src/msg/gas_payment/policies/none.rs @@ -52,7 +52,7 @@ async fn test_gas_payment_policy_none() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: None, }, ) @@ -70,7 +70,7 @@ async fn test_gas_payment_policy_none() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: Some(U256::from(22222u32)), }, ) diff --git a/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs b/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs index cdc017defa..f9665019a7 100644 --- a/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs +++ b/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs @@ -64,6 +64,7 @@ impl GasPaymentPolicy for GasPaymentPolicyOnChainFeeQuoting { #[cfg(test)] mod test { use hyperlane_core::H256; + use once_cell::sync::Lazy; use super::*; @@ -85,11 +86,11 @@ mod test { } const MIN: U256 = U256([1000, 0, 0, 0]); - const COST_ESTIMATE: TxCostEstimate = TxCostEstimate { + static COST_ESTIMATE: Lazy = Lazy::new(|| TxCostEstimate { gas_limit: U256([2000, 0, 0, 0]), // MIN * 2 - gas_price: U256([100001, 0, 0, 0]), + gas_price: U256([100001, 0, 0, 0]).try_into().unwrap(), l2_gas_limit: None, - }; + }); #[test] fn ensure_little_endian() { @@ -203,7 +204,7 @@ mod test { let tx_cost_estimate = TxCostEstimate { gas_limit: MIN * 100, // Large gas limit - gas_price: COST_ESTIMATE.gas_price, + gas_price: COST_ESTIMATE.gas_price.clone(), l2_gas_limit: Some(MIN * 2), }; @@ -217,7 +218,7 @@ mod test { ¤t_expenditure(0), &TxCostEstimate { l2_gas_limit: None, - ..tx_cost_estimate + ..tx_cost_estimate.clone() } ) .await diff --git a/rust/agents/relayer/src/msg/metadata/aggregation.rs b/rust/agents/relayer/src/msg/metadata/aggregation.rs index ffcde517df..fed501b622 100644 --- a/rust/agents/relayer/src/msg/metadata/aggregation.rs +++ b/rust/agents/relayer/src/msg/metadata/aggregation.rs @@ -9,7 +9,7 @@ use tracing::{info, instrument}; use hyperlane_core::{HyperlaneMessage, InterchainSecurityModule, ModuleType, H256, U256}; -use super::{BaseMetadataBuilder, MetadataBuilder}; +use super::{MessageMetadataBuilder, MetadataBuilder}; /// Bytes used to store one member of the (start, end) range tuple /// Copied from `AggregationIsmMetadata.sol` @@ -17,7 +17,7 @@ const METADATA_RANGE_SIZE: usize = 4; #[derive(Clone, Debug, new, Deref)] pub struct AggregationIsmMetadataBuilder { - base: BaseMetadataBuilder, + base: MessageMetadataBuilder, } #[derive(Clone, Debug, new, PartialEq, Eq)] diff --git a/rust/agents/relayer/src/msg/metadata/base.rs b/rust/agents/relayer/src/msg/metadata/base.rs index 054cd8574e..950723e74f 100644 --- a/rust/agents/relayer/src/msg/metadata/base.rs +++ b/rust/agents/relayer/src/msg/metadata/base.rs @@ -1,4 +1,11 @@ -use std::{collections::HashMap, fmt::Debug, str::FromStr, sync::Arc}; +use std::{ + collections::HashMap, + fmt::Debug, + ops::Deref, + str::FromStr, + sync::Arc, + time::{Duration, Instant}, +}; use crate::{ merkle_tree::builder::MerkleTreeBuilder, @@ -7,6 +14,7 @@ use crate::{ AggregationIsmMetadataBuilder, CcipReadIsmMetadataBuilder, NullMetadataBuilder, RoutingIsmMetadataBuilder, }, + settings::matching_list::MatchingList, }; use async_trait::async_trait; use derive_new::new; @@ -18,9 +26,10 @@ use hyperlane_base::{ }; use hyperlane_core::{ accumulator::merkle::Proof, AggregationIsm, CcipReadIsm, Checkpoint, HyperlaneDomain, - HyperlaneMessage, InterchainSecurityModule, ModuleType, MultisigIsm, RoutingIsm, + HyperlaneMessage, InterchainSecurityModule, Mailbox, ModuleType, MultisigIsm, RoutingIsm, ValidatorAnnounce, H160, H256, }; + use tokio::sync::RwLock; use tracing::{debug, info, instrument, warn}; @@ -40,39 +49,129 @@ pub struct IsmWithMetadataAndType { #[async_trait] pub trait MetadataBuilder: Send + Sync { - #[allow(clippy::async_yields_async)] async fn build(&self, ism_address: H256, message: &HyperlaneMessage) -> Result>>; } -#[derive(Clone, new)] -pub struct BaseMetadataBuilder { - destination_chain_setup: ChainConf, - origin_prover_sync: Arc>, - origin_validator_announce: Arc, - allow_local_checkpoint_syncers: bool, - metrics: Arc, - db: HyperlaneRocksDB, +/// Allows fetching the default ISM, caching the value for a period of time +/// to avoid fetching it all the time. +/// TODO: make this generic +#[derive(Debug)] +pub struct DefaultIsmCache { + value: RwLock>, + mailbox: Arc, +} + +impl DefaultIsmCache { + /// Time to live for the cached default ISM. 10 mins. + const TTL: Duration = Duration::from_secs(60 * 10); + + pub fn new(mailbox: Arc) -> Self { + Self { + value: RwLock::new(None), + mailbox, + } + } + + /// Gets the default ISM, fetching it from onchain if the cached value + /// is stale. + /// TODO: this can and should be made generic eventually + pub async fn get(&self) -> Result { + // If the duration since the value was last updated does not + // exceed the TTL, return the cached value. + // This is in its own block to avoid holding the lock during the + // async operation to fetch the on-chain default ISM if + // the cached value is stale. + { + let value = self.value.read().await; + + if let Some(value) = *value { + if value.1.elapsed() < Self::TTL { + return Ok(value.0); + } + } + } + + let default_ism = self.mailbox.default_ism().await?; + // Update the cached value. + { + let mut value = self.value.write().await; + *value = Some((default_ism, Instant::now())); + } + + Ok(default_ism) + } +} + +/// Classifies messages into an app context if they have one. +#[derive(Debug)] +pub struct AppContextClassifier { + default_ism: DefaultIsmCache, + app_matching_lists: Vec<(MatchingList, String)>, +} + +impl AppContextClassifier { + pub fn new( + destination_mailbox: Arc, + app_matching_lists: Vec<(MatchingList, String)>, + ) -> Self { + Self { + default_ism: DefaultIsmCache::new(destination_mailbox), + app_matching_lists, + } + } + + /// Classifies messages into an app context if they have one, or None + /// if they don't. + /// An app context is a string that identifies the app that sent the message + /// and exists just for metrics. + /// An app context is chosen based on: + /// - the first element in `app_matching_lists` that matches the message + /// - if the message's ISM is the default ISM, the app context is "default_ism" + pub async fn get_app_context( + &self, + message: &HyperlaneMessage, + root_ism: H256, + ) -> Result> { + // Give priority to the matching list. If the app from the matching list happens + // to use the default ISM, it's preferable to use the app context from the matching + // list. + for (matching_list, app_context) in self.app_matching_lists.iter() { + if matching_list.msg_matches(message, false) { + return Ok(Some(app_context.clone())); + } + } + + let default_ism = self.default_ism.get().await?; + if root_ism == default_ism { + return Ok(Some("default_ism".to_string())); + } + + Ok(None) + } +} + +/// Builds metadata for a message. +#[derive(Debug, Clone)] +pub struct MessageMetadataBuilder { + pub base: Arc, /// ISMs can be structured recursively. We keep track of the depth /// of the recursion to avoid infinite loops. - #[new(default)] - depth: u32, - max_depth: u32, + pub depth: u32, + pub app_context: Option, } -impl Debug for BaseMetadataBuilder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "MetadataBuilder {{ chain_setup: {:?}, validator_announce: {:?} }}", - self.destination_chain_setup, self.origin_validator_announce - ) +impl Deref for MessageMetadataBuilder { + type Target = BaseMetadataBuilder; + + fn deref(&self) -> &Self::Target { + &self.base } } #[async_trait] -impl MetadataBuilder for BaseMetadataBuilder { - #[instrument(err, skip(self), fields(domain=self.domain().name()))] +impl MetadataBuilder for MessageMetadataBuilder { + #[instrument(err, skip(self), fields(destination_domain=self.destination_domain().name()))] async fn build( &self, ism_address: H256, @@ -84,12 +183,24 @@ impl MetadataBuilder for BaseMetadataBuilder { } } -impl BaseMetadataBuilder { - pub fn domain(&self) -> &HyperlaneDomain { - &self.destination_chain_setup.domain +impl MessageMetadataBuilder { + pub async fn new( + ism_address: H256, + message: &HyperlaneMessage, + base: Arc, + ) -> Result { + let app_context = base + .app_context_classifier + .get_app_context(message, ism_address) + .await?; + Ok(Self { + base, + depth: 0, + app_context, + }) } - pub fn clone_with_incremented_depth(&self) -> Result { + fn clone_with_incremented_depth(&self) -> Result { let mut cloned = self.clone(); cloned.depth += 1; if cloned.depth > cloned.max_depth { @@ -99,6 +210,82 @@ impl BaseMetadataBuilder { } } + #[instrument(err, skip(self), fields(destination_domain=self.destination_domain().name()))] + pub async fn build_ism_and_metadata( + &self, + ism_address: H256, + message: &HyperlaneMessage, + ) -> Result { + let ism: Box = self + .build_ism(ism_address) + .await + .context("When building ISM")?; + + let module_type = ism + .module_type() + .await + .context("When fetching module type")?; + let cloned = self.clone_with_incremented_depth()?; + + let metadata_builder: Box = match module_type { + ModuleType::MerkleRootMultisig => { + Box::new(MerkleRootMultisigMetadataBuilder::new(cloned)) + } + ModuleType::MessageIdMultisig => { + Box::new(MessageIdMultisigMetadataBuilder::new(cloned)) + } + ModuleType::Routing => Box::new(RoutingIsmMetadataBuilder::new(cloned)), + ModuleType::Aggregation => Box::new(AggregationIsmMetadataBuilder::new(cloned)), + ModuleType::Null => Box::new(NullMetadataBuilder::new()), + ModuleType::CcipRead => Box::new(CcipReadIsmMetadataBuilder::new(cloned)), + _ => return Err(MetadataBuilderError::UnsupportedModuleType(module_type).into()), + }; + let meta = metadata_builder + .build(ism_address, message) + .await + .context("When building metadata"); + Ok(IsmWithMetadataAndType { + ism, + metadata: meta?, + module_type, + }) + } +} + +/// Base metadata builder with types used by higher level metadata builders. +#[allow(clippy::too_many_arguments)] +#[derive(new)] +pub struct BaseMetadataBuilder { + origin_domain: HyperlaneDomain, + destination_chain_setup: ChainConf, + origin_prover_sync: Arc>, + origin_validator_announce: Arc, + allow_local_checkpoint_syncers: bool, + metrics: Arc, + db: HyperlaneRocksDB, + max_depth: u32, + app_context_classifier: AppContextClassifier, +} + +impl Debug for BaseMetadataBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BaseMetadataBuilder {{ origin_domain: {:?} destination_chain_setup: {:?}, validator_announce: {:?} }}", + self.origin_domain, self.destination_chain_setup, self.origin_validator_announce + ) + } +} + +impl BaseMetadataBuilder { + pub fn origin_domain(&self) -> &HyperlaneDomain { + &self.origin_domain + } + + pub fn destination_domain(&self) -> &HyperlaneDomain { + &self.destination_chain_setup.domain + } + pub async fn get_proof(&self, leaf_index: u32, checkpoint: Checkpoint) -> Result { const CTX: &str = "When fetching message proof"; let proof = self @@ -162,6 +349,7 @@ impl BaseMetadataBuilder { pub async fn build_checkpoint_syncer( &self, validators: &[H256], + app_context: Option, ) -> Result { let storage_locations = self .origin_validator_announce @@ -193,7 +381,7 @@ impl BaseMetadataBuilder { continue; } - match config.build(None) { + match config.build(None).await { Ok(checkpoint_syncer) => { // found the syncer for this validator checkpoint_syncers.insert(validator.into(), checkpoint_syncer.into()); @@ -221,45 +409,10 @@ impl BaseMetadataBuilder { } } } - Ok(MultisigCheckpointSyncer::new(checkpoint_syncers)) - } - - #[instrument(err, skip(self), fields(domain=self.domain().name()))] - pub async fn build_ism_and_metadata( - &self, - ism_address: H256, - message: &HyperlaneMessage, - ) -> Result { - let ism: Box = self - .build_ism(ism_address) - .await - .context("When building ISM")?; - - let module_type = ism - .module_type() - .await - .context("When fetching module type")?; - let base = self.clone_with_incremented_depth()?; - - let metadata_builder: Box = match module_type { - ModuleType::MerkleRootMultisig => { - Box::new(MerkleRootMultisigMetadataBuilder::new(base)) - } - ModuleType::MessageIdMultisig => Box::new(MessageIdMultisigMetadataBuilder::new(base)), - ModuleType::Routing => Box::new(RoutingIsmMetadataBuilder::new(base)), - ModuleType::Aggregation => Box::new(AggregationIsmMetadataBuilder::new(base)), - ModuleType::Null => Box::new(NullMetadataBuilder::new()), - ModuleType::CcipRead => Box::new(CcipReadIsmMetadataBuilder::new(base)), - _ => return Err(MetadataBuilderError::UnsupportedModuleType(module_type).into()), - }; - let meta = metadata_builder - .build(ism_address, message) - .await - .context("When building metadata"); - Ok(IsmWithMetadataAndType { - ism, - metadata: meta?, - module_type, - }) + Ok(MultisigCheckpointSyncer::new( + checkpoint_syncers, + self.metrics.clone(), + app_context, + )) } } diff --git a/rust/agents/relayer/src/msg/metadata/ccip_read.rs b/rust/agents/relayer/src/msg/metadata/ccip_read.rs index 7bce48ce40..1ca3fbe1b6 100644 --- a/rust/agents/relayer/src/msg/metadata/ccip_read.rs +++ b/rust/agents/relayer/src/msg/metadata/ccip_read.rs @@ -3,7 +3,7 @@ use derive_more::Deref; use derive_new::new; use ethers::{abi::AbiDecode, core::utils::hex::decode as hex_decode}; use eyre::Context; -use hyperlane_core::{HyperlaneMessage, RawHyperlaneMessage, H256}; +use hyperlane_core::{utils::bytes_to_hex, HyperlaneMessage, RawHyperlaneMessage, H256}; use hyperlane_ethereum::OffchainLookup; use regex::Regex; use reqwest::Client; @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{info, instrument}; -use super::{BaseMetadataBuilder, MetadataBuilder}; +use super::{base::MessageMetadataBuilder, MetadataBuilder}; #[derive(Serialize, Deserialize)] struct OffchainResponse { @@ -20,7 +20,7 @@ struct OffchainResponse { #[derive(Clone, Debug, new, Deref)] pub struct CcipReadIsmMetadataBuilder { - base: BaseMetadataBuilder, + base: MessageMetadataBuilder, } #[async_trait] @@ -54,13 +54,18 @@ impl MetadataBuilder for CcipReadIsmMetadataBuilder { }; for url in info.urls.iter() { + // Need to explicitly convert the sender H160 the hex because the `ToString` implementation + // for `H160` truncates the output. (e.g. `0xc66a…7b6f` instead of returning + // the full address) + let sender_as_bytes = &bytes_to_hex(info.sender.as_bytes()); + let data_as_bytes = &info.call_data.to_string(); let interpolated_url = url - .replace("{sender}", &info.sender.to_string()) - .replace("{data}", &info.call_data.to_string()); + .replace("{sender}", sender_as_bytes) + .replace("{data}", data_as_bytes); let res = if !url.contains("{data}") { let body = json!({ - "data": info.call_data.to_string(), - "sender": info.sender.to_string(), + "sender": sender_as_bytes, + "data": data_as_bytes }); Client::new() .post(interpolated_url) diff --git a/rust/agents/relayer/src/msg/metadata/mod.rs b/rust/agents/relayer/src/msg/metadata/mod.rs index 258a10a74f..2b3c3bf3d7 100644 --- a/rust/agents/relayer/src/msg/metadata/mod.rs +++ b/rust/agents/relayer/src/msg/metadata/mod.rs @@ -6,8 +6,8 @@ mod null_metadata; mod routing; use aggregation::AggregationIsmMetadataBuilder; -pub(crate) use base::BaseMetadataBuilder; pub(crate) use base::MetadataBuilder; +pub(crate) use base::{AppContextClassifier, BaseMetadataBuilder, MessageMetadataBuilder}; use ccip_read::CcipReadIsmMetadataBuilder; use null_metadata::NullMetadataBuilder; use routing::RoutingIsmMetadataBuilder; diff --git a/rust/agents/relayer/src/msg/metadata/multisig/base.rs b/rust/agents/relayer/src/msg/metadata/multisig/base.rs index cbe6226158..328b8848ba 100644 --- a/rust/agents/relayer/src/msg/metadata/multisig/base.rs +++ b/rust/agents/relayer/src/msg/metadata/multisig/base.rs @@ -12,7 +12,8 @@ use hyperlane_core::{HyperlaneMessage, MultisigSignedCheckpoint, H256}; use strum::Display; use tracing::{debug, info}; -use crate::msg::metadata::BaseMetadataBuilder; +use crate::msg::metadata::base::MessageMetadataBuilder; + use crate::msg::metadata::MetadataBuilder; #[derive(new, AsRef, Deref)] @@ -36,7 +37,7 @@ pub enum MetadataToken { } #[async_trait] -pub trait MultisigIsmMetadataBuilder: AsRef + Send + Sync { +pub trait MultisigIsmMetadataBuilder: AsRef + Send + Sync { async fn fetch_metadata( &self, validators: &[H256], @@ -92,7 +93,6 @@ pub trait MultisigIsmMetadataBuilder: AsRef + Send + Sync { #[async_trait] impl MetadataBuilder for T { - #[allow(clippy::async_yields_async)] async fn build( &self, ism_address: H256, @@ -117,7 +117,7 @@ impl MetadataBuilder for T { let checkpoint_syncer = self .as_ref() - .build_checkpoint_syncer(&validators) + .build_checkpoint_syncer(&validators, self.as_ref().app_context.clone()) .await .context(CTX)?; diff --git a/rust/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs b/rust/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs index 125aa918d5..b8bcca0402 100644 --- a/rust/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs +++ b/rust/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs @@ -9,12 +9,12 @@ use hyperlane_base::MultisigCheckpointSyncer; use hyperlane_core::{unwrap_or_none_result, HyperlaneMessage, H256}; use tracing::debug; -use crate::msg::metadata::BaseMetadataBuilder; +use crate::msg::metadata::MessageMetadataBuilder; use super::base::{MetadataToken, MultisigIsmMetadataBuilder, MultisigMetadata}; #[derive(Debug, Clone, Deref, new, AsRef)] -pub struct MerkleRootMultisigMetadataBuilder(BaseMetadataBuilder); +pub struct MerkleRootMultisigMetadataBuilder(MessageMetadataBuilder); #[async_trait] impl MultisigIsmMetadataBuilder for MerkleRootMultisigMetadataBuilder { fn token_layout(&self) -> Vec { @@ -55,7 +55,9 @@ impl MultisigIsmMetadataBuilder for MerkleRootMultisigMetadataBuilder { validators, threshold as usize, leaf_index, - highest_leaf_index + highest_leaf_index, + self.origin_domain(), + self.destination_domain(), ) .await .context(CTX)?, diff --git a/rust/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs b/rust/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs index e5feffb85f..9866c98b07 100644 --- a/rust/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs +++ b/rust/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs @@ -9,12 +9,12 @@ use hyperlane_base::MultisigCheckpointSyncer; use hyperlane_core::{unwrap_or_none_result, HyperlaneMessage, H256}; use tracing::{debug, warn}; -use crate::msg::metadata::BaseMetadataBuilder; +use crate::msg::metadata::MessageMetadataBuilder; use super::base::{MetadataToken, MultisigIsmMetadataBuilder, MultisigMetadata}; #[derive(Debug, Clone, Deref, new, AsRef)] -pub struct MessageIdMultisigMetadataBuilder(BaseMetadataBuilder); +pub struct MessageIdMultisigMetadataBuilder(MessageMetadataBuilder); #[async_trait] impl MultisigIsmMetadataBuilder for MessageIdMultisigMetadataBuilder { @@ -46,6 +46,16 @@ impl MultisigIsmMetadataBuilder for MessageIdMultisigMetadataBuilder { "No merkle leaf found for message id, must have not been enqueued in the tree" ) ); + + // Update the validator latest checkpoint metrics. + let _ = checkpoint_syncer + .get_validator_latest_checkpoints_and_update_metrics( + validators, + self.origin_domain(), + self.destination_domain(), + ) + .await; + let quorum_checkpoint = unwrap_or_none_result!( checkpoint_syncer .fetch_checkpoint(validators, threshold as usize, leaf_index) diff --git a/rust/agents/relayer/src/msg/metadata/routing.rs b/rust/agents/relayer/src/msg/metadata/routing.rs index 0a55b137f1..c51cd69baf 100644 --- a/rust/agents/relayer/src/msg/metadata/routing.rs +++ b/rust/agents/relayer/src/msg/metadata/routing.rs @@ -5,11 +5,11 @@ use eyre::Context; use hyperlane_core::{HyperlaneMessage, H256}; use tracing::instrument; -use super::{BaseMetadataBuilder, MetadataBuilder}; +use super::{MessageMetadataBuilder, MetadataBuilder}; #[derive(Clone, Debug, new, Deref)] pub struct RoutingIsmMetadataBuilder { - base: BaseMetadataBuilder, + base: MessageMetadataBuilder, } #[async_trait] diff --git a/rust/agents/relayer/src/msg/pending_message.rs b/rust/agents/relayer/src/msg/pending_message.rs index 860c1017af..385c8db736 100644 --- a/rust/agents/relayer/src/msg/pending_message.rs +++ b/rust/agents/relayer/src/msg/pending_message.rs @@ -6,7 +6,7 @@ use std::{ use async_trait::async_trait; use derive_new::new; -use eyre::{Context, Result}; +use eyre::Result; use hyperlane_base::{db::HyperlaneRocksDB, CoreMetrics}; use hyperlane_core::{HyperlaneChain, HyperlaneDomain, HyperlaneMessage, Mailbox, U256}; use prometheus::{IntCounter, IntGauge}; @@ -14,7 +14,7 @@ use tracing::{debug, error, info, instrument, trace, warn}; use super::{ gas_payment::GasPaymentEnforcer, - metadata::{BaseMetadataBuilder, MetadataBuilder}, + metadata::{BaseMetadataBuilder, MessageMetadataBuilder, MetadataBuilder}, pending_operation::*, }; @@ -35,7 +35,7 @@ pub struct MessageContext { pub origin_db: HyperlaneRocksDB, /// Used to construct the ISM metadata needed to verify a message from the /// origin. - pub metadata_builder: BaseMetadataBuilder, + pub metadata_builder: Arc, /// Used to determine if messages from the origin have made sufficient gas /// payments. pub origin_gas_payment_enforcer: Arc, @@ -153,9 +153,18 @@ impl PendingOperation for PendingMessage { "fetching ISM address. Potentially malformed recipient ISM address." ); + let message_metadata_builder = op_try!( + MessageMetadataBuilder::new( + ism_address, + &self.message, + self.ctx.metadata_builder.clone() + ) + .await, + "getting the message metadata builder" + ); + let Some(metadata) = op_try!( - self.ctx - .metadata_builder + message_metadata_builder .build(ism_address, &self.message) .await, "building metadata" @@ -238,7 +247,7 @@ impl PendingOperation for PendingMessage { "processing message" ); - op_try!(critical: self.ctx.origin_gas_payment_enforcer.record_tx_outcome(&self.message, tx_outcome), "recording tx outcome"); + op_try!(critical: self.ctx.origin_gas_payment_enforcer.record_tx_outcome(&self.message, tx_outcome.clone()), "recording tx outcome"); if tx_outcome.executed { info!( txid=?tx_outcome.transaction_id, diff --git a/rust/agents/relayer/src/msg/pending_operation.rs b/rust/agents/relayer/src/msg/pending_operation.rs index a1219a98b0..6192cd7cd3 100644 --- a/rust/agents/relayer/src/msg/pending_operation.rs +++ b/rust/agents/relayer/src/msg/pending_operation.rs @@ -2,7 +2,6 @@ use std::{cmp::Ordering, time::Instant}; use async_trait::async_trait; use enum_dispatch::enum_dispatch; -use eyre::Report; use hyperlane_core::HyperlaneDomain; #[allow(unused_imports)] // required for enum_dispatch @@ -110,9 +109,6 @@ pub enum PendingOperationResult { Reprepare, /// Do not attempt to run the operation again, forget about it Drop, - /// Pass the error up the chain, this is non-recoverable and indicates a - /// system failure. - CriticalFailure(Report), } /// create a `op_try!` macro for the `on_retry` handler. @@ -121,30 +117,27 @@ macro_rules! make_op_try { /// Handle a result and either return early with retry or a critical failure on /// error. macro_rules! op_try { - (critical: $e:expr, $ctx:literal) => { - match $e { - Ok(v) => v, - Err(e) => { - error!(error=?e, concat!("Error when ", $ctx)); - return PendingOperationResult::CriticalFailure( - Err::<(), _>(e) - .context(concat!("When ", $ctx)) - .unwrap_err() - ); - } - } - }; - ($e:expr, $ctx:literal) => { - match $e { - Ok(v) => v, - Err(e) => { - warn!(error=?e, concat!("Error when ", $ctx)); - #[allow(clippy::redundant_closure_call)] - return $on_retry(); - } - } - }; + (critical: $e:expr, $ctx:literal) => { + match $e { + Ok(v) => v, + Err(e) => { + error!(error=?e, concat!("Critical error when ", $ctx)); + #[allow(clippy::redundant_closure_call)] + return $on_retry(); } + } + }; + ($e:expr, $ctx:literal) => { + match $e { + Ok(v) => v, + Err(e) => { + warn!(error=?e, concat!("Error when ", $ctx)); + #[allow(clippy::redundant_closure_call)] + return $on_retry(); + } + } + }; + } }; } diff --git a/rust/agents/relayer/src/msg/processor.rs b/rust/agents/relayer/src/msg/processor.rs index 14584d0c42..746601bbef 100644 --- a/rust/agents/relayer/src/msg/processor.rs +++ b/rust/agents/relayer/src/msg/processor.rs @@ -183,7 +183,8 @@ mod test { use crate::{ merkle_tree::builder::MerkleTreeBuilder, msg::{ - gas_payment::GasPaymentEnforcer, metadata::BaseMetadataBuilder, + gas_payment::GasPaymentEnforcer, + metadata::{AppContextClassifier, BaseMetadataBuilder}, pending_operation::PendingOperation, }, processor::Processor, @@ -240,16 +241,23 @@ mod test { } fn dummy_metadata_builder( - domain: &HyperlaneDomain, + origin_domain: &HyperlaneDomain, + destination_domain: &HyperlaneDomain, db: &HyperlaneRocksDB, ) -> BaseMetadataBuilder { let mut settings = Settings::default(); - settings - .chains - .insert(domain.name().to_owned(), dummy_chain_conf(domain)); - let destination_chain_conf = settings.chain_setup(domain).unwrap(); + settings.chains.insert( + origin_domain.name().to_owned(), + dummy_chain_conf(origin_domain), + ); + settings.chains.insert( + destination_domain.name().to_owned(), + dummy_chain_conf(destination_domain), + ); + let destination_chain_conf = settings.chain_setup(destination_domain).unwrap(); let core_metrics = CoreMetrics::new("dummy_relayer", 37582, Registry::new()).unwrap(); BaseMetadataBuilder::new( + origin_domain.clone(), destination_chain_conf.clone(), Arc::new(RwLock::new(MerkleTreeBuilder::new())), Arc::new(MockValidatorAnnounceContract::default()), @@ -257,6 +265,7 @@ mod test { Arc::new(core_metrics), db.clone(), 5, + AppContextClassifier::new(Arc::new(MockMailboxContract::default()), vec![]), ) } @@ -268,11 +277,11 @@ mod test { MessageProcessor, UnboundedReceiver>, ) { - let base_metadata_builder = dummy_metadata_builder(origin_domain, db); + let base_metadata_builder = dummy_metadata_builder(origin_domain, destination_domain, db); let message_context = Arc::new(MessageContext { destination_mailbox: Arc::new(MockMailboxContract::default()), origin_db: db.clone(), - metadata_builder: base_metadata_builder, + metadata_builder: Arc::new(base_metadata_builder), origin_gas_payment_enforcer: Arc::new(GasPaymentEnforcer::new([], db.clone())), transaction_gas_limit: Default::default(), metrics: dummy_submission_metrics(), @@ -412,7 +421,7 @@ mod test { .iter() .zip(msg_retries_to_set.iter()) .for_each(|(pm, expected_retries)| { - // Round up the actuall backoff because it was calculated with an `Instant::now()` that was a fraction of a second ago + // Round up the actual backoff because it was calculated with an `Instant::now()` that was a fraction of a second ago let expected_backoff = PendingMessage::calculate_msg_backoff(*expected_retries) .map(|b| b.as_secs_f32().round()); let actual_backoff = pm._next_attempt_after().map(|instant| { diff --git a/rust/agents/relayer/src/msg/serial_submitter.rs b/rust/agents/relayer/src/msg/serial_submitter.rs index b677006dfc..a4fbb13ce0 100644 --- a/rust/agents/relayer/src/msg/serial_submitter.rs +++ b/rust/agents/relayer/src/msg/serial_submitter.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use std::time::Duration; use derive_new::new; -use eyre::{bail, Result}; use futures_util::future::try_join_all; use prometheus::{IntCounter, IntGauge}; use tokio::spawn; @@ -81,12 +80,12 @@ pub struct SerialSubmitter { } impl SerialSubmitter { - pub fn spawn(self) -> Instrumented>> { + pub fn spawn(self) -> Instrumented> { let span = info_span!("SerialSubmitter", destination=%self.domain); spawn(async move { self.run().await }).instrument(span) } - async fn run(self) -> Result<()> { + async fn run(self) { let Self { domain, metrics, @@ -128,10 +127,13 @@ impl SerialSubmitter { )), ]; - for i in try_join_all(tasks).await? { - i? + if let Err(err) = try_join_all(tasks).await { + tracing::error!( + error=?err, + ?domain, + "SerialSubmitter task panicked for domain" + ); } - Ok(()) } } @@ -140,7 +142,7 @@ async fn receive_task( domain: HyperlaneDomain, mut rx: mpsc::UnboundedReceiver>, prepare_queue: OpQueue, -) -> Result<()> { +) { // Pull any messages sent to this submitter while let Some(op) = rx.recv().await { trace!(?op, "Received new operation"); @@ -149,7 +151,6 @@ async fn receive_task( debug_assert_eq!(*op.domain(), domain); prepare_queue.lock().await.push(Reverse(op)); } - bail!("Submitter receive channel was closed") } #[instrument(skip_all, fields(%domain))] @@ -158,7 +159,7 @@ async fn prepare_task( prepare_queue: OpQueue, tx_submit: mpsc::Sender>, metrics: SerialSubmitterMetrics, -) -> Result<()> { +) { loop { // Pick the next message to try preparing. let next = { @@ -179,7 +180,9 @@ async fn prepare_task( debug!(?op, "Operation prepared"); metrics.ops_prepared.inc(); // this send will pause this task if the submitter is not ready to accept yet - tx_submit.send(op).await?; + if let Err(err) = tx_submit.send(op).await { + tracing::error!(error=?err, "Failed to send prepared operation to submitter"); + } } PendingOperationResult::NotReady => { // none of the operations are ready yet, so wait for a little bit @@ -193,9 +196,6 @@ async fn prepare_task( PendingOperationResult::Drop => { metrics.ops_dropped.inc(); } - PendingOperationResult::CriticalFailure(e) => { - return Err(e); - } } } } @@ -207,7 +207,7 @@ async fn submit_task( prepare_queue: OpQueue, confirm_queue: OpQueue, metrics: SerialSubmitterMetrics, -) -> Result<()> { +) { while let Some(mut op) = rx_submit.recv().await { trace!(?op, "Submitting operation"); debug_assert_eq!(*op.domain(), domain); @@ -228,10 +228,8 @@ async fn submit_task( PendingOperationResult::Drop => { metrics.ops_dropped.inc(); } - PendingOperationResult::CriticalFailure(e) => return Err(e), } } - bail!("Internal submitter channel was closed"); } #[instrument(skip_all, fields(%domain))] @@ -240,7 +238,7 @@ async fn confirm_task( prepare_queue: OpQueue, confirm_queue: OpQueue, metrics: SerialSubmitterMetrics, -) -> Result<()> { +) { loop { // Pick the next message to try confirming. let next = { @@ -272,7 +270,6 @@ async fn confirm_task( PendingOperationResult::Drop => { metrics.ops_dropped.inc(); } - PendingOperationResult::CriticalFailure(e) => return Err(e), } } } diff --git a/rust/agents/relayer/src/processor.rs b/rust/agents/relayer/src/processor.rs index d18397be08..56dbe3eb8d 100644 --- a/rust/agents/relayer/src/processor.rs +++ b/rust/agents/relayer/src/processor.rs @@ -5,7 +5,7 @@ use derive_new::new; use eyre::Result; use hyperlane_core::HyperlaneDomain; use tokio::task::JoinHandle; -use tracing::{info_span, instrument, instrument::Instrumented, Instrument}; +use tracing::{instrument, warn}; #[async_trait] pub trait ProcessorExt: Send + Debug { @@ -23,15 +23,17 @@ pub struct Processor { } impl Processor { - pub fn spawn(self) -> Instrumented>> { - let span = info_span!("MessageProcessor"); - tokio::spawn(async move { self.main_loop().await }).instrument(span) + pub fn spawn(self) -> JoinHandle<()> { + tokio::spawn(async move { self.main_loop().await }) } - #[instrument(ret, err, skip(self), level = "info", fields(domain=%self.ticker.domain()))] - async fn main_loop(mut self) -> Result<()> { + #[instrument(ret, skip(self), level = "info", fields(domain=%self.ticker.domain()))] + async fn main_loop(mut self) { loop { - self.ticker.tick().await?; + if let Err(err) = self.ticker.tick().await { + warn!(error=%err, "Error in processor tick"); + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } } } } diff --git a/rust/agents/relayer/src/relayer.rs b/rust/agents/relayer/src/relayer.rs index 5ff91466a2..035ab66752 100644 --- a/rust/agents/relayer/src/relayer.rs +++ b/rust/agents/relayer/src/relayer.rs @@ -7,9 +7,12 @@ use std::{ use async_trait::async_trait; use derive_more::AsRef; use eyre::Result; +use futures_util::future::try_join_all; use hyperlane_base::{ db::{HyperlaneRocksDB, DB}, - run_all, BaseAgent, ContractSyncMetrics, CoreMetrics, HyperlaneAgentCore, + metrics::{AgentMetrics, MetricsUpdater}, + settings::ChainConf, + BaseAgent, ChainMetrics, ContractSyncMetrics, CoreMetrics, HyperlaneAgentCore, SequencedDataContractSync, WatermarkContractSync, }; use hyperlane_core::{ @@ -22,15 +25,14 @@ use tokio::{ }, task::JoinHandle, }; -use tracing::{info, info_span, instrument::Instrumented, Instrument}; +use tracing::{info, info_span, instrument::Instrumented, warn, Instrument}; -use crate::merkle_tree::processor::{MerkleTreeProcessor, MerkleTreeProcessorMetrics}; -use crate::processor::{Processor, ProcessorExt}; +use crate::processor::Processor; use crate::{ merkle_tree::builder::MerkleTreeBuilder, msg::{ gas_payment::GasPaymentEnforcer, - metadata::BaseMetadataBuilder, + metadata::{AppContextClassifier, BaseMetadataBuilder}, pending_message::{MessageContext, MessageSubmissionMetrics}, pending_operation::DynPendingOperation, processor::{MessageProcessor, MessageProcessorMetrics}, @@ -38,6 +40,10 @@ use crate::{ }, settings::{matching_list::MatchingList, RelayerSettings}, }; +use crate::{ + merkle_tree::processor::{MerkleTreeProcessor, MerkleTreeProcessorMetrics}, + processor::ProcessorExt, +}; #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] struct ContextKey { @@ -49,7 +55,7 @@ struct ContextKey { #[derive(AsRef)] pub struct Relayer { origin_chains: HashSet, - destination_chains: HashSet, + destination_chains: HashMap, #[as_ref] core: HyperlaneAgentCore, message_syncs: HashMap>>, @@ -67,6 +73,11 @@ pub struct Relayer { transaction_gas_limit: Option, skip_transaction_gas_limit_for: HashSet, allow_local_checkpoint_syncers: bool, + core_metrics: Arc, + // TODO: decide whether to consolidate `agent_metrics` and `chain_metrics` into a single struct + // or move them in `core_metrics`, like the validator metrics + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, } impl Debug for Relayer { @@ -92,11 +103,16 @@ impl BaseAgent for Relayer { type Settings = RelayerSettings; - async fn from_settings(settings: Self::Settings, metrics: Arc) -> Result + async fn from_settings( + settings: Self::Settings, + core_metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, + ) -> Result where Self: Sized, { - let core = settings.build_hyperlane_core(metrics.clone()); + let core = settings.build_hyperlane_core(core_metrics.clone()); let db = DB::from_path(&settings.db)?; let dbs = settings .origin_chains @@ -105,18 +121,18 @@ impl BaseAgent for Relayer { .collect::>(); let mailboxes = settings - .build_mailboxes(settings.destination_chains.iter(), &metrics) + .build_mailboxes(settings.destination_chains.iter(), &core_metrics) .await?; let validator_announces = settings - .build_validator_announces(settings.origin_chains.iter(), &metrics) + .build_validator_announces(settings.origin_chains.iter(), &core_metrics) .await?; - let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&metrics)); + let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&core_metrics)); let message_syncs = settings .build_message_indexers( settings.origin_chains.iter(), - &metrics, + &core_metrics, &contract_sync_metrics, dbs.iter() .map(|(d, db)| (d.clone(), Arc::new(db.clone()) as _)) @@ -126,7 +142,7 @@ impl BaseAgent for Relayer { let interchain_gas_payment_syncs = settings .build_interchain_gas_payment_indexers( settings.origin_chains.iter(), - &metrics, + &core_metrics, &contract_sync_metrics, dbs.iter() .map(|(d, db)| (d.clone(), Arc::new(db.clone()) as _)) @@ -136,7 +152,7 @@ impl BaseAgent for Relayer { let merkle_tree_hook_syncs = settings .build_merkle_tree_hook_indexers( settings.origin_chains.iter(), - &metrics, + &core_metrics, &contract_sync_metrics, dbs.iter() .map(|(d, db)| (d.clone(), Arc::new(db.clone()) as _)) @@ -188,9 +204,10 @@ impl BaseAgent for Relayer { .collect(); let mut msg_ctxs = HashMap::new(); + let mut destination_chains = HashMap::new(); for destination in &settings.destination_chains { let destination_chain_setup = core.settings.chain_setup(destination).unwrap().clone(); - + destination_chains.insert(destination.clone(), destination_chain_setup.clone()); let transaction_gas_limit: Option = if skip_transaction_gas_limit_for.contains(&destination.id()) { None @@ -201,6 +218,7 @@ impl BaseAgent for Relayer { for origin in &settings.origin_chains { let db = dbs.get(origin).unwrap().clone(); let metadata_builder = BaseMetadataBuilder::new( + origin.clone(), destination_chain_setup.clone(), prover_syncs[origin].clone(), validator_announces[origin].clone(), @@ -208,6 +226,10 @@ impl BaseAgent for Relayer { core.metrics.clone(), db, 5, + AppContextClassifier::new( + mailboxes[destination].clone(), + settings.metric_app_contexts.clone(), + ), ); msg_ctxs.insert( @@ -218,10 +240,10 @@ impl BaseAgent for Relayer { Arc::new(MessageContext { destination_mailbox: mailboxes[destination].clone(), origin_db: dbs.get(origin).unwrap().clone(), - metadata_builder, + metadata_builder: Arc::new(metadata_builder), origin_gas_payment_enforcer: gas_payment_enforcers[origin].clone(), transaction_gas_limit, - metrics: MessageSubmissionMetrics::new(&metrics, origin, destination), + metrics: MessageSubmissionMetrics::new(&core_metrics, origin, destination), }), ); } @@ -230,7 +252,7 @@ impl BaseAgent for Relayer { Ok(Self { dbs, origin_chains: settings.origin_chains, - destination_chains: settings.destination_chains, + destination_chains, msg_ctxs, core, message_syncs, @@ -242,21 +264,44 @@ impl BaseAgent for Relayer { transaction_gas_limit, skip_transaction_gas_limit_for, allow_local_checkpoint_syncers: settings.allow_local_checkpoint_syncers, + core_metrics, + agent_metrics, + chain_metrics, }) } #[allow(clippy::async_yields_async)] - async fn run(self) -> Instrumented>> { + async fn run(self) { let mut tasks = vec![]; + // running http server + let server = self + .core + .settings + .server(self.core_metrics.clone()) + .expect("Failed to create server"); + let server_task = server.run(vec![]).instrument(info_span!("Relayer server")); + tasks.push(server_task); + // send channels by destination chain let mut send_channels = HashMap::with_capacity(self.destination_chains.len()); - for destination in &self.destination_chains { + for (dest_domain, dest_conf) in &self.destination_chains { let (send_channel, receive_channel) = mpsc::unbounded_channel::>(); - send_channels.insert(destination.id(), send_channel); + send_channels.insert(dest_domain.id(), send_channel); + + tasks.push(self.run_destination_submitter(dest_domain, receive_channel)); - tasks.push(self.run_destination_submitter(destination, receive_channel)); + let metrics_updater = MetricsUpdater::new( + dest_conf, + self.core_metrics.clone(), + self.agent_metrics.clone(), + self.chain_metrics.clone(), + Self::AGENT_NAME.to_string(), + ) + .await + .unwrap(); + tasks.push(metrics_updater.spawn()); } for origin in &self.origin_chains { @@ -271,15 +316,17 @@ impl BaseAgent for Relayer { tasks.push(self.run_merkle_tree_processor(origin)); } - run_all(tasks) + if let Err(err) = try_join_all(tasks).await { + tracing::error!( + error=?err, + "Relayer task panicked" + ); + } } } impl Relayer { - async fn run_message_sync( - &self, - origin: &HyperlaneDomain, - ) -> Instrumented>> { + async fn run_message_sync(&self, origin: &HyperlaneDomain) -> Instrumented> { let index_settings = self.as_ref().settings.chains[origin.name()].index_settings(); let contract_sync = self.message_syncs.get(origin).unwrap().clone(); let cursor = contract_sync @@ -297,7 +344,7 @@ impl Relayer { async fn run_interchain_gas_payment_sync( &self, origin: &HyperlaneDomain, - ) -> Instrumented>> { + ) -> Instrumented> { let index_settings = self.as_ref().settings.chains[origin.name()].index_settings(); let contract_sync = self .interchain_gas_payment_syncs @@ -312,7 +359,7 @@ impl Relayer { async fn run_merkle_tree_hook_syncs( &self, origin: &HyperlaneDomain, - ) -> Instrumented>> { + ) -> Instrumented> { let index_settings = self.as_ref().settings.chains[origin.name()].index.clone(); let contract_sync = self.merkle_tree_hook_syncs.get(origin).unwrap().clone(); let cursor = contract_sync @@ -326,15 +373,15 @@ impl Relayer { &self, origin: &HyperlaneDomain, send_channels: HashMap>>, - ) -> Instrumented>> { + ) -> Instrumented> { let metrics = MessageProcessorMetrics::new( &self.core.metrics, origin, - self.destination_chains.iter(), + self.destination_chains.keys(), ); - let destination_ctxs = self + let destination_ctxs: HashMap<_, _> = self .destination_chains - .iter() + .keys() .filter(|&destination| destination != origin) .map(|destination| { ( @@ -347,6 +394,7 @@ impl Relayer { ) }) .collect(); + let message_processor = MessageProcessor::new( self.dbs.get(origin).unwrap().clone(), self.whitelist.clone(), @@ -358,18 +406,11 @@ impl Relayer { let span = info_span!("MessageProcessor", origin=%message_processor.domain()); let processor = Processor::new(Box::new(message_processor)); - tokio::spawn(async move { - let res = tokio::try_join!(processor.spawn())?; - info!(?res, "try_join finished for message processor"); - Ok(()) - }) - .instrument(span) + + processor.spawn().instrument(span) } - fn run_merkle_tree_processor( - &self, - origin: &HyperlaneDomain, - ) -> Instrumented>> { + fn run_merkle_tree_processor(&self, origin: &HyperlaneDomain) -> Instrumented> { let metrics = MerkleTreeProcessorMetrics::new(); let merkle_tree_processor = MerkleTreeProcessor::new( self.dbs.get(origin).unwrap().clone(), @@ -379,12 +420,7 @@ impl Relayer { let span = info_span!("MerkleTreeProcessor", origin=%merkle_tree_processor.domain()); let processor = Processor::new(Box::new(merkle_tree_processor)); - tokio::spawn(async move { - let res = tokio::try_join!(processor.spawn())?; - info!(?res, "try_join finished for merkle tree processor"); - Ok(()) - }) - .instrument(span) + processor.spawn().instrument(span) } #[allow(clippy::too_many_arguments)] @@ -393,19 +429,22 @@ impl Relayer { &self, destination: &HyperlaneDomain, receiver: UnboundedReceiver>, - ) -> Instrumented>> { + ) -> Instrumented> { let serial_submitter = SerialSubmitter::new( destination.clone(), receiver, SerialSubmitterMetrics::new(&self.core.metrics, destination), ); let span = info_span!("SerialSubmitter", destination=%destination); - let submit_fut = serial_submitter.spawn(); - + let destination = destination.clone(); tokio::spawn(async move { - let res = tokio::try_join!(submit_fut)?; - info!(?res, "try_join finished for submitter"); - Ok(()) + // Propagate task panics + serial_submitter.spawn().await.unwrap_or_else(|err| { + panic!( + "destination submitter panicked for destination {}: {:?}", + destination, err + ) + }); }) .instrument(span) } diff --git a/rust/agents/relayer/src/settings/matching_list.rs b/rust/agents/relayer/src/settings/matching_list.rs index 483b65ed9f..da037f19bc 100644 --- a/rust/agents/relayer/src/settings/matching_list.rs +++ b/rust/agents/relayer/src/settings/matching_list.rs @@ -186,8 +186,8 @@ impl<'de> Visitor<'de> for FilterVisitor { A: SeqAccess<'de>, { let mut values = Vec::new(); - while let Some(i) = seq.next_element::<&str>()? { - values.push(parse_addr(i)?) + while let Some(i) = seq.next_element::()? { + values.push(parse_addr(&i)?) } Ok(Self::Value::Enumerated(values)) } @@ -454,4 +454,19 @@ mod test { r#"[{"origindomain":1399811151,"senderaddress":"DdTMkk9nuqH5LnD56HLkPiKMV3yB3BNEYSQfgmJHa5i7","destinationdomain":11155111,"recipientaddress":"0x6AD4DEBA8A147d000C09de6465267a9047d1c217"}]"#, ).unwrap(); } + + #[test] + fn supports_sequence_h256s() { + let json_str = r#"[{"origindomain":1399811151,"senderaddress":["0x6AD4DEBA8A147d000C09de6465267a9047d1c217","0x6AD4DEBA8A147d000C09de6465267a9047d1c218"],"destinationdomain":11155111,"recipientaddress":["0x6AD4DEBA8A147d000C09de6465267a9047d1c217","0x6AD4DEBA8A147d000C09de6465267a9047d1c218"]}]"#; + + // Test parsing directly into MatchingList + serde_json::from_str::(json_str).unwrap(); + + // Test parsing into a Value and then into MatchingList, which is the path used + // by the agent config parser. + let val: serde_json::Value = serde_json::from_str(json_str).unwrap(); + let value_parser = + hyperlane_base::settings::parser::ValueParser::new(Default::default(), &val); + crate::settings::parse_matching_list(value_parser).unwrap(); + } } diff --git a/rust/agents/relayer/src/settings/mod.rs b/rust/agents/relayer/src/settings/mod.rs index 18ea52dddb..c6934a77c7 100644 --- a/rust/agents/relayer/src/settings/mod.rs +++ b/rust/agents/relayer/src/settings/mod.rs @@ -54,6 +54,8 @@ pub struct RelayerSettings { /// If true, allows local storage based checkpoint syncers. /// Not intended for production use. pub allow_local_checkpoint_syncers: bool, + /// App contexts used for metrics. + pub metric_app_contexts: Vec<(MatchingList, String)>, } /// Config for gas payment enforcement @@ -118,32 +120,17 @@ impl FromRawConf for RelayerSettings { .parse_from_str("Expected database path") .unwrap_or_else(|| std::env::current_dir().unwrap().join("hyperlane_db")); - let (raw_gas_payment_enforcement_path, raw_gas_payment_enforcement) = match p + let (raw_gas_payment_enforcement_path, raw_gas_payment_enforcement) = p .get_opt_key("gasPaymentEnforcement") .take_config_err_flat(&mut err) - { - None => None, - Some(ValueParser { - val: Value::String(policy_str), - cwp, - }) => serde_json::from_str::(policy_str) - .context("Expected JSON string") - .take_err(&mut err, || cwp.clone()) - .map(|v| (cwp, recase_json_value(v, Case::Flat))), - Some(ValueParser { - val: value @ Value::Array(_), - cwp, - }) => Some((cwp, value.clone())), - Some(_) => Err(eyre!("Expected JSON array or stringified JSON")) - .take_err(&mut err, || cwp.clone()), - } - .unwrap_or_else(|| (&p.cwp + "gas_payment_enforcement", Value::Array(vec![]))); + .and_then(parse_json_array) + .unwrap_or_else(|| (&p.cwp + "gas_payment_enforcement", Value::Array(vec![]))); let gas_payment_enforcement_parser = ValueParser::new( raw_gas_payment_enforcement_path, &raw_gas_payment_enforcement, ); - let gas_payment_enforcement = gas_payment_enforcement_parser.into_array_iter().map(|itr| { + let mut gas_payment_enforcement = gas_payment_enforcement_parser.into_array_iter().map(|itr| { itr.filter_map(|policy| { let policy_type = policy.chain(&mut err).get_opt_key("type").parse_string().end(); let minimum_is_defined = matches!(policy.get_opt_key("minimum"), Ok(Some(_))); @@ -187,7 +174,11 @@ impl FromRawConf for RelayerSettings { matching_list, }) }).collect_vec() - }).unwrap_or_else(|_| vec![GasPaymentEnforcementConf::default()]); + }).unwrap_or_default(); + + if gas_payment_enforcement.is_empty() { + gas_payment_enforcement.push(GasPaymentEnforcementConf::default()); + } let whitelist = p .chain(&mut err) @@ -243,6 +234,32 @@ impl FromRawConf for RelayerSettings { }) .collect(); + let (raw_metric_app_contexts_path, raw_metric_app_contexts) = p + .get_opt_key("metricAppContexts") + .take_config_err_flat(&mut err) + .and_then(parse_json_array) + .unwrap_or_else(|| (&p.cwp + "metric_app_contexts", Value::Array(vec![]))); + + let metric_app_contexts_parser = + ValueParser::new(raw_metric_app_contexts_path, &raw_metric_app_contexts); + let metric_app_contexts = metric_app_contexts_parser + .into_array_iter() + .map(|itr| { + itr.filter_map(|policy| { + let name = policy.chain(&mut err).get_key("name").parse_string().end(); + + let matching_list = policy + .chain(&mut err) + .get_key("matchingList") + .and_then(parse_matching_list) + .unwrap_or_default(); + + name.map(|name| (matching_list, name.to_owned())) + }) + .collect_vec() + }) + .unwrap_or_default(); + err.into_result(RelayerSettings { base, db, @@ -254,28 +271,35 @@ impl FromRawConf for RelayerSettings { transaction_gas_limit, skip_transaction_gas_limit_for, allow_local_checkpoint_syncers, + metric_app_contexts, }) } } -fn parse_matching_list(p: ValueParser) -> ConfigResult { +fn parse_json_array(p: ValueParser) -> Option<(ConfigPath, Value)> { let mut err = ConfigParsingError::default(); - let raw_list = match &p { + match p { ValueParser { - val: Value::String(matching_list_str), + val: Value::String(array_str), cwp, - } => serde_json::from_str::(matching_list_str) + } => serde_json::from_str::(array_str) .context("Expected JSON string") .take_err(&mut err, || cwp.clone()) - .map(|v| recase_json_value(v, Case::Flat)), + .map(|v| (cwp, recase_json_value(v, Case::Flat))), ValueParser { val: value @ Value::Array(_), - .. - } => Some((*value).clone()), + cwp, + } => Some((cwp, value.clone())), _ => Err(eyre!("Expected JSON array or stringified JSON")) .take_err(&mut err, || p.cwp.clone()), - }; + } +} + +fn parse_matching_list(p: ValueParser) -> ConfigResult { + let mut err = ConfigParsingError::default(); + + let raw_list = parse_json_array(p.clone()).map(|(_, v)| v); let Some(raw_list) = raw_list else { return err.into_result(MatchingList::default()); }; diff --git a/rust/agents/scraper/Cargo.toml b/rust/agents/scraper/Cargo.toml index 0daeea7e50..587a56b8dd 100644 --- a/rust/agents/scraper/Cargo.toml +++ b/rust/agents/scraper/Cargo.toml @@ -18,6 +18,7 @@ eyre.workspace = true futures.workspace = true itertools.workspace = true num-bigint.workspace = true +num-traits.workspace = true prometheus.workspace = true sea-orm = { workspace = true } serde.workspace = true diff --git a/rust/agents/scraper/migration/bin/common.rs b/rust/agents/scraper/migration/bin/common.rs index 096f7628c1..df7173fc0e 100644 --- a/rust/agents/scraper/migration/bin/common.rs +++ b/rust/agents/scraper/migration/bin/common.rs @@ -1,9 +1,11 @@ -use std::env; +use std::{env, time::Duration}; use migration::sea_orm::{Database, DatabaseConnection}; pub use migration::{DbErr, Migrator, MigratorTrait as _}; +use sea_orm::ConnectOptions; const LOCAL_DATABASE_URL: &str = "postgresql://postgres:47221c18c610@localhost:5432/postgres"; +const CONNECT_TIMEOUT: u64 = 20; pub fn url() -> String { env::var("DATABASE_URL").unwrap_or_else(|_| LOCAL_DATABASE_URL.into()) @@ -16,6 +18,8 @@ pub async fn init() -> Result { .init(); let url = url(); + let mut options: ConnectOptions = url.clone().into(); + options.connect_timeout(Duration::from_secs(CONNECT_TIMEOUT)); println!("Connecting to {url}"); - Database::connect(url).await + Database::connect(options).await } diff --git a/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs b/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs index be7d938e6a..d6dee6809a 100644 --- a/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs +++ b/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs @@ -47,10 +47,10 @@ const DOMAINS: &[RawDomain] = &[ is_deprecated: false, }, RawDomain { - name: "basegoerli", + name: "base", token: "ETH", - domain: 84531, - chain_id: 84531, + domain: 8453, + chain_id: 8453, is_test_net: false, is_deprecated: false, }, @@ -94,6 +94,14 @@ const DOMAINS: &[RawDomain] = &[ is_test_net: true, is_deprecated: false, }, + RawDomain { + name: "gnosis", + token: "xDAI", + domain: 100, + chain_id: 100, + is_test_net: false, + is_deprecated: false, + }, RawDomain { name: "goerli", token: "ETH", @@ -103,10 +111,10 @@ const DOMAINS: &[RawDomain] = &[ is_deprecated: false, }, RawDomain { - name: "gnosis", - token: "xDAI", - domain: 100, - chain_id: 100, + name: "mantapacific", + token: "ETH", + domain: 169, + chain_id: 169, is_test_net: false, is_deprecated: false, }, @@ -159,19 +167,11 @@ const DOMAINS: &[RawDomain] = &[ is_deprecated: false, }, RawDomain { - name: "scrollsepolia", - token: "ETH", - domain: 534351, - chain_id: 534351, - is_test_net: true, - is_deprecated: false, - }, - RawDomain { - name: "sepolia", + name: "polygonzkevm", token: "ETH", - domain: 11155111, - chain_id: 11155111, - is_test_net: true, + domain: 1101, + chain_id: 1101, + is_test_net: false, is_deprecated: false, }, RawDomain { @@ -183,26 +183,42 @@ const DOMAINS: &[RawDomain] = &[ is_deprecated: false, }, RawDomain { - name: "polygonzkevm", + name: "scroll", token: "ETH", - domain: 1101, - chain_id: 1101, + domain: 534352, + chain_id: 534352, is_test_net: false, is_deprecated: false, }, RawDomain { - name: "base", + name: "scrollsepolia", token: "ETH", - domain: 8453, - chain_id: 8453, - is_test_net: false, + domain: 534351, + chain_id: 534351, + is_test_net: true, is_deprecated: false, }, RawDomain { - name: "scroll", + name: "sepolia", token: "ETH", - domain: 534352, - chain_id: 534352, + domain: 11155111, + chain_id: 11155111, + is_test_net: true, + is_deprecated: false, + }, + RawDomain { + name: "viction", + token: "VIC", + domain: 88, + chain_id: 88, + is_test_net: false, + is_deprecated: false, + }, + RawDomain { + name: "inevm", + token: "INJ", + domain: 2525, + chain_id: 2525, is_test_net: false, is_deprecated: false, }, diff --git a/rust/agents/scraper/src/agent.rs b/rust/agents/scraper/src/agent.rs index b582f8e2ef..6d414e90bb 100644 --- a/rust/agents/scraper/src/agent.rs +++ b/rust/agents/scraper/src/agent.rs @@ -2,9 +2,10 @@ use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use derive_more::AsRef; +use futures::future::try_join_all; use hyperlane_base::{ - run_all, settings::IndexSettings, BaseAgent, ContractSyncMetrics, CoreMetrics, - HyperlaneAgentCore, + metrics::AgentMetrics, settings::IndexSettings, BaseAgent, ChainMetrics, ContractSyncMetrics, + CoreMetrics, HyperlaneAgentCore, MetricsUpdater, }; use hyperlane_core::HyperlaneDomain; use tokio::task::JoinHandle; @@ -19,8 +20,11 @@ pub struct Scraper { #[as_ref] core: HyperlaneAgentCore, contract_sync_metrics: Arc, - metrics: Arc, scrapers: HashMap, + settings: ScraperSettings, + core_metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, } #[derive(Debug)] @@ -38,6 +42,8 @@ impl BaseAgent for Scraper { async fn from_settings( settings: Self::Settings, metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, ) -> eyre::Result where Self: Sized, @@ -75,26 +81,53 @@ impl BaseAgent for Scraper { Ok(Self { core, - metrics, contract_sync_metrics, scrapers, + settings, + core_metrics: metrics, + agent_metrics, + chain_metrics, }) } #[allow(clippy::async_yields_async)] - async fn run(self) -> Instrumented>> { + async fn run(self) { let mut tasks = Vec::with_capacity(self.scrapers.len()); - for domain in self.scrapers.keys() { + + // running http server + let server = self + .core + .settings + .server(self.core_metrics.clone()) + .expect("Failed to create server"); + let server_task = server.run(vec![]).instrument(info_span!("Relayer server")); + tasks.push(server_task); + + for (domain, scraper) in self.scrapers.iter() { tasks.push(self.scrape(*domain).await); + + let chain_conf = self.settings.chain_setup(&scraper.domain).unwrap(); + let metrics_updater = MetricsUpdater::new( + chain_conf, + self.core_metrics.clone(), + self.agent_metrics.clone(), + self.chain_metrics.clone(), + Self::AGENT_NAME.to_string(), + ) + .await + .unwrap(); + tasks.push(metrics_updater.spawn()); + } + if let Err(err) = try_join_all(tasks).await { + tracing::error!(error = ?err, "Scraper task panicked"); } - run_all(tasks) } } impl Scraper { /// Sync contract data and other blockchain with the current chain state. /// This will spawn long-running contract sync tasks - async fn scrape(&self, domain_id: u32) -> Instrumented>> { + async fn scrape(&self, domain_id: u32) -> Instrumented> { let scraper = self.scrapers.get(&domain_id).unwrap(); let db = scraper.db.clone(); let index_settings = scraper.index_settings.clone(); @@ -104,7 +137,7 @@ impl Scraper { tasks.push( self.build_message_indexer( domain.clone(), - self.metrics.clone(), + self.core_metrics.clone(), self.contract_sync_metrics.clone(), db.clone(), index_settings.clone(), @@ -114,7 +147,7 @@ impl Scraper { tasks.push( self.build_delivery_indexer( domain.clone(), - self.metrics.clone(), + self.core_metrics.clone(), self.contract_sync_metrics.clone(), db.clone(), index_settings.clone(), @@ -124,14 +157,19 @@ impl Scraper { tasks.push( self.build_interchain_gas_payment_indexer( domain, - self.metrics.clone(), + self.core_metrics.clone(), self.contract_sync_metrics.clone(), db, index_settings.clone(), ) .await, ); - run_all(tasks) + + tokio::spawn(async move { + // If any of the tasks panic, we want to propagate it, so we unwrap + try_join_all(tasks).await.unwrap(); + }) + .instrument(info_span!("Scraper Tasks")) } } @@ -145,7 +183,7 @@ macro_rules! spawn_sync_task { contract_sync_metrics: Arc, db: HyperlaneSqlDb, index_settings: IndexSettings, - ) -> Instrumented>> { + ) -> Instrumented> { let sync = self .as_ref() .settings @@ -169,6 +207,7 @@ macro_rules! spawn_sync_task { } } } + impl Scraper { async fn build_message_indexer( &self, @@ -177,7 +216,7 @@ impl Scraper { contract_sync_metrics: Arc, db: HyperlaneSqlDb, index_settings: IndexSettings, - ) -> Instrumented>> { + ) -> Instrumented> { let sync = self .as_ref() .settings diff --git a/rust/agents/scraper/src/chain_scraper/mod.rs b/rust/agents/scraper/src/chain_scraper/mod.rs index 4d91fed5a8..4240115d53 100644 --- a/rust/agents/scraper/src/chain_scraper/mod.rs +++ b/rust/agents/scraper/src/chain_scraper/mod.rs @@ -8,7 +8,7 @@ use eyre::Result; use hyperlane_base::settings::IndexSettings; use hyperlane_core::{ unwrap_or_none_result, BlockInfo, Delivery, HyperlaneDomain, HyperlaneLogStore, - HyperlaneMessage, HyperlaneProvider, HyperlaneSequenceIndexerStore, + HyperlaneMessage, HyperlaneProvider, HyperlaneSequenceAwareIndexerStoreReader, HyperlaneWatermarkedLogStore, InterchainGasPayment, LogMeta, H256, }; use itertools::Itertools; @@ -370,7 +370,7 @@ impl HyperlaneLogStore for HyperlaneSqlDb { } #[async_trait] -impl HyperlaneSequenceIndexerStore for HyperlaneSqlDb { +impl HyperlaneSequenceAwareIndexerStoreReader for HyperlaneSqlDb { /// Gets a message by its nonce. async fn retrieve_by_sequence(&self, sequence: u32) -> Result> { let message = self @@ -381,7 +381,7 @@ impl HyperlaneSequenceIndexerStore for HyperlaneSqlDb { } /// Gets the block number at which the log occurred. - async fn retrieve_log_block_number(&self, sequence: u32) -> Result> { + async fn retrieve_log_block_number_by_sequence(&self, sequence: u32) -> Result> { let tx_id = unwrap_or_none_result!( self.db .retrieve_dispatched_tx_id(self.domain().id(), &self.mailbox_address, sequence) diff --git a/rust/agents/scraper/src/db/message.rs b/rust/agents/scraper/src/db/message.rs index ba5b33843e..8dd1a8b52f 100644 --- a/rust/agents/scraper/src/db/message.rs +++ b/rust/agents/scraper/src/db/message.rs @@ -79,7 +79,7 @@ impl ScraperDb { { Ok(Some(HyperlaneMessage { // We do not write version to the DB. - version: 0, + version: 3, origin: message.origin as u32, destination: message.destination as u32, nonce: message.nonce as u32, diff --git a/rust/agents/validator/Cargo.toml b/rust/agents/validator/Cargo.toml index f562938db2..9f90f47730 100644 --- a/rust/agents/validator/Cargo.toml +++ b/rust/agents/validator/Cargo.toml @@ -11,10 +11,13 @@ version.workspace = true [dependencies] async-trait.workspace = true +axum.workspace = true config.workspace = true derive_more.workspace = true +derive-new.workspace = true ethers.workspace = true eyre.workspace = true +futures.workspace = true futures-util.workspace = true prometheus.workspace = true serde.workspace = true @@ -24,13 +27,14 @@ tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } tracing-futures.workspace = true tracing.workspace = true -hyperlane-core = { path = "../../hyperlane-core", features = ["agent"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "fallback-provider"] } hyperlane-base = { path = "../../hyperlane-base" } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } [dev-dependencies] tokio-test.workspace = true +reqwest.workspace = true hyperlane-test = { path = "../../hyperlane-test" } k256.workspace = true diff --git a/rust/agents/validator/src/main.rs b/rust/agents/validator/src/main.rs index a5cc31a4ee..ebc24974c2 100644 --- a/rust/agents/validator/src/main.rs +++ b/rust/agents/validator/src/main.rs @@ -9,6 +9,7 @@ use hyperlane_base::agent_main; use crate::validator::Validator; +mod server; mod settings; mod submit; mod validator; diff --git a/rust/agents/validator/src/server/eigen_node.rs b/rust/agents/validator/src/server/eigen_node.rs new file mode 100644 index 0000000000..ef69e148ef --- /dev/null +++ b/rust/agents/validator/src/server/eigen_node.rs @@ -0,0 +1,275 @@ +//! A server that serves EigenLayer specific routes +//! compliant with the spec here https://eigen.nethermind.io/docs/spec/api/ +//! +//! Base URL /eigen +//! Routes +//! - /node - Node Info +//! eg. response {"node_name":"Hyperlane Validator","spec_version":"0.1.0","node_version":"0.1.0"} +//! - /node/health - Node Health +//! eg. response 200 - healthy, 206 - partially healthy, 503 - unhealthy +//! - /node/services - List of Services +//! eg. response [{"id":"hyperlane-validator-indexer","name":"indexer","description":"indexes the messages from the origin chain mailbox","status":"up"},{"id":"hyperlane-validator-submitter","name":"submitter","description":"signs messages indexed from the indexer","status":"up"}] +//! - /node/services/:service_id/health - Service Health +//! eg. response 200 - healthy, 503 - unhealthy + +use axum::{ + http::StatusCode, + response::IntoResponse, + routing::{get, Router}, + Json, +}; +use derive_new::new; +use hyperlane_base::CoreMetrics; +use hyperlane_core::HyperlaneDomain; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +enum ServiceStatus { + Up, + Down, + Initializing, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct NodeInfo { + node_name: String, + spec_version: String, + node_version: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct Service { + id: String, + name: String, + description: String, + status: ServiceStatus, +} + +#[derive(new)] +pub struct EigenNodeAPI { + origin_chain: HyperlaneDomain, + core_metrics: Arc, +} + +impl EigenNodeAPI { + pub fn router(&self) -> Router { + let core_metrics_clone = self.core_metrics.clone(); + let origin_chain = self.origin_chain.clone(); + + tracing::info!("Serving the EigenNodeAPI routes..."); + + let health_route = get(move || { + Self::node_health_handler(origin_chain.clone(), core_metrics_clone.clone()) + }); + let services_route = Router::new() + .route("/", get(Self::node_services_handler)) + .route("/:service_id/health", get(Self::service_health_handler)); + + let node_route = Router::new() + .route("/health", health_route) + .nest("/services", services_route) + .route("/", get(Self::node_info_handler)); + + Router::new().nest("/node", node_route) + } + + pub async fn node_info_handler() -> impl IntoResponse { + let node_info = NodeInfo { + node_name: "Hyperlane Validator".to_string(), + spec_version: "0.1.0".to_string(), + node_version: "0.1.0".to_string(), + }; + Json(node_info) + } + + /// Method to return the NodeInfo data + /// if signed_checkpoint - observed_checkpoint <= 1 return 200 - healthy + /// else if observed_checkpoint - signed_checkpoint <= 10 return 203 - partially healthy + /// else return 503 - unhealthy + pub async fn node_health_handler( + origin_chain: HyperlaneDomain, + core_metrics: Arc, + ) -> impl IntoResponse { + let checkpoint_delta = core_metrics.get_latest_checkpoint_validator_delta(origin_chain); + + // logic to check if the node is healthy + if checkpoint_delta <= 1 { + // 200 - healthy + StatusCode::OK + } else if checkpoint_delta <= 10 { + // 206 - partially healthy + StatusCode::PARTIAL_CONTENT + } else { + // 503 - unhealthy + StatusCode::SERVICE_UNAVAILABLE + } + } + + /// Method to return a list of services + /// NB: hardcoded for now + pub async fn node_services_handler() -> impl IntoResponse { + let services = vec![ + Service { + id: "hyperlane-validator-indexer".to_string(), + name: "indexer".to_string(), + description: "indexes the messages from the origin chain mailbox".to_string(), + status: ServiceStatus::Up, + }, + Service { + id: "hyperlane-validator-submitter".to_string(), + name: "submitter".to_string(), + description: "signs messages indexed from the indexer".to_string(), + status: ServiceStatus::Up, + }, + ]; + Json(services) + } + + /// Method to return the health of a service + pub async fn service_health_handler(_service_id: String) -> impl IntoResponse { + // TODO: implement logic to check if the service is healthy + // now just return 200 + StatusCode::OK + } +} + +#[cfg(test)] +mod tests { + use std::net::SocketAddr; + + use super::*; + use axum::http::StatusCode; + use prometheus::Registry; + + const PARTIALLY_HEALTHY_OBSERVED_CHECKPOINT: i64 = 34; + const HEALTHY_OBSERVED_CHECKPOINT: i64 = 42; + + async fn setup_test_server() -> (reqwest::Client, SocketAddr, Arc) { + let core_metrics = + Arc::new(CoreMetrics::new("dummy_validator", 37582, Registry::new()).unwrap()); + // Initialize the Prometheus registry + core_metrics + .latest_checkpoint() + .with_label_values(&["validator_observed", "ethereum"]) + .set(HEALTHY_OBSERVED_CHECKPOINT); + + let node_api = EigenNodeAPI::new( + HyperlaneDomain::new_test_domain("ethereum"), + Arc::clone(&core_metrics), + ); + let app = node_api.router(); + + // Running the app in the background using a test server + let server = + axum::Server::bind(&"127.0.0.1:0".parse().unwrap()).serve(app.into_make_service()); + let addr = server.local_addr(); + tokio::spawn(server); + + // Create a client + let client = reqwest::Client::new(); + + (client, addr, core_metrics) + } + + #[tokio::test] + async fn test_eigen_node_api() { + let (client, addr, _) = setup_test_server().await; + let res = client + .get(format!("http://{}/node", addr)) + .send() + .await + .expect("Failed to send request"); + + // Check that the response status is OK + assert_eq!(res.status(), StatusCode::OK); + + // what to expect when you're expecting + let expected = NodeInfo { + node_name: "Hyperlane Validator".to_string(), + spec_version: "0.1.0".to_string(), + node_version: "0.1.0".to_string(), + }; + + // check the response body if needed + let json: NodeInfo = res.json().await.expect("Failed to parse json"); + assert_eq!(json, expected); + } + + #[tokio::test] + async fn test_eigen_node_health_api() { + let (client, addr, core_metrics) = setup_test_server().await; + let res = client + .get(format!("http://{}/node/health", addr)) + .send() + .await + .expect("Failed to send request"); + assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); + + core_metrics + .latest_checkpoint() + .with_label_values(&["validator_processed", "ethereum"]) + .set(PARTIALLY_HEALTHY_OBSERVED_CHECKPOINT); + let res = client + .get(format!("http://{}/node/health", addr)) + .send() + .await + .expect("Failed to send request"); + assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); + + core_metrics + .latest_checkpoint() + .with_label_values(&["validator_processed", "ethereum"]) + .set(HEALTHY_OBSERVED_CHECKPOINT); + let res = client + .get(format!("http://{}/node/health", addr)) + .send() + .await + .expect("Failed to send request"); + assert_eq!(res.status(), StatusCode::OK); + } + + #[tokio::test] + async fn test_eigen_node_services_handler() { + let (client, addr, _) = setup_test_server().await; + let res = client + .get(format!("http://{}/node/services", addr)) + .send() + .await + .expect("Failed to send request"); + assert_eq!(res.status(), StatusCode::OK); + + let expected_services = vec![ + Service { + id: "hyperlane-validator-indexer".to_string(), + name: "indexer".to_string(), + description: "indexes the messages from the origin chain mailbox".to_string(), + status: ServiceStatus::Up, + }, + Service { + id: "hyperlane-validator-submitter".to_string(), + name: "submitter".to_string(), + description: "signs messages indexed from the indexer".to_string(), + status: ServiceStatus::Up, + }, + ]; + let services: Vec = res.json().await.expect("Failed to parse json"); + assert_eq!(services, expected_services); + } + + #[tokio::test] + async fn test_service_health_handler() { + let (client, addr, _) = setup_test_server().await; + let res = client + .get(format!( + "http://{}/node/services/hyperlane-validator-indexer/health", + addr + )) + .send() + .await + .expect("Failed to send request"); + + // Check that the response status is OK + assert_eq!(res.status(), StatusCode::OK); + } +} diff --git a/rust/agents/validator/src/server/mod.rs b/rust/agents/validator/src/server/mod.rs new file mode 100644 index 0000000000..a269d69e95 --- /dev/null +++ b/rust/agents/validator/src/server/mod.rs @@ -0,0 +1,5 @@ +pub mod eigen_node; +pub use eigen_node::EigenNodeAPI; + +pub mod validator_server; +pub use validator_server::ValidatorServer; diff --git a/rust/agents/validator/src/server/validator_server.rs b/rust/agents/validator/src/server/validator_server.rs new file mode 100644 index 0000000000..772559ac37 --- /dev/null +++ b/rust/agents/validator/src/server/validator_server.rs @@ -0,0 +1,20 @@ +use crate::server::eigen_node::EigenNodeAPI; +use axum::routing::Router; +use hyperlane_base::CoreMetrics; // Add missing import statement +use hyperlane_core::HyperlaneDomain; +use std::sync::Arc; + +pub struct ValidatorServer { + pub routes: Vec<(&'static str, Router)>, +} + +impl ValidatorServer { + // add routes for servering EigenLayer specific routes compliant with the spec here https://eigen.nethermind.io/docs/spec/api/ + pub fn new(origin_chain: HyperlaneDomain, metrics: Arc) -> Self { + let mut routes = vec![]; + let eigen_node_api = EigenNodeAPI::new(origin_chain, metrics); + routes.push(("/eigen", eigen_node_api.router())); + + Self { routes } + } +} diff --git a/rust/agents/validator/src/validator.rs b/rust/agents/validator/src/validator.rs index 96feb97b8f..19c1ccf02c 100644 --- a/rust/agents/validator/src/validator.rs +++ b/rust/agents/validator/src/validator.rs @@ -1,17 +1,20 @@ use std::{num::NonZeroU64, sync::Arc, time::Duration}; +use crate::server::validator_server::ValidatorServer; use async_trait::async_trait; use derive_more::AsRef; use eyre::Result; -use futures_util::future::ready; +use futures_util::future::try_join_all; use tokio::{task::JoinHandle, time::sleep}; use tracing::{error, info, info_span, instrument::Instrumented, warn, Instrument}; use hyperlane_base::{ db::{HyperlaneRocksDB, DB}, - run_all, BaseAgent, CheckpointSyncer, ContractSyncMetrics, CoreMetrics, HyperlaneAgentCore, - SequencedDataContractSync, + metrics::AgentMetrics, + settings::ChainConf, + BaseAgent, ChainMetrics, CheckpointSyncer, ContractSyncMetrics, CoreMetrics, + HyperlaneAgentCore, MetricsUpdater, SequencedDataContractSync, }; use hyperlane_core::{ @@ -30,6 +33,7 @@ use crate::{ #[derive(Debug, AsRef)] pub struct Validator { origin_chain: HyperlaneDomain, + origin_chain_conf: ChainConf, #[as_ref] core: HyperlaneAgentCore, db: HyperlaneRocksDB, @@ -43,6 +47,9 @@ pub struct Validator { reorg_period: u64, interval: Duration, checkpoint_syncer: Arc, + core_metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, } #[async_trait] @@ -51,7 +58,12 @@ impl BaseAgent for Validator { type Settings = ValidatorSettings; - async fn from_settings(settings: Self::Settings, metrics: Arc) -> Result + async fn from_settings( + settings: Self::Settings, + metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, + ) -> Result where Self: Sized, { @@ -62,7 +74,7 @@ impl BaseAgent for Validator { let (signer_instance, signer) = SingletonSigner::new(settings.validator.build().await?); let core = settings.build_hyperlane_core(metrics.clone()); - let checkpoint_syncer = settings.checkpoint_syncer.build(None)?.into(); + let checkpoint_syncer = settings.checkpoint_syncer.build(None).await?.into(); let mailbox = settings .build_mailbox(&settings.origin_chain, &metrics) @@ -76,6 +88,12 @@ impl BaseAgent for Validator { .build_validator_announce(&settings.origin_chain, &metrics) .await?; + let origin_chain_conf = core + .settings + .chain_setup(&settings.origin_chain) + .unwrap() + .clone(); + let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&metrics)); let merkle_tree_hook_sync = settings @@ -90,6 +108,7 @@ impl BaseAgent for Validator { Ok(Self { origin_chain: settings.origin_chain, + origin_chain_conf, core, db: msg_db, mailbox: mailbox.into(), @@ -101,13 +120,32 @@ impl BaseAgent for Validator { reorg_period: settings.reorg_period, interval: settings.interval, checkpoint_syncer, + agent_metrics, + chain_metrics, + core_metrics: metrics, }) } #[allow(clippy::async_yields_async)] - async fn run(mut self) -> Instrumented>> { + async fn run(mut self) { let mut tasks = vec![]; + let routes = + ValidatorServer::new(self.origin_chain.clone(), self.core.metrics.clone()).routes; + + // run server + let server = self + .core + .settings + .server(self.core_metrics.clone()) + .expect("Failed to create server"); + let server_task = tokio::spawn(async move { + server.run(routes); + Ok(()) + }) + .instrument(info_span!("Validator server")); + tasks.push(server_task); + if let Some(signer_instance) = self.signer_instance.take() { tasks.push( tokio::spawn(async move { @@ -118,6 +156,23 @@ impl BaseAgent for Validator { ); } + let metrics_updater = MetricsUpdater::new( + &self.origin_chain_conf, + self.core_metrics.clone(), + self.agent_metrics.clone(), + self.chain_metrics.clone(), + Self::AGENT_NAME.to_string(), + ) + .await + .unwrap(); + tasks.push( + tokio::spawn(async move { + metrics_updater.spawn().await.unwrap(); + Ok(()) + }) + .instrument(info_span!("MetricsUpdater")), + ); + // announce the validator after spawning the signer task self.announce().await.expect("Failed to announce validator"); @@ -140,12 +195,14 @@ impl BaseAgent for Validator { } _ => { // Future that immediately resolves - return tokio::spawn(ready(Ok(()))).instrument(info_span!("Validator")); + return; } } } - run_all(tasks) + if let Err(err) = try_join_all(tasks).await { + error!(?err, "One of the validator tasks returned an error"); + } } } @@ -157,8 +214,11 @@ impl Validator { let cursor = contract_sync .forward_backward_message_sync_cursor(index_settings) .await; - tokio::spawn(async move { contract_sync.clone().sync("merkle_tree_hook", cursor).await }) - .instrument(info_span!("MerkleTreeHookSyncer")) + tokio::spawn(async move { + contract_sync.clone().sync("merkle_tree_hook", cursor).await; + Ok(()) + }) + .instrument(info_span!("MerkleTreeHookSyncer")) } async fn run_checkpoint_submitters(&self) -> Vec>>> { diff --git a/rust/chains/hyperlane-cosmos/Cargo.toml b/rust/chains/hyperlane-cosmos/Cargo.toml index 12b084ba93..c105faec66 100644 --- a/rust/chains/hyperlane-cosmos/Cargo.toml +++ b/rust/chains/hyperlane-cosmos/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-cosmos" documentation = { workspace = true } @@ -14,10 +16,15 @@ bech32 = { workspace = true } cosmrs = { workspace = true, features = ["cosmwasm", "tokio", "grpc", "rpc"] } derive-new = { workspace = true } hex = { workspace = true } -hpl-interface.workspace = true +http = { workspace = true } +hyperlane-cosmwasm-interface.workspace = true hyper = { workspace = true } hyper-tls = { workspace = true } +injective-protobuf = { workspace = true } +injective-std = { workspace = true } +itertools = { workspace = true } once_cell = { workspace = true } +protobuf = { workspace = true } ripemd = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -27,9 +34,9 @@ tendermint = { workspace = true, features = ["rust-crypto", "secp256k1"] } tendermint-rpc = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } -tonic = { workspace = true } +tonic = { workspace = true, features = ["transport", "tls", "tls-roots","tls-roots-common"] } tracing = { workspace = true } tracing-futures = { workspace = true } url = { workspace = true } -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["fallback-provider"]} diff --git a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs index c9d7200117..df41c440b3 100644 --- a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs @@ -1,15 +1,17 @@ use std::str::FromStr; use crate::{ - address::CosmosAddress, - grpc::{WasmGrpcProvider, WasmProvider}, - payloads::aggregate_ism::{ModulesAndThresholdRequest, ModulesAndThresholdResponse}, + grpc::WasmProvider, + payloads::{ + ism_routes::QueryIsmGeneralRequest, + multisig_ism::{VerifyInfoRequest, VerifyInfoRequestInner, VerifyInfoResponse}, + }, ConnectionConf, CosmosProvider, Signer, }; use async_trait::async_trait; use hyperlane_core::{ AggregationIsm, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, - HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, H256, + HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, RawHyperlaneMessage, H160, H256, }; use tracing::instrument; @@ -18,7 +20,7 @@ use tracing::instrument; pub struct CosmosAggregationIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: Box, } impl CosmosAggregationIsm { @@ -28,7 +30,12 @@ impl CosmosAggregationIsm { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), @@ -50,7 +57,7 @@ impl HyperlaneChain for CosmosAggregationIsm { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + self.provider.clone() } } @@ -61,15 +68,33 @@ impl AggregationIsm for CosmosAggregationIsm { &self, message: &HyperlaneMessage, ) -> ChainResult<(Vec, u8)> { - let payload = ModulesAndThresholdRequest::new(message); + let payload = VerifyInfoRequest { + verify_info: VerifyInfoRequestInner { + message: hex::encode(RawHyperlaneMessage::from(message)), + }, + }; - let data = self.provider.wasm_query(payload, None).await?; - let response: ModulesAndThresholdResponse = serde_json::from_slice(&data)?; + let data = self + .provider + .grpc() + .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) + .await?; + let response: VerifyInfoResponse = serde_json::from_slice(&data)?; + // Note that due to a misnomer in the CosmWasm implementation, the `modules` field is called `validators`. let modules: ChainResult> = response - .modules - .into_iter() - .map(|module| CosmosAddress::from_str(&module).map(|ca| ca.digest())) + .validators + .iter() + .map(|module| { + // The returned values are Bech32-decoded Cosmos addresses. + // Since they are not EOAs but rather contracts, they can be 32 bytes long and + // need to be parsed directly as an `H256`. + if let Ok(res) = H256::from_str(module) { + return Ok(res); + } + // If the address is not 32 bytes long, it is a 20-byte address + H160::from_str(module).map(H256::from).map_err(Into::into) + }) .collect(); Ok((modules?, response.threshold)) diff --git a/rust/chains/hyperlane-cosmos/src/error.rs b/rust/chains/hyperlane-cosmos/src/error.rs index 92af0bce05..06fffaff7e 100644 --- a/rust/chains/hyperlane-cosmos/src/error.rs +++ b/rust/chains/hyperlane-cosmos/src/error.rs @@ -1,5 +1,6 @@ use cosmrs::proto::prost; use hyperlane_core::ChainCommunicationError; +use std::fmt::Debug; /// Errors from the crates specific to the hyperlane-cosmos /// implementation. @@ -28,12 +29,21 @@ pub enum HyperlaneCosmosError { /// Tonic error #[error("{0}")] Tonic(#[from] tonic::transport::Error), + /// Tonic codegen error + #[error("{0}")] + TonicGenError(#[from] tonic::codegen::StdError), /// Tendermint RPC Error #[error(transparent)] TendermintError(#[from] tendermint_rpc::error::Error), - /// protobuf error + /// Prost error + #[error("{0}")] + Prost(#[from] prost::DecodeError), + /// Protobuf error #[error("{0}")] - Protobuf(#[from] prost::DecodeError), + Protobuf(#[from] protobuf::ProtobufError), + /// Fallback providers failed + #[error("Fallback providers failed. (Errors: {0:?})")] + FallbackProvidersFailed(Vec), } impl From for ChainCommunicationError { diff --git a/rust/chains/hyperlane-cosmos/src/interchain_gas.rs b/rust/chains/hyperlane-cosmos/src/interchain_gas.rs index d96bfb0bab..2beafe4e78 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_gas.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_gas.rs @@ -4,13 +4,12 @@ use cosmrs::tendermint::abci::EventAttribute; use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexer, InterchainGasPaymaster, InterchainGasPayment, - LogMeta, SequenceIndexer, H256, U256, + LogMeta, SequenceAwareIndexer, H256, U256, }; use once_cell::sync::Lazy; use std::ops::RangeInclusive; use crate::{ - grpc::WasmGrpcProvider, rpc::{CosmosWasmIndexer, ParsedEvent, WasmIndexer}, signers::Signer, utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}, @@ -22,6 +21,7 @@ use crate::{ pub struct CosmosInterchainGasPaymaster { domain: HyperlaneDomain, address: H256, + provider: CosmosProvider, } impl HyperlaneContract for CosmosInterchainGasPaymaster { @@ -36,7 +36,7 @@ impl HyperlaneChain for CosmosInterchainGasPaymaster { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -49,11 +49,17 @@ impl CosmosInterchainGasPaymaster { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, + provider, }) } } @@ -210,8 +216,8 @@ impl Indexer for CosmosInterchainGasPaymasterIndexer { } #[async_trait] -impl SequenceIndexer for CosmosInterchainGasPaymasterIndexer { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { +impl SequenceAwareIndexer for CosmosInterchainGasPaymasterIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { // TODO: implement when cosmwasm scraper support is implemented let tip = self.get_finalized_block_number().await?; Ok((None, tip)) diff --git a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs index 72a0ac984d..dd495be89a 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs @@ -1,12 +1,14 @@ use async_trait::async_trait; use hyperlane_core::{ ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, - HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, H256, U256, + HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, RawHyperlaneMessage, + H256, U256, }; use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, + grpc::WasmProvider, payloads::{ + aggregate_ism::{VerifyRequest, VerifyRequestInner, VerifyResponse}, general::EmptyStruct, ism_routes::{QueryIsmGeneralRequest, QueryIsmModuleTypeRequest}, }, @@ -22,7 +24,7 @@ pub struct CosmosInterchainSecurityModule { /// The address of the ISM contract. address: H256, /// The provider for the ISM contract. - provider: Box, + provider: CosmosProvider, } /// The Cosmos Interchain Security Module Implementation. @@ -33,13 +35,17 @@ impl CosmosInterchainSecurityModule { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider: WasmGrpcProvider = - WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -56,7 +62,7 @@ impl HyperlaneChain for CosmosInterchainSecurityModule { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -71,11 +77,12 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { let data = self .provider + .grpc() .wasm_query(QueryIsmGeneralRequest { ism: query }, None) .await?; let module_type_response = - serde_json::from_slice::(&data)?; + serde_json::from_slice::(&data)?; Ok(IsmType(module_type_response.typ).into()) } @@ -86,6 +93,23 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { message: &HyperlaneMessage, metadata: &[u8], ) -> ChainResult> { - Ok(Some(U256::from(1000))) // TODO + let payload = VerifyRequest { + verify: VerifyRequestInner { + metadata: hex::encode(metadata), + message: hex::encode(RawHyperlaneMessage::from(message)), + }, + }; + let data = self + .provider + .grpc() + .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) + .await?; + let response: VerifyResponse = serde_json::from_slice(&data)?; + // We can't simulate the `verify` call in CosmWasm because + // it's not marked as an entrypoint. So we just use the query interface + // and hardcode a gas value - this can be inefficient if one ISM is + // vastly cheaper than another one. + let dummy_gas_value = U256::one(); + Ok(response.verified.then_some(dummy_gas_value)) } } diff --git a/rust/chains/hyperlane-cosmos/src/lib.rs b/rust/chains/hyperlane-cosmos/src/lib.rs index 82a4a0ece1..c0ce3ad549 100644 --- a/rust/chains/hyperlane-cosmos/src/lib.rs +++ b/rust/chains/hyperlane-cosmos/src/lib.rs @@ -16,6 +16,7 @@ mod multisig_ism; mod payloads; mod providers; mod routing_ism; +mod rpc_clients; mod signers; mod trait_builder; mod types; diff --git a/rust/chains/hyperlane-cosmos/src/libs/address.rs b/rust/chains/hyperlane-cosmos/src/libs/address.rs index d5970b9b82..e594cb4603 100644 --- a/rust/chains/hyperlane-cosmos/src/libs/address.rs +++ b/rust/chains/hyperlane-cosmos/src/libs/address.rs @@ -12,7 +12,7 @@ use tendermint::public_key::PublicKey as TendermintPublicKey; use crate::HyperlaneCosmosError; /// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct CosmosAddress { /// Bech32 encoded cosmos account account_id: AccountId, @@ -22,7 +22,7 @@ pub struct CosmosAddress { impl CosmosAddress { /// Returns a Bitcoin style address: RIPEMD160(SHA256(pubkey)) - /// Source: https://github.com/cosmos/cosmos-sdk/blob/177e7f45959215b0b4e85babb7c8264eaceae052/crypto/keys/secp256k1/secp256k1.go#L154 + /// Source: `` pub fn from_pubkey(pubkey: PublicKey, prefix: &str) -> ChainResult { // Get the inner type let tendermint_pubkey = TendermintPublicKey::from(pubkey); @@ -36,7 +36,7 @@ impl CosmosAddress { Ok(CosmosAddress::new(account_id, digest)) } - /// Creates a wrapper arround a cosmrs AccountId from a private key byte array + /// Creates a wrapper around a cosmrs AccountId from a private key byte array pub fn from_privkey(priv_key: &[u8], prefix: &str) -> ChainResult { let pubkey = SigningKey::from_slice(priv_key) .map_err(Into::::into)? @@ -44,13 +44,25 @@ impl CosmosAddress { Self::from_pubkey(pubkey, prefix) } - /// Creates a wrapper arround a cosmrs AccountId from a H256 digest + /// Creates a wrapper around a cosmrs AccountId from a H256 digest /// /// - digest: H256 digest (hex representation of address) /// - prefix: Bech32 prefix - pub fn from_h256(digest: H256, prefix: &str) -> ChainResult { + /// - byte_count: Number of bytes to truncate the digest to. Cosmos addresses can sometimes + /// be less than 32 bytes, so this helps to serialize it in bech32 with the appropriate + /// length. + pub fn from_h256(digest: H256, prefix: &str, byte_count: usize) -> ChainResult { // This is the hex-encoded version of the address - let bytes = digest.as_bytes(); + let untruncated_bytes = digest.as_bytes(); + + if byte_count > untruncated_bytes.len() { + return Err(Overflow.into()); + } + + let remainder_bytes_start = untruncated_bytes.len() - byte_count; + // Left-truncate the digest to the desired length + let bytes = &untruncated_bytes[remainder_bytes_start..]; + // Bech32 encode it let account_id = AccountId::new(prefix, bytes).map_err(Into::::into)?; @@ -132,6 +144,13 @@ pub mod test { addr.address(), "neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj" ); + + // Create an address with the same digest & explicitly set the byte count to 20, + // which should have the same result as the above. + let digest = addr.digest(); + let addr2 = + CosmosAddress::from_h256(digest, prefix, 20).expect("Cosmos address creation failed"); + assert_eq!(addr.address(), addr2.address()); } #[test] @@ -139,10 +158,19 @@ pub mod test { let hex_key = "0x1b16866227825a5166eb44031cdcf6568b3e80b52f2806e01b89a34dc90ae616"; let key = hex_or_base58_to_h256(hex_key).unwrap(); let prefix = "dual"; - let addr = CosmosAddress::from_h256(key, prefix).expect("Cosmos address creation failed"); + let addr = + CosmosAddress::from_h256(key, prefix, 32).expect("Cosmos address creation failed"); assert_eq!( addr.address(), "dual1rvtgvc38sfd9zehtgsp3eh8k269naq949u5qdcqm3x35mjg2uctqfdn3yq" ); + + // Last 20 bytes only, which is 0x1cdcf6568b3e80b52f2806e01b89a34dc90ae616 + let addr = + CosmosAddress::from_h256(key, prefix, 20).expect("Cosmos address creation failed"); + assert_eq!( + addr.address(), + "dual1rnw0v45t86qt2tegqmsphzdrfhys4esk9ktul7" + ); } } diff --git a/rust/chains/hyperlane-cosmos/src/mailbox.rs b/rust/chains/hyperlane-cosmos/src/mailbox.rs index 4df968edc9..8f5bab03d6 100644 --- a/rust/chains/hyperlane-cosmos/src/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/mailbox.rs @@ -14,10 +14,7 @@ use crate::payloads::{general, mailbox}; use crate::rpc::{CosmosWasmIndexer, ParsedEvent, WasmIndexer}; use crate::CosmosProvider; use crate::{address::CosmosAddress, types::tx_response_to_outcome}; -use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, - HyperlaneCosmosError, -}; +use crate::{grpc::WasmProvider, HyperlaneCosmosError}; use crate::{signers::Signer, utils::get_block_height_for_lag, ConnectionConf}; use async_trait::async_trait; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; @@ -26,12 +23,12 @@ use once_cell::sync::Lazy; use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}; use hyperlane_core::{ - utils::fmt_bytes, ChainResult, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + utils::bytes_to_hex, ChainResult, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, Mailbox, TxCostEstimate, TxOutcome, H256, U256, }; use hyperlane_core::{ - ChainCommunicationError, ContractLocator, Decode, RawHyperlaneMessage, SequenceIndexer, + ChainCommunicationError, ContractLocator, Decode, RawHyperlaneMessage, SequenceAwareIndexer, }; use tracing::{instrument, warn}; @@ -40,7 +37,7 @@ pub struct CosmosMailbox { config: ConnectionConf, domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosMailbox { @@ -51,19 +48,28 @@ impl CosmosMailbox { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { config: conf, domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } /// Prefix used in the bech32 address encoding - pub fn prefix(&self) -> String { - self.config.get_prefix() + pub fn bech32_prefix(&self) -> String { + self.config.get_bech32_prefix() + } + + fn contract_address_bytes(&self) -> usize { + self.config.get_contract_address_bytes() } } @@ -79,7 +85,7 @@ impl HyperlaneChain for CosmosMailbox { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -94,7 +100,7 @@ impl Debug for CosmosMailbox { impl Mailbox for CosmosMailbox { #[instrument(level = "debug", err, ret, skip(self))] async fn count(&self, lag: Option) -> ChainResult { - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; self.nonce_at_block(block_height).await } @@ -107,6 +113,7 @@ impl Mailbox for CosmosMailbox { let delivered = match self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await { @@ -136,18 +143,24 @@ impl Mailbox for CosmosMailbox { let data = self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await?; let response: mailbox::DefaultIsmResponse = serde_json::from_slice(&data)?; - // convert Hex to H256 - let ism = H256::from_slice(&hex::decode(response.default_ism)?); - Ok(ism) + // convert bech32 to H256 + let ism = CosmosAddress::from_str(&response.default_ism)?; + Ok(ism.digest()) } #[instrument(err, ret, skip(self))] async fn recipient_ism(&self, recipient: H256) -> ChainResult { - let address = CosmosAddress::from_h256(recipient, &self.prefix())?.address(); + let address = CosmosAddress::from_h256( + recipient, + &self.bech32_prefix(), + self.contract_address_bytes(), + )? + .address(); let payload = mailbox::RecipientIsmRequest { recipient_ism: mailbox::RecipientIsmRequestInner { @@ -157,11 +170,12 @@ impl Mailbox for CosmosMailbox { let data = self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await?; let response: mailbox::RecipientIsmResponse = serde_json::from_slice(&data)?; - // convert Hex to H256 + // convert bech32 to H256 let ism = CosmosAddress::from_str(&response.ism)?; Ok(ism.digest()) } @@ -182,13 +196,14 @@ impl Mailbox for CosmosMailbox { let response: TxResponse = self .provider + .grpc() .wasm_send(process_message, tx_gas_limit) .await?; Ok(tx_response_to_outcome(response)?) } - #[instrument(err, ret, skip(self), fields(msg=%message, metadata=%fmt_bytes(metadata)))] + #[instrument(err, ret, skip(self), fields(msg=%message, metadata=%bytes_to_hex(metadata)))] async fn process_estimate_costs( &self, message: &HyperlaneMessage, @@ -201,11 +216,15 @@ impl Mailbox for CosmosMailbox { }, }; - let gas_limit = self.provider.wasm_estimate_gas(process_message).await?; + let gas_limit = self + .provider + .grpc() + .wasm_estimate_gas(process_message) + .await?; let result = TxCostEstimate { gas_limit: gas_limit.into(), - gas_price: U256::from(2500), + gas_price: self.provider.grpc().gas_price(), l2_gas_limit: None, }; @@ -226,6 +245,7 @@ impl CosmosMailbox { let data = self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, block_height) .await?; @@ -358,8 +378,8 @@ impl Indexer for CosmosMailboxIndexer { } #[async_trait] -impl SequenceIndexer for CosmosMailboxIndexer { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { +impl SequenceAwareIndexer for CosmosMailboxIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = Indexer::::get_finalized_block_number(&self).await?; // No sequence for message deliveries. @@ -368,8 +388,8 @@ impl SequenceIndexer for CosmosMailboxIndexer { } #[async_trait] -impl SequenceIndexer for CosmosMailboxIndexer { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { +impl SequenceAwareIndexer for CosmosMailboxIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = Indexer::::get_finalized_block_number(&self).await?; let sequence = self.mailbox.nonce_at_block(Some(tip.into())).await?; diff --git a/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs b/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs index 15db14ff7a..6d8ebe9104 100644 --- a/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs @@ -6,13 +6,13 @@ use cosmrs::tendermint::abci::EventAttribute; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, - Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, SequenceIndexer, H256, + Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, }; use once_cell::sync::Lazy; use tracing::instrument; use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, + grpc::WasmProvider, payloads::{ general::{self}, merkle_tree_hook, @@ -33,7 +33,7 @@ pub struct CosmosMerkleTreeHook { /// Contract address address: H256, /// Provider - provider: Box, + provider: CosmosProvider, } impl CosmosMerkleTreeHook { @@ -43,12 +43,17 @@ impl CosmosMerkleTreeHook { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -65,7 +70,7 @@ impl HyperlaneChain for CosmosMerkleTreeHook { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -78,10 +83,11 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { tree: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; let data = self .provider + .grpc() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -111,7 +117,7 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { count: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; self.count_at_block(block_height).await } @@ -122,10 +128,11 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { check_point: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; let data = self .provider + .grpc() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -153,6 +160,7 @@ impl CosmosMerkleTreeHook { let data = self .provider + .grpc() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -292,8 +300,8 @@ impl Indexer for CosmosMerkleTreeHookIndexer { } #[async_trait] -impl SequenceIndexer for CosmosMerkleTreeHookIndexer { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { +impl SequenceAwareIndexer for CosmosMerkleTreeHookIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = self.get_finalized_block_number().await?; let sequence = self .merkle_tree_hook diff --git a/rust/chains/hyperlane-cosmos/src/multisig_ism.rs b/rust/chains/hyperlane-cosmos/src/multisig_ism.rs index a9d84dec7f..d558acfa37 100644 --- a/rust/chains/hyperlane-cosmos/src/multisig_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/multisig_ism.rs @@ -1,9 +1,7 @@ use std::str::FromStr; use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, - payloads::ism_routes::QueryIsmGeneralRequest, - signers::Signer, + grpc::WasmProvider, payloads::ism_routes::QueryIsmGeneralRequest, signers::Signer, ConnectionConf, CosmosProvider, }; use async_trait::async_trait; @@ -19,7 +17,7 @@ use crate::payloads::multisig_ism::{self, VerifyInfoRequest, VerifyInfoRequestIn pub struct CosmosMultisigIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosMultisigIsm { @@ -29,12 +27,17 @@ impl CosmosMultisigIsm { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -51,7 +54,7 @@ impl HyperlaneChain for CosmosMultisigIsm { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -70,6 +73,7 @@ impl MultisigIsm for CosmosMultisigIsm { let data = self .provider + .grpc() .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) .await?; let response: multisig_ism::VerifyInfoResponse = serde_json::from_slice(&data)?; diff --git a/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs b/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs index 7bb5d40d09..23bb35a8f8 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs @@ -1,30 +1,17 @@ -use hyperlane_core::{HyperlaneMessage, RawHyperlaneMessage}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] -pub struct ModulesAndThresholdRequest { - modules_and_threshold: ModulesAndThresholdRequestInner, -} - -impl ModulesAndThresholdRequest { - pub fn new(message: &HyperlaneMessage) -> Self { - Self { - modules_and_threshold: ModulesAndThresholdRequestInner { - message: hex::encode(RawHyperlaneMessage::from(message)), - }, - } - } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyRequest { + pub verify: VerifyRequestInner, } -#[derive(Serialize, Deserialize, Debug)] -struct ModulesAndThresholdRequestInner { - /// Hex-encoded Hyperlane message +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyRequestInner { + pub metadata: String, pub message: String, } #[derive(Serialize, Deserialize, Debug)] -pub struct ModulesAndThresholdResponse { - pub threshold: u8, - /// Bech32-encoded module addresses - pub modules: Vec, +pub struct VerifyResponse { + pub verified: bool, } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/general.rs b/rust/chains/hyperlane-cosmos/src/payloads/general.rs index 488cae2d37..af2a4b0b6e 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/general.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/general.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct EmptyStruct {} #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs b/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs index 052a1cc48b..4a0563945f 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs @@ -1,12 +1,12 @@ use super::general::EmptyStruct; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct IsmRouteRequest { pub route: IsmRouteRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct IsmRouteRequestInner { pub message: String, // hexbinary } @@ -16,22 +16,22 @@ pub struct IsmRouteRespnose { pub ism: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryRoutingIsmGeneralRequest { pub routing_ism: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryRoutingIsmRouteResponse { pub ism: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryIsmGeneralRequest { pub ism: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryIsmModuleTypeRequest { pub module_type: EmptyStruct, } @@ -39,5 +39,5 @@ pub struct QueryIsmModuleTypeRequest { #[derive(Serialize, Deserialize, Debug)] pub struct QueryIsmModuleTypeResponse { #[serde(rename = "type")] - pub typ: hpl_interface::ism::IsmType, + pub typ: hyperlane_cosmwasm_interface::ism::IsmType, } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs b/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs index 145ba5b16c..75eef04595 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs @@ -3,52 +3,52 @@ use serde::{Deserialize, Serialize}; use super::general::EmptyStruct; // Requests -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GeneralMailboxQuery { pub mailbox: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct CountRequest { pub count: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct NonceRequest { pub nonce: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RecipientIsmRequest { pub recipient_ism: RecipientIsmRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RecipientIsmRequestInner { pub recipient_addr: String, // hexbinary } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DefaultIsmRequest { pub default_ism: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DeliveredRequest { pub message_delivered: DeliveredRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DeliveredRequestInner { pub id: String, // hexbinary } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProcessMessageRequest { pub process: ProcessMessageRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProcessMessageRequestInner { pub metadata: String, pub message: String, diff --git a/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs b/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs index 7635f0ef72..e960628771 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs @@ -4,24 +4,24 @@ use super::general::EmptyStruct; const TREE_DEPTH: usize = 32; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MerkleTreeGenericRequest { pub merkle_hook: T, } // --------- Requests --------- -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MerkleTreeRequest { pub tree: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MerkleTreeCountRequest { pub count: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct CheckPointRequest { pub check_point: EmptyStruct, } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs b/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs index 204e726dc7..c56588d1d6 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs @@ -1,11 +1,11 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyInfoRequest { pub verify_info: VerifyInfoRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyInfoRequestInner { pub message: String, // hexbinary } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs b/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs index fdf449c7c4..cf4e5eb1f8 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs @@ -2,17 +2,17 @@ use serde::{Deserialize, Serialize}; use super::general::EmptyStruct; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetAnnouncedValidatorsRequest { pub get_announced_validators: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetAnnounceStorageLocationsRequest { pub get_announce_storage_locations: GetAnnounceStorageLocationsRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetAnnounceStorageLocationsRequestInner { pub validators: Vec, } diff --git a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs index a47d660ded..a6bc070aba 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs @@ -5,6 +5,7 @@ use cosmrs::{ auth::v1beta1::{ query_client::QueryClient as QueryAccountClient, BaseAccount, QueryAccountRequest, }, + bank::v1beta1::{query_client::QueryClient as QueryBalanceClient, QueryBalanceRequest}, base::{ abci::v1beta1::TxResponse, tendermint::v1beta1::{service_client::ServiceClient, GetLatestBlockRequest}, @@ -21,19 +22,25 @@ use cosmrs::{ traits::Message, }, tx::{self, Fee, MessageExt, SignDoc, SignerInfo}, - Amount, Coin, + Any, Coin, }; -use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator, U256}; +use derive_new::new; +use hyperlane_core::{ + rpc_clients::{BlockNumberGetter, FallbackProvider}, + ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, HyperlaneDomain, U256, +}; +use protobuf::Message as _; use serde::Serialize; -use tonic::transport::{Channel, Endpoint}; +use tonic::{ + transport::{Channel, Endpoint}, + GrpcMethod, IntoRequest, +}; +use url::Url; -use crate::address::CosmosAddress; -use crate::HyperlaneCosmosError; +use crate::{address::CosmosAddress, CosmosAmount}; +use crate::{rpc_clients::CosmosFallbackProvider, HyperlaneCosmosError}; use crate::{signers::Signer, ConnectionConf}; -/// The gas price to use for transactions. -/// TODO: is there a nice way to get a suggested price dynamically? -const DEFAULT_GAS_PRICE: f64 = 0.05; /// A multiplier applied to a simulated transaction's gas usage to /// calculate the estimated gas. const GAS_ESTIMATE_MULTIPLIER: f64 = 1.25; @@ -41,6 +48,36 @@ const GAS_ESTIMATE_MULTIPLIER: f64 = 1.25; /// be valid for. const TIMEOUT_BLOCKS: u64 = 1000; +#[derive(Debug, Clone, new)] +struct CosmosChannel { + channel: Channel, + /// The url that this channel is connected to. + /// Not explicitly used, but useful for debugging. + _url: Url, +} + +#[async_trait] +impl BlockNumberGetter for CosmosChannel { + async fn get_block_number(&self) -> Result { + let mut client = ServiceClient::new(self.channel.clone()); + let request = tonic::Request::new(GetLatestBlockRequest {}); + + let response = client + .get_latest_block(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + let height = response + .block + .ok_or_else(|| ChainCommunicationError::from_other_str("block not present"))? + .header + .ok_or_else(|| ChainCommunicationError::from_other_str("header not present"))? + .height; + + Ok(height as u64) + } +} + #[async_trait] /// Cosmwasm GRPC Provider pub trait WasmProvider: Send + Sync { @@ -52,14 +89,14 @@ pub trait WasmProvider: Send + Sync { async fn latest_block_height(&self) -> ChainResult; /// Perform a wasm query against the stored contract address. - async fn wasm_query( + async fn wasm_query( &self, payload: T, block_height: Option, ) -> ChainResult>; /// Perform a wasm query against a specified contract address. - async fn wasm_query_to( + async fn wasm_query_to( &self, to: String, payload: T, @@ -67,48 +104,78 @@ pub trait WasmProvider: Send + Sync { ) -> ChainResult>; /// Send a wasm tx. - async fn wasm_send( + async fn wasm_send( &self, payload: T, gas_limit: Option, ) -> ChainResult; /// Estimate gas for a wasm tx. - async fn wasm_estimate_gas(&self, payload: T) -> ChainResult; + async fn wasm_estimate_gas( + &self, + payload: T, + ) -> ChainResult; } -#[derive(Debug)] +#[derive(Debug, Clone)] /// CosmWasm GRPC provider. pub struct WasmGrpcProvider { + /// Hyperlane domain, used for special cases depending on the chain. + domain: HyperlaneDomain, /// Connection configuration. conf: ConnectionConf, /// A contract address that can be used as the default /// for queries / sends / estimates. - contract_address: CosmosAddress, + contract_address: Option, /// Signer for transactions. signer: Option, /// GRPC Channel that can be cheaply cloned. - /// See https://docs.rs/tonic/latest/tonic/transport/struct.Channel.html#multiplexing-requests - channel: Channel, + /// See `` + provider: CosmosFallbackProvider, + gas_price: CosmosAmount, } impl WasmGrpcProvider { /// Create new CosmWasm GRPC Provider. pub fn new( + domain: HyperlaneDomain, conf: ConnectionConf, - locator: ContractLocator, + gas_price: CosmosAmount, + locator: Option, signer: Option, ) -> ChainResult { - let endpoint = - Endpoint::new(conf.get_grpc_url()).map_err(Into::::into)?; - let channel = endpoint.connect_lazy(); - let contract_address = CosmosAddress::from_h256(locator.address, &conf.get_prefix())?; + // get all the configured grpc urls and convert them to a Vec + let channels: Result, _> = conf + .get_grpc_urls() + .into_iter() + .map(|url| { + Endpoint::new(url.to_string()) + .map(|e| CosmosChannel::new(e.connect_lazy(), url)) + .map_err(Into::::into) + }) + .collect(); + let mut builder = FallbackProvider::builder(); + builder = builder.add_providers(channels?); + let fallback_provider = builder.build(); + let provider = CosmosFallbackProvider::new(fallback_provider); + + let contract_address = locator + .map(|l| { + CosmosAddress::from_h256( + l.address, + &conf.get_bech32_prefix(), + conf.get_contract_address_bytes(), + ) + }) + .transpose()?; Ok(Self { + domain, conf, contract_address, signer, - channel, + provider, + gas_price, }) } @@ -118,9 +185,12 @@ impl WasmGrpcProvider { .as_ref() .ok_or(ChainCommunicationError::SignerUnavailable) } -} -impl WasmGrpcProvider { + /// Get the gas price + pub fn gas_price(&self) -> FixedPointNumber { + self.gas_price.amount.clone() + } + /// Generates an unsigned SignDoc for a transaction. async fn generate_unsigned_sign_doc( &self, @@ -142,9 +212,13 @@ impl WasmGrpcProvider { ); let signer_info = SignerInfo::single_direct(Some(signer.public_key), account_info.sequence); + let amount: u128 = (FixedPointNumber::from(gas_limit) * self.gas_price()) + .ceil_to_integer() + .try_into()?; let auth_info = signer_info.auth_info(Fee::from_amount_and_gas( Coin::new( - Amount::from((gas_limit as f64 * DEFAULT_GAS_PRICE) as u64), + // The fee to pay is the gas limit * the gas price + amount, self.conf.get_canonical_asset().as_str(), ) .map_err(Into::::into)?, @@ -199,37 +273,96 @@ impl WasmGrpcProvider { // https://github.com/cosmos/cosmjs/blob/44893af824f0712d1f406a8daa9fcae335422235/packages/stargate/src/modules/tx/queries.ts#L67 signatures: vec![vec![]], }; - - let mut client = TxServiceClient::new(self.channel.clone()); let tx_bytes = raw_tx .to_bytes() .map_err(ChainCommunicationError::from_other)?; - #[allow(deprecated)] - let sim_req = tonic::Request::new(SimulateRequest { tx: None, tx_bytes }); - let gas_used = client - .simulate(sim_req) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner() - .gas_info - .ok_or_else(|| ChainCommunicationError::from_other_str("gas info not present"))? - .gas_used; + let gas_used = self + .provider + .call(move |provider| { + let tx_bytes_clone = tx_bytes.clone(); + let future = async move { + let mut client = TxServiceClient::new(provider.channel.clone()); + #[allow(deprecated)] + let sim_req = tonic::Request::new(SimulateRequest { + tx: None, + tx_bytes: tx_bytes_clone, + }); + let gas_used = client + .simulate(sim_req) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner() + .gas_info + .ok_or_else(|| { + ChainCommunicationError::from_other_str("gas info not present") + })? + .gas_used; + + Ok(gas_used) + }; + Box::pin(future) + }) + .await?; let gas_estimate = (gas_used as f64 * GAS_ESTIMATE_MULTIPLIER) as u64; Ok(gas_estimate) } + /// Fetches balance for a given `address` and `denom` + pub async fn get_balance(&self, address: String, denom: String) -> ChainResult { + let response = self + .provider + .call(move |provider| { + let address = address.clone(); + let denom = denom.clone(); + let future = async move { + let mut client = QueryBalanceClient::new(provider.channel.clone()); + let balance_request = + tonic::Request::new(QueryBalanceRequest { address, denom }); + let response = client + .balance(balance_request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; + + let balance = response + .balance + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))?; + + Ok(U256::from_dec_str(&balance.amount)?) + } + /// Queries an account. - async fn account_query(&self, account: String) -> ChainResult { - let mut client = QueryAccountClient::new(self.channel.clone()); + pub async fn account_query(&self, account: String) -> ChainResult { + // Injective is a special case where their account query requires + // the use of different protobuf types. + if self.domain.is_injective() { + return self.account_query_injective(account).await; + } - let request = tonic::Request::new(QueryAccountRequest { address: account }); - let response = client - .account(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); + let response = self + .provider + .call(move |provider| { + let address = account.clone(); + let future = async move { + let mut client = QueryAccountClient::new(provider.channel.clone()); + let request = tonic::Request::new(QueryAccountRequest { address }); + let response = client + .account(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; let account = BaseAccount::decode( response @@ -241,19 +374,95 @@ impl WasmGrpcProvider { .map_err(Into::::into)?; Ok(account) } + + /// Injective-specific logic for querying an account. + async fn account_query_injective(&self, account: String) -> ChainResult { + let response = self + .provider + .call(move |provider| { + let address = account.clone(); + let future = async move { + let request = tonic::Request::new( + injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest { + address, + }, + ); + + // Borrowed from the logic of `QueryAccountClient` in `cosmrs`, but using injective types. + + let mut grpc_client = tonic::client::Grpc::new(provider.channel.clone()); + grpc_client + .ready() + .await + .map_err(Into::::into)?; + + let codec = tonic::codec::ProstCodec::default(); + let path = + http::uri::PathAndQuery::from_static("/cosmos.auth.v1beta1.Query/Account"); + let mut req: tonic::Request< + injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest, + > = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.auth.v1beta1.Query", "Account")); + + let response: tonic::Response< + injective_std::types::cosmos::auth::v1beta1::QueryAccountResponse, + > = grpc_client + .unary(req, path, codec) + .await + .map_err(Into::::into)?; + + Ok(response) + }; + Box::pin(future) + }) + .await?; + + let mut eth_account = injective_protobuf::proto::account::EthAccount::parse_from_bytes( + response + .into_inner() + .account + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? + .value + .as_slice(), + ) + .map_err(Into::::into)?; + + let base_account = eth_account.take_base_account(); + let pub_key = base_account.pub_key.into_option(); + + Ok(BaseAccount { + address: base_account.address, + pub_key: pub_key.map(|pub_key| Any { + type_url: pub_key.type_url, + value: pub_key.value, + }), + account_number: base_account.account_number, + sequence: base_account.sequence, + }) + } } #[async_trait] impl WasmProvider for WasmGrpcProvider { async fn latest_block_height(&self) -> ChainResult { - let mut client = ServiceClient::new(self.channel.clone()); - let request = tonic::Request::new(GetLatestBlockRequest {}); + let response = self + .provider + .call(move |provider| { + let future = async move { + let mut client = ServiceClient::new(provider.channel.clone()); + let request = tonic::Request::new(GetLatestBlockRequest {}); + let response = client + .get_latest_block(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; - let response = client - .get_latest_block(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); let height = response .block .ok_or_else(|| ChainCommunicationError::from_other_str("block not present"))? @@ -266,9 +475,12 @@ impl WasmProvider for WasmGrpcProvider { async fn wasm_query(&self, payload: T, block_height: Option) -> ChainResult> where - T: Serialize + Send + Sync, + T: Serialize + Send + Sync + Clone, { - self.wasm_query_to(self.contract_address.address(), payload, block_height) + let contract_address = self.contract_address.as_ref().ok_or_else(|| { + ChainCommunicationError::from_other_str("No contract address available") + })?; + self.wasm_query_to(contract_address.address(), payload, block_height) .await } @@ -279,47 +491,56 @@ impl WasmProvider for WasmGrpcProvider { block_height: Option, ) -> ChainResult> where - T: Serialize + Send + Sync, + T: Serialize + Send + Sync + Clone, { - let mut client = WasmQueryClient::new(self.channel.clone()); - let mut request = tonic::Request::new(QuerySmartContractStateRequest { - address: to, - query_data: serde_json::to_string(&payload)?.as_bytes().to_vec(), - }); - - if let Some(block_height) = block_height { - request - .metadata_mut() - .insert("x-cosmos-block-height", block_height.into()); - } - - let response = client - .smart_contract_state(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); + let query_data = serde_json::to_string(&payload)?.as_bytes().to_vec(); + let response = self + .provider + .call(move |provider| { + let to = to.clone(); + let query_data = query_data.clone(); + let future = async move { + let mut client = WasmQueryClient::new(provider.channel.clone()); + + let mut request = tonic::Request::new(QuerySmartContractStateRequest { + address: to, + query_data, + }); + if let Some(block_height) = block_height { + request + .metadata_mut() + .insert("x-cosmos-block-height", block_height.into()); + } + let response = client + .smart_contract_state(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; Ok(response.data) } async fn wasm_send(&self, payload: T, gas_limit: Option) -> ChainResult where - T: Serialize + Send + Sync, + T: Serialize + Send + Sync + Clone, { let signer = self.get_signer()?; - let mut client = TxServiceClient::new(self.channel.clone()); - + let contract_address = self.contract_address.as_ref().ok_or_else(|| { + ChainCommunicationError::from_other_str("No contract address available") + })?; let msgs = vec![MsgExecuteContract { sender: signer.address.clone(), - contract: self.contract_address.address(), + contract: contract_address.address(), msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), funds: vec![], } .to_any() .map_err(ChainCommunicationError::from_other)?]; - - // We often use U256s to represent gas limits, but Cosmos expects u64s. Try to convert, - // and if it fails, just fallback to None which will result in gas estimation. let gas_limit: Option = gas_limit.and_then(|limit| match limit.try_into() { Ok(limit) => Some(limit), Err(err) => { @@ -330,20 +551,30 @@ impl WasmProvider for WasmGrpcProvider { None } }); - - let tx_req = BroadcastTxRequest { - tx_bytes: self.generate_raw_signed_tx(msgs, gas_limit).await?, - mode: BroadcastMode::Sync as i32, - }; - - let tx_res = client - .broadcast_tx(tx_req) - .await - .map_err(Into::::into)? - .into_inner() - .tx_response - .ok_or_else(|| ChainCommunicationError::from_other_str("Empty tx_response"))?; - + let tx_bytes = self.generate_raw_signed_tx(msgs, gas_limit).await?; + let tx_res = self + .provider + .call(move |provider| { + let tx_bytes = tx_bytes.clone(); + let future = async move { + let mut client = TxServiceClient::new(provider.channel.clone()); + // We often use U256s to represent gas limits, but Cosmos expects u64s. Try to convert, + // and if it fails, just fallback to None which will result in gas estimation. + let tx_req = BroadcastTxRequest { + tx_bytes, + mode: BroadcastMode::Sync as i32, + }; + client + .broadcast_tx(tx_req) + .await + .map_err(Into::::into)? + .into_inner() + .tx_response + .ok_or_else(|| ChainCommunicationError::from_other_str("Empty tx_response")) + }; + Box::pin(future) + }) + .await?; Ok(tx_res) } @@ -354,9 +585,12 @@ impl WasmProvider for WasmGrpcProvider { // Estimating gas requires a signer, which we can reasonably expect to have // since we need one to send a tx with the estimated gas anyways. let signer = self.get_signer()?; + let contract_address = self.contract_address.as_ref().ok_or_else(|| { + ChainCommunicationError::from_other_str("No contract address available") + })?; let msg = MsgExecuteContract { sender: signer.address.clone(), - contract: self.contract_address.address(), + contract: contract_address.address(), msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), funds: vec![], }; @@ -370,3 +604,10 @@ impl WasmProvider for WasmGrpcProvider { Ok(response) } } + +#[async_trait] +impl BlockNumberGetter for WasmGrpcProvider { + async fn get_block_number(&self) -> Result { + self.latest_block_height().await + } +} diff --git a/rust/chains/hyperlane-cosmos/src/providers/mod.rs b/rust/chains/hyperlane-cosmos/src/providers/mod.rs index cf9422b2f8..2d1e121e88 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/mod.rs @@ -1,23 +1,70 @@ use async_trait::async_trait; use hyperlane_core::{ - BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, + BlockInfo, ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain, + HyperlaneProvider, TxnInfo, H256, U256, }; +use tendermint_rpc::{client::CompatMode, HttpClient}; + +use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer}; + +use self::grpc::WasmGrpcProvider; /// cosmos grpc provider pub mod grpc; /// cosmos rpc provider pub mod rpc; -/// A reference to a Cosmos chain -#[derive(Debug)] +/// Abstraction over a connection to a Cosmos chain +#[derive(Debug, Clone)] pub struct CosmosProvider { domain: HyperlaneDomain, + canonical_asset: String, + grpc_client: WasmGrpcProvider, + rpc_client: HttpClient, } impl CosmosProvider { /// Create a reference to a Cosmos chain - pub fn new(domain: HyperlaneDomain) -> Self { - Self { domain } + pub fn new( + domain: HyperlaneDomain, + conf: ConnectionConf, + locator: Option, + signer: Option, + ) -> ChainResult { + let gas_price = CosmosAmount::try_from(conf.get_minimum_gas_price().clone())?; + let grpc_client = WasmGrpcProvider::new( + domain.clone(), + conf.clone(), + gas_price.clone(), + locator, + signer, + )?; + let rpc_client = HttpClient::builder( + conf.get_rpc_url() + .parse() + .map_err(Into::::into)?, + ) + // Consider supporting different compatibility modes. + .compat_mode(CompatMode::latest()) + .build() + .map_err(Into::::into)?; + + Ok(Self { + domain, + rpc_client, + grpc_client, + canonical_asset: conf.get_canonical_asset(), + }) + } + + /// Get a grpc client + pub fn grpc(&self) -> &WasmGrpcProvider { + &self.grpc_client + } + + /// Get an rpc client + pub fn rpc(&self) -> &HttpClient { + &self.rpc_client } } @@ -27,9 +74,7 @@ impl HyperlaneChain for CosmosProvider { } fn provider(&self) -> Box { - Box::new(CosmosProvider { - domain: self.domain.clone(), - }) + Box::new(self.clone()) } } @@ -47,4 +92,15 @@ impl HyperlaneProvider for CosmosProvider { // FIXME Ok(true) } + + async fn get_balance(&self, address: String) -> ChainResult { + Ok(self + .grpc_client + .get_balance(address, self.canonical_asset.clone()) + .await?) + } + + async fn get_chain_metrics(&self) -> ChainResult> { + Ok(None) + } } diff --git a/rust/chains/hyperlane-cosmos/src/providers/rpc.rs b/rust/chains/hyperlane-cosmos/src/providers/rpc.rs index 88c5ded065..04c4f2f12f 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/rpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/rpc.rs @@ -1,7 +1,7 @@ use std::ops::RangeInclusive; use async_trait::async_trait; -use cosmrs::rpc::client::{Client, CompatMode, HttpClient}; +use cosmrs::rpc::client::Client; use cosmrs::rpc::endpoint::{tx, tx_search::Response as TxSearchResponse}; use cosmrs::rpc::query::Query; use cosmrs::rpc::Order; @@ -10,7 +10,7 @@ use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator, LogM use tracing::{instrument, trace}; use crate::address::CosmosAddress; -use crate::{ConnectionConf, HyperlaneCosmosError}; +use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError}; const PAGINATION_LIMIT: u8 = 100; @@ -50,7 +50,7 @@ impl ParsedEvent { #[derive(Debug)] /// Cosmwasm RPC Provider pub struct CosmosWasmIndexer { - client: HttpClient, + provider: CosmosProvider, contract_address: CosmosAddress, target_event_kind: String, reorg_period: u32, @@ -66,20 +66,18 @@ impl CosmosWasmIndexer { event_type: String, reorg_period: u32, ) -> ChainResult { - let client = HttpClient::builder( - conf.get_rpc_url() - .parse() - .map_err(Into::::into)?, - ) - // Consider supporting different compatibility modes. - .compat_mode(CompatMode::latest()) - .build() - .map_err(Into::::into)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + None, + )?; Ok(Self { - client, + provider, contract_address: CosmosAddress::from_h256( locator.address, - conf.get_prefix().as_str(), + conf.get_bech32_prefix().as_str(), + conf.get_contract_address_bytes(), )?, target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type), reorg_period, @@ -91,7 +89,8 @@ impl CosmosWasmIndexer { #[instrument(level = "trace", err, skip(self))] async fn tx_search(&self, query: Query, page: u32) -> ChainResult { Ok(self - .client + .provider + .rpc() .tx_search(query, false, page, PAGINATION_LIMIT, Order::Ascending) .await .map_err(Into::::into)?) @@ -176,7 +175,8 @@ impl CosmosWasmIndexer { impl WasmIndexer for CosmosWasmIndexer { async fn get_finalized_block_number(&self) -> ChainResult { let latest_height: u32 = self - .client + .provider + .rpc() .latest_block() .await .map_err(Into::::into)? diff --git a/rust/chains/hyperlane-cosmos/src/routing_ism.rs b/rust/chains/hyperlane-cosmos/src/routing_ism.rs index 0a646c005b..63b759f1b9 100644 --- a/rust/chains/hyperlane-cosmos/src/routing_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/routing_ism.rs @@ -9,7 +9,7 @@ use hyperlane_core::{ use crate::{ address::CosmosAddress, - grpc::{WasmGrpcProvider, WasmProvider}, + grpc::WasmProvider, payloads::ism_routes::{ IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest, }, @@ -22,7 +22,7 @@ use crate::{ pub struct CosmosRoutingIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosRoutingIsm { @@ -32,12 +32,17 @@ impl CosmosRoutingIsm { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -54,7 +59,7 @@ impl HyperlaneChain for CosmosRoutingIsm { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -69,6 +74,7 @@ impl RoutingIsm for CosmosRoutingIsm { let data = self .provider + .grpc() .wasm_query( QueryRoutingIsmGeneralRequest { routing_ism: payload, diff --git a/rust/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs b/rust/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs new file mode 100644 index 0000000000..dc979ea784 --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs @@ -0,0 +1,134 @@ +use std::{ + fmt::{Debug, Formatter}, + ops::Deref, +}; + +use derive_new::new; +use hyperlane_core::rpc_clients::FallbackProvider; + +/// Wrapper of `FallbackProvider` for use in `hyperlane-cosmos` +#[derive(new, Clone)] +pub struct CosmosFallbackProvider { + fallback_provider: FallbackProvider, +} + +impl Deref for CosmosFallbackProvider { + type Target = FallbackProvider; + + fn deref(&self) -> &Self::Target { + &self.fallback_provider + } +} + +impl Debug for CosmosFallbackProvider +where + C: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.fallback_provider.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use async_trait::async_trait; + use hyperlane_core::rpc_clients::test::ProviderMock; + use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProviderBuilder}; + use hyperlane_core::ChainCommunicationError; + use tokio::time::sleep; + + use super::*; + + #[derive(Debug, Clone, Default)] + struct CosmosProviderMock(ProviderMock); + + impl Deref for CosmosProviderMock { + type Target = ProviderMock; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl CosmosProviderMock { + fn new(request_sleep: Option) -> Self { + Self(ProviderMock::new(request_sleep)) + } + } + + #[async_trait] + impl BlockNumberGetter for CosmosProviderMock { + async fn get_block_number(&self) -> Result { + Ok(0) + } + } + + impl From for Box { + fn from(val: CosmosProviderMock) -> Self { + Box::new(val) + } + } + + impl CosmosFallbackProvider { + async fn low_level_test_call(&mut self) -> Result<(), ChainCommunicationError> { + self.call(|provider| { + provider.push("GET", "http://localhost:1234"); + let future = async move { + let body = tonic::body::BoxBody::default(); + let response = http::Response::builder().status(200).body(body).unwrap(); + if let Some(sleep_duration) = provider.request_sleep() { + sleep(sleep_duration).await; + } + Ok(response) + }; + Box::pin(future) + }) + .await?; + Ok(()) + } + } + + #[tokio::test] + async fn test_first_provider_is_attempted() { + let fallback_provider_builder = FallbackProviderBuilder::default(); + let providers = vec![ + CosmosProviderMock::default(), + CosmosProviderMock::default(), + CosmosProviderMock::default(), + ]; + let fallback_provider = fallback_provider_builder.add_providers(providers).build(); + let mut cosmos_fallback_provider = CosmosFallbackProvider::new(fallback_provider); + cosmos_fallback_provider + .low_level_test_call() + .await + .unwrap(); + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(&cosmos_fallback_provider).await; + assert_eq!(provider_call_count, vec![1, 0, 0]); + } + + #[tokio::test] + async fn test_one_stalled_provider() { + let fallback_provider_builder = FallbackProviderBuilder::default(); + let providers = vec![ + CosmosProviderMock::new(Some(Duration::from_millis(10))), + CosmosProviderMock::default(), + CosmosProviderMock::default(), + ]; + let fallback_provider = fallback_provider_builder + .add_providers(providers) + .with_max_block_time(Duration::from_secs(0)) + .build(); + let mut cosmos_fallback_provider = CosmosFallbackProvider::new(fallback_provider); + cosmos_fallback_provider + .low_level_test_call() + .await + .unwrap(); + + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(&cosmos_fallback_provider).await; + assert_eq!(provider_call_count, vec![0, 0, 1]); + } +} diff --git a/rust/chains/hyperlane-cosmos/src/rpc_clients/mod.rs b/rust/chains/hyperlane-cosmos/src/rpc_clients/mod.rs new file mode 100644 index 0000000000..536845688d --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/rpc_clients/mod.rs @@ -0,0 +1,3 @@ +pub use self::fallback::*; + +mod fallback; diff --git a/rust/chains/hyperlane-cosmos/src/trait_builder.rs b/rust/chains/hyperlane-cosmos/src/trait_builder.rs index 8629970bd9..1bb3627b9d 100644 --- a/rust/chains/hyperlane-cosmos/src/trait_builder.rs +++ b/rust/chains/hyperlane-cosmos/src/trait_builder.rs @@ -1,16 +1,58 @@ +use std::str::FromStr; + +use derive_new::new; +use hyperlane_core::{ChainCommunicationError, FixedPointNumber}; +use url::Url; + /// Cosmos connection configuration #[derive(Debug, Clone)] pub struct ConnectionConf { /// The GRPC url to connect to - grpc_url: String, + grpc_urls: Vec, /// The RPC url to connect to rpc_url: String, /// The chain ID chain_id: String, - /// The prefix for the account address - prefix: String, + /// The human readable address prefix for the chains using bech32. + bech32_prefix: String, /// Canoncial Assets Denom canonical_asset: String, + /// The gas price set by the cosmos-sdk validator. Note that this represents the + /// minimum price set by the validator. + /// More details here: https://docs.cosmos.network/main/learn/beginner/gas-fees#antehandler + gas_price: RawCosmosAmount, + /// The number of bytes used to represent a contract address. + /// Cosmos address lengths are sometimes less than 32 bytes, so this helps to serialize it in + /// bech32 with the appropriate length. + contract_address_bytes: usize, +} + +/// Untyped cosmos amount +#[derive(serde::Serialize, serde::Deserialize, new, Clone, Debug)] +pub struct RawCosmosAmount { + /// Coin denom (e.g. `untrn`) + pub denom: String, + /// Amount in the given denom + pub amount: String, +} + +/// Typed cosmos amount +#[derive(Clone, Debug)] +pub struct CosmosAmount { + /// Coin denom (e.g. `untrn`) + pub denom: String, + /// Amount in the given denom + pub amount: FixedPointNumber, +} + +impl TryFrom for CosmosAmount { + type Error = ChainCommunicationError; + fn try_from(raw: RawCosmosAmount) -> Result { + Ok(Self { + denom: raw.denom, + amount: FixedPointNumber::from_str(&raw.amount)?, + }) + } } /// An error type when parsing a connection configuration. @@ -35,8 +77,8 @@ pub enum ConnectionConfError { impl ConnectionConf { /// Get the GRPC url - pub fn get_grpc_url(&self) -> String { - self.grpc_url.clone() + pub fn get_grpc_urls(&self) -> Vec { + self.grpc_urls.clone() } /// Get the RPC url @@ -49,9 +91,9 @@ impl ConnectionConf { self.chain_id.clone() } - /// Get the prefix - pub fn get_prefix(&self) -> String { - self.prefix.clone() + /// Get the bech32 prefix + pub fn get_bech32_prefix(&self) -> String { + self.bech32_prefix.clone() } /// Get the asset @@ -59,20 +101,34 @@ impl ConnectionConf { self.canonical_asset.clone() } + /// Get the minimum gas price + pub fn get_minimum_gas_price(&self) -> RawCosmosAmount { + self.gas_price.clone() + } + + /// Get the number of bytes used to represent a contract address + pub fn get_contract_address_bytes(&self) -> usize { + self.contract_address_bytes + } + /// Create a new connection configuration pub fn new( - grpc_url: String, + grpc_urls: Vec, rpc_url: String, chain_id: String, - prefix: String, + bech32_prefix: String, canonical_asset: String, + minimum_gas_price: RawCosmosAmount, + contract_address_bytes: usize, ) -> Self { Self { - grpc_url, + grpc_urls, rpc_url, chain_id, - prefix, + bech32_prefix, canonical_asset, + gas_price: minimum_gas_price, + contract_address_bytes, } } } diff --git a/rust/chains/hyperlane-cosmos/src/types.rs b/rust/chains/hyperlane-cosmos/src/types.rs index 264ae8791e..aa5a954650 100644 --- a/rust/chains/hyperlane-cosmos/src/types.rs +++ b/rust/chains/hyperlane-cosmos/src/types.rs @@ -1,10 +1,10 @@ use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use hyperlane_core::{ChainResult, ModuleType, TxOutcome, H256, U256}; -pub struct IsmType(pub hpl_interface::ism::IsmType); +pub struct IsmType(pub hyperlane_cosmwasm_interface::ism::IsmType); -impl From for IsmType { - fn from(value: hpl_interface::ism::IsmType) -> Self { +impl From for IsmType { + fn from(value: hyperlane_cosmwasm_interface::ism::IsmType) -> Self { IsmType(value) } } @@ -12,14 +12,20 @@ impl From for IsmType { impl From for ModuleType { fn from(value: IsmType) -> Self { match value.0 { - hpl_interface::ism::IsmType::Unused => ModuleType::Unused, - hpl_interface::ism::IsmType::Routing => ModuleType::Routing, - hpl_interface::ism::IsmType::Aggregation => ModuleType::Aggregation, - hpl_interface::ism::IsmType::LegacyMultisig => ModuleType::MessageIdMultisig, - hpl_interface::ism::IsmType::MerkleRootMultisig => ModuleType::MerkleRootMultisig, - hpl_interface::ism::IsmType::MessageIdMultisig => ModuleType::MessageIdMultisig, - hpl_interface::ism::IsmType::Null => ModuleType::Null, - hpl_interface::ism::IsmType::CcipRead => ModuleType::CcipRead, + hyperlane_cosmwasm_interface::ism::IsmType::Unused => ModuleType::Unused, + hyperlane_cosmwasm_interface::ism::IsmType::Routing => ModuleType::Routing, + hyperlane_cosmwasm_interface::ism::IsmType::Aggregation => ModuleType::Aggregation, + hyperlane_cosmwasm_interface::ism::IsmType::LegacyMultisig => { + ModuleType::MessageIdMultisig + } + hyperlane_cosmwasm_interface::ism::IsmType::MerkleRootMultisig => { + ModuleType::MerkleRootMultisig + } + hyperlane_cosmwasm_interface::ism::IsmType::MessageIdMultisig => { + ModuleType::MessageIdMultisig + } + hyperlane_cosmwasm_interface::ism::IsmType::Null => ModuleType::Null, + hyperlane_cosmwasm_interface::ism::IsmType::CcipRead => ModuleType::CcipRead, } } } @@ -29,6 +35,6 @@ pub fn tx_response_to_outcome(response: TxResponse) -> ChainResult { transaction_id: H256::from_slice(hex::decode(response.txhash)?.as_slice()).into(), executed: response.code == 0, gas_used: U256::from(response.gas_used), - gas_price: U256::one(), + gas_price: U256::one().try_into()?, }) } diff --git a/rust/chains/hyperlane-cosmos/src/validator_announce.rs b/rust/chains/hyperlane-cosmos/src/validator_announce.rs index 69f7121b88..6b0ee04930 100644 --- a/rust/chains/hyperlane-cosmos/src/validator_announce.rs +++ b/rust/chains/hyperlane-cosmos/src/validator_announce.rs @@ -7,7 +7,7 @@ use hyperlane_core::{ }; use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, + grpc::WasmProvider, payloads::validator_announce::{ self, AnnouncementRequest, AnnouncementRequestInner, GetAnnounceStorageLocationsRequest, GetAnnounceStorageLocationsRequestInner, @@ -22,7 +22,7 @@ use crate::{ pub struct CosmosValidatorAnnounce { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosValidatorAnnounce { @@ -32,12 +32,17 @@ impl CosmosValidatorAnnounce { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -54,7 +59,7 @@ impl HyperlaneChain for CosmosValidatorAnnounce { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -76,7 +81,7 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { }, }; - let data: Vec = self.provider.wasm_query(payload, None).await?; + let data: Vec = self.provider.grpc().wasm_query(payload, None).await?; let response: validator_announce::GetAnnounceStorageLocationsResponse = serde_json::from_slice(&data)?; @@ -102,6 +107,7 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { let response: TxResponse = self .provider + .grpc() .wasm_send(announce_request, tx_gas_limit) .await?; diff --git a/rust/chains/hyperlane-ethereum/Cargo.toml b/rust/chains/hyperlane-ethereum/Cargo.toml index 8d6db17f45..a72855a00b 100644 --- a/rust/chains/hyperlane-ethereum/Cargo.toml +++ b/rust/chains/hyperlane-ethereum/Cargo.toml @@ -30,7 +30,7 @@ tracing-futures.workspace = true tracing.workspace = true url.workspace = true -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["fallback-provider"]} ethers-prometheus = { path = "../../ethers-prometheus", features = ["serde"] } [build-dependencies] diff --git a/rust/chains/hyperlane-ethereum/src/error.rs b/rust/chains/hyperlane-ethereum/src/error.rs new file mode 100644 index 0000000000..597703cf85 --- /dev/null +++ b/rust/chains/hyperlane-ethereum/src/error.rs @@ -0,0 +1,23 @@ +use ethers::providers::ProviderError; +use hyperlane_core::ChainCommunicationError; + +/// Errors from the crates specific to the hyperlane-ethereum +/// implementation. +/// This error can then be converted into the broader error type +/// in hyperlane-core using the `From` trait impl +#[derive(Debug, thiserror::Error)] +pub enum HyperlaneEthereumError { + /// provider Error + #[error("{0}")] + ProviderError(#[from] ProviderError), + + /// Some details from a queried block are missing + #[error("Some details from a queried block are missing")] + MissingBlockDetails, +} + +impl From for ChainCommunicationError { + fn from(value: HyperlaneEthereumError) -> Self { + ChainCommunicationError::from_other(value) + } +} diff --git a/rust/chains/hyperlane-ethereum/src/interchain_gas.rs b/rust/chains/hyperlane-ethereum/src/interchain_gas.rs index 24b7ae502a..53b5d80fcd 100644 --- a/rust/chains/hyperlane-ethereum/src/interchain_gas.rs +++ b/rust/chains/hyperlane-ethereum/src/interchain_gas.rs @@ -10,7 +10,7 @@ use ethers::prelude::Middleware; use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexer, InterchainGasPaymaster, - InterchainGasPayment, LogMeta, SequenceIndexer, H160, H256, + InterchainGasPayment, LogMeta, SequenceAwareIndexer, H160, H256, }; use tracing::instrument; @@ -36,7 +36,7 @@ pub struct InterchainGasPaymasterIndexerBuilder { #[async_trait] impl BuildableWithProvider for InterchainGasPaymasterIndexerBuilder { - type Output = Box>; + type Output = Box>; async fn build_with_provider( &self, @@ -84,6 +84,7 @@ impl Indexer for EthereumInterchainGasPaymasterIndexer< where M: Middleware + 'static, { + /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] async fn fetch_logs( &self, @@ -126,17 +127,17 @@ where } #[async_trait] -impl SequenceIndexer for EthereumInterchainGasPaymasterIndexer +impl SequenceAwareIndexer for EthereumInterchainGasPaymasterIndexer where M: Middleware + 'static, { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { - // The InterchainGasPaymasterIndexerBuilder must return a `SequenceIndexer` type. + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + // The InterchainGasPaymasterIndexerBuilder must return a `SequenceAwareIndexer` type. // It's fine if only a blanket implementation is provided for EVM chains, since their - // indexing only uses the `Index` trait, which is a supertrait of `SequenceIndexer`. - // TODO: if `SequenceIndexer` turns out to not depend on `Indexer` at all, then the supertrait + // indexing only uses the `Index` trait, which is a supertrait of `SequenceAwareIndexer`. + // TODO: if `SequenceAwareIndexer` turns out to not depend on `Indexer` at all, then the supertrait // dependency could be removed, even if the builder would still need to return a type that is both - // ``SequenceIndexer` and `Indexer`. + // ``SequenceAwareIndexer` and `Indexer`. let tip = self.get_finalized_block_number().await?; Ok((None, tip)) } diff --git a/rust/chains/hyperlane-ethereum/src/lib.rs b/rust/chains/hyperlane-ethereum/src/lib.rs index 2d42850bc4..90ec70c019 100644 --- a/rust/chains/hyperlane-ethereum/src/lib.rs +++ b/rust/chains/hyperlane-ethereum/src/lib.rs @@ -75,6 +75,7 @@ mod signers; mod singleton_signer; mod config; +mod error; fn extract_fn_map(abi: &'static Lazy) -> HashMap, &'static str> { abi.functions() diff --git a/rust/chains/hyperlane-ethereum/src/mailbox.rs b/rust/chains/hyperlane-ethereum/src/mailbox.rs index 60bf35c9d8..4f7323ed06 100644 --- a/rust/chains/hyperlane-ethereum/src/mailbox.rs +++ b/rust/chains/hyperlane-ethereum/src/mailbox.rs @@ -13,9 +13,9 @@ use ethers_contract::builders::ContractCall; use tracing::instrument; use hyperlane_core::{ - utils::fmt_bytes, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, + utils::bytes_to_hex, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProtocolError, - HyperlaneProvider, Indexer, LogMeta, Mailbox, RawHyperlaneMessage, SequenceIndexer, + HyperlaneProvider, Indexer, LogMeta, Mailbox, RawHyperlaneMessage, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H160, H256, U256, }; @@ -40,7 +40,7 @@ pub struct SequenceIndexerBuilder { #[async_trait] impl BuildableWithProvider for SequenceIndexerBuilder { - type Output = Box>; + type Output = Box>; async fn build_with_provider( &self, @@ -61,7 +61,7 @@ pub struct DeliveryIndexerBuilder { #[async_trait] impl BuildableWithProvider for DeliveryIndexerBuilder { - type Output = Box>; + type Output = Box>; async fn build_with_provider( &self, @@ -125,6 +125,7 @@ where self.get_finalized_block_number().await } + /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] async fn fetch_logs( &self, @@ -147,12 +148,12 @@ where } #[async_trait] -impl SequenceIndexer for EthereumMailboxIndexer +impl SequenceAwareIndexer for EthereumMailboxIndexer where M: Middleware + 'static, { #[instrument(err, skip(self))] - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = Indexer::::get_finalized_block_number(self).await?; let sequence = self.contract.nonce().block(u64::from(tip)).call().await?; Ok((Some(sequence), tip)) @@ -168,6 +169,7 @@ where self.get_finalized_block_number().await } + /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] async fn fetch_logs(&self, range: RangeInclusive) -> ChainResult> { Ok(self @@ -184,13 +186,13 @@ where } #[async_trait] -impl SequenceIndexer for EthereumMailboxIndexer +impl SequenceAwareIndexer for EthereumMailboxIndexer where M: Middleware + 'static, { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { // A blanket implementation for this trait is fine for the EVM. - // TODO: Consider removing `Indexer` as a supertrait of `SequenceIndexer` + // TODO: Consider removing `Indexer` as a supertrait of `SequenceAwareIndexer` let tip = Indexer::::get_finalized_block_number(self).await?; Ok((None, tip)) } @@ -325,7 +327,7 @@ where .into()) } - #[instrument(skip(self), fields(metadata=%fmt_bytes(metadata)))] + #[instrument(skip(self), fields(metadata=%bytes_to_hex(metadata)))] async fn process( &self, message: &HyperlaneMessage, @@ -339,7 +341,7 @@ where Ok(receipt.into()) } - #[instrument(skip(self), fields(msg=%message, metadata=%fmt_bytes(metadata)))] + #[instrument(skip(self), fields(msg=%message, metadata=%bytes_to_hex(metadata)))] async fn process_estimate_costs( &self, message: &HyperlaneMessage, @@ -373,15 +375,16 @@ where None }; - let gas_price = self + let gas_price: U256 = self .provider .get_gas_price() .await - .map_err(ChainCommunicationError::from_other)?; + .map_err(ChainCommunicationError::from_other)? + .into(); Ok(TxCostEstimate { gas_limit: gas_limit.into(), - gas_price: gas_price.into(), + gas_price: gas_price.try_into()?, l2_gas_limit: l2_gas_limit.map(|v| v.into()), }) } @@ -484,7 +487,7 @@ mod test { tx_cost_estimate, TxCostEstimate { gas_limit: estimated_gas_limit, - gas_price, + gas_price: gas_price.try_into().unwrap(), l2_gas_limit: Some(l2_gas_limit), }, ); diff --git a/rust/chains/hyperlane-ethereum/src/merkle_tree_hook.rs b/rust/chains/hyperlane-ethereum/src/merkle_tree_hook.rs index 1beffed5bc..4bf7cea618 100644 --- a/rust/chains/hyperlane-ethereum/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-ethereum/src/merkle_tree_hook.rs @@ -11,7 +11,7 @@ use tracing::instrument; use hyperlane_core::{ ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexer, LogMeta, MerkleTreeHook, - MerkleTreeInsertion, SequenceIndexer, H256, + MerkleTreeInsertion, SequenceAwareIndexer, H256, }; use crate::contracts::merkle_tree_hook::{MerkleTreeHook as MerkleTreeHookContract, Tree}; @@ -57,7 +57,7 @@ pub struct MerkleTreeHookIndexerBuilder { #[async_trait] impl BuildableWithProvider for MerkleTreeHookIndexerBuilder { - type Output = Box>; + type Output = Box>; async fn build_with_provider( &self, @@ -105,6 +105,7 @@ impl Indexer for EthereumMerkleTreeHookIndexer where M: Middleware + 'static, { + /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] async fn fetch_logs( &self, @@ -143,14 +144,14 @@ where } #[async_trait] -impl SequenceIndexer for EthereumMerkleTreeHookIndexer +impl SequenceAwareIndexer for EthereumMerkleTreeHookIndexer where M: Middleware + 'static, { - // TODO: if `SequenceIndexer` turns out to not depend on `Indexer` at all, then the supertrait + // TODO: if `SequenceAwareIndexer` turns out to not depend on `Indexer` at all, then the supertrait // dependency could be removed, even if the builder would still need to return a type that is both - // `SequenceIndexer` and `Indexer`. - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { + // `SequenceAwareIndexer` and `Indexer`. + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = self.get_finalized_block_number().await?; let sequence = self.contract.count().block(u64::from(tip)).call().await?; Ok((Some(sequence), tip)) diff --git a/rust/chains/hyperlane-ethereum/src/provider.rs b/rust/chains/hyperlane-ethereum/src/provider.rs index 6ea06433d6..54b119bb27 100644 --- a/rust/chains/hyperlane-ethereum/src/provider.rs +++ b/rust/chains/hyperlane-ethereum/src/provider.rs @@ -6,7 +6,8 @@ use std::time::Duration; use async_trait::async_trait; use derive_new::new; use ethers::prelude::Middleware; -use hyperlane_core::ethers_core_types; +use ethers_core::{abi::Address, types::BlockNumber}; +use hyperlane_core::{ethers_core_types, ChainInfo, HyperlaneCustomErrorWrapper, U256}; use tokio::time::sleep; use tracing::instrument; @@ -20,10 +21,7 @@ use crate::BuildableWithProvider; /// Connection to an ethereum provider. Useful for querying information about /// the blockchain. #[derive(Debug, Clone, new)] -pub struct EthereumProvider -where - M: Middleware, -{ +pub struct EthereumProvider { provider: Arc, domain: HyperlaneDomain, } @@ -105,6 +103,46 @@ where .map_err(ChainCommunicationError::from_other)?; Ok(!code.is_empty()) } + + #[instrument(err, skip(self))] + async fn get_balance(&self, address: String) -> ChainResult { + // Can't use the address directly as a string, because ethers interprets it + // as an ENS name rather than an address. + let addr: Address = address.parse()?; + let balance = self + .provider + .get_balance(addr, None) + .await + .map_err(ChainCommunicationError::from_other)?; + Ok(balance.into()) + } + + async fn get_chain_metrics(&self) -> ChainResult> { + let Some(block) = self + .provider + .get_block(BlockNumber::Latest) + .await + .map_err(|e| { + ChainCommunicationError::Other(HyperlaneCustomErrorWrapper::new(Box::new(e))) + })? + else { + return Ok(None); + }; + + // Given the block is queried with `BlockNumber::Latest` rather than `BlockNumber::Pending`, + // if `block` is Some at this point, we're guaranteed to have its `hash` and `number` defined, + // so it's safe to unwrap below + // more info at + let chain_metrics = ChainInfo::new( + BlockInfo { + hash: block.hash.unwrap().into(), + timestamp: block.timestamp.as_u64(), + number: block.number.unwrap().as_u64(), + }, + block.base_fee_per_gas.map(Into::into), + ); + Ok(Some(chain_metrics)) + } } impl EthereumProvider diff --git a/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs b/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs index af1dd28ce2..6d57a10e2e 100644 --- a/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs +++ b/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs @@ -1,67 +1,34 @@ use derive_new::new; +use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProvider}; use std::fmt::{Debug, Formatter}; -use std::sync::Arc; -use std::time::{Duration, Instant}; -use tokio::sync::RwLock; +use std::ops::Deref; +use std::time::Duration; +use thiserror::Error; use async_trait::async_trait; use ethers::providers::{HttpClientError, JsonRpcClient, ProviderError}; -use ethers_core::types::U64; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; -use thiserror::Error; use tokio::time::sleep; -use tracing::{info, instrument, warn_span}; +use tracing::{instrument, warn_span}; -use ethers_prometheus::json_rpc_client::PrometheusJsonRpcClientConfigExt; +use ethers_prometheus::json_rpc_client::{JsonRpcBlockGetter, PrometheusJsonRpcClientConfigExt}; use crate::rpc_clients::{categorize_client_response, CategorizedResponse}; -const MAX_BLOCK_TIME: Duration = Duration::from_secs(2 * 60); -const BLOCK_NUMBER_RPC: &str = "eth_blockNumber"; +/// Wrapper of `FallbackProvider` for use in `hyperlane-ethereum` +#[derive(new)] +pub struct EthereumFallbackProvider(FallbackProvider); -#[derive(Clone, Copy, new)] -struct PrioritizedProviderInner { - // Index into the `providers` field of `PrioritizedProviders` - index: usize, - // Tuple of the block number and the time when it was queried - #[new(value = "(0, Instant::now())")] - last_block_height: (u64, Instant), -} +impl Deref for EthereumFallbackProvider { + type Target = FallbackProvider; -impl PrioritizedProviderInner { - fn from_block_height(index: usize, block_height: u64) -> Self { - Self { - index, - last_block_height: (block_height, Instant::now()), - } + fn deref(&self) -> &Self::Target { + &self.0 } } -struct PrioritizedProviders { - /// Sorted list of providers this provider calls in order of most primary to - /// most fallback. - providers: Vec, - priorities: RwLock>, -} - -/// A provider that bundles multiple providers and attempts to call the first, -/// then the second, and so on until a response is received. -pub struct FallbackProvider { - inner: Arc>, - max_block_time: Duration, -} - -impl Clone for FallbackProvider { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - max_block_time: self.max_block_time, - } - } -} - -impl Debug for FallbackProvider +impl Debug for EthereumFallbackProvider where C: JsonRpcClient + PrometheusJsonRpcClientConfigExt, { @@ -90,134 +57,6 @@ where } } -impl FallbackProvider { - /// Convenience method for creating a `FallbackProviderBuilder` with same - /// `JsonRpcClient` types - pub fn builder() -> FallbackProviderBuilder { - FallbackProviderBuilder::default() - } - - /// Create a new fallback provider - pub fn new(providers: impl IntoIterator) -> Self { - Self::builder().add_providers(providers).build() - } -} - -impl FallbackProvider -where - C: JsonRpcClient, -{ - async fn handle_stalled_provider( - &self, - priority: &PrioritizedProviderInner, - provider: &C, - ) -> Result<(), ProviderError> { - let now = Instant::now(); - if now - .duration_since(priority.last_block_height.1) - .le(&self.max_block_time) - { - // Do nothing, it's too early to tell if the provider has stalled - return Ok(()); - } - - let current_block_height: u64 = provider - .request(BLOCK_NUMBER_RPC, ()) - .await - .map(|r: U64| r.as_u64()) - .unwrap_or(priority.last_block_height.0); - if current_block_height <= priority.last_block_height.0 { - // The `max_block_time` elapsed but the block number returned by the provider has not increased - self.deprioritize_provider(*priority).await; - info!( - provider_index=%priority.index, - ?provider, - "Deprioritizing an inner provider in FallbackProvider", - ); - } else { - self.update_last_seen_block(priority.index, current_block_height) - .await; - } - Ok(()) - } - - async fn deprioritize_provider(&self, priority: PrioritizedProviderInner) { - // De-prioritize the current provider by moving it to the end of the queue - let mut priorities = self.inner.priorities.write().await; - priorities.retain(|&p| p.index != priority.index); - priorities.push(priority); - } - - async fn update_last_seen_block(&self, provider_index: usize, current_block_height: u64) { - let mut priorities = self.inner.priorities.write().await; - // Get provider position in the up-to-date priorities vec - if let Some(position) = priorities.iter().position(|p| p.index == provider_index) { - priorities[position] = - PrioritizedProviderInner::from_block_height(provider_index, current_block_height); - } - } - - async fn take_priorities_snapshot(&self) -> Vec { - let read_lock = self.inner.priorities.read().await; - (*read_lock).clone() - } -} - -/// Builder to create a new fallback provider. -#[derive(Debug, Clone)] -pub struct FallbackProviderBuilder { - providers: Vec, - max_block_time: Duration, -} - -impl Default for FallbackProviderBuilder { - fn default() -> Self { - Self { - providers: Vec::new(), - max_block_time: MAX_BLOCK_TIME, - } - } -} - -impl FallbackProviderBuilder { - /// Add a new provider to the set. Each new provider will be a lower - /// priority than the previous. - pub fn add_provider(mut self, provider: T) -> Self { - self.providers.push(provider); - self - } - - /// Add many providers sorted by highest priority to lowest. - pub fn add_providers(mut self, providers: impl IntoIterator) -> Self { - self.providers.extend(providers); - self - } - - #[cfg(test)] - pub fn with_max_block_time(mut self, max_block_time: Duration) -> Self { - self.max_block_time = max_block_time; - self - } - - /// Create a fallback provider. - pub fn build(self) -> FallbackProvider { - let provider_count = self.providers.len(); - let prioritized_providers = PrioritizedProviders { - providers: self.providers, - // The order of `self.providers` gives the initial priority. - priorities: RwLock::new( - (0..provider_count) - .map(PrioritizedProviderInner::new) - .collect(), - ), - }; - FallbackProvider { - inner: Arc::new(prioritized_providers), - max_block_time: self.max_block_time, - } - } -} - /// Errors specific to fallback provider. #[derive(Error, Debug)] pub enum FallbackError { @@ -234,12 +73,17 @@ impl From for ProviderError { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl JsonRpcClient for FallbackProvider +impl JsonRpcClient for EthereumFallbackProvider> where - C: JsonRpcClient + PrometheusJsonRpcClientConfigExt, + C: JsonRpcClient + + Into> + + PrometheusJsonRpcClientConfigExt + + Clone, + JsonRpcBlockGetter: BlockNumberGetter, { type Error = ProviderError; + // TODO: Refactor to use `FallbackProvider::call` #[instrument] async fn request(&self, method: &str, params: T) -> Result where @@ -263,9 +107,9 @@ where _ => provider.request(method, ¶ms), }; let resp = fut.await; - self.handle_stalled_provider(priority, provider).await?; + self.handle_stalled_provider(priority, provider).await; let _span = - warn_span!("request_with_fallback", fallback_count=%idx, provider_index=%priority.index, ?provider).entered(); + warn_span!("request", fallback_count=%idx, provider_index=%priority.index, ?provider).entered(); match categorize_client_response(method, resp) { IsOk(v) => return Ok(serde_json::from_value(v)?), @@ -281,33 +125,32 @@ where #[cfg(test)] mod tests { + use ethers_prometheus::json_rpc_client::{JsonRpcBlockGetter, BLOCK_NUMBER_RPC}; + use hyperlane_core::rpc_clients::test::ProviderMock; + use hyperlane_core::rpc_clients::FallbackProviderBuilder; + use super::*; - use std::sync::Mutex; - #[derive(Debug)] - struct ProviderMock { - // Store requests as tuples of (method, params) - // Even if the tests were single-threaded, need the arc-mutex - // for interior mutability in `JsonRpcClient::request` - requests: Arc>>, - } + #[derive(Debug, Clone, Default)] + struct EthereumProviderMock(ProviderMock); - impl ProviderMock { - fn new() -> Self { - Self { - requests: Arc::new(Mutex::new(vec![])), - } + impl Deref for EthereumProviderMock { + type Target = ProviderMock; + + fn deref(&self) -> &Self::Target { + &self.0 } + } - fn push(&self, method: &str, params: T) { - self.requests - .lock() - .unwrap() - .push((method.to_owned(), format!("{:?}", params))); + impl EthereumProviderMock { + fn new(request_sleep: Option) -> Self { + Self(ProviderMock::new(request_sleep)) } + } - fn requests(&self) -> Vec<(String, String)> { - self.requests.lock().unwrap().clone() + impl From for JsonRpcBlockGetter { + fn from(val: EthereumProviderMock) -> Self { + JsonRpcBlockGetter::new(val) } } @@ -319,7 +162,7 @@ mod tests { } #[async_trait] - impl JsonRpcClient for ProviderMock { + impl JsonRpcClient for EthereumProviderMock { type Error = HttpClientError; /// Pushes the `(method, params)` to the back of the `requests` queue, @@ -330,12 +173,14 @@ mod tests { params: T, ) -> Result { self.push(method, params); - sleep(Duration::from_millis(10)).await; + if let Some(sleep_duration) = self.request_sleep() { + sleep(sleep_duration).await; + } dummy_return_value() } } - impl PrometheusJsonRpcClientConfigExt for ProviderMock { + impl PrometheusJsonRpcClientConfigExt for EthereumProviderMock { fn node_host(&self) -> &str { todo!() } @@ -345,34 +190,32 @@ mod tests { } } - async fn get_call_counts(fallback_provider: &FallbackProvider) -> Vec { - fallback_provider - .inner - .priorities - .read() - .await - .iter() - .map(|p| { - let provider = &fallback_provider.inner.providers[p.index]; - provider.requests().len() - }) - .collect() + impl EthereumFallbackProvider> + where + C: JsonRpcClient + + PrometheusJsonRpcClientConfigExt + + Into> + + Clone, + JsonRpcBlockGetter: BlockNumberGetter, + { + async fn low_level_test_call(&self) { + self.request::<_, u64>(BLOCK_NUMBER_RPC, ()).await.unwrap(); + } } #[tokio::test] async fn test_first_provider_is_attempted() { let fallback_provider_builder = FallbackProviderBuilder::default(); let providers = vec![ - ProviderMock::new(), - ProviderMock::new(), - ProviderMock::new(), + EthereumProviderMock::default(), + EthereumProviderMock::default(), + EthereumProviderMock::default(), ]; let fallback_provider = fallback_provider_builder.add_providers(providers).build(); - fallback_provider - .request::<_, u64>(BLOCK_NUMBER_RPC, ()) - .await - .unwrap(); - let provider_call_count: Vec<_> = get_call_counts(&fallback_provider).await; + let ethereum_fallback_provider = EthereumFallbackProvider::new(fallback_provider); + ethereum_fallback_provider.low_level_test_call().await; + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(ðereum_fallback_provider).await; assert_eq!(provider_call_count, vec![1, 0, 0]); } @@ -380,20 +223,18 @@ mod tests { async fn test_one_stalled_provider() { let fallback_provider_builder = FallbackProviderBuilder::default(); let providers = vec![ - ProviderMock::new(), - ProviderMock::new(), - ProviderMock::new(), + EthereumProviderMock::new(Some(Duration::from_millis(10))), + EthereumProviderMock::default(), + EthereumProviderMock::default(), ]; let fallback_provider = fallback_provider_builder .add_providers(providers) .with_max_block_time(Duration::from_secs(0)) .build(); - fallback_provider - .request::<_, u64>(BLOCK_NUMBER_RPC, ()) - .await - .unwrap(); - - let provider_call_count: Vec<_> = get_call_counts(&fallback_provider).await; + let ethereum_fallback_provider = EthereumFallbackProvider::new(fallback_provider); + ethereum_fallback_provider.low_level_test_call().await; + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(ðereum_fallback_provider).await; assert_eq!(provider_call_count, vec![0, 0, 2]); } diff --git a/rust/chains/hyperlane-ethereum/src/trait_builder.rs b/rust/chains/hyperlane-ethereum/src/trait_builder.rs index 03a33c2fdc..7fed53ca55 100644 --- a/rust/chains/hyperlane-ethereum/src/trait_builder.rs +++ b/rust/chains/hyperlane-ethereum/src/trait_builder.rs @@ -1,4 +1,4 @@ -use std::fmt::Write; +use std::fmt::{Debug, Write}; use std::sync::Arc; use std::time::Duration; @@ -10,24 +10,23 @@ use ethers::prelude::{ Http, JsonRpcClient, Middleware, NonceManagerMiddleware, Provider, Quorum, QuorumProvider, SignerMiddleware, WeightedProvider, Ws, WsClientError, }; +use hyperlane_core::rpc_clients::FallbackProvider; use reqwest::{Client, Url}; use thiserror::Error; use ethers_prometheus::json_rpc_client::{ - JsonRpcClientMetrics, JsonRpcClientMetricsBuilder, NodeInfo, PrometheusJsonRpcClient, - PrometheusJsonRpcClientConfig, -}; -use ethers_prometheus::middleware::{ - MiddlewareMetrics, PrometheusMiddleware, PrometheusMiddlewareConf, + JsonRpcBlockGetter, JsonRpcClientMetrics, JsonRpcClientMetricsBuilder, NodeInfo, + PrometheusJsonRpcClient, PrometheusJsonRpcClientConfig, }; +use ethers_prometheus::middleware::{MiddlewareMetrics, PrometheusMiddlewareConf}; use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, }; -use crate::{signers::Signers, ConnectionConf, FallbackProvider, RetryingProvider}; +use crate::EthereumFallbackProvider; +use crate::{signers::Signers, ConnectionConf, RetryingProvider}; // This should be whatever the prometheus scrape interval is -const METRICS_SCRAPE_INTERVAL: Duration = Duration::from_secs(60); const HTTP_CLIENT_TIMEOUT: Duration = Duration::from_secs(60); /// An error when connecting to an ethereum provider. @@ -94,8 +93,7 @@ pub trait BuildableWithProvider { builder = builder.add_provider(weighted_provider); } let quorum_provider = builder.build(); - self.build(quorum_provider, locator, signer, middleware_metrics) - .await? + self.build(quorum_provider, locator, signer).await? } ConnectionConf::HttpFallback { urls } => { let mut builder = FallbackProvider::builder(); @@ -114,7 +112,11 @@ pub trait BuildableWithProvider { builder = builder.add_provider(metrics_provider); } let fallback_provider = builder.build(); - self.build(fallback_provider, locator, signer, middleware_metrics) + let ethereum_fallback_provider = EthereumFallbackProvider::< + _, + JsonRpcBlockGetter>, + >::new(fallback_provider); + self.build(ethereum_fallback_provider, locator, signer) .await? } ConnectionConf::Http { url } => { @@ -130,14 +132,13 @@ pub trait BuildableWithProvider { &middleware_metrics, ); let retrying_http_provider = RetryingProvider::new(metrics_provider, None, None); - self.build(retrying_http_provider, locator, signer, middleware_metrics) - .await? + self.build(retrying_http_provider, locator, signer).await? } ConnectionConf::Ws { url } => { let ws = Ws::connect(url) .await .map_err(EthereumProviderConnectionError::from)?; - self.build(ws, locator, signer, middleware_metrics).await? + self.build(ws, locator, signer).await? } }) } @@ -178,27 +179,19 @@ pub trait BuildableWithProvider { ) } - /// Create the provider, applying any middlewares (e.g. gas oracle, signer, metrics) as needed, + /// Create the provider, applying any middlewares (e.g. gas oracle, signer) as needed, /// and then create the associated trait. async fn build

( &self, client: P, locator: &ContractLocator, signer: Option, - metrics: Option<(MiddlewareMetrics, PrometheusMiddlewareConf)>, ) -> ChainResult where P: JsonRpcClient + 'static, { let provider = wrap_with_gas_oracle(Provider::new(client), locator.domain)?; - - Ok(if let Some(metrics) = metrics { - let provider = Arc::new(PrometheusMiddleware::new(provider, metrics.0, metrics.1)); - tokio::spawn(provider.start_updating_on_interval(METRICS_SCRAPE_INTERVAL)); - self.build_with_signer(provider, locator, signer).await? - } else { - self.build_with_signer(provider, locator, signer).await? - }) + self.build_with_signer(provider, locator, signer).await } /// Wrap the provider creation with a signing provider if signers were diff --git a/rust/chains/hyperlane-ethereum/src/tx.rs b/rust/chains/hyperlane-ethereum/src/tx.rs index 6919b4a03e..85e0edef03 100644 --- a/rust/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/chains/hyperlane-ethereum/src/tx.rs @@ -9,7 +9,7 @@ use ethers::{ }; use ethers_contract::builders::ContractCall; use ethers_core::types::BlockNumber; -use hyperlane_core::{utils::fmt_bytes, ChainCommunicationError, ChainResult, H256, U256}; +use hyperlane_core::{utils::bytes_to_hex, ChainCommunicationError, ChainResult, H256, U256}; use tracing::{error, info}; use crate::Middleware; @@ -26,7 +26,7 @@ where let data = tx .tx .data() - .map(|b| fmt_bytes(b)) + .map(|b| bytes_to_hex(b)) .unwrap_or_else(|| "None".into()); let to = tx diff --git a/rust/chains/hyperlane-ethereum/tests/signer_output.rs b/rust/chains/hyperlane-ethereum/tests/signer_output.rs index a69629114b..4284dac70d 100644 --- a/rust/chains/hyperlane-ethereum/tests/signer_output.rs +++ b/rust/chains/hyperlane-ethereum/tests/signer_output.rs @@ -22,7 +22,7 @@ use hyperlane_core::{ pub fn output_message() { let hyperlane_message = HyperlaneMessage { nonce: 0, - version: 0, + version: 3, origin: 1000, sender: H256::from(H160::from_str("0x1111111111111111111111111111111111111111").unwrap()), destination: 2000, diff --git a/rust/chains/hyperlane-fuel/Cargo.toml b/rust/chains/hyperlane-fuel/Cargo.toml index 7dabcdd514..82bdbc782e 100644 --- a/rust/chains/hyperlane-fuel/Cargo.toml +++ b/rust/chains/hyperlane-fuel/Cargo.toml @@ -19,7 +19,7 @@ tracing-futures.workspace = true tracing.workspace = true url.workspace = true -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["fallback-provider"]} [build-dependencies] abigen = { path = "../../utils/abigen", features = ["fuels"] } diff --git a/rust/chains/hyperlane-fuel/src/mailbox.rs b/rust/chains/hyperlane-fuel/src/mailbox.rs index 7402ac2783..dbf130235f 100644 --- a/rust/chains/hyperlane-fuel/src/mailbox.rs +++ b/rust/chains/hyperlane-fuel/src/mailbox.rs @@ -8,7 +8,7 @@ use fuels::prelude::{Bech32ContractId, WalletUnlocked}; use tracing::instrument; use hyperlane_core::{ - utils::fmt_bytes, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, + utils::bytes_to_hex, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, Mailbox, TxCostEstimate, TxOutcome, H256, U256, }; @@ -105,7 +105,7 @@ impl Mailbox for FuelMailbox { todo!() } - #[instrument(err, ret, skip(self), fields(msg=%message, metadata=%fmt_bytes(metadata)))] + #[instrument(err, ret, skip(self), fields(msg=%message, metadata=%bytes_to_hex(metadata)))] async fn process_estimate_costs( &self, message: &HyperlaneMessage, diff --git a/rust/chains/hyperlane-fuel/src/provider.rs b/rust/chains/hyperlane-fuel/src/provider.rs index 92303f5795..992608fac8 100644 --- a/rust/chains/hyperlane-fuel/src/provider.rs +++ b/rust/chains/hyperlane-fuel/src/provider.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; use hyperlane_core::{ - BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, + BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, + H256, U256, }; /// A wrapper around a fuel provider to get generic blockchain information. @@ -31,4 +32,12 @@ impl HyperlaneProvider for FuelProvider { async fn is_contract(&self, address: &H256) -> ChainResult { todo!() } + + async fn get_balance(&self, address: String) -> ChainResult { + todo!() + } + + async fn get_chain_metrics(&self) -> ChainResult> { + Ok(None) + } } diff --git a/rust/chains/hyperlane-sealevel/Cargo.toml b/rust/chains/hyperlane-sealevel/Cargo.toml index 248e3dfac1..ab4e9b17f6 100644 --- a/rust/chains/hyperlane-sealevel/Cargo.toml +++ b/rust/chains/hyperlane-sealevel/Cargo.toml @@ -24,7 +24,7 @@ tracing.workspace = true url.workspace = true account-utils = { path = "../../sealevel/libraries/account-utils" } -hyperlane-core = { path = "../../hyperlane-core", features = ["solana"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["solana", "fallback-provider"] } hyperlane-sealevel-interchain-security-module-interface = { path = "../../sealevel/libraries/interchain-security-module-interface" } hyperlane-sealevel-mailbox = { path = "../../sealevel/programs/mailbox", features = ["no-entrypoint"] } hyperlane-sealevel-igp = { path = "../../sealevel/programs/hyperlane-sealevel-igp", features = ["no-entrypoint"] } diff --git a/rust/chains/hyperlane-sealevel/src/error.rs b/rust/chains/hyperlane-sealevel/src/error.rs new file mode 100644 index 0000000000..55b81b4167 --- /dev/null +++ b/rust/chains/hyperlane-sealevel/src/error.rs @@ -0,0 +1,23 @@ +use hyperlane_core::ChainCommunicationError; +use solana_client::client_error::ClientError; +use solana_sdk::pubkey::ParsePubkeyError; + +/// Errors from the crates specific to the hyperlane-sealevel +/// implementation. +/// This error can then be converted into the broader error type +/// in hyperlane-core using the `From` trait impl +#[derive(Debug, thiserror::Error)] +pub enum HyperlaneSealevelError { + /// ParsePubkeyError error + #[error("{0}")] + ParsePubkeyError(#[from] ParsePubkeyError), + /// ClientError error + #[error("{0}")] + ClientError(#[from] ClientError), +} + +impl From for ChainCommunicationError { + fn from(value: HyperlaneSealevelError) -> Self { + ChainCommunicationError::from_other(value) + } +} diff --git a/rust/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/chains/hyperlane-sealevel/src/interchain_gas.rs index 3c3adedd25..cb4a3543e9 100644 --- a/rust/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use hyperlane_core::{ config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexer, - InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceIndexer, H256, H512, + InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, }; use hyperlane_sealevel_igp::{ accounts::{GasPaymentAccount, ProgramDataAccount}, @@ -35,6 +35,7 @@ pub struct SealevelInterchainGasPaymaster { data_pda_pubkey: Pubkey, domain: HyperlaneDomain, igp_account: H256, + provider: SealevelProvider, } impl SealevelInterchainGasPaymaster { @@ -43,12 +44,9 @@ impl SealevelInterchainGasPaymaster { conf: &ConnectionConf, igp_account_locator: &ContractLocator<'_>, ) -> ChainResult { - let rpc_client = RpcClientWithDebug::new_with_commitment( - conf.url.to_string(), - CommitmentConfig::processed(), - ); + let provider = SealevelProvider::new(igp_account_locator.domain.clone(), conf); let program_id = - Self::determine_igp_program_id(&rpc_client, &igp_account_locator.address).await?; + Self::determine_igp_program_id(provider.rpc(), &igp_account_locator.address).await?; let (data_pda_pubkey, _) = Pubkey::find_program_address(igp_program_data_pda_seeds!(), &program_id); @@ -57,6 +55,7 @@ impl SealevelInterchainGasPaymaster { data_pda_pubkey, domain: igp_account_locator.domain.clone(), igp_account: igp_account_locator.address, + provider, }) } @@ -91,7 +90,7 @@ impl HyperlaneChain for SealevelInterchainGasPaymaster { } fn provider(&self) -> Box { - Box::new(SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -274,9 +273,9 @@ impl Indexer for SealevelInterchainGasPaymasterIndexer { } #[async_trait] -impl SequenceIndexer for SealevelInterchainGasPaymasterIndexer { +impl SequenceAwareIndexer for SealevelInterchainGasPaymasterIndexer { #[instrument(err, skip(self))] - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let program_data_account = self .rpc_client .get_account_with_commitment(&self.igp.data_pda_pubkey, CommitmentConfig::finalized()) diff --git a/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs b/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs index 953b2eac5b..0f92432eb1 100644 --- a/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs @@ -10,29 +10,31 @@ use hyperlane_core::{ use hyperlane_sealevel_interchain_security_module_interface::InterchainSecurityModuleInstruction; use serializable_account_meta::SimulationReturnData; -use crate::{utils::simulate_instruction, ConnectionConf, RpcClientWithDebug}; +use crate::{utils::simulate_instruction, ConnectionConf, RpcClientWithDebug, SealevelProvider}; /// A reference to an InterchainSecurityModule contract on some Sealevel chain #[derive(Debug)] pub struct SealevelInterchainSecurityModule { - rpc_client: RpcClientWithDebug, payer: Option, program_id: Pubkey, - domain: HyperlaneDomain, + provider: SealevelProvider, } impl SealevelInterchainSecurityModule { /// Create a new sealevel InterchainSecurityModule pub fn new(conf: &ConnectionConf, locator: ContractLocator, payer: Option) -> Self { - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); Self { - rpc_client, payer, program_id, - domain: locator.domain.clone(), + provider, } } + + fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } } impl HyperlaneContract for SealevelInterchainSecurityModule { @@ -43,11 +45,11 @@ impl HyperlaneContract for SealevelInterchainSecurityModule { impl HyperlaneChain for SealevelInterchainSecurityModule { fn domain(&self) -> &HyperlaneDomain { - &self.domain + self.provider.domain() } fn provider(&self) -> Box { - Box::new(crate::SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -63,7 +65,7 @@ impl InterchainSecurityModule for SealevelInterchainSecurityModule { ); let module = simulate_instruction::>( - &self.rpc_client, + self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, diff --git a/rust/chains/hyperlane-sealevel/src/lib.rs b/rust/chains/hyperlane-sealevel/src/lib.rs index 864e449061..8cd8830f57 100644 --- a/rust/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/chains/hyperlane-sealevel/src/lib.rs @@ -15,6 +15,7 @@ pub use solana_sdk::signer::keypair::Keypair; pub use trait_builder::*; pub use validator_announce::*; +mod error; mod interchain_gas; mod interchain_security_module; mod mailbox; diff --git a/rust/chains/hyperlane-sealevel/src/mailbox.rs b/rust/chains/hyperlane-sealevel/src/mailbox.rs index 840661b3c9..bcf79be867 100644 --- a/rust/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/chains/hyperlane-sealevel/src/mailbox.rs @@ -9,9 +9,9 @@ use tracing::{debug, info, instrument, warn}; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, - ContractLocator, Decode as _, Encode as _, HyperlaneAbi, HyperlaneChain, HyperlaneContract, - HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, Mailbox, - MerkleTreeHook, SequenceIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, + ContractLocator, Decode as _, Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, + HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, + Mailbox, MerkleTreeHook, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, }; use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, @@ -69,8 +69,7 @@ pub struct SealevelMailbox { pub(crate) program_id: Pubkey, inbox: (Pubkey, u8), pub(crate) outbox: (Pubkey, u8), - pub(crate) rpc_client: RpcClient, - pub(crate) domain: HyperlaneDomain, + pub(crate) provider: SealevelProvider, payer: Option, } @@ -81,10 +80,7 @@ impl SealevelMailbox { locator: ContractLocator, payer: Option, ) -> ChainResult { - // Set the `processed` commitment at rpc level - let rpc_client = - RpcClient::new_with_commitment(conf.url.to_string(), CommitmentConfig::processed()); - + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); let domain = locator.domain.id(); let inbox = Pubkey::find_program_address(mailbox_inbox_pda_seeds!(), &program_id); @@ -99,8 +95,7 @@ impl SealevelMailbox { program_id, inbox, outbox, - rpc_client, - domain: locator.domain.clone(), + provider, payer, }) } @@ -112,16 +107,20 @@ impl SealevelMailbox { self.outbox } + pub fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } + /// Simulates an instruction, and attempts to deserialize it into a T. /// If no return data at all was returned, returns Ok(None). - /// If some return data was returned but deserialization was unsuccesful, + /// If some return data was returned but deserialization was unsuccessful, /// an Err is returned. pub async fn simulate_instruction( &self, instruction: Instruction, ) -> ChainResult> { simulate_instruction( - &self.rpc_client, + &self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -136,7 +135,7 @@ impl SealevelMailbox { instruction: Instruction, ) -> ChainResult> { get_account_metas( - &self.rpc_client, + &self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -263,11 +262,11 @@ impl HyperlaneContract for SealevelMailbox { impl HyperlaneChain for SealevelMailbox { fn domain(&self) -> &HyperlaneDomain { - &self.domain + &self.provider.domain() } fn provider(&self) -> Box { - Box::new(SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -295,7 +294,7 @@ impl Mailbox for SealevelMailbox { ); let account = self - .rpc_client + .rpc() .get_account_with_commitment( &processed_message_account_key, CommitmentConfig::finalized(), @@ -309,7 +308,7 @@ impl Mailbox for SealevelMailbox { #[instrument(err, ret, skip(self))] async fn default_ism(&self) -> ChainResult { let inbox_account = self - .rpc_client + .rpc() .get_account(&self.inbox.0) .await .map_err(ChainCommunicationError::from_other)?; @@ -436,7 +435,7 @@ impl Mailbox for SealevelMailbox { }; instructions.push(inbox_instruction); let (recent_blockhash, _) = self - .rpc_client + .rpc() .get_latest_blockhash_with_commitment(commitment) .await .map_err(ChainCommunicationError::from_other)?; @@ -451,7 +450,7 @@ impl Mailbox for SealevelMailbox { tracing::info!(?txn, "Created sealevel transaction to process message"); let signature = self - .rpc_client + .rpc() .send_and_confirm_transaction(&txn) .await .map_err(ChainCommunicationError::from_other)?; @@ -459,7 +458,7 @@ impl Mailbox for SealevelMailbox { tracing::info!(?txn, ?signature, "Sealevel transaction sent"); let executed = self - .rpc_client + .rpc() .confirm_transaction_with_commitment(&signature, commitment) .await .map_err(|err| warn!("Failed to confirm inbox process transaction: {}", err)) @@ -471,7 +470,7 @@ impl Mailbox for SealevelMailbox { transaction_id: txid, executed, // TODO use correct data upon integrating IGP support - gas_price: U256::zero(), + gas_price: U256::zero().try_into()?, gas_used: U256::zero(), }) } @@ -485,7 +484,7 @@ impl Mailbox for SealevelMailbox { // TODO use correct data upon integrating IGP support Ok(TxCostEstimate { gas_limit: U256::zero(), - gas_price: U256::zero(), + gas_price: FixedPointNumber::zero(), l2_gas_limit: None, }) } @@ -498,7 +497,6 @@ impl Mailbox for SealevelMailbox { /// Struct that retrieves event data for a Sealevel Mailbox contract #[derive(Debug)] pub struct SealevelMailboxIndexer { - rpc_client: RpcClientWithDebug, mailbox: SealevelMailbox, program_id: Pubkey, } @@ -506,18 +504,20 @@ pub struct SealevelMailboxIndexer { impl SealevelMailboxIndexer { pub fn new(conf: &ConnectionConf, locator: ContractLocator) -> ChainResult { let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); let mailbox = SealevelMailbox::new(conf, locator, None)?; Ok(Self { program_id, - rpc_client, mailbox, }) } + fn rpc(&self) -> &RpcClientWithDebug { + &self.mailbox.rpc() + } + async fn get_finalized_block_number(&self) -> ChainResult { let height = self - .rpc_client + .rpc() .get_block_height() .await .map_err(ChainCommunicationError::from_other)? @@ -560,7 +560,7 @@ impl SealevelMailboxIndexer { with_context: Some(false), }; let accounts = self - .rpc_client + .rpc() .get_program_accounts_with_config(&self.mailbox.program_id, config) .await .map_err(ChainCommunicationError::from_other)?; @@ -595,7 +595,7 @@ impl SealevelMailboxIndexer { // Now that we have the valid message storage PDA pubkey, we can get the full account data. let account = self - .rpc_client + .rpc() .get_account_with_commitment( &valid_message_storage_pda_pubkey, CommitmentConfig::finalized(), @@ -630,10 +630,10 @@ impl SealevelMailboxIndexer { } #[async_trait] -impl SequenceIndexer for SealevelMailboxIndexer { +impl SequenceAwareIndexer for SealevelMailboxIndexer { #[instrument(err, skip(self))] - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { - let tip = Indexer::::get_finalized_block_number(self as _).await?; + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + let tip = Indexer::::get_finalized_block_number(self).await?; // TODO: need to make sure the call and tip are at the same height? let count = Mailbox::count(&self.mailbox, None).await?; Ok((Some(count), tip)) @@ -660,7 +660,7 @@ impl Indexer for SealevelMailboxIndexer { } async fn get_finalized_block_number(&self) -> ChainResult { - get_finalized_block_number(&self.rpc_client).await + get_finalized_block_number(&self.rpc()).await } } @@ -676,8 +676,8 @@ impl Indexer for SealevelMailboxIndexer { } #[async_trait] -impl SequenceIndexer for SealevelMailboxIndexer { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { +impl SequenceAwareIndexer for SealevelMailboxIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { // TODO: implement when sealevel scraper support is implemented info!("Message delivery indexing not implemented"); let tip = Indexer::::get_finalized_block_number(self).await?; diff --git a/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs b/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs index 1b15cb5331..30dea8c535 100644 --- a/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs @@ -4,13 +4,14 @@ use async_trait::async_trait; use derive_new::new; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, - Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, SequenceIndexer, + HyperlaneChain, HyperlaneMessage, Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, + SequenceAwareIndexer, }; use hyperlane_sealevel_mailbox::accounts::OutboxAccount; use solana_sdk::commitment_config::CommitmentConfig; use tracing::instrument; -use crate::SealevelMailbox; +use crate::{SealevelMailbox, SealevelMailboxIndexer}; #[async_trait] impl MerkleTreeHook for SealevelMailbox { @@ -22,7 +23,7 @@ impl MerkleTreeHook for SealevelMailbox { ); let outbox_account = self - .rpc_client + .rpc() .get_account_with_commitment(&self.outbox.0, CommitmentConfig::finalized()) .await .map_err(ChainCommunicationError::from_other)? @@ -58,7 +59,7 @@ impl MerkleTreeHook for SealevelMailbox { })?; let checkpoint = Checkpoint { merkle_tree_hook_address: self.program_id.to_bytes().into(), - mailbox_domain: self.domain.id(), + mailbox_domain: self.domain().id(), root, index, }; @@ -76,26 +77,38 @@ impl MerkleTreeHook for SealevelMailbox { } /// Struct that retrieves event data for a Sealevel merkle tree hook contract +/// For now it's just a wrapper around the SealevelMailboxIndexer #[derive(Debug, new)] -pub struct SealevelMerkleTreeHookIndexer {} +pub struct SealevelMerkleTreeHookIndexer(SealevelMailboxIndexer); #[async_trait] impl Indexer for SealevelMerkleTreeHookIndexer { async fn fetch_logs( &self, - _range: RangeInclusive, + range: RangeInclusive, ) -> ChainResult> { - Ok(vec![]) + let messages = Indexer::::fetch_logs(&self.0, range).await?; + let merkle_tree_insertions = messages + .into_iter() + .map(|(m, meta)| (message_to_merkle_tree_insertion(&m), meta)) + .collect(); + Ok(merkle_tree_insertions) } async fn get_finalized_block_number(&self) -> ChainResult { - Ok(0) + Indexer::::get_finalized_block_number(&self.0).await } } #[async_trait] -impl SequenceIndexer for SealevelMerkleTreeHookIndexer { - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)> { - Ok((None, 0)) +impl SequenceAwareIndexer for SealevelMerkleTreeHookIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + SequenceAwareIndexer::::latest_sequence_count_and_tip(&self.0).await } } + +fn message_to_merkle_tree_insertion(message: &HyperlaneMessage) -> MerkleTreeInsertion { + let leaf_index = message.nonce; + let message_id = message.id(); + MerkleTreeInsertion::new(leaf_index, message_id) +} diff --git a/rust/chains/hyperlane-sealevel/src/multisig_ism.rs b/rust/chains/hyperlane-sealevel/src/multisig_ism.rs index 71cdc7136f..794e19c145 100644 --- a/rust/chains/hyperlane-sealevel/src/multisig_ism.rs +++ b/rust/chains/hyperlane-sealevel/src/multisig_ism.rs @@ -24,25 +24,29 @@ use multisig_ism::interface::{ /// A reference to a MultisigIsm contract on some Sealevel chain #[derive(Debug)] pub struct SealevelMultisigIsm { - rpc_client: RpcClientWithDebug, payer: Option, program_id: Pubkey, domain: HyperlaneDomain, + provider: SealevelProvider, } impl SealevelMultisigIsm { /// Create a new Sealevel MultisigIsm. pub fn new(conf: &ConnectionConf, locator: ContractLocator, payer: Option) -> Self { - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); Self { - rpc_client, payer, program_id, domain: locator.domain.clone(), + provider, } } + + fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } } impl HyperlaneContract for SealevelMultisigIsm { @@ -57,7 +61,7 @@ impl HyperlaneChain for SealevelMultisigIsm { } fn provider(&self) -> Box { - Box::new(SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -84,7 +88,7 @@ impl MultisigIsm for SealevelMultisigIsm { let validators_and_threshold = simulate_instruction::>( - &self.rpc_client, + self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -132,7 +136,7 @@ impl SealevelMultisigIsm { ); get_account_metas( - &self.rpc_client, + self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, diff --git a/rust/chains/hyperlane-sealevel/src/provider.rs b/rust/chains/hyperlane-sealevel/src/provider.rs index b853e30e4b..42b2665501 100644 --- a/rust/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/chains/hyperlane-sealevel/src/provider.rs @@ -1,19 +1,48 @@ +use std::{str::FromStr, sync::Arc}; + use async_trait::async_trait; use hyperlane_core::{ - BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, + BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, + H256, U256, }; +use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; + +use crate::{client::RpcClientWithDebug, error::HyperlaneSealevelError, ConnectionConf}; /// A wrapper around a Sealevel provider to get generic blockchain information. #[derive(Debug)] pub struct SealevelProvider { domain: HyperlaneDomain, + rpc_client: Arc, } impl SealevelProvider { /// Create a new Sealevel provider. - pub fn new(domain: HyperlaneDomain) -> Self { - SealevelProvider { domain } + pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self { + // Set the `processed` commitment at rpc level + let rpc_client = Arc::new(RpcClientWithDebug::new_with_commitment( + conf.url.to_string(), + CommitmentConfig::processed(), + )); + + SealevelProvider { domain, rpc_client } + } + + /// Get an rpc client + pub fn rpc(&self) -> &RpcClientWithDebug { + &self.rpc_client + } + + /// Get the balance of an address + pub async fn get_balance(&self, address: String) -> ChainResult { + let pubkey = Pubkey::from_str(&address).map_err(Into::::into)?; + let balance = self + .rpc_client + .get_balance(&pubkey) + .await + .map_err(Into::::into)?; + Ok(balance.into()) } } @@ -25,6 +54,7 @@ impl HyperlaneChain for SealevelProvider { fn provider(&self) -> Box { Box::new(SealevelProvider { domain: self.domain.clone(), + rpc_client: self.rpc_client.clone(), }) } } @@ -43,4 +73,12 @@ impl HyperlaneProvider for SealevelProvider { // FIXME Ok(true) } + + async fn get_balance(&self, address: String) -> ChainResult { + self.get_balance(address).await + } + + async fn get_chain_metrics(&self) -> ChainResult> { + Ok(None) + } } diff --git a/rust/chains/hyperlane-sealevel/src/validator_announce.rs b/rust/chains/hyperlane-sealevel/src/validator_announce.rs index 5fbb470af0..c6ce2233dd 100644 --- a/rust/chains/hyperlane-sealevel/src/validator_announce.rs +++ b/rust/chains/hyperlane-sealevel/src/validator_announce.rs @@ -8,7 +8,7 @@ use hyperlane_core::{ }; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; -use crate::{ConnectionConf, RpcClientWithDebug}; +use crate::{ConnectionConf, RpcClientWithDebug, SealevelProvider}; use hyperlane_sealevel_validator_announce::{ accounts::ValidatorStorageLocationsAccount, validator_storage_locations_pda_seeds, }; @@ -17,21 +17,25 @@ use hyperlane_sealevel_validator_announce::{ #[derive(Debug)] pub struct SealevelValidatorAnnounce { program_id: Pubkey, - rpc_client: RpcClientWithDebug, domain: HyperlaneDomain, + provider: SealevelProvider, } impl SealevelValidatorAnnounce { /// Create a new Sealevel ValidatorAnnounce pub fn new(conf: &ConnectionConf, locator: ContractLocator) -> Self { - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); Self { program_id, - rpc_client, domain: locator.domain.clone(), + provider, } } + + fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } } impl HyperlaneContract for SealevelValidatorAnnounce { @@ -46,7 +50,7 @@ impl HyperlaneChain for SealevelValidatorAnnounce { } fn provider(&self) -> Box { - Box::new(crate::SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -74,7 +78,7 @@ impl ValidatorAnnounce for SealevelValidatorAnnounce { // Get all validator storage location accounts. // If an account doesn't exist, it will be returned as None. let accounts = self - .rpc_client + .rpc() .get_multiple_accounts_with_commitment(&account_pubkeys, CommitmentConfig::finalized()) .await .map_err(ChainCommunicationError::from_other)? @@ -124,7 +128,7 @@ impl ValidatorAnnounce for SealevelValidatorAnnounce { transaction_id: H512::zero(), executed: false, gas_used: U256::zero(), - gas_price: U256::zero(), + gas_price: U256::zero().try_into()?, }) } } diff --git a/rust/config/mainnet3_config.json b/rust/config/mainnet3_config.json index 70f804cc91..231892e0fe 100644 --- a/rust/config/mainnet3_config.json +++ b/rust/config/mainnet3_config.json @@ -1,64 +1,84 @@ { "chains": { "arbitrum": { + "blockExplorers": [ + { + "apiUrl": "https://api.arbiscan.io/api", + "family": "etherscan", + "name": "Arbiscan", + "url": "https://arbiscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 0 + }, "chainId": 42161, + "displayName": "Arbitrum", "domainId": 42161, + "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-arbitrum.safe.global/", "name": "arbitrum", - "protocol": "ethereum", - "displayName": "Arbitrum", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://arb1.arbitrum.io/rpc" } ], - "blockExplorers": [ - { - "name": "Arbiscan", - "url": "https://arbiscan.io", - "apiUrl": "https://api.arbiscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 3 - }, - "gasCurrencyCoinGeckoId": "ethereum", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-arbitrum.safe.global/", "storageGasOracle": "0xD3805207b65d99C075ceA938Fa7c0587026a5DF5", "proxyAdmin": "0x80Cebd56A65e46c474a1A101e89E76C4c51D179c", "merkleRootMultisigIsmFactory": "0x3C330D4A2e2b8443AFaB8E326E64ab4251B7Eae0", "messageIdMultisigIsmFactory": "0x12Df53079d399a47e9E730df095b712B0FDFA791", "aggregationIsmFactory": "0xD4883084389fC1Eeb4dAfB2ADcFc36B711c310EB", "aggregationHookFactory": "0x9B5f440bBb64Fee337F37e03362b628711Ea09C7", - "routingIsmFactory": "0xC020F8A7b00178dFA0fcC75C159e14b79F8e5c63", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", "aggregationHook": "0xe0cb37cFc47296f1c4eD77EFf92Aed478644d10c", "protocolFee": "0xD0199067DACb8526e7dc524a9a7DCBb57Cd25421", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xa2931C37957f3079d3B21b877d56E1db930e02a5", + "pausableHook": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "fallbackRoutingHook": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", + "interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa", "index": { - "from": 143699718 + "from": 18422579 } }, "avalanche": { + "blockExplorers": [ + { + "apiUrl": "https://api.snowtrace.io/api", + "family": "other", + "name": "SnowTrace", + "url": "https://snowtrace.io" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 2, + "reorgPeriod": 3 + }, "chainId": 43114, + "displayName": "Avalanche", "domainId": 43114, + "gasCurrencyCoinGeckoId": "avalanche-2", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-avalanche.safe.global/", "name": "avalanche", - "protocol": "ethereum", - "displayName": "Avalanche", "nativeToken": { "decimals": 18, "name": "Avalanche", "symbol": "AVAX" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://api.avax.network/ext/bc/C/rpc", @@ -68,49 +88,54 @@ } } ], - "blockExplorers": [ - { - "name": "SnowTrace", - "url": "https://snowtrace.io", - "apiUrl": "https://api.snowtrace.io/api", - "family": "other" - } - ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 3, - "estimateBlockTime": 2 - }, - "gasCurrencyCoinGeckoId": "avalanche-2", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-avalanche.safe.global/", "storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21", "proxyAdmin": "0xd7CF8c05fd81b8cA7CfF8E6C49B08a9D63265c9B", "merkleRootMultisigIsmFactory": "0x896cF1D1B66cD211633eDd589fF158E8Cfaf9B54", "messageIdMultisigIsmFactory": "0x8819D653DF5b1FC0DdB32189a2704E471AF8483c", "aggregationIsmFactory": "0xa5E13796eB7d2EDCc88012c8cfF90D69B51FcF9f", "aggregationHookFactory": "0x3bF6Ac986C7Af9A9Ac356C0e99C0041EFd8D96e7", - "routingIsmFactory": "0xA9Ddc70f50009aF8bDB312aA757B4304b0F7BbB3", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", "aggregationHook": "0x0165a22BA489F7DA37DAf6397781777D9FCB5708", "protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x28F7907911C7E321c596686AE6D1F20516450037", + "pausableHook": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "fallbackRoutingHook": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", + "interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60", "index": { - "from": 36881761 + "from": 36874693 } }, "base": { + "blockExplorers": [ + { + "apiUrl": "https://api.basescan.org/api", + "family": "etherscan", + "name": "BaseScan", + "url": "https://basescan.org" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 1 + }, "chainId": 8453, + "displayName": "Base", "domainId": 8453, + "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-base.safe.global/", "name": "base", - "protocol": "ethereum", - "displayName": "Base", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://base.publicnode.com/" @@ -122,25 +147,10 @@ "http": "https://base.blockpi.network/v1/rpc/public" } ], - "blockExplorers": [ - { - "name": "BaseScan", - "url": "https://basescan.org", - "apiUrl": "https://api.basescan.org/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 2 - }, - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-base.safe.global/", "merkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "messageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "aggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "aggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "routingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", @@ -149,45 +159,52 @@ "aggregationHook": "0x13f3d4B0Ee0a713430fded9E18f7fb6c91A6E41F", "protocolFee": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B", + "routingIsmFactory": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", + "pausableHook": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "fallbackRoutingHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F", "index": { - "from": 5702757 + "from": 5695475 } }, "bsc": { + "blockExplorers": [ + { + "apiUrl": "https://api.bscscan.com/api", + "family": "etherscan", + "name": "BscScan", + "url": "https://bscscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 15 + }, "chainId": 56, - "domainId": 56, - "name": "bsc", - "protocol": "ethereum", "displayName": "Binance Smart Chain", "displayNameShort": "Binance", + "domainId": 56, + "gasCurrencyCoinGeckoId": "binancecoin", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-bsc.safe.global/", + "name": "bsc", "nativeToken": { "decimals": 18, "name": "BNB", "symbol": "BNB" }, + "protocol": "ethereum", "rpcUrls": [ { - "http": "https://bsc-dataseed.binance.org" + "http": "https://rpc.ankr.com/bsc" }, { - "http": "https://rpc.ankr.com/bsc" - } - ], - "blockExplorers": [ + "http": "https://bsc.drpc.org" + }, { - "name": "BscScan", - "url": "https://bscscan.com", - "apiUrl": "https://api.bscscan.com/api", - "family": "etherscan" + "http": "https://bscrpc.com" } ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 15, - "estimateBlockTime": 3 - }, - "gasCurrencyCoinGeckoId": "binancecoin", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-bsc.safe.global/", "transactionOverrides": { "gasPrice": 7000000000 }, @@ -197,81 +214,114 @@ "messageIdMultisigIsmFactory": "0x4B1d8352E35e3BDE36dF5ED2e73C24E35c4a96b7", "aggregationIsmFactory": "0x38B3878c4fb44d201DA924c4a04bae3EE728c065", "aggregationHookFactory": "0xe70E86a7D1e001D419D71F960Cb6CaD59b6A3dB6", - "routingIsmFactory": "0xc40481D13419BC8090e6AD07074Ef39E538c09CE", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", "aggregationHook": "0x402Fc106576462a892355d69ACF03D46A888ae88", "protocolFee": "0xA8Aa5f14a5463a78E45CC068F11c867949F3E367", "validatorAnnounce": "0x7024078130D9c2100fEA474DAD009C2d1703aCcd", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xe6Af5720d34213C805C08e2470aea979e3F72F75", + "interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61", + "fallbackRoutingHook": "0x237E81f87F57Badad9e09f13CC676D986cA852e7", + "pausableHook": "0x7DBdAd1b4A922B65d37d7258a4227b6658344b7f", "index": { - "from": 32897848 + "from": 32893043 } }, "celo": { + "blockExplorers": [ + { + "apiUrl": "https://api.celoscan.io/api", + "family": "etherscan", + "name": "CeloScan", + "url": "https://celoscan.io" + }, + { + "apiUrl": "https://explorer.celo.org/mainnet/api", + "family": "blockscout", + "name": "Blockscout", + "url": "https://explorer.celo.org" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 0 + }, "chainId": 42220, + "displayName": "Celo", "domainId": 42220, + "gnosisSafeTransactionServiceUrl": "https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/", "name": "celo", - "protocol": "ethereum", - "displayName": "Celo", "nativeToken": { "decimals": 18, "name": "CELO", "symbol": "CELO" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://forno.celo.org" } ], - "blockExplorers": [ - { - "name": "CeloScan", - "url": "https://celoscan.io", - "apiUrl": "https://api.celoscan.io/api", - "family": "etherscan" - }, - { - "name": "Blockscout", - "url": "https://explorer.celo.org", - "apiUrl": "https://explorer.celo.org/mainnet/api", - "family": "blockscout" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 5 - }, - "gnosisSafeTransactionServiceUrl": "https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/", "storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C", "proxyAdmin": "0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9", "merkleRootMultisigIsmFactory": "0x4C96a1abc44dc846775CE702C9E9BE821D3b487c", "messageIdMultisigIsmFactory": "0xaB402f227e892Ef37C105bf06619c0fa106a1fB2", "aggregationIsmFactory": "0x1722dd970a1F56040712129f5Eeb76B003fd7500", "aggregationHookFactory": "0xc3745652EFB8555A8b064A0EA78d295133d326D2", - "routingIsmFactory": "0xec748b5623f0B50E4c5eB1CFa7Bd46C3213608b6", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", "aggregationHook": "0xc65890329066FB20c339Bc5C22f1756e9D3a4fF5", "protocolFee": "0x89886d431f9c3eEE64DCD6dAbA3f7D689D98D899", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x2A2c22B0a8615ad24839fA6Af302E896Af32d1a3", + "domainRoutingIsm": "0xf18E32428dad0802C5D6F723cB80A6Da889777c4", + "pausableIsm": "0x6Bc4437ce69696C9461Cbc89582c259AC8847A58", + "staticAggregationIsm": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", + "interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", + "fallbackRoutingHook": "0xDC98a856fb9112894c2fE32267DA8bF35645FAF3", + "pausableHook": "0x80672c5D9Fd26B235654C24adc1CFcDeb8d15115", "index": { - "from": 22105253 + "from": 22102340 } }, "ethereum": { + "blockExplorers": [ + { + "apiUrl": "https://api.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://etherscan.io" + }, + { + "apiUrl": "https://blockscout.com/eth/mainnet/api", + "family": "blockscout", + "name": "Blockscout", + "url": "https://blockscout.com/eth/mainnet" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 13, + "reorgPeriod": 14 + }, "chainId": 1, + "displayName": "Ethereum", "domainId": 1, + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-mainnet.safe.global/", "name": "ethereum", - "protocol": "ethereum", - "displayName": "Ethereum", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" @@ -280,26 +330,6 @@ "http": "https://cloudflare-eth.com" } ], - "blockExplorers": [ - { - "name": "Etherscan", - "url": "https://etherscan.io", - "apiUrl": "https://api.etherscan.io/api", - "family": "etherscan" - }, - { - "name": "Blockscout", - "url": "https://blockscout.com/eth/mainnet", - "apiUrl": "https://blockscout.com/eth/mainnet/api", - "family": "blockscout" - } - ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 14, - "estimateBlockTime": 13 - }, - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-mainnet.safe.global/", "transactionOverrides": { "maxFeePerGas": 150000000000, "maxPriorityFeePerGas": 5000000000 @@ -310,28 +340,48 @@ "messageIdMultisigIsmFactory": "0xfA21D9628ADce86531854C2B7ef00F07394B0B69", "aggregationIsmFactory": "0x46FA191Ad972D9674Ed752B69f9659A0d7b22846", "aggregationHookFactory": "0x6D2555A8ba483CcF4409C39013F5e9a3285D3C9E", - "routingIsmFactory": "0xCb74c6aE411236CEE6803619916694BE86cF5987", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", "aggregationHook": "0xb87AC8EA4533AE017604E44470F7c1E550AC6F10", "protocolFee": "0x8B05BF30F6247a90006c5837eA63C7905D79e6d8", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "validatorAnnounce": "0xCe74905e51497b4adD3639366708b821dcBcff96", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x28fA9552F19039b450498B0d8e5DEAe0d0aAc559", + "pausableHook": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "fallbackRoutingHook": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "interchainSecurityModule": "0x43Ce4Eb4aE3585dDe9Ac6967Db5b06f7f6764C8a", "index": { - "from": 18423787 + "from": 18422581 } }, "gnosis": { + "blockExplorers": [ + { + "apiUrl": "https://api.gnosisscan.io/api", + "family": "etherscan", + "name": "GnosisScan", + "url": "https://gnosisscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 14 + }, "chainId": 100, + "displayName": "Gnosis", "domainId": 100, + "gasCurrencyCoinGeckoId": "xdai", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-gnosis-chain.safe.global/", "name": "gnosis", - "protocol": "ethereum", - "displayName": "Gnosis", "nativeToken": { + "decimals": 18, "name": "xDai", - "symbol": "xDai", - "decimals": 18 + "symbol": "xDai" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://rpc.gnosischain.com", @@ -341,21 +391,6 @@ } } ], - "blockExplorers": [ - { - "name": "GnosisScan", - "url": "https://gnosisscan.io", - "apiUrl": "https://api.gnosisscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 14, - "estimateBlockTime": 5 - }, - "gasCurrencyCoinGeckoId": "xdai", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-gnosis-chain.safe.global/", "storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", "proxyAdmin": "0x81a92A1a272cb09d7b4970b07548463dC7aE0cB7", "merkleRootMultisigIsmFactory": "0x8E273260EAd8B72A085B19346A676d355740e875", @@ -363,44 +398,56 @@ "aggregationIsmFactory": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", "aggregationHookFactory": "0xbC8AA096dabDf4A0200BB9f8D4Cbb644C3D86d7B", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", - "routingIsmFactory": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", "aggregationHook": "0xdD1FA1C12496474c1dDC67a658Ba81437F818861", "protocolFee": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", "validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xbB5Df000113e767dE11343A16f83De733e5bCC0F", + "pausableHook": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "fallbackRoutingHook": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", + "interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1", "index": { - "from": 30623434 + "from": 30620793 } }, "mantapacific": { - "protocol": "ethereum", - "domainId": 169, + "blockExplorers": [ + { + "apiUrl": "https://pacific-explorer.manta.network/api", + "family": "blockscout", + "name": "Manta Pacific Explorer", + "url": "https://pacific-explorer.manta.network" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, "chainId": 169, - "name": "mantapacific", "displayName": "Manta Pacific", "displayNameShort": "Manta", + "domainId": 169, + "isTestnet": false, + "name": "mantapacific", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 - }, - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 3 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://pacific-rpc.manta.network/http" } ], - "isTestnet": false, "merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "routingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", @@ -410,8 +457,12 @@ "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "routingIsmFactory": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", + "pausableHook": "0x7556a0E61d577D921Cba8Fca0d7D6299d36E607E", + "fallbackRoutingHook": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", + "interchainSecurityModule": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", "index": { - "from": 437384 + "from": 437300 } }, "neutron": { @@ -423,15 +474,23 @@ "validatorAnnounce": "0xf3aa0d652226e21ae35cd9035c492ae41725edc9036edf0d6a48701b153b90a0", "merkleTreeHook": "0xcd30a0001cc1f436c41ef764a712ebabc5a144140e3fd03eafe64a9a24e4e27c", "protocol": "cosmos", - "finalityBlocks": 1, "rpcUrls": [ { "http": "https://rpc-kralum.neutron-1.neutron.org" } ], - "grpcUrl": "https://grpc-kralum.neutron-1.neutron.org:80", + "grpcUrls": [ + { + "http": "https://grpc-kralum.neutron-1.neutron.org:80" + } + ], "canonicalAsset": "untrn", - "prefix": "neutron", + "bech32Prefix": "neutron", + "gasPrice": { + "amount": "0.57", + "denom": "untrn" + }, + "contractAddressBytes": 32, "index": { "from": 4000000, "chunk": 100000 @@ -445,112 +504,220 @@ "prefix": "neutron" } }, + "injective": { + "name": "injective", + "domainId": "6909546", + "chainId": "injective-1", + "mailbox": "0x0f7fb53961d70687e352aa55cb329ca76edc0c19", + "interchainGasPaymaster": "0x27ae52298e5b53b34b7ae0ca63e05845c31e1f59", + "validatorAnnounce": "0x1fb225b2fcfbe75e614a1d627de97ff372242eed", + "merkleTreeHook": "0x568ad3638447f07def384969f4ea39fae3802962", + "protocol": "cosmos", + "rpcUrls": [ + { + "http": "https://rpc-injective.goldenratiostaking.net:443" + } + ], + "grpcUrls": [ + { + "http": "https://injective-grpc.goldenratiostaking.net:443" + } + ], + "canonicalAsset": "inj", + "bech32Prefix": "inj", + "gasPrice": { + "amount": "700000000", + "denom": "inj" + }, + "contractAddressBytes": 20, + "index": { + "from": 58419500, + "chunk": 100000 + }, + "blocks": { + "reorgPeriod": 10 + } + }, "moonbeam": { + "blockExplorers": [ + { + "apiUrl": "https://api-moonbeam.moonscan.io/api", + "family": "etherscan", + "name": "MoonScan", + "url": "https://moonscan.io" + } + ], + "blocks": { + "confirmations": 2, + "estimateBlockTime": 12, + "reorgPeriod": 2 + }, "chainId": 1284, + "displayName": "Moonbeam", "domainId": 1284, + "gnosisSafeTransactionServiceUrl": "https://transaction.multisig.moonbeam.network", "name": "moonbeam", - "protocol": "ethereum", - "displayName": "Moonbeam", "nativeToken": { "decimals": 18, "name": "GLMR", "symbol": "GLMR" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://rpc.api.moonbeam.network" } ], - "blockExplorers": [ - { - "name": "MoonScan", - "url": "https://moonscan.io", - "apiUrl": "https://api-moonbeam.moonscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 2, - "reorgPeriod": 2, - "estimateBlockTime": 12 - }, - "gnosisSafeTransactionServiceUrl": "https://transaction.multisig.moonbeam.network", "storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819", "proxyAdmin": "0x6A9cdA3dd1F593983BFd142Eb35e6ce4137bd5ce", "merkleRootMultisigIsmFactory": "0xE2f485bc031Feb5a4C41C1967bf028653d75f0C3", "messageIdMultisigIsmFactory": "0x84Df48F8f241f11d0fA302d09d73030429Bd9C73", "aggregationIsmFactory": "0x40c6Abcb6A2CdC8882d4bEcaC47927005c7Bb8c2", "aggregationHookFactory": "0x59cC3E7A49DdC4893eB8754c7908f96072A7DbE8", - "routingIsmFactory": "0x98Aa6239FfCcEc73A662a5e5e26Bc3fD7c7291B7", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", "aggregationHook": "0x23cca255aE83F57F39EAf9D14fB9FdaDF22D5863", "protocolFee": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", "validatorAnnounce": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x8061Af3A459093540d17823D651BC5E2A92669a7", + "pausableHook": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "fallbackRoutingHook": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9", "index": { - "from": 4720894 + "from": 4719713 } }, - "optimism": { - "chainId": 10, - "domainId": 10, - "name": "optimism", - "protocol": "ethereum", - "displayName": "Optimism", + "inevm": { + "blockExplorers": [ + { + "apiUrl": "https://inevm.calderaexplorer.xyz/api", + "family": "blockscout", + "name": "Caldera inEVM Explorer", + "url": "https://inevm.calderaexplorer.xyz/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 0 + }, + "chainId": 2525, + "domainId": 2525, + "displayName": "Injective EVM", + "displayNameShort": "inEVM", + "name": "inevm", "nativeToken": { - "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "decimals": 18, + "name": "Injective", + "symbol": "INJ" }, + "protocol": "ethereum", "rpcUrls": [ { - "http": "https://mainnet.optimism.io" + "http": "https://inevm.calderachain.xyz/http" } ], + "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "aggregationHook": "0xe0dDb5dE7D52918237cC1Ae131F29dcAbcb0F62B", + "protocolFee": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B", + "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "index": { + "from": 18972465 + } + }, + "optimism": { "blockExplorers": [ { - "name": "Etherscan", - "url": "https://optimistic.etherscan.io", "apiUrl": "https://api-optimistic.etherscan.io/api", - "family": "etherscan" + "family": "etherscan", + "name": "Etherscan", + "url": "https://optimistic.etherscan.io" } ], "blocks": { "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 3 + "estimateBlockTime": 3, + "reorgPeriod": 0 }, + "chainId": 10, + "displayName": "Optimism", + "domainId": 10, "gasCurrencyCoinGeckoId": "ethereum", "gnosisSafeTransactionServiceUrl": "https://safe-transaction-optimism.safe.global/", + "name": "optimism", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://mainnet.optimism.io" + } + ], "storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", "proxyAdmin": "0xE047cb95FB3b7117989e911c6afb34771183fC35", "merkleRootMultisigIsmFactory": "0xCA6Cb9Bc3cfF9E11003A06617cF934B684Bc78BC", "messageIdMultisigIsmFactory": "0xAa4Be20E9957fE21602c74d7C3cF5CB1112EA9Ef", "aggregationIsmFactory": "0x7491843F3A5Ba24E0f17a22645bDa04A1Ae2c584", "aggregationHookFactory": "0x15DEeAB8dECDe553bb0B1F9C00984cbcae1af3D7", - "routingIsmFactory": "0x89E3530137aD51743536443a3EC838b502E72eb7", "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", "aggregationHook": "0x4ccC6d8eB79f2a1EC9bcb0f211fef7907631F91f", "protocolFee": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xD2e905108c5e44dADA680274740f896Ea96Cf2Fb", + "pausableHook": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "fallbackRoutingHook": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2", "index": { - "from": 111298042 + "from": 111290758 } }, "polygon": { + "blockExplorers": [ + { + "apiUrl": "https://api.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://polygonscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 256 + }, "chainId": 137, + "displayName": "Polygon", "domainId": 137, + "gasCurrencyCoinGeckoId": "matic-network", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-polygon.safe.global/", "name": "polygon", - "protocol": "ethereum", - "displayName": "Polygon", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://rpc-mainnet.matic.quiknode.pro", @@ -561,26 +728,14 @@ }, { "http": "https://polygon-rpc.com" - } - ], - "blockExplorers": [ + }, { - "name": "PolygonScan", - "url": "https://polygonscan.com", - "apiUrl": "https://api.polygonscan.com/api", - "family": "etherscan" + "http": "https://rpc.ankr.com/polygon" } ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 256, - "estimateBlockTime": 2 - }, - "gasCurrencyCoinGeckoId": "matic-network", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-polygon.safe.global/", "transactionOverrides": { - "maxFeePerGas": 500000000000, - "maxPriorityFeePerGas": 100000000000 + "maxFeePerGas": 1000000000000, + "maxPriorityFeePerGas": 200000000000 }, "storageGasOracle": "0xA3a24EC5670F1F416AB9fD554FcE2f226AE9D7eB", "proxyAdmin": "0xC4F7590C5d30BE959225dC75640657954A86b980", @@ -588,57 +743,57 @@ "messageIdMultisigIsmFactory": "0xEa5Be2AD66BB1BA321B7aCf0A079fBE304B09Ca0", "aggregationIsmFactory": "0x81AdDD9Ca89105063DaDEBd5B4408551Ce850E22", "aggregationHookFactory": "0xFeeB86e70e4a640cDd29636CCE19BD6fe8628135", - "routingIsmFactory": "0xF0752A65ffB2153EaE53F6a70c858a87022d5c56", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", "aggregationHook": "0x34dAb05650Cf590088bA18aF9d597f3e081bCc47", "protocolFee": "0xF8F3629e308b4758F8396606405989F8D8C9c578", "validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x0d0E816eE4557689d34fAd5885C53b9393C1D9fA", + "interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB", + "fallbackRoutingHook": "0xca4cCe24E7e06241846F5EA0cda9947F0507C40C", + "pausableHook": "0x748040afB89B8FdBb992799808215419d36A0930", "index": { - "from": 49114872 + "from": 49108065 } }, "polygonzkevm": { - "protocol": "ethereum", + "blockExplorers": [ + { + "apiUrl": "https://api-zkevm.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://zkevm.polygonscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 10, + "reorgPeriod": 1 + }, "chainId": 1101, + "displayName": "Polygon zkEVM", + "displayNameShort": "zkEVM", "domainId": 1101, + "gasCurrencyCoinGeckoId": "ethereum", "name": "polygonzkevm", - "displayName": "Polygon zkEVM", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { - "http": "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo" - }, - { - "http": "https://rpc.ankr.com/polygon_zkevm" - }, - { - "http": "https://zkevm.polygonscan.com/" + "http": "https://polygon-zkevm.drpc.org" } ], - "blockExplorers": [ - { - "name": "PolygonScan", - "url": "https://zkevm.polygonscan.com/", - "apiUrl": "https://api-zkevm.polygonscan.com/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 10 - }, "merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "routingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", @@ -647,47 +802,48 @@ "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "routingIsmFactory": "0xe4057c5B0c43Dc18E36b08C39B419F190D29Ac2d", + "interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141", + "fallbackRoutingHook": "0x01aE937A7B05d187bBCBE80F44F41879D3D335a4", + "pausableHook": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", "index": { - "from": 6581140 + "from": 6577743 } }, "scroll": { + "blockExplorers": [ + { + "apiUrl": "https://api.scrollscan.com/api", + "family": "etherscan", + "name": "Scroll Explorer", + "url": "https://scrollscan.com/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, "chainId": 534352, + "displayName": "Scroll", "domainId": 534352, + "gasCurrencyCoinGeckoId": "ethereum", "name": "scroll", - "protocol": "ethereum", - "displayName": "Scroll", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://scroll.blockpi.network/v1/rpc/public" - }, - { - "http": "https://scroll-mainnet.public.blastapi.io" } ], - "blockExplorers": [ - { - "name": "Scroll Explorer", - "url": "https://scrollscan.com/", - "apiUrl": "https://api.scrollscan.com/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 3 - }, "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", @@ -696,11 +852,54 @@ "protocolFee": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "validatorAnnounce": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "routingIsmFactory": "0xe03dad16074BC5EEA9A9311257BF02Eb0B6AAA2b", + "pausableHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "fallbackRoutingHook": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", + "index": { + "from": 271840 + } + }, + "viction": { + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 0 + }, + "blockExplorers": [], + "chainId": 88, + "domainId": 88, + "displayName": "Viction", + "name": "viction", + "nativeToken": { + "decimals": 18, + "name": "Viction", + "symbol": "VIC" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://viction.blockpi.network/v1/rpc/public" + } + ], + "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "index": { - "from": 426670, - "chunk": 999 + "from": 73573878, + "chunk": 1000 } } }, "defaultRpcConsensusType": "fallback" -} +} \ No newline at end of file diff --git a/rust/config/test-sealevel-keys/test_deployer-account.json b/rust/config/test-sealevel-keys/test_deployer-account.json new file mode 100644 index 0000000000..43e9524005 --- /dev/null +++ b/rust/config/test-sealevel-keys/test_deployer-account.json @@ -0,0 +1,13 @@ +{ + "pubkey": "E9VrvAdGRvCguN2XgXsgu9PNmMM3vZsU8LSUrM68j8ty", + "account": { + "lamports": 500000000000000000, + "data": [ + "", + "base64" + ], + "owner": "11111111111111111111111111111111", + "executable": false, + "rentEpoch": 0 + } + } \ No newline at end of file diff --git a/rust/config/test-sealevel-keys/test_deployer-keypair.json b/rust/config/test-sealevel-keys/test_deployer-keypair.json new file mode 100644 index 0000000000..36e1ec6786 --- /dev/null +++ b/rust/config/test-sealevel-keys/test_deployer-keypair.json @@ -0,0 +1 @@ +[137,43,246,148,154,244,35,62,98,248,84,203,54,24,188,26,62,227,52,29,199,26,218,8,196,213,222,202,35,154,207,79,195,85,53,151,7,182,83,94,59,5,131,252,40,75,87,11,243,118,71,59,195,222,212,148,179,233,253,121,97,210,114,98] \ No newline at end of file diff --git a/rust/config/test_sealevel_config.json b/rust/config/test_sealevel_config.json new file mode 100644 index 0000000000..5eebd592a3 --- /dev/null +++ b/rust/config/test_sealevel_config.json @@ -0,0 +1,50 @@ +{ + "chains": { + "sealeveltest1": { + "name": "sealeveltest1", + "chainId": 13375, + "domainId": 13375, + "mailbox": "692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1", + "merkleTreeHook": "692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1", + "interchainGasPaymaster": "DrFtxirPPsfdY4HQiNZj2A9o4Ux7JaL3gELANgAoihhp", + "validatorAnnounce": "DH43ae1LwemXAboWwSh8zc9pG8j72gKUEXNi57w8fEnn", + "protocol": "sealevel", + "blocks": { + "reorgPeriod": 0, + "confirmations": 0 + }, + "rpcUrls": [ + { + "http": "http://localhost:8899" + } + ], + "index": { + "from": 1, + "mode": "sequence" + } + }, + "sealeveltest2": { + "name": "sealeveltest2", + "chainId": 13376, + "domainId": 13376, + "mailbox": "9tCUWNjpqcf3NUSrtp7vquYVCwbEByvLjZUrhG5dgvhj", + "merkleTreeHook": "9tCUWNjpqcf3NUSrtp7vquYVCwbEByvLjZUrhG5dgvhj", + "interchainGasPaymaster": "G5rGigZBL8NmxCaukK2CAKr9Jq4SUfAhsjzeri7GUraK", + "validatorAnnounce": "3Uo5j2Bti9aZtrDqJmAyuwiFaJFPFoNL5yxTpVCNcUhb", + "protocol": "sealevel", + "blocks": { + "reorgPeriod": 0, + "confirmations": 0 + }, + "rpcUrls": [ + { + "http": "http://localhost:8899" + } + ], + "index": { + "from": 1, + "mode": "sequence" + } + } + } +} diff --git a/rust/config/testnet4_config.json b/rust/config/testnet4_config.json index 12621e6035..15da34b729 100644 --- a/rust/config/testnet4_config.json +++ b/rust/config/testnet4_config.json @@ -1,46 +1,45 @@ { "chains": { "alfajores": { + "blockExplorers": [ + { + "apiUrl": "https://api-alfajores.celoscan.io/api", + "family": "etherscan", + "name": "CeloScan", + "url": "https://alfajores.celoscan.io" + }, + { + "apiUrl": "https://explorer.celo.org/alfajores/api", + "family": "blockscout", + "name": "Blockscout", + "url": "https://explorer.celo.org/alfajores" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 0 + }, "chainId": 44787, + "displayName": "Alfajores", "domainId": 44787, + "isTestnet": true, "name": "alfajores", - "protocol": "ethereum", - "displayName": "Alfajores", "nativeToken": { "decimals": 18, "name": "CELO", "symbol": "CELO" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://alfajores-forno.celo-testnet.org" } ], - "blockExplorers": [ - { - "name": "CeloScan", - "url": "https://alfajores.celoscan.io", - "apiUrl": "https://api-alfajores.celoscan.io/api", - "family": "etherscan" - }, - { - "name": "Blockscout", - "url": "https://explorer.celo.org/alfajores", - "apiUrl": "https://explorer.celo.org/alfajores/api", - "family": "blockscout" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 5 - }, - "isTestnet": true, "merkleRootMultisigIsmFactory": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", "messageIdMultisigIsmFactory": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", "aggregationIsmFactory": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", "aggregationHookFactory": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", - "routingIsmFactory": "0x37308d498bc7B0f002cb02Cf8fA01770dC2169c8", "proxyAdmin": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "validatorAnnounce": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", @@ -49,257 +48,98 @@ "interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297", "aggregationHook": "0xdBabD76358897E68E4964647C1fb8Bf524f5EFdB", "protocolFee": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", - "defaultIsm": "0xFBb1D475d2275D4643B6ba9Dae16f2F5465F9436", - "basegoerli": { - "MERKLE_ROOT_MULTISIG": "0x79c7799a9E686Ed93EEa208B67a2D7E93852F182", - "LEGACY_MULTISIG": "0x2c940Cfe1Fa2bdf2F2746ba52acDf2f0106c3cf1" - }, - "fuji": { - "MERKLE_ROOT_MULTISIG": "0x3c9eD95aD9a3613a7c036F44fB6745052861f74C", - "LEGACY_MULTISIG": "0x8d3cf154259E3aF61BA8F62410d9D0E644c6A19f" - }, - "mumbai": { - "MERKLE_ROOT_MULTISIG": "0x3e844a5dA9247756F0182D652680065502BCB078", - "LEGACY_MULTISIG": "0x5aABB6B06112FC5Bb9f7e1BC8Ec17c06B01fb6Ed" - }, - "bsctestnet": { - "MERKLE_ROOT_MULTISIG": "0x6b6bEF043905810994a60Cd08F7CdF5BB798f19c", - "LEGACY_MULTISIG": "0xf75B0F727f1dea2FA491caD2Faf8f3B04B705294" - }, - "goerli": { - "MERKLE_ROOT_MULTISIG": "0x2aEEc24F5997D7C19833Bf9f520b1e6c0Ef1Eda5", - "LEGACY_MULTISIG": "0x62f6EfC5a7A978821a5111261902b0A218ABF99F" - }, - "scrollsepolia": { - "MERKLE_ROOT_MULTISIG": "0x0caB24DBBDcA1E1bc27dd33481c09d9b306AE4a1", - "LEGACY_MULTISIG": "0xf8afa14F1cd8600d802C5a82A4406ca83629FC23" - }, - "sepolia": { - "MERKLE_ROOT_MULTISIG": "0x2183183bdc371c67302097DC60952e6E99484720", - "LEGACY_MULTISIG": "0x631749C86E90Cea9cF3Fb7686eBb6E80EfC9064c" - }, - "moonbasealpha": { - "MERKLE_ROOT_MULTISIG": "0x4A0eFE3CCE8DEDAD4faC8A8521F920F5C2f553fC", - "LEGACY_MULTISIG": "0x19eeB6f283aBAb2A9A70a11C2a8972D416aA2af9" - }, - "optimismgoerli": { - "MERKLE_ROOT_MULTISIG": "0x5E81d0bc59A632f319C75E339874209a29A0D9D2", - "LEGACY_MULTISIG": "0x533fb8cAb191B7094C545A191Ac770deBb1B8EEc" - }, - "arbitrumgoerli": { - "MERKLE_ROOT_MULTISIG": "0xf32f54Ec925AFf962ad164f94E7437E288901015", - "LEGACY_MULTISIG": "0x6BbF423f3742cB6594f9B9D844e780381aa4Ad91" - }, - "polygonzkevmtestnet": { - "MERKLE_ROOT_MULTISIG": "0xe48BDf85C269603AcB79444D874bb80DFDd93FC5", - "LEGACY_MULTISIG": "0x17B141F3278624B9882e275B8D1aC6a310afCCbe" - }, - "ROUTING": "0x2b5D84351aA22d860CE206EB79089F9bE8050890", - "AGGREGATION": "0x6f36a2dCC2eE58eB6b4B43330788Baa350F952EC", - "fallbackRoutingHook": "0xE1386148385275A27D29fC39Bd58a969CD5dCAF0", + "fallbackRoutingHook": "0x3528B1aeF3a3d29E0eae90ad777A2b4A6a48aC3F", + "testRecipient": "0x6489d13AcAd3B8dce4c5B31f375DE4f9451E7b38", + "testTokenRecipient": "0x92dC0a76452a9D9358D2d2dEd8CddA209DF67c45", + "routingIsmFactory": "0x30d9A03762431F8A917a0C469E7A62Bf55092Ca6", "index": { - "from": 20566929 + "from": 20231908 } }, "arbitrumgoerli": { + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, "chainId": 421613, - "domainId": 421613, - "name": "arbitrumgoerli", - "protocol": "ethereum", "displayName": "Arbitrum Goerli", "displayNameShort": "Arb. Goerli", + "domainId": 421613, + "isTestnet": true, + "name": "arbitrumgoerli", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://goerli-rollup.arbitrum.io/rpc" } ], - "blockExplorers": [ - { - "name": "Arbiscan", - "url": "https://goerli.arbiscan.io", - "apiUrl": "https://api-goerli.arbiscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 3 - }, - "isTestnet": true, "merkleRootMultisigIsmFactory": "0x17D58eBb5Ea0E2d360c877E119FAef4C4052e6B9", "messageIdMultisigIsmFactory": "0x922CeEe9e8832a047e6aD68Df4F079F271b73Ac3", "aggregationIsmFactory": "0xC5Bb8CDD44B6c56695df45c7AA8012a97dD6ED13", "aggregationHookFactory": "0x39a8711BF44165A2292Cb5cB43229659c2Bb11c9", - "routingIsmFactory": "0x735491727b9a1206E16AF4964aF68d5BB9122333", "proxyAdmin": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "mailbox": "0x13dABc0351407d5aAa0A50003a166A73b4febfDc", "validatorAnnounce": "0x4a01EEBa1CC20F47A2e60aE4ec932051601FcB9e", - "defaultIsm": "0x8C841784947dEa42f78263D54bec15c0cF65fA22", "merkleTreeHook": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", "storageGasOracle": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", "interchainGasPaymaster": "0x76189acFA212298d7022624a4633411eE0d2f26F", "aggregationHook": "0xf852EB6b98d84A4296754043a56759a0Ae0E06df", "protocolFee": "0x0358ba0D90ED2d90fB8cBb610F27C274D8077a0B", - "fallbackRoutingHook": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f", + "fallbackRoutingHook": "0xEdA6f85f4761A1f9e42FD40CA5a4E8Ce1C764015", + "testRecipient": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", + "testTokenRecipient": "0x207db41AB053213451f1a71d936353C9056A0205", + "routingIsmFactory": "0x4D6b4fe86cA1B49ea9CcDFA92F97e4EA0C27Cef2", "index": { - "from": 50669378 + "from": 9815754 } }, - "basegoerli": { - "chainId": 84531, - "domainId": 84531, - "name": "basegoerli", - "protocol": "ethereum", - "displayName": "Base Goerli", - "nativeToken": { - "name": "Ether", - "symbol": "ETH", - "decimals": 18 - }, - "rpcUrls": [ - { - "http": "https://base-goerli.publicnode.com" - }, - { - "http": "https://goerli.base.org" - } - ], + "bsctestnet": { "blockExplorers": [ { - "name": "BaseScan", - "url": "https://goerli.basescan.org", - "apiUrl": "https://api-goerli.basescan.org/api", - "family": "etherscan" + "apiUrl": "https://api-testnet.bscscan.com/api", + "family": "etherscan", + "name": "BscScan", + "url": "https://testnet.bscscan.com" } ], "blocks": { "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 3 - }, - "isTestnet": true, - "merkleRootMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "messageIdMultisigIsmFactory": "0x54148470292C24345fb828B003461a9444414517", - "aggregationIsmFactory": "0x589C201a07c26b4725A4A829d772f24423da480B", - "aggregationHookFactory": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "routingIsmFactory": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", - "proxyAdmin": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "mailbox": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "validatorAnnounce": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "defaultIsm": "0x7147a48429D6FA06ec08Ed95b500e68356819f2b", - "merkleTreeHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "storageGasOracle": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "aggregationHook": "0x168e606fE4A9c8d7F83a3aAA132E831f153e4bAa", - "protocolFee": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "fallbackRoutingHook": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "alfajores": { - "messageIdMultisigIsm": "0xCc44a0dB101E08CB0C13f928aa8d4686042dA576", - "merkleRootMultisigIsm": "0xf113Ea1a825505840451A09113A9bb53908ED8f1", - "staticAggregationIsm": "0x3F5Bd4c5B3c0D91F11Aa7b57099fc3d71e5d26A4" - }, - "fuji": { - "messageIdMultisigIsm": "0x27F351ae5f5C0A58ea18aEAD9c1Dc07a53401721", - "merkleRootMultisigIsm": "0xefde4A00A72ef1eb08FFAd3475bCbCd6D336cA4f", - "staticAggregationIsm": "0x7A86e695a0A051Bcf1CB83128613E7561090c677" - }, - "mumbai": { - "messageIdMultisigIsm": "0x8Ce0Eda893a1727D171A55515D11420f1841549c", - "merkleRootMultisigIsm": "0x8FEc8D3efEe43Fd096F53b851c60E465A04384C7", - "staticAggregationIsm": "0xE9094c6172b905972D77FF4F5E3f28a73A6c5Dc1" - }, - "bsctestnet": { - "messageIdMultisigIsm": "0x4E7EAcA5D2d3B01005cFb0528d3c52cfF09BCA36", - "merkleRootMultisigIsm": "0x3eE9CA0355ae566A8776B41b8D5f96A996d6144e", - "staticAggregationIsm": "0xe111Ac8b252c41D787b4b68F7987B8aAAC3bc1Ce" - }, - "goerli": { - "opStackIsm": "0x14EE2f01907707Ce8d13C4F5DBC40778b5b664e0" - }, - "scrollsepolia": { - "messageIdMultisigIsm": "0xD5161cD144Ca3C88F87A1db8228D33708c5938Cc", - "merkleRootMultisigIsm": "0xEc5b9b4dc7088B450aa74994EBf65d569d027716", - "staticAggregationIsm": "0x343513A06727FE3268Ef78c3E53591fC67599Ec1" - }, - "sepolia": { - "messageIdMultisigIsm": "0x4d7C3c7592A874b8e1d6396646fC45005874beC9", - "merkleRootMultisigIsm": "0x173C1626542165A5F1B5cE20c0026e5E23ef19Ad", - "staticAggregationIsm": "0x0CAC5c5e7Ce997C39B51877E15ce18C5a9a4d00D" - }, - "moonbasealpha": { - "messageIdMultisigIsm": "0xC2A3802E2f6150a4d633b5328c6C01797177D2C0", - "merkleRootMultisigIsm": "0x31C77b1C9AD838ed75AAb535fe4fDca1b5dEfFfD", - "staticAggregationIsm": "0xEAfC371E012CC89D5a335AfF326F38b474Bc5E27" + "estimateBlockTime": 3, + "reorgPeriod": 9 }, - "optimismgoerli": { - "messageIdMultisigIsm": "0x75e1ad301B0E96f82d5a87D0Eb95cF1A250bf071", - "merkleRootMultisigIsm": "0xB98cE9298891Be9371F9b7fE021beaDBaD1189DD", - "staticAggregationIsm": "0x1629F7cf0561b7863C2E31F126Ab726Fe2bc307b" - }, - "arbitrumgoerli": { - "messageIdMultisigIsm": "0x19b34FDb98F4A6F41e803225bf4E258b7eC55876", - "merkleRootMultisigIsm": "0x93db3fA75bA99a040e6D5d3474350A0564E6E722", - "staticAggregationIsm": "0x830e7814c7C02a174208786CA65917405B845f20" - }, - "polygonzkevmtestnet": { - "messageIdMultisigIsm": "0x2c1bF9f9FE80D41Db769050Fb096be9aB41327Bc", - "merkleRootMultisigIsm": "0x8B2069eaa894995141F1879dDB612E05874F0116", - "staticAggregationIsm": "0x74bf2D0B09D371D54BD087df006786384E5525F8" - }, - "domainRoutingIsm": "0x4c8b1B480988f24b0F3602A4de641776586DbF90", - "index": { - "from": 11714799 - } - }, - "bsctestnet": { "chainId": 97, + "displayName": "BSC Testnet", "domainId": 97, + "isTestnet": true, "name": "bsctestnet", - "protocol": "ethereum", - "displayName": "BSC Testnet", "nativeToken": { "decimals": 18, "name": "BNB", "symbol": "BNB" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://bsc-testnet.publicnode.com" }, - { - "http": "https://bsc-testnet.public.blastapi.io" - }, { "http": "https://bsc-testnet.blockpi.network/v1/rpc/public" } ], - "blockExplorers": [ - { - "name": "BscScan", - "url": "https://testnet.bscscan.com", - "apiUrl": "https://api-testnet.bscscan.com/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 9, - "estimateBlockTime": 3 + "transactionOverrides": { + "gasPrice": 80000000000 }, - "isTestnet": true, "merkleRootMultisigIsmFactory": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", "messageIdMultisigIsmFactory": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", "aggregationIsmFactory": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", "aggregationHookFactory": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", - "routingIsmFactory": "0xea12ECFD1f241da323e93F12b4ed936403990190", "proxyAdmin": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", - "defaultIsm": "0xEf7cacD303D1886b3dE396B45a184d16f39248E0", "storageGasOracle": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", "interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", "aggregationHook": "0x3d675bB93250Ab7603F40cbb9194bae210784627", @@ -307,68 +147,38 @@ "mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", "merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", "validatorAnnounce": "0xf09701B0a93210113D175461b6135a96773B5465", - "alfajores": { - "MERKLE_ROOT_MULTISIG": "0x352b56D019C85B1e7Db163c08876f0E4060e0EC0", - "LEGACY_MULTISIG": "0xc9764347811A0DDB36180273ADB6226E25e8134f" - }, - "basegoerli": { - "MERKLE_ROOT_MULTISIG": "0x32e4C6C49e9a568Da17f87ee80A9542B5c3cE0bf", - "LEGACY_MULTISIG": "0x55eC2978E6c63DbF4AD2670d36539737d0341c76" - }, - "fuji": { - "MERKLE_ROOT_MULTISIG": "0x4E368F65Ef1F765acf149052A5ad656AB4ecFeaf", - "LEGACY_MULTISIG": "0xB751c8366708A8cAa8E55580A6aE2CCAeD97Ca76" - }, - "mumbai": { - "MERKLE_ROOT_MULTISIG": "0x13999996CbA4E3BB793726e6Ed66B6DD0b8c19A6", - "LEGACY_MULTISIG": "0x6Bc6514ace1edf23cea6477d3fEDA7954571940C" - }, - "goerli": { - "MERKLE_ROOT_MULTISIG": "0xD953CC38fb2FF4CDE97739A024cd7D87e398Dcaf", - "LEGACY_MULTISIG": "0x16D1B0F3B3279c2793337FE8AfCC804c639C6627" - }, - "scrollsepolia": { - "MERKLE_ROOT_MULTISIG": "0xcc7e00D24249D7BCF0aD2c79f6D90951C80CB47B", - "LEGACY_MULTISIG": "0x0521e8571a72445B8a5F758ffF2707d055C0b53F" - }, - "sepolia": { - "MERKLE_ROOT_MULTISIG": "0x16A3e41135b1339B925c6e49E64CEEadA5bAdBb7", - "LEGACY_MULTISIG": "0x5AE613e0D9aEF132f7034d6eF36b418ac9dE1f2E" - }, - "moonbasealpha": { - "MERKLE_ROOT_MULTISIG": "0x6e37E41e7E4682d0dF68E34606f3254D89B7cDD0", - "LEGACY_MULTISIG": "0x8FEA7B15d7BCA0E09cb5B3BEB1dE71738A4a92B5" - }, - "optimismgoerli": { - "MERKLE_ROOT_MULTISIG": "0xE61fF63Ff2000Da24B72f1C29209554E8Ba79171", - "LEGACY_MULTISIG": "0x363Fa03085A7D089245d0b637D7FfCF5A1aacb7F" - }, - "arbitrumgoerli": { - "MERKLE_ROOT_MULTISIG": "0x91aB65A54DaE3B71f8cE31A20B42e4f337126ffF", - "LEGACY_MULTISIG": "0x32241E9d721E0D61c82BFF037C8A6e33D6cB8F18" - }, - "polygonzkevmtestnet": { - "MERKLE_ROOT_MULTISIG": "0xdFf1FE7F04c03A8788a728E3fcAe8A50eEAd6e11", - "LEGACY_MULTISIG": "0x6528cB0B4f9065bB4562096963a6C98BC619da7e" - }, - "ROUTING": "0x5a052832973d0A988cb553C46F5CfAcA4E078c92", - "AGGREGATION": "0x16c328B3976e5624D8AC38E362574e694676Ac6b", - "fallbackRoutingHook": "0xc278DDe83018F0e8c624b208e6D9E6251d263B1d", + "fallbackRoutingHook": "0x2670ED2EC08cAd135307556685a96bD4c16b007b", + "testRecipient": "0xfbcD1c00a3d809f36cC1A15918694B17B32c0b6c", + "testTokenRecipient": "0x260f6024119549a40595d0937471e607411E8ea5", "index": { - "from": 34506952 + "from": 34323977 } }, "fuji": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.snowtrace.io/api", + "family": "etherscan", + "name": "SnowTrace", + "url": "https://testnet.snowtrace.io" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 2, + "reorgPeriod": 3 + }, "chainId": 43113, + "displayName": "Fuji", "domainId": 43113, + "isTestnet": true, "name": "fuji", - "protocol": "ethereum", - "displayName": "Fuji", "nativeToken": { "decimals": 18, "name": "Avalanche", "symbol": "AVAX" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://api.avax-test.network/ext/bc/C/rpc", @@ -377,128 +187,64 @@ } } ], - "blockExplorers": [ - { - "name": "SnowTrace", - "url": "https://testnet.snowtrace.io", - "apiUrl": "https://api-testnet.snowtrace.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 3, - "estimateBlockTime": 2 - }, - "isTestnet": true, "merkleRootMultisigIsmFactory": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", "messageIdMultisigIsmFactory": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", "aggregationIsmFactory": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", "aggregationHookFactory": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", - "routingIsmFactory": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", "proxyAdmin": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", "validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a", - "defaultIsm": "0x2d6891ac730E14521Ea0C45A00d3Eb62f2e432df", "merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", "storageGasOracle": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", "interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", "aggregationHook": "0x8E9b4006171c6B75111823e7545Ee5400CEce0B3", "protocolFee": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", - "alfajores": { - "MERKLE_ROOT_MULTISIG": "0xd8325468566964245FAdf15Ef0BD3587B598b3bc", - "LEGACY_MULTISIG": "0x04cB6d0616b0059751F2CFc0Ea7dde62d959CEEC" - }, - "basegoerli": { - "MERKLE_ROOT_MULTISIG": "0x0f56E6D62ddc37cbe2e4a8FA31dCA994c67c2A3d", - "LEGACY_MULTISIG": "0x8F2e9a004d3F0e8E1CEbE52375b44D79fB6Ff6ea" - }, - "mumbai": { - "MERKLE_ROOT_MULTISIG": "0x5C38d0d8686aAb40F81Ff5D9929bFD01f9C38899", - "LEGACY_MULTISIG": "0x4509Ee247C37738DC067b22fc84be9b8d7c18e83" - }, - "bsctestnet": { - "MERKLE_ROOT_MULTISIG": "0xA62d029c16354F5845AC3c2728C8027645f16517", - "LEGACY_MULTISIG": "0xb7FFc29CF7be4a969282AE3E171c953D43212C30" - }, - "goerli": { - "MERKLE_ROOT_MULTISIG": "0x1b50E9729155D3F7e3eE886003818D1Fa80920Fd", - "LEGACY_MULTISIG": "0x4D57430F0cb764A9835521acad3653199b039327" - }, - "scrollsepolia": { - "MERKLE_ROOT_MULTISIG": "0xA90541C64f3eDc22a47Bc28D7cfdfE17F573A434", - "LEGACY_MULTISIG": "0xc1440Dbb7FeE4179D85d9dAD598A4E78cc5E222B" - }, - "sepolia": { - "MERKLE_ROOT_MULTISIG": "0xE5dd4fDE4c99DCeB5F753B6F536E99523dA6EE06", - "LEGACY_MULTISIG": "0x4Bd1319ABc8Bf7633198A1f0e529158D91cfDa9F" - }, - "moonbasealpha": { - "MERKLE_ROOT_MULTISIG": "0x40D79400513cf7fccaE4d110d3B74c23B08337B6", - "LEGACY_MULTISIG": "0x0a2cD3A4D324A322821A829A1211a8483214ef2a" - }, - "optimismgoerli": { - "MERKLE_ROOT_MULTISIG": "0x33b1A7480EC6dB39bE5919d695d06776E3bD7fA9", - "LEGACY_MULTISIG": "0x807AEb2b851Cc843Ae3d4D6f931cAEA893698823" - }, - "arbitrumgoerli": { - "MERKLE_ROOT_MULTISIG": "0x247Dd2058D239ADC1348674BFA929F952Af331a0", - "LEGACY_MULTISIG": "0xD6e42987D0533cED33fcC9003B708bC56896C7Fe" - }, - "polygonzkevmtestnet": { - "MERKLE_ROOT_MULTISIG": "0xD02753F38198DEB05924e503867f0e7bEC04d500", - "LEGACY_MULTISIG": "0x1A28Bc9F1D65661C4338bc374D4f133cB5763c50" - }, - "ROUTING": "0xd914570ea19385334e8DAcf5F1794f75895010dF", - "AGGREGATION": "0x39202263246c48eb80879870e9c547Bc787cdfC6", - "fallbackRoutingHook": "0x50897eDCb3f1bB2A90f20DA5a8dF0e5c57A146e3", + "fallbackRoutingHook": "0xc684f7F50DB4b2563218512e021fBdd0BeD6b57E", + "testRecipient": "0x44a7e1d76fD8AfA244AdE7278336E3D5C658D398", + "testTokenRecipient": "0x9CC10c844B3Bbae2444E39991aB027C4A05D1F2e", + "routingIsmFactory": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", "index": { - "from": 27090437 + "from": 26503317 } }, "goerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://goerli.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, "chainId": 5, + "displayName": "Goerli", "domainId": 5, + "isTestnet": true, "name": "goerli", - "protocol": "ethereum", - "displayName": "Goerli", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" }, { "http": "https://rpc.ankr.com/eth_goerli" - }, - { - "http": "https://eth-goerli.public.blastapi.io" - } - ], - "blockExplorers": [ - { - "name": "Etherscan", - "url": "https://goerli.etherscan.io", - "apiUrl": "https://api-goerli.etherscan.io/api", - "family": "etherscan" } ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 2, - "estimateBlockTime": 13 - }, - "isTestnet": true, "merkleRootMultisigIsmFactory": "0x8e43aCfb338B137A3befd9b92BfD84E128adE0B8", "messageIdMultisigIsmFactory": "0xDdB54502A8e2a31C48148C62A8a9E83a693d6173", "aggregationIsmFactory": "0x8a176773d54292123d271FA0B9C7C8Def4c3a31b", "aggregationHookFactory": "0x6bc243963f80AEa80948e8538bB114d4122DD9c5", - "routingIsmFactory": "0xd16c3f34d6A2e62185aC61f76F83D3AA1E969018", "proxyAdmin": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", - "defaultIsm": "0x8BbdB0023ef47992b3E83E2B1B290D72A7477EfE", "storageGasOracle": "0xeC34c715ee6d050b2172E8aF650Db779561266C1", "interchainGasPaymaster": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", "aggregationHook": "0x2dF77b3efe9B8f9aEDf7bFC86f40B048178d8116", @@ -506,147 +252,39 @@ "merkleTreeHook": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", "mailbox": "0x49cfd6Ef774AcAb14814D699e3F7eE36Fdfba932", "validatorAnnounce": "0x3c182AD9cA8A71bc107Ef440C2667E8360e1158E", - "alfajores": { - "messageIdMultisigIsm": "0x4683D18bD896acf67bC022f2dc0Cf9913E83a8C2", - "merkleRootMultisigIsm": "0xA09db9436C89376FbDCC731c61f3e96194d77549", - "staticAggregationIsm": "0xc80989C697d2dB54827293E9399461F5E17b50AE" - }, - "basegoerli": { - "messageIdMultisigIsm": "0x6EF750Fef341239fa0Fd7c9081508590A9527C69", - "merkleRootMultisigIsm": "0x8BB1F2bc7dd305440Dfe4E91c003510ef14044Ba", - "staticAggregationIsm": "0x110C79D5104f3d6a0291397d2f2896f10157C0f6" - }, - "fuji": { - "messageIdMultisigIsm": "0x44B0757b1C9512cff0C51089c7D5094C47D9316E", - "merkleRootMultisigIsm": "0xf3C288C5a4E17DCE46d06257B36ec7f4A82C1aF9", - "staticAggregationIsm": "0x0DB339E394665A7bDCb621B8f05A6dE746E4d28D" - }, - "mumbai": { - "MERKLE_ROOT_MULTISIG": "0x374A6953e95ADdC76c2d87cC7B89C3227DAf1Fd9", - "LEGACY_MULTISIG": "0x5d05c8152667f73e219CBb37531425C09e894de2", - "messageIdMultisigIsm": "0x374A6953e95ADdC76c2d87cC7B89C3227DAf1Fd9", - "merkleRootMultisigIsm": "0x5d05c8152667f73e219CBb37531425C09e894de2", - "staticAggregationIsm": "0x582643Ca4235195167b2195A4a8F71BDe56b8A1F" - }, - "bsctestnet": { - "MERKLE_ROOT_MULTISIG": "0x72a6Fcd41b68e6FAE71A5d0F21e574F3e6Ec5B9D", - "LEGACY_MULTISIG": "0x0F7C158d6afea27987f655A2d464E16fAe2aD8c7", - "messageIdMultisigIsm": "0x72a6Fcd41b68e6FAE71A5d0F21e574F3e6Ec5B9D", - "merkleRootMultisigIsm": "0x0F7C158d6afea27987f655A2d464E16fAe2aD8c7", - "staticAggregationIsm": "0x444a6FAc09220024f9D65943598829Fd4BD38bD7" - }, - "scrollsepolia": { - "MERKLE_ROOT_MULTISIG": "0x89c32A1Ca1Ae39886b18B0466dA5C97a6e031ca2", - "LEGACY_MULTISIG": "0x050798aFB2EF5A847f6A77764F19D207086bdDD3", - "messageIdMultisigIsm": "0x89c32A1Ca1Ae39886b18B0466dA5C97a6e031ca2", - "merkleRootMultisigIsm": "0x050798aFB2EF5A847f6A77764F19D207086bdDD3", - "staticAggregationIsm": "0x4A58088ff4B77dCcf8678A04C945CC552D6e65D6" - }, - "sepolia": { - "MERKLE_ROOT_MULTISIG": "0xf45819C46A54cdD816DC8c9EE580712e76eaAc0C", - "LEGACY_MULTISIG": "0xD22fddaB4595a7C0f78Fa3d20cc9485b19Ee861d", - "messageIdMultisigIsm": "0xf45819C46A54cdD816DC8c9EE580712e76eaAc0C", - "merkleRootMultisigIsm": "0xD22fddaB4595a7C0f78Fa3d20cc9485b19Ee861d", - "staticAggregationIsm": "0xE0593C964e8BA2481d0f23BAF27e36DC7CCd9F13" - }, - "moonbasealpha": { - "MERKLE_ROOT_MULTISIG": "0x9B04e5971738c427c1b0660e89859BD4164B0d67", - "LEGACY_MULTISIG": "0xD3Ad23079611e2cc306E8a60ddFdAD250512d43F", - "messageIdMultisigIsm": "0x9B04e5971738c427c1b0660e89859BD4164B0d67", - "merkleRootMultisigIsm": "0xD3Ad23079611e2cc306E8a60ddFdAD250512d43F", - "staticAggregationIsm": "0xc3AF7BC41D75909cFA34e424Fd505b7121C0CF90" - }, - "optimismgoerli": { - "MERKLE_ROOT_MULTISIG": "0x432B0ad68cbA0DFf644F30CC8b3275663Cb692E8", - "LEGACY_MULTISIG": "0xA50e98B2d52d4c9B242bD86f1B5C54D7E33190a6", - "messageIdMultisigIsm": "0x432B0ad68cbA0DFf644F30CC8b3275663Cb692E8", - "merkleRootMultisigIsm": "0xA50e98B2d52d4c9B242bD86f1B5C54D7E33190a6", - "staticAggregationIsm": "0x5d77806Fde4BbA8a665720d528CEACBb694B43b2" - }, - "arbitrumgoerli": { - "MERKLE_ROOT_MULTISIG": "0x07733788489efD7F10533ab889144A7C67F6d9EF", - "LEGACY_MULTISIG": "0x44D117A356AfFE5C67D4A53b3eC1Ded1260Bd20d", - "messageIdMultisigIsm": "0x07733788489efD7F10533ab889144A7C67F6d9EF", - "merkleRootMultisigIsm": "0x44D117A356AfFE5C67D4A53b3eC1Ded1260Bd20d", - "staticAggregationIsm": "0x7F28dFed655a4C1a7AeC911B983Bc68EF9195D90" - }, - "polygonzkevmtestnet": { - "MERKLE_ROOT_MULTISIG": "0x070eDaC389b85aEe38507339Cd80aeBd95b1cb22", - "LEGACY_MULTISIG": "0x5c7e4Eb938DB4f1dE746aBb01eC1228EC38Ed3bd", - "messageIdMultisigIsm": "0x070eDaC389b85aEe38507339Cd80aeBd95b1cb22", - "merkleRootMultisigIsm": "0x5c7e4Eb938DB4f1dE746aBb01eC1228EC38Ed3bd", - "staticAggregationIsm": "0xE2fc443F458B6DbdD8987CDb6830d4F9a1AfDFeb" - }, - "ROUTING": "0x656cC76A0D05bC3F40c11DEBe0fF6EEEDabB856e", - "AGGREGATION": "0x595e45990F67A9795cE4FEB0b6ba4684c6258cD0", - "domainRoutingIsm": "0xE2401EB414B41DC2A97100f2Dab75bBcD7377119", - "opStackHook": "0xce59701919507F2d379270657A4e410F570aBe0D", - "fallbackRoutingHook": "0xDd66CB60D4Ffb7f0d8FB91CB1D20aBcaBC82900a", + "fallbackRoutingHook": "0xd9E546CBB9577dC6346EdB40b24E86aE52487ab8", + "testRecipient": "0x4fC0Ac163eFFEb7890937cB89275B2C231880F22", + "testTokenRecipient": "0xd8958706B33E20C88679a22203F0AFa6158c834d", + "routingIsmFactory": "0xeB998dC788E2c1e772d198d32e50890544776e75", "index": { - "from": 9954053 + "from": 9890775 } }, - "moonbasealpha": { - "chainId": 1287, - "domainId": 1287, - "name": "moonbasealpha", - "protocol": "ethereum", - "displayName": "Moonbase Alpha", - "displayNameShort": "Moonbase", - "nativeToken": { - "decimals": 18, - "name": "DEV", - "symbol": "DEV" - }, - "rpcUrls": [ - { - "http": "https://rpc.api.moonbase.moonbeam.network" - } - ], + "mumbai": { "blockExplorers": [ { - "name": "MoonScan", - "url": "https://moonbase.moonscan.io", - "apiUrl": "https://api-moonbase.moonscan.io/api", - "family": "etherscan" + "apiUrl": "https://api-testnet.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://mumbai.polygonscan.com" } ], "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 12 + "confirmations": 3, + "estimateBlockTime": 5, + "reorgPeriod": 32 }, - "isTestnet": true, - "merkleRootMultisigIsmFactory": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", - "messageIdMultisigIsmFactory": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", - "aggregationIsmFactory": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", - "aggregationHookFactory": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", - "routingIsmFactory": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", - "proxyAdmin": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", - "mailbox": "0x76189acFA212298d7022624a4633411eE0d2f26F", - "defaultIsm": "0x367241b198C29C6a8B2aa0c9D8350B6F81C2Abb3", - "merkleTreeHook": "0x155B1CD2f7Cbc58d403B9BE341FaB6CD77425175", - "storageGasOracle": "0x62fA20dE68Dbe425f0bc474b12235a4F8449E608", - "interchainGasPaymaster": "0x92F05669A354a032A84FcfABfD13beE1aBc5bFd0", - "aggregationHook": "0xaA9d918C49Cea0D2a877252aFb7976B6e3A48623", - "protocolFee": "0xe2A73F106902983452713F24Bd019F6eb8712986", - "validatorAnnounce": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", - "fallbackRoutingHook": "0x6c9EB73793F9Cd535DB1bF86dC307f6d899b2fE3", - "index": { - "from": 5355188 - } - }, - "mumbai": { "chainId": 80001, + "displayName": "Mumbai", "domainId": 80001, + "isTestnet": true, "name": "mumbai", - "protocol": "ethereum", - "displayName": "Mumbai", "nativeToken": { + "decimals": 18, "name": "MATIC", - "symbol": "MATIC", - "decimals": 18 + "symbol": "MATIC" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://rpc.ankr.com/polygon_mumbai", @@ -654,25 +292,8 @@ "maxBlockRange": 10000, "minBlockNumber": 22900000 } - }, - { - "http": "https://matic-mumbai.chainstacklabs.com" - } - ], - "blockExplorers": [ - { - "name": "PolygonScan", - "url": "https://mumbai.polygonscan.com", - "apiUrl": "https://api-testnet.polygonscan.com/api", - "family": "etherscan" } ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 32, - "estimateBlockTime": 5 - }, - "isTestnet": true, "transactionOverrides": { "maxFeePerGas": 150000000000, "maxPriorityFeePerGas": 40000000000 @@ -681,8 +302,6 @@ "messageIdMultisigIsmFactory": "0x23c2483ab814177bA79DCDCb5dFA1B105387AAB1", "aggregationIsmFactory": "0x54b0d9AB6a99E9C9425D20fa4D9eE9dbf067e886", "aggregationHookFactory": "0x54CA9De95B37365909364672D363D2ecFC4e1Af4", - "routingIsmFactory": "0x276C07098879f44F6C4a6ab91B6AAca6a56AD4B1", - "defaultIsm": "0xCbDc6B43fcC9465E18E0eE925170f6F2893625b8", "merkleTreeHook": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", "proxyAdmin": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", "storageGasOracle": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", @@ -691,311 +310,260 @@ "protocolFee": "0x244d1F7e30Be144A87602905baBF86630e8f39DC", "mailbox": "0x2d1889fe5B092CD988972261434F7E5f26041115", "validatorAnnounce": "0x99303EFF09332cDd93E8BC8b2F07b2416e4501e5", - "alfajores": { - "MERKLE_ROOT_MULTISIG": "0x2ab7f5DeC6f8Ef411315487c23a0FD1955824274", - "LEGACY_MULTISIG": "0x46c3f1a2734568Ab4582E5e990Be37e9A90c8d5C" - }, - "basegoerli": { - "MERKLE_ROOT_MULTISIG": "0xcA58F63034D79d00742153636f40975616996569", - "LEGACY_MULTISIG": "0x256578935Ea39D5B6eD49722C20D6c7734c17442" - }, - "fuji": { - "MERKLE_ROOT_MULTISIG": "0xbd3Cd908B44b1AB4A5c9A2E2Abc567dbd3265BaF", - "LEGACY_MULTISIG": "0xB24FadDB99664D85755a4ee5402Cce2f936e2A85" - }, - "bsctestnet": { - "MERKLE_ROOT_MULTISIG": "0x20fC9FF58AA29FB015195735Cb87999D1169acA7", - "LEGACY_MULTISIG": "0x1A445E9f7f5E6230A11a960E1d77af94ec1Dc70B" - }, - "goerli": { - "MERKLE_ROOT_MULTISIG": "0x94AfA49591B42C681CaFcBA9C1deb6d394dd358c", - "LEGACY_MULTISIG": "0x0025156297d59772cc3836EB15BcEb7b65bF2Ac0" - }, - "scrollsepolia": { - "MERKLE_ROOT_MULTISIG": "0xf90Ad611612d3D7f45C1EB525e2f487373286717", - "LEGACY_MULTISIG": "0x00a20F2637437151cBDB5DD8e80672458bcCAE68" - }, - "sepolia": { - "MERKLE_ROOT_MULTISIG": "0x4234b78713bB6623b123CdBC4E163F497643D2b1", - "LEGACY_MULTISIG": "0x22A56d69B838897dacA4f87d6cC9c602de6bFaDC" - }, - "moonbasealpha": { - "MERKLE_ROOT_MULTISIG": "0xDbb5fc87d9CA83dE79E111acFb96882A70AE490c", - "LEGACY_MULTISIG": "0xe32F2c4129FeA483c55241Ab11413e0E9F38c716" - }, - "optimismgoerli": { - "MERKLE_ROOT_MULTISIG": "0xeD282a84F9cd27A827fF07D6894FE9dc63a64D63", - "LEGACY_MULTISIG": "0x45B1784587aCE88E534588e8d43E1A3282ecBA8b" - }, - "arbitrumgoerli": { - "MERKLE_ROOT_MULTISIG": "0x75204D086D263d475A6390b476126B2BeB6c37A7", - "LEGACY_MULTISIG": "0x1004f5636f3348bbAae626cAf5b6578f2f7e0425" - }, - "polygonzkevmtestnet": { - "MERKLE_ROOT_MULTISIG": "0xBae6ED82c61C1aa9a0df9741193aE5a8eE84fB28", - "LEGACY_MULTISIG": "0x2487E9d66E62B3de58C246b3e1d588f41b39d433" - }, - "ROUTING": "0xBAad51c021e4bb94b9a10b1fC82b773Ca9e35D30", - "AGGREGATION": "0xE794ff773fcd25ad43749CbCAE8e69f7f64810EF", - "fallbackRoutingHook": "0xFA005A892EbDACFcc3f1EF0111A7406c779c3647", + "fallbackRoutingHook": "0x31191BA83143b4745745389fEe64990c65F36829", + "testRecipient": "0xF45A4D54223DA32bf7b5D43a9a460Ef3C94C713B", + "testTokenRecipient": "0x57d098e6952B6C1c85Ce0B68C9Deada3dCf7D05A", + "routingIsmFactory": "0x832Ea28749C93C05E5AaF8207E4e61Bd56aE3877", "index": { - "from": 41618135 + "from": 40879305 } }, "optimismgoerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli-optimism.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://goerli-optimism.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, "chainId": 420, - "domainId": 420, - "name": "optimismgoerli", - "protocol": "ethereum", "displayName": "Optimism Goerli", "displayNameShort": "Opt. Goerli", + "domainId": 420, + "isTestnet": true, + "name": "optimismgoerli", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://goerli.optimism.io" } ], - "blockExplorers": [ - { - "name": "Etherscan", - "url": "https://goerli-optimism.etherscan.io", - "apiUrl": "https://api-goerli-optimism.etherscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 3 - }, - "isTestnet": true, "merkleRootMultisigIsmFactory": "0xAbC25d7daDD748948F5cC912A807b0f8FcBb56a9", "messageIdMultisigIsmFactory": "0x7868B6026E36C4b6E2ca6a0CaBDb1A6D0CcC443B", "aggregationIsmFactory": "0xf666A33C451E8371907aD22dd545E1678fCa1582", "aggregationHookFactory": "0x00cE81F7B02e0673815a8b0A54e62AeabDE78685", - "routingIsmFactory": "0x1807e7d67F00393a49c445E367face82D65d86c7", "proxyAdmin": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", "mailbox": "0xB5f021728Ea6223E3948Db2da61d612307945eA2", "validatorAnnounce": "0x24D31e12E4d3bc2C46C994FcE0c828b218A1aeAb", - "defaultIsm": "0x986e076aA22342282B6c2a287e9AaBC8a36161f3", "merkleTreeHook": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", "storageGasOracle": "0x4927C33299091033D935C15DE6b6073164e99BE0", "interchainGasPaymaster": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", "aggregationHook": "0x1C8A2588b8038BF9B7b1b60dD0EdF5b995A45599", "protocolFee": "0x962e30F6A3ECDA85c7fa1FcF38cD04efA991Ee20", - "fallbackRoutingHook": "0xba962f31B8DE02238fDdf8CE6a21260Af8C5Dd2F", + "fallbackRoutingHook": "0xc775c748F8c9F5443151Fd989e8B61375657474d", + "testRecipient": "0x518eA1802407b4b5AAF3aA92c1A803FfbA9FB7fe", + "testTokenRecipient": "0xB9E45eA920DE14e95A16Ed5e1275F893552f2e32", + "routingIsmFactory": "0xce8E9D701A1DFfe672c1d8dB20De2B3fa6F4437D", "index": { - "from": 16395052 + "from": 15557498 } }, - "polygonzkevmtestnet": { - "protocol": "ethereum", - "chainId": 1442, - "domainId": 1442, - "name": "polygonzkevmtestnet", - "displayName": "Polygon zkEVM Testnet", + "plumetestnet": { + "blockExplorers": [ + { + "apiUrl": "https://plume-testnet.explorer.caldera.xyz/api", + "family": "blockscout", + "name": "Plume Testnet Explorer", + "url": "https://plume-testnet.explorer.caldera.xyz" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 161221135, + "displayName": "Plume Testnet", + "domainId": 161221135, + "isTestnet": true, + "name": "plumetestnet", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { - "http": "https://rpc.public.zkevm-test.net" + "http": "https://plume-testnet.rpc.caldera.xyz/http" } ], + "transactionOverrides": { + "gasPrice": 1000000 + }, + "merkleRootMultisigIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "messageIdMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "aggregationIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "aggregationHookFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "routingIsmFactory": "0x54148470292C24345fb828B003461a9444414517", + "mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "proxyAdmin": "0x589C201a07c26b4725A4A829d772f24423da480B", + "validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", + "interchainSecurityModule": "0x7B40deb01A127E3A5eECdbCDF263e41899a90078", + "merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", + "pausableHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", + "storageGasOracle": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962", + "testRecipient": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", + "index": { + "from": 5284139 + } + }, + "polygonzkevmtestnet": { "blockExplorers": [ { - "name": "PolygonScan", - "url": "https://testnet-zkevm.polygonscan.com/", "apiUrl": "https://api-testnet-zkevm.polygonscan.com/api", - "family": "etherscan" + "family": "etherscan", + "name": "PolygonScan", + "url": "https://testnet-zkevm.polygonscan.com" } ], "blocks": { "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 3 + "estimateBlockTime": 3, + "reorgPeriod": 1 }, + "chainId": 1442, + "displayName": "Polygon zkEVM Testnet", + "displayNameShort": "ZkEvm Testnet", + "domainId": 1442, "isTestnet": true, + "name": "polygonzkevmtestnet", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.public.zkevm-test.net" + } + ], "merkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", "messageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "aggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "aggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "routingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", "proxyAdmin": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", "mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "validatorAnnounce": "0x7914A3349107A7295Bbf2374db5A973d73D1b324", - "defaultIsm": "0xfF5512D605018c185ac159B20354994BD3d75Ae2", "merkleTreeHook": "0x68311418D79fE8d96599384ED767d225635d88a8", "storageGasOracle": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", "interchainGasPaymaster": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", "aggregationHook": "0x0Fd2C6F0Ad45e766660b9fDebCF36a2AD69536D1", "protocolFee": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", - "fallbackRoutingHook": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", + "fallbackRoutingHook": "0xBF2C366530C1269d531707154948494D3fF4AcA7", + "testRecipient": "0x11918DC33E067C5DA83EEF58E50F856398b8Df4C", + "testTokenRecipient": "0x04438ef7622f5412f82915F59caD4f704C61eA48", + "routingIsmFactory": "0xc08675806BA844467E559E45E4bB59e66778bDcd", "index": { - "from": 3019086 + "from": 2846728 } }, "scrollsepolia": { + "blockExplorers": [ + { + "apiUrl": "https://api-sepolia.scrollscan.com/api", + "family": "etherscan", + "name": "Scroll Explorer", + "url": "https://sepolia.scrollscan.dev/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, "chainId": 534351, + "displayName": "Scroll Sepolia", "domainId": 534351, + "isTestnet": true, "name": "scrollsepolia", - "protocol": "ethereum", - "displayName": "Scroll Sepolia", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { "http": "https://sepolia-rpc.scroll.io" } ], - "blockExplorers": [ - { - "name": "Scroll Explorer", - "url": "https://sepolia.scrollscan.dev/", - "apiUrl": "https://api-sepolia.scrollscan.com/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 1, - "estimateBlockTime": 3 - }, - "isTestnet": true, - "transactionOverrides": { - "gasLimit": 5000000 - }, "merkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "messageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "aggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "aggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "routingIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "proxyAdmin": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", "validatorAnnounce": "0x527768930D889662Fe7ACF64294871e86e4C2381", - "defaultIsm": "0x4983DDBd279DB930cd883B44AF3f0da7567a06E6", "merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0", "storageGasOracle": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", "interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", "aggregationHook": "0x7b63Aa270335F8896717c2A809205F4b650E4268", "protocolFee": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "alfajores": { - "MERKLE_ROOT_MULTISIG": "0xd9cbF08CaC905F78d961A72716Ef8EeD3aB7e5Eb", - "LEGACY_MULTISIG": "0xdC87a06493FaDE515c623464BE3F1580E5d8EC9A", - "MESSAGE_ID_MULTISIG": "0xd9cbF08CaC905F78d961A72716Ef8EeD3aB7e5Eb" - }, - "basegoerli": { - "MERKLE_ROOT_MULTISIG": "0xCC7DfEB63bbE762D46C9bb1B27D680b226A94b23", - "LEGACY_MULTISIG": "0xE38a0156e2968d336D43efEC716De54cF243974A", - "MESSAGE_ID_MULTISIG": "0xCC7DfEB63bbE762D46C9bb1B27D680b226A94b23" - }, - "fuji": { - "MERKLE_ROOT_MULTISIG": "0x6479E7a0f62db3E4cfC16dfa4960953572cE4d91", - "LEGACY_MULTISIG": "0x8671d101e6A5430D856234811e493CbB3e08f00b", - "MESSAGE_ID_MULTISIG": "0x6479E7a0f62db3E4cfC16dfa4960953572cE4d91" - }, - "mumbai": { - "MERKLE_ROOT_MULTISIG": "0xf449003Fdb780bEFac5F946CfC12172dc1e732DF", - "LEGACY_MULTISIG": "0x8838412195Eec3B10Ad78aE3e05d7b3f29d93A4B", - "MESSAGE_ID_MULTISIG": "0xf449003Fdb780bEFac5F946CfC12172dc1e732DF" - }, - "bsctestnet": { - "MERKLE_ROOT_MULTISIG": "0x3074E40aA0C52d83FbB9Be642946f5fadA2212bf", - "LEGACY_MULTISIG": "0xB766e93CFf3B16cBfA698955E71f27a0bE0BD42e", - "MESSAGE_ID_MULTISIG": "0x3074E40aA0C52d83FbB9Be642946f5fadA2212bf" - }, - "goerli": { - "MERKLE_ROOT_MULTISIG": "0xf98a3dfCC6384b6f19F3cdfD992552CA876261f3", - "LEGACY_MULTISIG": "0xde5013E6Ace9c9Da898f98Fafd1a85547f5B893b", - "MESSAGE_ID_MULTISIG": "0xf98a3dfCC6384b6f19F3cdfD992552CA876261f3" - }, - "sepolia": { - "MERKLE_ROOT_MULTISIG": "0xD3a84669f2F1896a1E9f77c7Dfe93F32EFf0Ea4E", - "LEGACY_MULTISIG": "0x4a072E0EB5AE0087A080F7b24E56d140E2aDd3f1", - "MESSAGE_ID_MULTISIG": "0xD3a84669f2F1896a1E9f77c7Dfe93F32EFf0Ea4E" - }, - "moonbasealpha": { - "MERKLE_ROOT_MULTISIG": "0x47D285b171B5E2FDDf387EBdDcBeC58b188445De", - "LEGACY_MULTISIG": "0x3C1FA7196AAEAd718e741014B5Af7D46068335e3", - "MESSAGE_ID_MULTISIG": "0x47D285b171B5E2FDDf387EBdDcBeC58b188445De" - }, - "optimismgoerli": { - "MERKLE_ROOT_MULTISIG": "0xef317A77273BE269a2d9861F3e10808126608f4A", - "LEGACY_MULTISIG": "0x21c9A0085c58E08693bC9Bf106066B8b23c51A19", - "MESSAGE_ID_MULTISIG": "0xef317A77273BE269a2d9861F3e10808126608f4A" - }, - "arbitrumgoerli": { - "MERKLE_ROOT_MULTISIG": "0x6a3D436940697D2e1b351b366816121E9d291dDd", - "LEGACY_MULTISIG": "0xc144381ccfEc30F40f32FC18C45dD5CC20510aCd", - "MESSAGE_ID_MULTISIG": "0x6a3D436940697D2e1b351b366816121E9d291dDd" - }, - "polygonzkevmtestnet": { - "MERKLE_ROOT_MULTISIG": "0x6B4374a792DbC69c0EfAdb076190D137df7145F9", - "LEGACY_MULTISIG": "0xD9b48cD1F0B98FaEBafa5BEB1A78bdaCd5731d08", - "MESSAGE_ID_MULTISIG": "0x6B4374a792DbC69c0EfAdb076190D137df7145F9" - }, - "ROUTING": "0x30861DDAEFa5F85DAB234F4f67a0bFF0a5E02C48", - "AGGREGATION": "0x5861Baf9aa7eC91e88585aB3bd8e3183B0768437", - "fallbackRoutingHook": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", + "fallbackRoutingHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96", + "testRecipient": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", + "testTokenRecipient": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", + "routingIsmFactory": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", "index": { - "from": 1846115 + "from": 1344054 } }, "sepolia": { + "blockExplorers": [ + { + "apiUrl": "https://api-sepolia.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://sepolia.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, "chainId": 11155111, + "displayName": "Sepolia", "domainId": 11155111, + "isTestnet": true, "name": "sepolia", - "protocol": "ethereum", - "displayName": "Sepolia", "nativeToken": { + "decimals": 18, "name": "Ether", - "symbol": "ETH", - "decimals": 18 + "symbol": "ETH" }, + "protocol": "ethereum", "rpcUrls": [ { - "http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public" + "http": "https://ethereum-sepolia.publicnode.com" }, { - "http": "https://eth-sepolia.g.alchemy.com/v2/demo" + "http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public" }, { "http": "https://rpc.sepolia.org" } ], - "blockExplorers": [ - { - "name": "Etherscan", - "url": "https://sepolia.etherscan.io", - "apiUrl": "https://api-sepolia.etherscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 2, - "estimateBlockTime": 13 - }, - "isTestnet": true, - "transactionOverrides": { - "gasLimit": 1000000 - }, "merkleRootMultisigIsmFactory": "0x0a71AcC99967829eE305a285750017C4916Ca269", "messageIdMultisigIsmFactory": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", "aggregationIsmFactory": "0xC83e12EF2627ACE445C298e6eC418684918a6002", "aggregationHookFactory": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", - "routingIsmFactory": "0x3603458990BfEb30f99E61B58427d196814D8ce1", "proxyAdmin": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", - "defaultIsm": "0x97FE534674A0fA312b730C946A8A8AC9DcF90100", "storageGasOracle": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", "aggregationHook": "0xe3147d5618f5e2e100690B50ec923009a4cde14A", @@ -1003,37 +571,62 @@ "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", "validatorAnnounce": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9", - "fallbackRoutingHook": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "fallbackRoutingHook": "0x17Dc724B7a2F09141C13b8AC33B396073785c2BC", + "testRecipient": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07", + "testTokenRecipient": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A", + "routingIsmFactory": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", + "interchainSecurityModule": "0x958124472b14B7940Ed5317C44a2508791dB1d48", + "pausableHook": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd", "index": { - "from": 4558491 + "from": 4517401 + } + } + }, + "solanatestnet": { + "name": "solanatestnet", + "chainId": 1399811150, + "domainId": 1399811150, + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "protocol": "sealevel", + "blocks": { + "reorgPeriod": 0, + "confirmations": 0 + }, + "rpcUrls": [ + { + "http": "https://api.testnet.solana.com" } + ], + "index": { + "from": 1, + "mode": "sequence" + } + }, + "eclipsetestnet": { + "name": "eclipsetestnet", + "chainId": 239092742, + "domainId": 239092742, + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "protocol": "sealevel", + "blocks": { + "reorgPeriod": 0, + "confirmations": 0 }, - "neutrontestnet": { - "name": "neutrontestnet", - "domainId": "33333", - "chainId": "duality-devnet", - "mailbox": "0xdb33c78ca39541dd740659fbfd86fdd601fe7225f10f26e87595c5b8df6bdcda", - "interchainGasPaymaster": "0xae38a168ced2b1cdafd7da5de2fbd22749b1ab52e88cd0b121f750a6c20a2814", - "validatorAnnounce": "0x29d5f702a35d4135d98abf52c62ddc0cfd74001663ec14d284edff7d0419fb58", - "merkleTreeHook": "0xb6aac0c4650129ded8e645e6ef8dcbba710623b826e5a1dae34158a27247c668", - "protocol": "cosmos", - "finalityBlocks": 1, - "rpcUrls": [ - { - "http": "http://54.149.31.83:26657" - } - ], - "grpcUrl": "http://52.43.22.152:9090", - "canonicalAsset": "token", - "prefix": "dual", - "index": { - "from": 1, - "chunk": 100000 - }, - "blocks": { - "reorgPeriod": 1 + "rpcUrls": [ + { + "http": "https://testnet.dev2.eclipsenetwork.xyz" } + ], + "index": { + "from": 1, + "mode": "sequence" } }, "defaultRpcConsensusType": "fallback" -} \ No newline at end of file +} diff --git a/rust/ethers-prometheus/Cargo.toml b/rust/ethers-prometheus/Cargo.toml index 73dc90bd23..2f4344fa5d 100644 --- a/rust/ethers-prometheus/Cargo.toml +++ b/rust/ethers-prometheus/Cargo.toml @@ -14,6 +14,7 @@ async-trait.workspace = true derive-new.workspace = true derive_builder.workspace = true ethers.workspace = true +ethers-core.workspace = true futures.workspace = true log.workspace = true maplit.workspace = true @@ -26,6 +27,7 @@ tokio = { workspace = true, features = ["time", "sync", "parking_lot"] } # enable feature for this crate that is imported by ethers-rs primitive-types = { workspace = true, features = ["fp-conversion"] } +hyperlane-core = { path = "../hyperlane-core", features = ["agent", "float"] } [build-dependencies] abigen = { path = "../utils/abigen", features = ["ethers"] } diff --git a/rust/ethers-prometheus/src/json_rpc_client.rs b/rust/ethers-prometheus/src/json_rpc_client.rs index 4116d7d63a..2cc8defe9b 100644 --- a/rust/ethers-prometheus/src/json_rpc_client.rs +++ b/rust/ethers-prometheus/src/json_rpc_client.rs @@ -8,6 +8,9 @@ use async_trait::async_trait; use derive_builder::Builder; use derive_new::new; use ethers::prelude::JsonRpcClient; +use ethers_core::types::U64; +use hyperlane_core::rpc_clients::BlockNumberGetter; +use hyperlane_core::ChainCommunicationError; use maplit::hashmap; use prometheus::{CounterVec, IntCounterVec}; use serde::{de::DeserializeOwned, Serialize}; @@ -111,6 +114,16 @@ pub struct PrometheusJsonRpcClient { config: PrometheusJsonRpcClientConfig, } +impl Clone for PrometheusJsonRpcClient { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + metrics: self.metrics.clone(), + config: self.config.clone(), + } + } +} + impl Debug for PrometheusJsonRpcClient where C: JsonRpcClient, @@ -172,3 +185,34 @@ where res } } + +impl From> + for JsonRpcBlockGetter> +{ + fn from(val: PrometheusJsonRpcClient) -> Self { + JsonRpcBlockGetter::new(val) + } +} + +/// Utility struct for implementing `BlockNumberGetter` +#[derive(Debug, new)] +pub struct JsonRpcBlockGetter(T); + +/// RPC method for getting the latest block number +pub const BLOCK_NUMBER_RPC: &str = "eth_blockNumber"; + +#[async_trait] +impl BlockNumberGetter for JsonRpcBlockGetter +where + C: JsonRpcClient, +{ + async fn get_block_number(&self) -> Result { + let res = self + .0 + .request(BLOCK_NUMBER_RPC, ()) + .await + .map(|r: U64| r.as_u64()) + .map_err(Into::into)?; + Ok(res) + } +} diff --git a/rust/ethers-prometheus/src/lib.rs b/rust/ethers-prometheus/src/lib.rs index 26a50ecb8b..8cf57329f0 100644 --- a/rust/ethers-prometheus/src/lib.rs +++ b/rust/ethers-prometheus/src/lib.rs @@ -3,8 +3,6 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] -use ethers::prelude::U256; - mod contracts; pub mod json_rpc_client; @@ -19,8 +17,3 @@ pub struct ChainInfo { /// "kovan". pub name: Option, } - -/// Convert a u256 scaled integer value into the corresponding f64 value. -fn u256_as_scaled_f64(value: U256, decimals: u8) -> f64 { - value.to_f64_lossy() / (10u64.pow(decimals as u32) as f64) -} diff --git a/rust/ethers-prometheus/src/middleware/mod.rs b/rust/ethers-prometheus/src/middleware/mod.rs index 18db31d764..592e6a200f 100644 --- a/rust/ethers-prometheus/src/middleware/mod.rs +++ b/rust/ethers-prometheus/src/middleware/mod.rs @@ -4,9 +4,8 @@ use std::clone::Clone; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; -use std::future::Future; use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::time::Instant; use async_trait::async_trait; use derive_builder::Builder; @@ -14,44 +13,17 @@ use ethers::abi::AbiEncode; use ethers::prelude::*; use ethers::types::transaction::eip2718::TypedTransaction; use ethers::utils::hex::ToHex; -use log::{debug, trace, warn}; use maplit::hashmap; -use prometheus::{CounterVec, GaugeVec, IntCounterVec, IntGaugeVec}; +use prometheus::{CounterVec, IntCounterVec}; use static_assertions::assert_impl_all; use tokio::sync::RwLock; -use tokio::time::MissedTickBehavior; pub use error::PrometheusMiddlewareError; -use crate::contracts::erc_20::Erc20; -use crate::u256_as_scaled_f64; pub use crate::ChainInfo; mod error; -/// Some basic information about a token. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))] -pub struct TokenInfo { - /// Full name of the token. E.g. Ether. - pub name: String, - /// Token symbol. E.g. ETH. - pub symbol: String, - /// Number of - pub decimals: u8, -} - -impl Default for TokenInfo { - fn default() -> Self { - Self { - name: "Unknown".into(), - symbol: "".into(), - decimals: 18, - } - } -} - /// Some basic information about a wallet. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] @@ -74,16 +46,6 @@ pub struct ContractInfo { pub functions: HashMap, } -/// Expected label names for the `block_height` metric. -pub const BLOCK_HEIGHT_LABELS: &[&str] = &["chain"]; -/// Help string for the metric. -pub const BLOCK_HEIGHT_HELP: &str = "Tracks the current block height of the chain"; - -/// Expected label names for the `gas_price_gwei` metric. -pub const GAS_PRICE_GWEI_LABELS: &[&str] = &["chain"]; -/// Help string for the metric. -pub const GAS_PRICE_GWEI_HELP: &str = "Tracks the current gas price of the chain"; - /// Expected label names for the `contract_call_duration_seconds` metric. pub const CONTRACT_CALL_DURATION_SECONDS_LABELS: &[&str] = &[ "chain", @@ -148,34 +110,9 @@ pub const TRANSACTION_SEND_TOTAL_LABELS: &[&str] = /// Help string for the metric. pub const TRANSACTION_SEND_TOTAL_HELP: &str = "Number of transactions sent"; -/// Expected label names for the `wallet_balance` metric. -pub const WALLET_BALANCE_LABELS: &[&str] = &[ - "chain", - "wallet_address", - "wallet_name", - "token_address", - "token_symbol", - "token_name", -]; -/// Help string for the metric. -pub const WALLET_BALANCE_HELP: &str = "Current balance of eth and other tokens in the `tokens` map for the wallet addresses in the `wallets` set"; - /// Container for all the relevant middleware metrics. #[derive(Clone, Builder)] pub struct MiddlewareMetrics { - /// Tracks the current block height of the chain. - /// - `chain`: the chain name (or ID if the name is unknown) of the chain - /// the block number refers to. - #[builder(setter(into, strip_option), default)] - block_height: Option, - - /// Tracks the current gas price of the chain. Uses the base_fee_per_gas if - /// available or else the median of the transactions. - /// - `chain`: the chain name (or chain ID if the name is unknown) of the - /// chain the gas price refers to. - #[builder(setter(into, strip_option), default)] - gas_price_gwei: Option, - /// Contract call durations by contract and function /// - `chain`: the chain name (or chain ID if the name is unknown) of the /// chain the tx occurred on. @@ -238,24 +175,12 @@ pub struct MiddlewareMetrics { /// - `txn_status`: `dispatched`, `completed`, or `failed` #[builder(setter(into, strip_option), default)] transaction_send_total: Option, - // /// Gas spent on completed transactions. // /// - `chain`: the chain name (or ID if the name is unknown) of the chain the tx occurred // on. /// - `address_from`: source address of the transaction. // /// - `address_to`: destination address of the transaction. // #[builder(setter(into, strip_option), default)] // transaction_send_gas_eth_total: Option, - /// Current balance of eth and other tokens in the `tokens` map for the - /// wallet addresses in the `wallets` set. - /// - `chain`: the chain name (or chain ID if the name is unknown) of the - /// chain the tx occurred on. - /// - `wallet_address`: Address of the wallet holding the funds. - /// - `wallet_name`: Name of the address holding the funds. - /// - `token_address`: Address of the token. - /// - `token_symbol`: Symbol of the token. - /// - `token_name`: Full name of the token. - #[builder(setter(into, strip_option), default)] - wallet_balance: Option, } /// An ethers-rs middleware that instruments calls with prometheus metrics. To @@ -273,14 +198,6 @@ pub struct PrometheusMiddleware { #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))] pub struct PrometheusMiddlewareConf { - /// The tokens to track and identifying info - #[cfg_attr(feature = "serde", serde(default))] - pub tokens: HashMap, - - /// The wallets to track and identifying info - #[cfg_attr(feature = "serde", serde(default))] - pub wallets: HashMap, - /// Contract info for more useful metrics #[cfg_attr(feature = "serde", serde(default))] pub contracts: HashMap, @@ -521,177 +438,6 @@ impl PrometheusMiddleware { conf: Arc::new(RwLock::new(conf)), } } - - /// Start tracking metrics for a new token. - pub async fn track_new_token(&self, addr: Address, info: TokenInfo) { - self.track_new_tokens([(addr, info)]).await; - } - - /// Start tacking metrics for new tokens. - pub async fn track_new_tokens(&self, iter: impl IntoIterator) { - let mut data = self.conf.write().await; - for (addr, info) in iter { - data.tokens.insert(addr, info); - } - } - - /// Start tracking metrics for a new wallet. - pub async fn track_new_wallet(&self, addr: Address, info: WalletInfo) { - self.track_new_wallets([(addr, info)]).await; - } - - /// Start tracking metrics for new wallets. - pub async fn track_new_wallets(&self, iter: impl IntoIterator) { - let mut data = self.conf.write().await; - for (addr, info) in iter { - data.wallets.insert(addr, info); - } - } -} - -impl PrometheusMiddleware { - /// Start the update cycle using tokio. This must be called if you want - /// some metrics to be updated automatically. Alternatively you could call - /// update yourself. - pub fn start_updating_on_interval( - self: &Arc, - period: Duration, - ) -> impl Future + Send { - let zelf = Arc::downgrade(self); - - async move { - let mut interval = tokio::time::interval(period); - interval.set_missed_tick_behavior(MissedTickBehavior::Skip); - loop { - if let Some(zelf) = zelf.upgrade() { - zelf.update().await; - } else { - return; - } - interval.tick().await; - } - } - } -} - -impl PrometheusMiddleware { - /// Update gauges. You should submit this on a schedule to your runtime to - /// be collected once on a regular interval that ideally aligns with the - /// prometheus scrape interval. - pub fn update(&self) -> impl Future { - // all metrics are Arcs internally so just clone the ones we want to report for. - let wallet_balance = self.metrics.wallet_balance.clone(); - let block_height = self.metrics.block_height.clone(); - let gas_price_gwei = self.metrics.gas_price_gwei.clone(); - - let data_ref = self.conf.clone(); - let client = self.inner.clone(); - - async move { - let data = data_ref.read().await; - let chain = chain_name(&data.chain); - debug!("Updating metrics for chain ({chain})"); - - if block_height.is_some() || gas_price_gwei.is_some() { - Self::update_block_details(&*client, chain, block_height, gas_price_gwei).await; - } - if let Some(wallet_balance) = wallet_balance { - Self::update_wallet_balances(client.clone(), &data, chain, wallet_balance).await; - } - - // more metrics to come... - } - } - - async fn update_block_details( - client: &M, - chain: &str, - block_height: Option, - gas_price_gwei: Option, - ) { - let current_block = if let Ok(Some(b)) = client.get_block(BlockNumber::Latest).await { - b - } else { - return; - }; - - if let Some(block_height) = block_height { - let height = current_block - .number - .expect("Block number should always be Some for included blocks.") - .as_u64() as i64; - trace!("Block height for chain {chain} is {height}"); - block_height - .with(&hashmap! { "chain" => chain }) - .set(height); - } - if let Some(gas_price_gwei) = gas_price_gwei { - if let Some(london_fee) = current_block.base_fee_per_gas { - let gas = u256_as_scaled_f64(london_fee, 18) * 1e9; - trace!("Gas price for chain {chain} is {gas:.1}gwei"); - gas_price_gwei.with(&hashmap! { "chain" => chain }).set(gas); - } else { - trace!("Gas price for chain {chain} unknown, chain is pre-london"); - } - } - } - - async fn update_wallet_balances( - client: Arc, - data: &PrometheusMiddlewareConf, - chain: &str, - wallet_balance_metric: GaugeVec, - ) { - for (wallet_addr, wallet_info) in data.wallets.iter() { - let wallet_addr_str: String = wallet_addr.encode_hex(); - let wallet_name = wallet_info.name.as_deref().unwrap_or("none"); - - match client.get_balance(*wallet_addr, None).await { - Ok(balance) => { - // Okay, so the native type is not a token, but whatever, close enough. - // Note: This is ETH for many chains, but not all so that is why we use `N` and `Native` - // TODO: can we get away with scaling as 18 in all cases here? I am guessing not. - let balance = u256_as_scaled_f64(balance, 18); - trace!("Wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance is {balance} of the native currency"); - wallet_balance_metric - .with(&hashmap! { - "chain" => chain, - "wallet_address" => wallet_addr_str.as_str(), - "wallet_name" => wallet_name, - "token_address" => "none", - "token_symbol" => "Native", - "token_name" => "Native" - }).set(balance) - }, - Err(e) => warn!("Metric update failed for wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance for native currency; {e}") - } - for (token_addr, token) in data.tokens.iter() { - let token_addr_str: String = token_addr.encode_hex(); - let balance = match Erc20::new(*token_addr, client.clone()) - .balance_of(*wallet_addr) - .call() - .await - { - Ok(b) => u256_as_scaled_f64(b, token.decimals), - Err(e) => { - warn!("Metric update failed for wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance for {name}; {e}", name=token.name); - continue; - } - }; - trace!("Wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance is {balance}{}", token.symbol); - wallet_balance_metric - .with(&hashmap! { - "chain" => chain, - "wallet_address" => wallet_addr_str.as_str(), - "wallet_name" => wallet_name, - "token_address" => token_addr_str.as_str(), - "token_symbol" => token.symbol.as_str(), - "token_name" => token.symbol.as_str() - }) - .set(balance); - } - } - } } impl Debug for PrometheusMiddleware { diff --git a/rust/helm/hyperlane-agent/templates/external-secret.yaml b/rust/helm/hyperlane-agent/templates/external-secret.yaml index 98a4bc3b29..a6c82953e3 100644 --- a/rust/helm/hyperlane-agent/templates/external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/external-secret.yaml @@ -28,17 +28,25 @@ spec: {{- range .Values.hyperlane.chains }} {{- if not .disabled }} HYP_CHAINS_{{ .name | upper }}_CUSTOMRPCURLS: {{ printf "'{{ .%s_rpcs | mustFromJson | join \",\" }}'" .name }} + {{- if eq .protocol "cosmos" }} + HYP_CHAINS_{{ .name | upper }}_CUSTOMGRPCURLS: {{ printf "'{{ .%s_grpcs | mustFromJson | join \",\" }}'" .name }} + {{- end }} {{- end }} {{- end }} data: {{- /* - * For each network, load the secret in GCP secret manager with the form: environment-rpc-endpoint-network, - * and associate it with the secret key networkname_rpc. + * For each network, load the secret in GCP secret manager with the form: environment-rpc-endpoints-network, + * and associate it with the secret key networkname_rpcs. */}} {{- range .Values.hyperlane.chains }} {{- if not .disabled }} - secretKey: {{ printf "%s_rpcs" .name }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv .name }} + {{- if eq .protocol "cosmos" }} + - secretKey: {{ printf "%s_grpcs" .name }} + remoteRef: + key: {{ printf "%s-grpc-endpoints-%s" $.Values.hyperlane.runEnv .name }} + {{- end }} {{- end }} {{- end }} diff --git a/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml b/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml index 35ac56b548..c9bcd9a27d 100644 --- a/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml @@ -22,20 +22,23 @@ spec: {{- include "agent-common.labels" . | nindent 10 }} data: {{- range .Values.hyperlane.relayerChains }} - {{- if eq .signer.type "hexKey" }} + {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") }} HYP_CHAINS_{{ .name | upper }}_SIGNER_KEY: {{ printf "'{{ .%s_signer_key | toString }}'" .name }} + {{- include "agent-common.config-env-vars" (dict "config" .signer "format" "config_map" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" (.name | upper))) | nindent 8 }} {{- end }} {{- if and (eq .signer.type "aws") $.Values.hyperlane.relayer.aws }} HYP_CHAINS_{{ .name | upper }}_SIGNER_TYPE: aws HYP_CHAINS_{{ .name | upper }}_SIGNER_ID: {{ .signer.id }} HYP_CHAINS_{{ .name | upper }}_SIGNER_REGION: {{ .signer.region}} + {{- end }} + {{- end }} + {{- if .Values.hyperlane.relayer.aws }} AWS_ACCESS_KEY_ID: {{ print "'{{ .aws_access_key_id | toString }}'" }} AWS_SECRET_ACCESS_KEY: {{ print "'{{ .aws_secret_access_key | toString }}'" }} {{- end }} - {{- end }} data: {{- range .Values.hyperlane.relayerChains }} - {{- if eq .signer.type "hexKey" }} + {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") }} - secretKey: {{ printf "%s_signer_key" .name }} remoteRef: {{- if $.Values.hyperlane.relayer.usingDefaultSignerKey }} diff --git a/rust/helm/hyperlane-agent/templates/validator-configmap.yaml b/rust/helm/hyperlane-agent/templates/validator-configmap.yaml index cabed1644a..8594b96e21 100644 --- a/rust/helm/hyperlane-agent/templates/validator-configmap.yaml +++ b/rust/helm/hyperlane-agent/templates/validator-configmap.yaml @@ -8,6 +8,7 @@ metadata: data: {{- range $index, $config := .Values.hyperlane.validator.configs }} validator-{{ $index }}.env: | - {{- include "agent-common.config-env-vars" (dict "config" $config "format" "dot_env") | nindent 4 }} + {{- include "agent-common.config-env-vars" (dict "config" (get $config "chainSigner") "format" "dot_env" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" ($config.originChainName | upper))) | nindent 4 }} + {{- include "agent-common.config-env-vars" (dict "config" (omit $config "chainSigner") "format" "dot_env") | nindent 4 }} {{- end }} {{- end }} diff --git a/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml b/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml index c15081f119..61f115584d 100644 --- a/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml @@ -26,18 +26,21 @@ spec: validator-{{ $index }}.env: | {{- if eq .validator.type "hexKey" }} HYP_VALIDATOR_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }} - HYP_CHAINS_{{ .originChainName | upper }}_SIGNER_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }} {{- end }} {{- if or (eq .checkpointSyncer.type "s3") $.Values.hyperlane.aws }} AWS_ACCESS_KEY_ID={{ printf "'{{ .aws_access_key_id_%d | toString }}'" $index }} AWS_SECRET_ACCESS_KEY={{ printf "'{{ .aws_secret_access_key_%d | toString }}'" $index }} {{- end }} + + {{- if or (eq .chainSigner.type "hexKey") (eq .chainSigner.type "cosmosKey") }} + HYP_CHAINS_{{ .originChainName | upper }}_SIGNER_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }} + {{- end }} {{ $index = add1 $index }} {{- end }} data: {{ $index = 0 }} {{- range .Values.hyperlane.validator.configs }} -{{- if eq .validator.type "hexKey" }} +{{- if or (eq .validator.type "hexKey") (eq .chainSigner.type "hexKey") (eq .chainSigner.type "cosmosKey") }} - secretKey: signer_key_{{ $index }} remoteRef: key: {{ printf "%s-%s-key-%s-validator-%d" $.Values.hyperlane.context $.Values.hyperlane.runEnv .originChainName $index }} diff --git a/rust/helm/hyperlane-agent/values.yaml b/rust/helm/hyperlane-agent/values.yaml index 299cf0e63a..7b2ca9de71 100644 --- a/rust/helm/hyperlane-agent/values.yaml +++ b/rust/helm/hyperlane-agent/values.yaml @@ -113,8 +113,8 @@ hyperlane: name: '' resources: requests: - cpu: 500m - memory: 256Mi + cpu: 1000m + memory: 1024Mi config: relayChains: '' multisigCheckpointSyncer: diff --git a/rust/hyperlane-base/Cargo.toml b/rust/hyperlane-base/Cargo.toml index 02d870e644..ca27e4a771 100644 --- a/rust/hyperlane-base/Cargo.toml +++ b/rust/hyperlane-base/Cargo.toml @@ -11,17 +11,21 @@ version.workspace = true [dependencies] async-trait.workspace = true +axum.workspace = true bs58.workspace = true color-eyre = { workspace = true, optional = true } config.workspace = true convert_case.workspace = true +derive_builder.workspace = true derive-new.workspace = true ed25519-dalek.workspace = true ethers.workspace = true eyre.workspace = true fuels.workspace = true +futures.worksapce = true futures-util.workspace = true itertools.workspace = true +maplit.workspace = true paste.workspace = true prometheus.workspace = true rocksdb.workspace = true @@ -38,6 +42,8 @@ tracing-subscriber = { workspace = true, features = ["json", "ansi"] } tracing.workspace = true url.workspace = true warp.workspace = true +ya-gcp.workspace = true + backtrace = { workspace = true, optional = true } backtrace-oneline = { path = "../utils/backtrace-oneline", optional = true } @@ -50,6 +56,7 @@ hyperlane-sealevel = { path = "../chains/hyperlane-sealevel" } hyperlane-cosmos = { path = "../chains/hyperlane-cosmos"} hyperlane-test = { path = "../hyperlane-test" } + # dependency version is determined by etheres rusoto_core = "*" rusoto_kms = "*" @@ -58,7 +65,9 @@ rusoto_sts = "*" [dev-dependencies] color-eyre.workspace = true +reqwest.workspace = true tempfile.workspace = true +tracing-test.workspace = true walkdir.workspace = true [features] diff --git a/rust/hyperlane-base/src/agent.rs b/rust/hyperlane-base/src/agent.rs index 540a32254c..b11a2a00f5 100644 --- a/rust/hyperlane-base/src/agent.rs +++ b/rust/hyperlane-base/src/agent.rs @@ -1,13 +1,15 @@ use std::{env, fmt::Debug, sync::Arc}; use async_trait::async_trait; -use eyre::{Report, Result}; -use futures_util::future::select_all; +use eyre::Result; use hyperlane_core::config::*; -use tokio::task::JoinHandle; -use tracing::{debug_span, instrument::Instrumented, Instrument}; -use crate::{metrics::CoreMetrics, settings::Settings}; +use crate::{ + create_chain_metrics, + metrics::{create_agent_metrics, AgentMetrics, CoreMetrics}, + settings::Settings, + ChainMetrics, +}; /// Properties shared across all hyperlane agents #[derive(Debug)] @@ -36,13 +38,18 @@ pub trait BaseAgent: Send + Sync + Debug { type Settings: LoadableFromSettings; /// Instantiate the agent from the standard settings object - async fn from_settings(settings: Self::Settings, metrics: Arc) -> Result + async fn from_settings( + settings: Self::Settings, + metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, + ) -> Result where Self: Sized; /// Start running this agent. #[allow(clippy::async_yields_async)] - async fn run(self) -> Instrumented>>; + async fn run(self); } /// Call this from `main` to fully initialize and run the agent for its entire @@ -68,29 +75,11 @@ pub async fn agent_main() -> Result<()> { let metrics = settings.as_ref().metrics(A::AGENT_NAME)?; core_settings.tracing.start_tracing(&metrics)?; - let agent = A::from_settings(settings, metrics.clone()).await?; - metrics.run_http_server(); + let agent_metrics = create_agent_metrics(&metrics)?; + let chain_metrics = create_chain_metrics(&metrics)?; + let agent = A::from_settings(settings, metrics.clone(), agent_metrics, chain_metrics).await?; - agent.run().await.await? -} - -/// Utility to run multiple tasks and shutdown if any one task ends. -#[allow(clippy::unit_arg, unused_must_use)] -pub fn run_all( - tasks: Vec>>>, -) -> Instrumented>> { - debug_assert!(!tasks.is_empty(), "No tasks submitted"); - let span = debug_span!("run_all"); - tokio::spawn(async move { - let (res, _, remaining) = select_all(tasks).await; - - for task in remaining.into_iter() { - let t = task.into_inner(); - t.abort(); - t.await; - } - - res? - }) - .instrument(span) + // This await will never end unless a panic occurs + agent.run().await; + Ok(()) } diff --git a/rust/hyperlane-base/src/contract_sync/cursor.rs b/rust/hyperlane-base/src/contract_sync/cursor.rs deleted file mode 100644 index cbbd393dbd..0000000000 --- a/rust/hyperlane-base/src/contract_sync/cursor.rs +++ /dev/null @@ -1,605 +0,0 @@ -use std::{ - cmp::Ordering, - fmt::Debug, - ops::RangeInclusive, - sync::Arc, - time::{Duration, Instant}, -}; - -use async_trait::async_trait; -use derive_new::new; -use eyre::Result; -use hyperlane_core::{ - ChainCommunicationError, ChainResult, ContractSyncCursor, CursorAction, - HyperlaneSequenceIndexerStore, HyperlaneWatermarkedLogStore, IndexMode, Indexer, LogMeta, - SequenceIndexer, Sequenced, -}; -use tokio::time::sleep; -use tracing::{debug, warn}; - -use crate::contract_sync::eta_calculator::SyncerEtaCalculator; - -/// Time window for the moving average used in the eta calculator in seconds. -const ETA_TIME_WINDOW: f64 = 2. * 60.; - -const MAX_SEQUENCE_RANGE: u32 = 100; - -/// A struct that holds the data needed for forwards and backwards -/// sequence sync cursors. -#[derive(Debug, new)] -pub(crate) struct SequenceSyncCursor { - indexer: Arc>, - db: Arc>, - sync_state: SyncState, -} - -#[derive(Debug, new)] -pub(crate) struct SyncState { - chunk_size: u32, - /// The starting block for the cursor - start_block: u32, - /// The next block that should be indexed. - next_block: u32, - mode: IndexMode, - /// The next sequence index that the cursor is looking for. - /// In the EVM, this is used for optimizing indexing, - /// because it's cheaper to make read calls for the sequence index than - /// to call `eth_getLogs` with a block range. - /// In Sealevel, historic queries aren't supported, so the sequence field - /// is used to query storage in sequence. - next_sequence: u32, - direction: SyncDirection, -} - -impl SyncState { - async fn get_next_range( - &mut self, - max_sequence: Option, - tip: u32, - ) -> ChainResult>> { - // We attempt to index a range of blocks that is as large as possible. - let range = match self.mode { - IndexMode::Block => self.block_range(tip), - IndexMode::Sequence => { - let max_sequence = max_sequence.ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Sequence indexing requires a max sequence", - ) - })?; - if let Some(range) = self.sequence_range(max_sequence)? { - range - } else { - return Ok(None); - } - } - }; - if range.is_empty() { - return Ok(None); - } - Ok(Some(range)) - } - - fn block_range(&mut self, tip: u32) -> RangeInclusive { - let (from, to) = match self.direction { - SyncDirection::Forward => { - let from = self.next_block; - let mut to = from + self.chunk_size; - to = u32::min(to, tip); - self.next_block = to + 1; - (from, to) - } - SyncDirection::Backward => { - let to = self.next_block; - let from = to.saturating_sub(self.chunk_size); - self.next_block = from.saturating_sub(1); - (from, to) - } - }; - from..=to - } - - /// Returns the next sequence range to index. - /// - /// # Arguments - /// - /// * `tip` - The current tip of the chain. - /// * `max_sequence` - The maximum sequence that should be indexed. - /// `max_sequence` is the exclusive upper bound of the range to be indexed. - /// (e.g. `0..max_sequence`) - fn sequence_range(&mut self, max_sequence: u32) -> ChainResult>> { - let (from, to) = match self.direction { - SyncDirection::Forward => { - let sequence_start = self.next_sequence; - let mut sequence_end = sequence_start + MAX_SEQUENCE_RANGE; - if self.next_sequence >= max_sequence { - return Ok(None); - } - sequence_end = u32::min(sequence_end, max_sequence.saturating_sub(1)); - self.next_sequence = sequence_end + 1; - (sequence_start, sequence_end) - } - SyncDirection::Backward => { - let sequence_end = self.next_sequence; - let sequence_start = sequence_end.saturating_sub(MAX_SEQUENCE_RANGE); - self.next_sequence = sequence_start.saturating_sub(1); - (sequence_start, sequence_end) - } - }; - Ok(Some(from..=to)) - } -} - -impl SequenceSyncCursor { - async fn retrieve_by_sequence(&self, sequence: u32) -> Option { - self.db.retrieve_by_sequence(sequence).await.ok().flatten() - } - - async fn retrieve_log_block_number(&self, sequence: u32) -> Option { - self.db - .retrieve_log_block_number(sequence) - .await - .ok() - .flatten() - .map(|num| u32::try_from(num).unwrap()) - } - - async fn update(&mut self, logs: Vec<(T, LogMeta)>, prev_sequence: u32) -> Result<()> { - // If we found logs, but did *not* find the log we were looking for, - // we need to rewind to the block at which we found the last log. - if !logs.is_empty() - && !logs - .iter() - .any(|m| m.0.sequence() == self.sync_state.next_sequence) - { - warn!(next_sequence=?self.sync_state.next_sequence, "Target sequence not found, rewinding"); - // If the previous sequence has been synced, rewind to the block number - // at which it was dispatched. Otherwise, rewind all the way back to the start block. - if let Some(block_number) = self.retrieve_log_block_number(prev_sequence).await { - self.sync_state.next_block = block_number; - warn!(block_number, "Rewound to previous known sequenced log"); - } else { - self.sync_state.next_block = self.sync_state.start_block; - } - Ok(()) - } else { - Ok(()) - } - } -} - -/// A SequenceSyncCursor that syncs forwards in perpetuity. -pub(crate) struct ForwardSequenceSyncCursor { - cursor: SequenceSyncCursor, -} - -impl ForwardSequenceSyncCursor { - pub fn new( - indexer: Arc>, - db: Arc>, - chunk_size: u32, - start_block: u32, - next_block: u32, - mode: IndexMode, - next_sequence: u32, - ) -> Self { - Self { - cursor: SequenceSyncCursor::new( - indexer, - db, - SyncState::new( - chunk_size, - start_block, - next_block, - mode, - next_sequence, - SyncDirection::Forward, - ), - ), - } - } - - async fn get_next_range(&mut self) -> ChainResult>> { - // Check if any new logs have been inserted into the DB, - // and update the cursor accordingly. - while self - .cursor - .retrieve_by_sequence(self.cursor.sync_state.next_sequence) - .await - .is_some() - { - if let Some(block_number) = self - .cursor - .retrieve_log_block_number(self.cursor.sync_state.next_sequence) - .await - { - debug!(next_block = block_number, "Fast forwarding next block"); - // It's possible that eth_getLogs dropped logs from this block, therefore we cannot do block_number + 1. - self.cursor.sync_state.next_block = block_number; - } - debug!( - next_sequence = self.cursor.sync_state.next_sequence + 1, - "Fast forwarding next sequence" - ); - self.cursor.sync_state.next_sequence += 1; - } - - let (Some(mailbox_count), tip) = self.cursor.indexer.sequence_and_tip().await? else { - return Ok(None); - }; - let cursor_count = self.cursor.sync_state.next_sequence; - Ok(match cursor_count.cmp(&mailbox_count) { - Ordering::Equal => { - // We are synced up to the latest sequence so we don't need to index anything. - // We update our next block number accordingly. - self.cursor.sync_state.next_block = tip; - None - } - Ordering::Less => { - // The cursor is behind the mailbox, so we need to index some blocks. - self.cursor - .sync_state - .get_next_range(Some(mailbox_count), tip) - .await? - } - Ordering::Greater => { - // Providers may be internally inconsistent, e.g. RPC request A could hit a node - // whose tip is N and subsequent RPC request B could hit a node whose tip is < N. - debug!("Cursor count is greater than Mailbox count"); - None - } - }) - } -} - -#[async_trait] -impl ContractSyncCursor for ForwardSequenceSyncCursor { - async fn next_action(&mut self) -> ChainResult<(CursorAction, Duration)> { - // TODO: Fix ETA calculation - let eta = Duration::from_secs(0); - if let Some(range) = self.get_next_range().await? { - Ok((CursorAction::Query(range), eta)) - } else { - // TODO: Define the sleep time from interval flag - Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)) - } - } - - fn latest_block(&self) -> u32 { - self.cursor.sync_state.next_block.saturating_sub(1) - } - - /// If the previous block has been synced, rewind to the block number - /// at which it was dispatched. - /// Otherwise, rewind all the way back to the start block. - async fn update(&mut self, logs: Vec<(T, LogMeta)>) -> Result<()> { - let prev_sequence = self.cursor.sync_state.next_sequence.saturating_sub(1); - // We may wind up having re-indexed logs that are previous to the sequence that we are looking for. - // We should not consider these logs when checking for continuity errors. - let filtered_logs = logs - .into_iter() - .filter(|m| m.0.sequence() >= self.cursor.sync_state.next_sequence) - .collect(); - self.cursor.update(filtered_logs, prev_sequence).await - } -} - -/// A SequenceSyncCursor that syncs backwards to sequence zero. -pub(crate) struct BackwardSequenceSyncCursor { - cursor: SequenceSyncCursor, - synced: bool, -} - -impl BackwardSequenceSyncCursor { - #[allow(clippy::too_many_arguments)] - pub fn new( - indexer: Arc>, - db: Arc>, - chunk_size: u32, - start_block: u32, - next_block: u32, - mode: IndexMode, - next_sequence: u32, - synced: bool, - ) -> Self { - Self { - cursor: SequenceSyncCursor::new( - indexer, - db, - SyncState::new( - chunk_size, - start_block, - next_block, - mode, - next_sequence, - SyncDirection::Backward, - ), - ), - synced, - } - } - - async fn get_next_range(&mut self) -> ChainResult>> { - // Check if any new logs have been inserted into the DB, - // and update the cursor accordingly. - while !self.synced { - if self - .cursor - .retrieve_by_sequence(self.cursor.sync_state.next_sequence) - .await - .is_none() - { - break; - }; - // If we found sequence zero or hit block zero, we are done rewinding. - if self.cursor.sync_state.next_sequence == 0 || self.cursor.sync_state.next_block == 0 { - self.synced = true; - break; - } - - if let Some(block_number) = self - .cursor - .retrieve_log_block_number(self.cursor.sync_state.next_sequence) - .await - { - // It's possible that eth_getLogs dropped logs from this block, therefore we cannot do block_number - 1. - self.cursor.sync_state.next_block = block_number; - } - - self.cursor.sync_state.next_sequence = - self.cursor.sync_state.next_sequence.saturating_sub(1); - } - if self.synced { - return Ok(None); - } - - // Just keep going backwards. - let (count, tip) = self.cursor.indexer.sequence_and_tip().await?; - self.cursor.sync_state.get_next_range(count, tip).await - } - - /// If the previous block has been synced, rewind to the block number - /// at which it was dispatched. - /// Otherwise, rewind all the way back to the start block. - async fn update(&mut self, logs: Vec<(T, LogMeta)>) -> Result<()> { - let prev_sequence = self.cursor.sync_state.next_sequence.saturating_add(1); - // We may wind up having re-indexed logs that are previous to the sequence that we are looking for. - // We should not consider these logs when checking for continuity errors. - let filtered_logs = logs - .into_iter() - .filter(|m| m.0.sequence() <= self.cursor.sync_state.next_sequence) - .collect(); - self.cursor.update(filtered_logs, prev_sequence).await - } -} - -#[derive(Debug)] -pub enum SyncDirection { - Forward, - Backward, -} - -/// A SequenceSyncCursor that syncs forwards in perpetuity. -pub(crate) struct ForwardBackwardSequenceSyncCursor { - forward: ForwardSequenceSyncCursor, - backward: BackwardSequenceSyncCursor, - direction: SyncDirection, -} - -impl ForwardBackwardSequenceSyncCursor { - /// Construct a new contract sync helper. - pub async fn new( - indexer: Arc>, - db: Arc>, - chunk_size: u32, - mode: IndexMode, - ) -> Result { - let (sequence, tip) = indexer.sequence_and_tip().await?; - let sequence = sequence.ok_or(ChainCommunicationError::from_other_str( - "Failed to query sequence", - ))?; - let forward_cursor = ForwardSequenceSyncCursor::new( - indexer.clone(), - db.clone(), - chunk_size, - tip, - tip, - mode, - sequence, - ); - let backward_cursor = BackwardSequenceSyncCursor::new( - indexer.clone(), - db.clone(), - chunk_size, - tip, - tip, - mode, - sequence.saturating_sub(1), - sequence == 0, - ); - Ok(Self { - forward: forward_cursor, - backward: backward_cursor, - direction: SyncDirection::Forward, - }) - } -} - -#[async_trait] -impl ContractSyncCursor for ForwardBackwardSequenceSyncCursor { - async fn next_action(&mut self) -> ChainResult<(CursorAction, Duration)> { - // TODO: Proper ETA for backwards sync - let eta = Duration::from_secs(0); - // Prioritize forward syncing over backward syncing. - if let Some(forward_range) = self.forward.get_next_range().await? { - self.direction = SyncDirection::Forward; - return Ok((CursorAction::Query(forward_range), eta)); - } - - if let Some(backward_range) = self.backward.get_next_range().await? { - self.direction = SyncDirection::Backward; - return Ok((CursorAction::Query(backward_range), eta)); - } - // TODO: Define the sleep time from interval flag - return Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)); - } - - fn latest_block(&self) -> u32 { - self.forward.cursor.sync_state.next_block.saturating_sub(1) - } - - async fn update(&mut self, logs: Vec<(T, LogMeta)>) -> Result<()> { - match self.direction { - SyncDirection::Forward => self.forward.update(logs).await, - SyncDirection::Backward => self.backward.update(logs).await, - } - } -} - -/// Tool for handling the logic of what the next block range that should be -/// queried is and also handling rate limiting. Rate limiting is automatically -/// performed by `next_action`. -pub(crate) struct RateLimitedContractSyncCursor { - indexer: Arc>, - db: Arc>, - tip: u32, - max_sequence: Option, - last_tip_update: Instant, - eta_calculator: SyncerEtaCalculator, - sync_state: SyncState, -} - -impl RateLimitedContractSyncCursor { - /// Construct a new contract sync helper. - pub async fn new( - indexer: Arc>, - db: Arc>, - chunk_size: u32, - initial_height: u32, - mode: IndexMode, - ) -> Result { - let (max_sequence, tip) = indexer.sequence_and_tip().await?; - Ok(Self { - indexer, - db, - tip, - max_sequence, - last_tip_update: Instant::now(), - eta_calculator: SyncerEtaCalculator::new(initial_height, tip, ETA_TIME_WINDOW), - sync_state: SyncState::new( - chunk_size, - initial_height, - initial_height, - mode, - Default::default(), - // The rate limited cursor currently only syncs in the forward direction. - SyncDirection::Forward, - ), - }) - } - - /// Wait based on how close we are to the tip and update the tip, - /// i.e. the highest block we may scrape. - async fn get_rate_limit(&mut self) -> ChainResult> { - if self.sync_state.next_block + self.sync_state.chunk_size < self.tip { - // If doing the full chunk wouldn't exceed the already known tip we do not need to rate limit. - Ok(None) - } else { - // We are within one chunk size of the known tip. - // If it's been fewer than 30s since the last tip update, sleep for a bit until we're ready to fetch the next tip. - if let Some(sleep_time) = - Duration::from_secs(30).checked_sub(self.last_tip_update.elapsed()) - { - return Ok(Some(sleep_time)); - } - match self.indexer.get_finalized_block_number().await { - Ok(tip) => { - // we retrieved a new tip value, go ahead and update. - self.last_tip_update = Instant::now(); - self.tip = tip; - Ok(None) - } - Err(e) => { - warn!(error = %e, "Failed to get next block range because we could not get the current tip"); - // we are failing to make a basic query, we should wait before retrying. - sleep(Duration::from_secs(10)).await; - Err(e) - } - } - } - } - - fn sync_end(&self) -> ChainResult { - match self.sync_state.mode { - IndexMode::Block => Ok(self.tip), - IndexMode::Sequence => { - self.max_sequence - .ok_or(ChainCommunicationError::from_other_str( - "Sequence indexing requires a max sequence", - )) - } - } - } - - fn sync_position(&self) -> u32 { - match self.sync_state.mode { - IndexMode::Block => self.sync_state.next_block, - IndexMode::Sequence => self.sync_state.next_sequence, - } - } - - fn sync_step(&self) -> u32 { - match self.sync_state.mode { - IndexMode::Block => self.sync_state.chunk_size, - IndexMode::Sequence => MAX_SEQUENCE_RANGE, - } - } -} - -#[async_trait] -impl ContractSyncCursor for RateLimitedContractSyncCursor -where - T: Send + Debug + 'static, -{ - async fn next_action(&mut self) -> ChainResult<(CursorAction, Duration)> { - let sync_end = self.sync_end()?; - let to = u32::min(sync_end, self.sync_position() + self.sync_step()); - let from = self.sync_position(); - let eta = if to < sync_end { - self.eta_calculator.calculate(from, sync_end) - } else { - Duration::from_secs(0) - }; - - let rate_limit = self.get_rate_limit().await?; - if let Some(rate_limit) = rate_limit { - return Ok((CursorAction::Sleep(rate_limit), eta)); - } - let (max_sequence, tip) = self.indexer.sequence_and_tip().await?; - self.tip = tip; - self.max_sequence = max_sequence; - if let Some(range) = self.sync_state.get_next_range(max_sequence, tip).await? { - return Ok((CursorAction::Query(range), eta)); - } - - // TODO: Define the sleep time from interval flag - Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)) - } - - fn latest_block(&self) -> u32 { - self.sync_state.next_block.saturating_sub(1) - } - - async fn update(&mut self, _: Vec<(T, LogMeta)>) -> Result<()> { - // Store a relatively conservative view of the high watermark, which should allow a single watermark to be - // safely shared across multiple cursors, so long as they are running sufficiently in sync - self.db - .store_high_watermark(u32::max( - self.sync_state.start_block, - self.sync_state - .next_block - .saturating_sub(self.sync_state.chunk_size), - )) - .await?; - Ok(()) - } -} diff --git a/rust/hyperlane-base/src/contract_sync/cursors/mod.rs b/rust/hyperlane-base/src/contract_sync/cursors/mod.rs new file mode 100644 index 0000000000..c9b8d7a015 --- /dev/null +++ b/rust/hyperlane-base/src/contract_sync/cursors/mod.rs @@ -0,0 +1,278 @@ +use std::{ + fmt::Debug, + ops::RangeInclusive, + sync::Arc, + time::{Duration, Instant}, +}; + +use async_trait::async_trait; +use derive_new::new; +use eyre::Result; +use hyperlane_core::{ + ChainCommunicationError, ContractSyncCursor, CursorAction, HyperlaneWatermarkedLogStore, + IndexMode, Indexer, LogMeta, SequenceAwareIndexer, +}; +use tokio::time::sleep; +use tracing::warn; + +use crate::contract_sync::eta_calculator::SyncerEtaCalculator; + +pub(crate) mod sequence_aware; + +pub(crate) use sequence_aware::{ + ForwardBackwardSequenceAwareSyncCursor, ForwardSequenceAwareSyncCursor, +}; + +/// Time window for the moving average used in the eta calculator in seconds. +const ETA_TIME_WINDOW: f64 = 2. * 60.; + +const MAX_SEQUENCE_RANGE: u32 = 20; + +#[derive(Debug, new)] +pub(crate) struct SyncState { + chunk_size: u32, + /// The starting block for the cursor + start_block: u32, + /// The next block that should be indexed. + next_block: u32, + mode: IndexMode, + /// The next sequence index that the cursor is looking for. + /// In the EVM, this is used for optimizing indexing, + /// because it's cheaper to make read calls for the sequence index than + /// to call `eth_getLogs` with a block range. + /// In Sealevel, historic queries aren't supported, so the sequence field + /// is used to query storage in sequence. + next_sequence: u32, + direction: SyncDirection, +} + +impl SyncState { + async fn get_next_range( + &mut self, + max_sequence: Option, + tip: u32, + ) -> Result>> { + // We attempt to index a range of blocks that is as large as possible. + let range = match self.mode { + IndexMode::Block => self.block_range(tip), + IndexMode::Sequence => { + let max_sequence = max_sequence.ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Sequence indexing requires a max sequence", + ) + })?; + if let Some(range) = self.sequence_range(max_sequence)? { + range + } else { + return Ok(None); + } + } + }; + if range.is_empty() { + return Ok(None); + } + Ok(Some(range)) + } + + fn block_range(&mut self, tip: u32) -> RangeInclusive { + let (from, to) = match self.direction { + SyncDirection::Forward => { + let from = self.next_block; + let mut to = from + self.chunk_size; + to = u32::min(to, tip); + self.next_block = to + 1; + (from, to) + } + SyncDirection::Backward => { + let to = self.next_block; + let from = to.saturating_sub(self.chunk_size); + self.next_block = from.saturating_sub(1); + (from, to) + } + }; + from..=to + } + + /// Returns the next sequence range to index. + /// + /// # Arguments + /// + /// * `tip` - The current tip of the chain. + /// * `max_sequence` - The maximum sequence that should be indexed. + /// `max_sequence` is the exclusive upper bound of the range to be indexed. + /// (e.g. `0..max_sequence`) + fn sequence_range(&mut self, max_sequence: u32) -> Result>> { + let (from, to) = match self.direction { + SyncDirection::Forward => { + let sequence_start = self.next_sequence; + let mut sequence_end = sequence_start + MAX_SEQUENCE_RANGE; + if self.next_sequence >= max_sequence { + return Ok(None); + } + sequence_end = u32::min(sequence_end, max_sequence.saturating_sub(1)); + self.next_sequence = sequence_end + 1; + (sequence_start, sequence_end) + } + SyncDirection::Backward => { + let sequence_end = self.next_sequence; + let sequence_start = sequence_end.saturating_sub(MAX_SEQUENCE_RANGE); + self.next_sequence = sequence_start.saturating_sub(1); + (sequence_start, sequence_end) + } + }; + Ok(Some(from..=to)) + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum SyncDirection { + Forward, + Backward, +} + +/// Tool for handling the logic of what the next block range that should be +/// queried is and also handling rate limiting. Rate limiting is automatically +/// performed by `next_action`. +pub(crate) struct RateLimitedContractSyncCursor { + indexer: Arc>, + db: Arc>, + tip: u32, + max_sequence: Option, + last_tip_update: Instant, + eta_calculator: SyncerEtaCalculator, + sync_state: SyncState, +} + +impl RateLimitedContractSyncCursor { + /// Construct a new contract sync helper. + pub async fn new( + indexer: Arc>, + db: Arc>, + chunk_size: u32, + initial_height: u32, + mode: IndexMode, + ) -> Result { + let (max_sequence, tip) = indexer.latest_sequence_count_and_tip().await?; + Ok(Self { + indexer, + db, + tip, + max_sequence, + last_tip_update: Instant::now(), + eta_calculator: SyncerEtaCalculator::new(initial_height, tip, ETA_TIME_WINDOW), + sync_state: SyncState::new( + chunk_size, + initial_height, + initial_height, + mode, + Default::default(), + // The rate limited cursor currently only syncs in the forward direction. + SyncDirection::Forward, + ), + }) + } + + /// Wait based on how close we are to the tip and update the tip, + /// i.e. the highest block we may scrape. + async fn get_rate_limit(&mut self) -> Result> { + if self.sync_state.next_block + self.sync_state.chunk_size < self.tip { + // If doing the full chunk wouldn't exceed the already known tip we do not need to rate limit. + Ok(None) + } else { + // We are within one chunk size of the known tip. + // If it's been fewer than 30s since the last tip update, sleep for a bit until we're ready to fetch the next tip. + if let Some(sleep_time) = + Duration::from_secs(30).checked_sub(self.last_tip_update.elapsed()) + { + return Ok(Some(sleep_time)); + } + match self.indexer.get_finalized_block_number().await { + Ok(tip) => { + // we retrieved a new tip value, go ahead and update. + self.last_tip_update = Instant::now(); + self.tip = tip; + Ok(None) + } + Err(e) => { + warn!(error = %e, "Failed to get next block range because we could not get the current tip"); + // we are failing to make a basic query, we should wait before retrying. + sleep(Duration::from_secs(10)).await; + Err(e.into()) + } + } + } + } + + fn sync_end(&self) -> Result { + match self.sync_state.mode { + IndexMode::Block => Ok(self.tip), + IndexMode::Sequence => self + .max_sequence + .ok_or(eyre::eyre!("Sequence indexing requires a max sequence",)), + } + } + + fn sync_position(&self) -> u32 { + match self.sync_state.mode { + IndexMode::Block => self.sync_state.next_block, + IndexMode::Sequence => self.sync_state.next_sequence, + } + } + + fn sync_step(&self) -> u32 { + match self.sync_state.mode { + IndexMode::Block => self.sync_state.chunk_size, + IndexMode::Sequence => MAX_SEQUENCE_RANGE, + } + } +} + +#[async_trait] +impl ContractSyncCursor for RateLimitedContractSyncCursor +where + T: Send + Debug + 'static, +{ + async fn next_action(&mut self) -> Result<(CursorAction, Duration)> { + let sync_end = self.sync_end()?; + let to = u32::min(sync_end, self.sync_position() + self.sync_step()); + let from = self.sync_position(); + let eta = if to < sync_end { + self.eta_calculator.calculate(from, sync_end) + } else { + Duration::from_secs(0) + }; + + let rate_limit = self.get_rate_limit().await?; + if let Some(rate_limit) = rate_limit { + return Ok((CursorAction::Sleep(rate_limit), eta)); + } + let (max_sequence, tip) = self.indexer.latest_sequence_count_and_tip().await?; + self.tip = tip; + self.max_sequence = max_sequence; + if let Some(range) = self.sync_state.get_next_range(max_sequence, tip).await? { + return Ok((CursorAction::Query(range), eta)); + } + + // TODO: Define the sleep time from interval flag + Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)) + } + + fn latest_queried_block(&self) -> u32 { + self.sync_state.next_block.saturating_sub(1) + } + + async fn update(&mut self, _: Vec<(T, LogMeta)>, _range: RangeInclusive) -> Result<()> { + // Store a relatively conservative view of the high watermark, which should allow a single watermark to be + // safely shared across multiple cursors, so long as they are running sufficiently in sync + self.db + .store_high_watermark(u32::max( + self.sync_state.start_block, + self.sync_state + .next_block + .saturating_sub(self.sync_state.chunk_size), + )) + .await?; + Ok(()) + } +} diff --git a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs new file mode 100644 index 0000000000..3dcbf7aa17 --- /dev/null +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs @@ -0,0 +1,989 @@ +//! A sequence-aware cursor that syncs backwards until there are no earlier logs to index. + +use std::{collections::HashSet, fmt::Debug, ops::RangeInclusive, sync::Arc, time::Duration}; + +use async_trait::async_trait; +use eyre::Result; +use hyperlane_core::{ + ContractSyncCursor, CursorAction, HyperlaneSequenceAwareIndexerStoreReader, IndexMode, LogMeta, + Sequenced, +}; +use itertools::Itertools; +use tracing::{debug, warn}; + +use super::{LastIndexedSnapshot, TargetSnapshot}; + +/// A sequence-aware cursor that syncs backward until there are no earlier logs to index. +#[derive(Debug)] +pub(crate) struct BackwardSequenceAwareSyncCursor { + /// The max chunk size to query for logs. + /// If in sequence mode, this is the max number of sequences to query. + /// If in block mode, this is the max number of blocks to query. + chunk_size: u32, + /// A DB used to check which logs have already been indexed. + db: Arc>, + /// A snapshot of the last log to be indexed, or if no indexing has occurred yet, + /// the initial log to start indexing backward from. + last_indexed_snapshot: LastIndexedSnapshot, + /// The current snapshot we're indexing. As this is a backward cursor, + /// if the last indexed snapshot was sequence 100, this would be sequence 99. + /// A None value indicates we're fully synced. + current_indexing_snapshot: Option, + /// The mode of indexing to use. + index_mode: IndexMode, +} + +impl BackwardSequenceAwareSyncCursor { + pub fn new( + chunk_size: u32, + db: Arc>, + current_sequence_count: u32, + start_block: u32, + index_mode: IndexMode, + ) -> Self { + // If the current sequence count is 0, we haven't indexed anything yet. + // Otherwise, consider the current sequence count as the last indexed snapshot, + // indicating the upper bound of sequences to index. + let last_indexed_snapshot = LastIndexedSnapshot { + sequence: (current_sequence_count > 0).then_some(current_sequence_count), + at_block: start_block, + }; + + Self { + chunk_size, + db, + current_indexing_snapshot: last_indexed_snapshot.previous_target(), + last_indexed_snapshot, + index_mode, + } + } + + /// Gets the next range of logs to query. + /// If the cursor is fully synced, this returns None. + /// Otherwise, it returns the next range to query, either by block or sequence depending on the mode. + pub async fn get_next_range(&mut self) -> Result>> { + // Skip any already indexed logs. + self.skip_indexed().await?; + + // If `self.current_indexing_snapshot` is None, we are synced and there are no more ranges to query. + // Otherwise, we query the next range, searching for logs prior to and including the current indexing snapshot. + Ok(self + .current_indexing_snapshot + .as_ref() + .map(|current_indexing_snapshot| match &self.index_mode { + IndexMode::Block => self.get_next_block_range(current_indexing_snapshot), + IndexMode::Sequence => self.get_next_sequence_range(current_indexing_snapshot), + })) + } + + /// Gets the next block range to index. + /// Only used in block mode. + fn get_next_block_range( + &self, + current_indexing_snapshot: &TargetSnapshot, + ) -> RangeInclusive { + // Query the block range ending at the current_indexing_snapshot's at_block. + current_indexing_snapshot + .at_block + .saturating_sub(self.chunk_size)..=current_indexing_snapshot.at_block + } + + /// Gets the next sequence range to index. + /// Only used in sequence mode. + fn get_next_sequence_range( + &self, + current_indexing_snapshot: &TargetSnapshot, + ) -> RangeInclusive { + // Query the sequence range ending at the current_indexing_snapshot's sequence. + current_indexing_snapshot + .sequence + .saturating_sub(self.chunk_size)..=current_indexing_snapshot.sequence + } + + /// Reads the DB to check if the current indexing sequence has already been indexed, + /// iterating until we find a sequence that hasn't been indexed. + async fn skip_indexed(&mut self) -> Result<()> { + // While we're not fully synced, check if the next log we're looking for has been + // inserted into the db, and update the cursor accordingly. + while let Some(current_indexing_sequence) = + self.current_indexing_snapshot.as_ref().map(|s| s.sequence) + { + // Require the block number as well. + if let Some(block_number) = self + .get_sequence_log_block_number(current_indexing_sequence) + .await? + { + self.last_indexed_snapshot = LastIndexedSnapshot { + sequence: Some(current_indexing_sequence), + at_block: block_number, + }; + + self.current_indexing_snapshot = self.last_indexed_snapshot.previous_target(); + + debug!( + last_indexed_snapshot=?self.last_indexed_snapshot, + current_indexing_snapshot=?self.current_indexing_snapshot, + "Fast forwarded current sequence" + ); + } else { + // If the sequence hasn't been indexed, break out of the loop. + break; + } + } + + Ok(()) + } + + /// Gets the log block number of a previously indexed sequence. Returns None if the + /// log for the sequence number hasn't been indexed. + async fn get_sequence_log_block_number(&self, sequence: u32) -> Result> { + // Ensure there's a full entry for the sequence. + if self.db.retrieve_by_sequence(sequence).await?.is_some() { + // And get the block number. + if let Some(block_number) = self + .db + .retrieve_log_block_number_by_sequence(sequence) + .await? + { + return Ok(Some(block_number.try_into()?)); + } + } + + Ok(None) + } + + /// Updates the cursor with the logs that were found in the range. + /// Only used in sequence mode. + /// Logs are expected to be sorted by sequence in ascending order and deduplicated. + /// + /// Behavior: + /// - Empty logs are allowed, but no gaps are allowed. The logs must build upon the last indexed snapshot. + /// - If there are any gaps, the cursor rewinds to the last indexed snapshot, and ranges will be retried. + fn update_block_range( + &mut self, + logs: Vec<(T, LogMeta)>, + all_log_sequences: &HashSet, + range: RangeInclusive, + current_indexing_snapshot: TargetSnapshot, + ) -> Result<()> { + // We require no sequence gaps and to build upon the last snapshot. + // A non-inclusive range is used to allow updates without any logs. + let expected_sequences = ((current_indexing_snapshot.sequence + 1) + .saturating_sub(logs.len() as u32) + ..(current_indexing_snapshot.sequence + 1)) + .collect::>(); + if all_log_sequences != &expected_sequences { + // If there are any missing sequences, rewind to just before the last indexed snapshot. + // Rewind to the last snapshot. + self.rewind_due_to_sequence_gaps(&logs, all_log_sequences, &expected_sequences, &range); + return Ok(()); + } + + let logs_len: u32 = logs.len().try_into()?; + + // If the number of logs, which start at the current sequence and go backwards, + // exceeds the current indexing snapshot sequence, we've synced everything including + // sequence 0. Otherwise, we're not fully synced yet. + self.current_indexing_snapshot = current_indexing_snapshot + .sequence + .checked_sub(logs_len) + .map(|new_current_sequence| TargetSnapshot { + sequence: new_current_sequence, + at_block: *range.start(), + }); + + // This means we indexed at least one log that builds on the last snapshot. + // Recall logs is sorted in ascending order, so the last log is the "oldest" / "earliest" + // log in the range. + if let Some(lowest_sequence_log) = logs.first() { + // Update the last snapshot. + self.last_indexed_snapshot = LastIndexedSnapshot { + sequence: Some(lowest_sequence_log.0.sequence()), + at_block: lowest_sequence_log.1.block_number.try_into()?, + }; + } + + Ok(()) + } + + /// Updates the cursor with the logs that were found in the range. + /// Only used in sequence mode. + /// Logs are expected to be sorted by sequence in ascending order and deduplicated. + /// + /// Behavior: + /// - The sequences of the logs must exactly match the range. + /// - If there are any gaps, the cursor rewinds and the range will be retried. + fn update_sequence_range( + &mut self, + logs: Vec<(T, LogMeta)>, + all_log_sequences: &HashSet, + range: RangeInclusive, + current_indexing_snapshot: TargetSnapshot, + ) -> Result<()> { + // We require that the range starts at the current sequence. + // This should always be the case, but to be extra safe we handle this case. + if *range.end() != current_indexing_snapshot.sequence { + warn!( + ?logs, + ?range, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + "Expected range to end at the current sequence", + ); + self.rewind(); + return Ok(()); + } + + // We require that we've gotten all sequences in the range. + let expected_sequences = range.clone().collect::>(); + if all_log_sequences != &expected_sequences { + // If there are any missing sequences, rewind to just before the last indexed snapshot. + // Rewind to the last snapshot. + self.rewind_due_to_sequence_gaps(&logs, all_log_sequences, &expected_sequences, &range); + return Ok(()); + } + + // If we've gotten here, it means we indexed the entire range. + // We update the last snapshot accordingly and set ourselves up to index the previous sequence. + // Recall logs is sorted in ascending order, so the first log is the lowest sequence. + let Some(lowest_sequence_log) = logs.first() else { + // Sequence range indexing should never have empty ranges, + // but to be safe we handle this anyways. + warn!( + ?logs, + ?range, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + "Expected non-empty logs and range in sequence mode", + ); + return Ok(()); + }; + + // Update the last indexed snapshot. + self.last_indexed_snapshot = LastIndexedSnapshot { + sequence: Some(lowest_sequence_log.0.sequence()), + at_block: lowest_sequence_log.1.block_number.try_into()?, + }; + // Position the current snapshot to the previous sequence. + self.current_indexing_snapshot = self.last_indexed_snapshot.previous_target(); + + Ok(()) + } + + /// Rewinds the cursor to target immediately preceding the last indexed snapshot, + /// and logs the inconsistencies. + fn rewind_due_to_sequence_gaps( + &mut self, + logs: &Vec<(T, LogMeta)>, + all_log_sequences: &HashSet, + expected_sequences: &HashSet, + expected_sequence_range: &RangeInclusive, + ) { + warn!( + all_log_sequences=?all_log_sequences.iter().sorted().collect::>(), + expected_sequences=?expected_sequences.iter().sorted().collect::>(), + ?expected_sequence_range, + missing_expected_sequences=?expected_sequences.difference(all_log_sequences).sorted().collect::>(), + unexpected_sequences=?all_log_sequences.difference(expected_sequences).sorted().collect::>(), + ?logs, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + "Log sequences don't exactly match the expected sequence range, rewinding to last indexed snapshot", + ); + // Rewind to the last snapshot. + self.rewind(); + } + + fn rewind(&mut self) { + self.current_indexing_snapshot = self.last_indexed_snapshot.previous_target(); + } +} + +#[async_trait] +impl ContractSyncCursor for BackwardSequenceAwareSyncCursor { + async fn next_action(&mut self) -> Result<(CursorAction, Duration)> { + // TODO: Fix ETA calculation + let eta = Duration::from_secs(0); + if let Some(range) = self.get_next_range().await? { + Ok((CursorAction::Query(range), eta)) + } else { + // TODO: Define the sleep time from interval flag + Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)) + } + } + + fn latest_queried_block(&self) -> u32 { + self.current_indexing_snapshot + .as_ref() + .map(|snapshot| snapshot.at_block) + .unwrap_or(self.last_indexed_snapshot.at_block) + } + + /// Updates the cursor with the logs that were found in the range. + /// + /// Inconsistencies in the logs are not considered errors, instead they're handled by rewinding the cursor + /// to retry ranges. + /// + /// ## logs + /// The logs to ingest. If any logs are duplicated or their sequence is higher than the current indexing snapshot, + /// they are filtered out. + async fn update(&mut self, logs: Vec<(T, LogMeta)>, range: RangeInclusive) -> Result<()> { + let Some(current_indexing_snapshot) = self.current_indexing_snapshot.clone() else { + // We're synced, no need to update at all. + return Ok(()); + }; + + // Remove any duplicates, filter out any logs with a higher sequence than our + // current snapshot, and sort in ascending order. + let logs = logs + .into_iter() + .unique_by(|(log, _)| log.sequence()) + .filter(|(log, _)| log.sequence() <= current_indexing_snapshot.sequence) + .sorted_by(|(log_a, _), (log_b, _)| log_a.sequence().cmp(&log_b.sequence())) + .collect::>(); + + let all_log_sequences = logs + .iter() + .map(|(log, _)| log.sequence()) + .collect::>(); + + match &self.index_mode { + IndexMode::Sequence => self.update_sequence_range( + logs, + &all_log_sequences, + range, + current_indexing_snapshot, + )?, + IndexMode::Block => { + self.update_block_range(logs, &all_log_sequences, range, current_indexing_snapshot)? + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::super::forward::test::*; + use super::*; + + const INITIAL_CURRENT_INDEXING_SNAPSHOT: TargetSnapshot = TargetSnapshot { + sequence: 99, + at_block: 1000, + }; + const INITIAL_LAST_INDEXED_SNAPSHOT: LastIndexedSnapshot = LastIndexedSnapshot { + sequence: Some(INITIAL_CURRENT_INDEXING_SNAPSHOT.sequence + 1), + at_block: INITIAL_CURRENT_INDEXING_SNAPSHOT.at_block, + }; + + // Start at sequence 101 to illustrate fast forwarding works + const INITIAL_SEQUENCE_COUNT: u32 = 101; + const INITIAL_START_BLOCK: u32 = 1001; + + /// Returns a cursor with the current indexing snapshot as INITIAL_CURRENT_INDEXING_SNAPSHOT. + async fn get_test_backward_sequence_aware_sync_cursor( + mode: IndexMode, + chunk_size: u32, + ) -> BackwardSequenceAwareSyncCursor { + let db = Arc::new(MockHyperlaneSequenceAwareIndexerStore { + logs: vec![ + ( + MockSequencedData::new(INITIAL_LAST_INDEXED_SNAPSHOT.sequence.unwrap()), + log_meta_with_block(INITIAL_LAST_INDEXED_SNAPSHOT.at_block.into()), + ), + ( + MockSequencedData::new(INITIAL_SEQUENCE_COUNT), + log_meta_with_block(INITIAL_START_BLOCK.into()), + ), + (MockSequencedData::new(102), log_meta_with_block(1002)), + ], + }); + + let mut cursor = BackwardSequenceAwareSyncCursor::new( + chunk_size, + db, + INITIAL_SEQUENCE_COUNT, + INITIAL_START_BLOCK, + mode, + ); + + // Skip any already indexed logs and sanity check we start at the correct spot. + cursor.skip_indexed().await.unwrap(); + assert_eq!( + cursor.current_indexing_snapshot, + Some(INITIAL_CURRENT_INDEXING_SNAPSHOT), + ); + assert_eq!(cursor.last_indexed_snapshot, INITIAL_LAST_INDEXED_SNAPSHOT); + + cursor + } + + mod block_range { + use super::*; + + const INDEX_MODE: IndexMode = IndexMode::Block; + const CHUNK_SIZE: u32 = 100; + + async fn get_cursor() -> BackwardSequenceAwareSyncCursor { + get_test_backward_sequence_aware_sync_cursor(INDEX_MODE, CHUNK_SIZE).await + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_normal_indexing() { + let mut cursor = get_cursor().await; + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 900..=1000; + assert_eq!(range, expected_range); + + // Calling get_next_range again should yield the same range. + let range = cursor.get_next_range().await.unwrap().unwrap(); + assert_eq!(range, expected_range); + + // Update the cursor with some found logs. + cursor + .update( + vec![ + (MockSequencedData::new(97), log_meta_with_block(970)), + (MockSequencedData::new(98), log_meta_with_block(980)), + (MockSequencedData::new(99), log_meta_with_block(990)), + ], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the previous sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 96, + at_block: 900, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(97), + at_block: 970, + } + ); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_multiple_ranges() { + let mut cursor = get_cursor().await; + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 900..=1000; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. + cursor.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have moved the current indexing snapshot's block number (but not sequence), + // and made no changes to the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 99, + at_block: 900, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(100), + at_block: 1000, + } + ); + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 800..=900; + assert_eq!(range, expected_range); + + // Update the cursor with some found logs now. + cursor + .update( + vec![ + (MockSequencedData::new(96), log_meta_with_block(850)), + (MockSequencedData::new(97), log_meta_with_block(860)), + (MockSequencedData::new(98), log_meta_with_block(870)), + (MockSequencedData::new(99), log_meta_with_block(880)), + ], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the previous sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 95, + at_block: 800, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(96), + at_block: 850, + } + ); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_for_sequence_gap() { + let mut cursor = get_cursor().await; + + async fn update_and_expect_rewind( + cur: &mut BackwardSequenceAwareSyncCursor, + logs: Vec<(MockSequencedData, LogMeta)>, + ) { + // For a more rigorous test case, first do a range where no logs are found, + // then in the next range there are issues, and we should rewind to the last indexed snapshot. + + // Expect the range to be: + // (current - chunk_size, current) + let range = cur.get_next_range().await.unwrap().unwrap(); + let expected_range = 900..=1000; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. + cur.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have moved the current indexing snapshot's block number (but not sequence), + // and made no changes to the last indexed snapshot. + assert_eq!( + cur.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 99, + at_block: 900, + }) + ); + assert_eq!( + cur.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(100), + at_block: 1000, + } + ); + + // Expect the range to be: + // (start, tip) + let range = cur.get_next_range().await.unwrap().unwrap(); + let expected_range = 800..=900; + assert_eq!(range, expected_range); + + // Update the cursor, expecting a rewind now + cur.update(logs, expected_range).await.unwrap(); + + // Expect the cursor rewound to just prior to the last indexed snapshot. + assert_eq!( + cur.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 99, + at_block: 1000, + }) + ); + assert_eq!( + cur.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(100), + at_block: 1000, + } + ); + } + + // Not building upon last snapshot + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(96), log_meta_with_block(850)), + (MockSequencedData::new(97), log_meta_with_block(860)), + (MockSequencedData::new(98), log_meta_with_block(870)), + ], + ) + .await; + + // Now with a gap, missing 98 + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(96), log_meta_with_block(850)), + (MockSequencedData::new(97), log_meta_with_block(860)), + (MockSequencedData::new(99), log_meta_with_block(890)), + ], + ) + .await; + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_handles_unexpected_logs() { + let mut cursor = get_cursor().await; + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 900..=1000; + assert_eq!(range, expected_range); + + // Update the cursor with some paritally bogus logs: + // - Three logs of sequence 99, i.e. duplicated + // - A log at sequence 100, which was already indexed and should be ignored + cursor + .update( + vec![ + (MockSequencedData::new(99), log_meta_with_block(990)), + (MockSequencedData::new(99), log_meta_with_block(990)), + (MockSequencedData::new(100), log_meta_with_block(1000)), + (MockSequencedData::new(99), log_meta_with_block(990)), + ], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the previous sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 98, + at_block: 900, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(99), + at_block: 990, + } + ); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_stops_after_indexing_sequence_0() { + let mut cursor = get_cursor().await; + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 900..=1000; + assert_eq!(range, expected_range); + + // Update the with all the missing logs. + cursor + .update( + (0..=99) + .map(|i| { + ( + MockSequencedData::new(i), + log_meta_with_block(900 + i as u64), + ) + }) + .collect(), + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to indicate that it's fully synced. + assert_eq!(cursor.current_indexing_snapshot, None,); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(0), + at_block: 900, + } + ); + + // Expect the range to be None + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_skip_indexed_when_fully_synced() { + let db = Arc::new(MockHyperlaneSequenceAwareIndexerStore { + logs: (0..=INITIAL_SEQUENCE_COUNT) + .map(|i| { + ( + MockSequencedData::new(i), + log_meta_with_block(900 + i as u64), + ) + }) + .collect(), + }); + + let mut cursor = BackwardSequenceAwareSyncCursor::new( + CHUNK_SIZE, + db, + INITIAL_SEQUENCE_COUNT, + INITIAL_START_BLOCK, + INDEX_MODE, + ); + + // We're fully synced, so expect no range + assert_eq!(cursor.get_next_range().await.unwrap(), None); + } + } + + mod sequence_range { + use super::*; + + const INDEX_MODE: IndexMode = IndexMode::Sequence; + const CHUNK_SIZE: u32 = 5; + + async fn get_cursor() -> BackwardSequenceAwareSyncCursor { + get_test_backward_sequence_aware_sync_cursor(INDEX_MODE, CHUNK_SIZE).await + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_normal_indexing() { + let mut cursor = get_cursor().await; + + // We should have fast forwarded to sequence 99, block 1000 + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 99, + at_block: 1000, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(100), + at_block: 1000, + } + ); + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 94..=99; + assert_eq!(range, expected_range); + + // Calling get_next_range again should yield the same range. + let range = cursor.get_next_range().await.unwrap().unwrap(); + assert_eq!(range, expected_range); + + // Update the cursor with some found logs. These have some duplicates + // and are not sorted, and we expect the cursor to handle this. + cursor + .update( + vec![ + (MockSequencedData::new(95), log_meta_with_block(950)), + (MockSequencedData::new(96), log_meta_with_block(960)), + (MockSequencedData::new(97), log_meta_with_block(970)), + // Add a duplicate here + (MockSequencedData::new(98), log_meta_with_block(980)), + (MockSequencedData::new(98), log_meta_with_block(980)), + (MockSequencedData::new(99), log_meta_with_block(990)), + // Put this out of order + (MockSequencedData::new(94), log_meta_with_block(940)), + ], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the previous sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 93, + at_block: 940, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(94), + at_block: 940, + } + ); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_if_updated_with_no_logs() { + let mut cursor = get_cursor().await; + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 94..=99; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. + cursor.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have "rewound", i.e. no changes to the current indexing snapshot or last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 99, + at_block: 1000, + }) + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(100), + at_block: 1000, + } + ); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_if_gap_or_unexpected_logs() { + // Starts with current snapshot at sequence 99, block 1000 + let mut cursor = get_cursor().await; + + async fn update_and_expect_rewind( + cur: &mut BackwardSequenceAwareSyncCursor, + logs: Vec<(MockSequencedData, LogMeta)>, + ) { + // Expect the range to be: + // (current - chunk_size, current) + let range = cur.get_next_range().await.unwrap().unwrap(); + let expected_range = 94..=99; + assert_eq!(range, expected_range); + + // Update the cursor + cur.update(logs, expected_range).await.unwrap(); + + // Expect the cursor to have "rewound", i.e. no changes to the current indexing snapshot or last indexed snapshot. + assert_eq!( + cur.current_indexing_snapshot, + Some(TargetSnapshot { + sequence: 99, + at_block: 1000, + }) + ); + assert_eq!( + cur.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(100), + at_block: 1000, + } + ); + } + + // First, try without building upon the last snapshot + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(94), log_meta_with_block(940)), + (MockSequencedData::new(95), log_meta_with_block(950)), + (MockSequencedData::new(96), log_meta_with_block(960)), + (MockSequencedData::new(98), log_meta_with_block(980)), + ], + ) + .await; + + // This time with a gap (missing 97) + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(94), log_meta_with_block(940)), + (MockSequencedData::new(95), log_meta_with_block(950)), + (MockSequencedData::new(96), log_meta_with_block(960)), + (MockSequencedData::new(98), log_meta_with_block(980)), + (MockSequencedData::new(99), log_meta_with_block(990)), + ], + ) + .await; + + // This time building upon the last snapshot, but the first sequence in the range isn't present + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(95), log_meta_with_block(950)), + (MockSequencedData::new(96), log_meta_with_block(960)), + (MockSequencedData::new(97), log_meta_with_block(970)), + (MockSequencedData::new(98), log_meta_with_block(980)), + (MockSequencedData::new(99), log_meta_with_block(990)), + ], + ) + .await; + + // An unexpected log, sequence 93 + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(93), log_meta_with_block(940)), + (MockSequencedData::new(94), log_meta_with_block(950)), + (MockSequencedData::new(95), log_meta_with_block(950)), + (MockSequencedData::new(96), log_meta_with_block(960)), + (MockSequencedData::new(97), log_meta_with_block(970)), + (MockSequencedData::new(98), log_meta_with_block(980)), + (MockSequencedData::new(99), log_meta_with_block(990)), + ], + ) + .await; + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_stops_after_indexing_sequence_0() { + let mut cursor = get_cursor().await; + + // Set the chunk size to 100 to make it easier to test. + cursor.chunk_size = 100; + + // Expect the range to be: + // (current - chunk_size, current) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 0..=99; + assert_eq!(range, expected_range); + + // Update the with all the missing logs. + cursor + .update( + (0..=99) + .map(|i| { + ( + MockSequencedData::new(i), + log_meta_with_block(900 + i as u64), + ) + }) + .collect(), + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to indicate that it's fully synced. + assert_eq!(cursor.current_indexing_snapshot, None); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(0), + at_block: 900, + } + ); + + // Expect the range to be None + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + } + } +} diff --git a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs new file mode 100644 index 0000000000..97b34aa40c --- /dev/null +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs @@ -0,0 +1,1186 @@ +//! A sequence-aware cursor that syncs forwards in perpetuity, reacting to gaps in log sequences +//! and only indexing ranges of logs that are likely to contain new logs. + +use std::{ + cmp::Ordering, collections::HashSet, fmt::Debug, ops::RangeInclusive, sync::Arc, time::Duration, +}; + +use async_trait::async_trait; +use eyre::Result; +use hyperlane_core::{ + ContractSyncCursor, CursorAction, HyperlaneSequenceAwareIndexerStoreReader, IndexMode, LogMeta, + SequenceAwareIndexer, Sequenced, +}; +use itertools::Itertools; +use tracing::{debug, warn}; + +use super::{LastIndexedSnapshot, TargetSnapshot}; + +/// A sequence-aware cursor that syncs forwards in perpetuity. +#[derive(Debug)] +pub(crate) struct ForwardSequenceAwareSyncCursor { + /// The max chunk size to query for logs. + /// If in sequence mode, this is the max number of sequences to query. + /// If in block mode, this is the max number of blocks to query. + chunk_size: u32, + /// The latest sequence count querier. + /// This is used to check if there are new logs to index and to + /// establish targets to index towards. + latest_sequence_querier: Arc>, + /// A DB used to check which logs have already been indexed. + db: Arc>, + /// A snapshot of the last indexed log, or if no indexing has occurred yet, + /// the initial log to start indexing forward from. + last_indexed_snapshot: LastIndexedSnapshot, + /// The current snapshot we're indexing. As this is a forward cursor, + /// if the last indexed snapshot was sequence 100, this would be sequence 101. + current_indexing_snapshot: TargetSnapshot, + /// The target snapshot to index towards. + target_snapshot: Option, + /// The mode of indexing. + index_mode: IndexMode, +} + +impl ForwardSequenceAwareSyncCursor { + pub fn new( + chunk_size: u32, + latest_sequence_querier: Arc>, + db: Arc>, + next_sequence: u32, + start_block: u32, + index_mode: IndexMode, + ) -> Self { + // If the next sequence is 0, we're starting from the beginning and haven't + // indexed anything yet. + let last_indexed_snapshot = LastIndexedSnapshot { + sequence: (next_sequence > 0).then(|| next_sequence.saturating_sub(1)), + at_block: start_block, + }; + + Self { + chunk_size, + latest_sequence_querier, + db, + last_indexed_snapshot, + current_indexing_snapshot: TargetSnapshot { + sequence: next_sequence, + at_block: start_block, + }, + target_snapshot: None, + index_mode, + } + } + + /// Gets the next range of logs to index. + /// If there are no logs to index, returns `None`. + /// If there are logs to index, returns the range of logs, either by sequence or block number + /// depending on the mode. + pub async fn get_next_range(&mut self) -> Result>> { + // Skip any already indexed logs. + self.skip_indexed().await?; + + let (Some(onchain_sequence_count), tip) = self + .latest_sequence_querier + .latest_sequence_count_and_tip() + .await? + else { + return Ok(None); + }; + + let current_sequence = self.current_indexing_snapshot.sequence; + let range = match current_sequence.cmp(&onchain_sequence_count) { + Ordering::Equal => { + // We are synced up to the latest sequence so we don't need to index anything. + + // We can update the current indexing snapshot to the tip. + // This will let us only index blocks that are likely to have new logs once + // there's a new sequence to search for. + self.current_indexing_snapshot.at_block = tip; + + None + } + Ordering::Less => { + // The cursor is behind the onchain sequence count, so we need to index. + + // Minus one because this is the sequence we're targeting, not the count. + let target_sequence = onchain_sequence_count.saturating_sub(1); + + // Set the target to the highest sequence and tip. + // We don't necessarily expect to hit this target in the next query (because we + // have limits to the range size based off the chunk size), but we will use it + // as an eventual target. + self.target_snapshot = Some(TargetSnapshot { + sequence: target_sequence, + at_block: tip, + }); + + match &self.index_mode { + IndexMode::Block => self.get_next_block_range(tip), + IndexMode::Sequence => { + Some(self.get_next_sequence_range(current_sequence, target_sequence)) + } + } + } + Ordering::Greater => { + // Providers may be internally inconsistent, e.g. RPC request A could hit a node + // whose tip is N and subsequent RPC request B could hit a node whose tip is < N. + // Just warn and try to continue as normal. + warn!( + current_sequence, + onchain_sequence_count, + "Current sequence is greater than the onchain sequence count" + ); + None + } + }; + + Ok(range) + } + + /// Gets the next block range to index. + /// Only used in block mode. + fn get_next_block_range(&self, tip: u32) -> Option> { + // This should never happen, but if it does, we log a warning and return None. + if self.current_indexing_snapshot.at_block > tip { + warn!( + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + target_snapshot=?self.target_snapshot, + tip, + "Current indexing snapshot's block number is greater than the tip" + ); + return None; + } + + // Query the block range starting from the current_indexing_snapshot's at_block. + Some( + self.current_indexing_snapshot.at_block + ..=u32::min( + self.current_indexing_snapshot.at_block + self.chunk_size, + tip, + ), + ) + } + + /// Gets the next sequence range to index. + /// Only used in sequence mode. + fn get_next_sequence_range( + &self, + current_sequence: u32, + target_sequence: u32, + ) -> RangeInclusive { + // Query the sequence range starting from the cursor count. + current_sequence..=u32::min(target_sequence, current_sequence + self.chunk_size) + } + + /// Reads the DB to check if the current indexing sequence has already been indexed, + /// iterating until we find a sequence that hasn't been indexed. + async fn skip_indexed(&mut self) -> Result<()> { + // Check if any new logs have been inserted into the DB, + // and update the cursor accordingly. + while let Some(block_number) = self + .get_sequence_log_block_number(self.current_indexing_snapshot.sequence) + .await? + { + self.last_indexed_snapshot = LastIndexedSnapshot { + sequence: Some(self.current_indexing_snapshot.sequence), + at_block: block_number, + }; + + self.current_indexing_snapshot = self.last_indexed_snapshot.next_target(); + + debug!( + last_indexed_snapshot=?self.last_indexed_snapshot, + current_indexing_snapshot=?self.current_indexing_snapshot, + "Fast forwarded current sequence" + ); + } + + Ok(()) + } + + /// Gets the log block number of a previously indexed sequence. Returns None if the + /// log for the sequence number hasn't been indexed. + async fn get_sequence_log_block_number(&self, sequence: u32) -> Result> { + // Ensure there's a full entry for the sequence. + if self.db.retrieve_by_sequence(sequence).await?.is_some() { + // And get the block number. + if let Some(block_number) = self + .db + .retrieve_log_block_number_by_sequence(sequence) + .await? + { + return Ok(Some(block_number.try_into()?)); + } + } + + Ok(None) + } + + /// Updates the cursor with the logs that were found in the range. + /// Only used in sequence mode. + /// Logs are expected to be sorted by sequence in ascending order and deduplicated. + /// + /// Behavior: + /// - Empty logs are allowed, but no gaps are allowed. The logs must build upon the last indexed snapshot. + /// - If there are any gaps, the cursor rewinds to the last indexed snapshot, and ranges will be retried. + /// - If the target block is reached and the target sequence hasn't been reached, the cursor rewinds to the last indexed snapshot. + fn update_block_range( + &mut self, + logs: Vec<(T, LogMeta)>, + all_log_sequences: &HashSet, + range: RangeInclusive, + ) -> Result<()> { + // We require no sequence gaps and to build upon the last snapshot. + // A non-inclusive range is used to allow updates without any logs. + let expected_sequences = (self.current_indexing_snapshot.sequence + ..(self.current_indexing_snapshot.sequence + logs.len() as u32)) + .collect::>(); + if all_log_sequences != &expected_sequences { + // If there are any missing sequences, rewind to just after the last snapshot. + self.rewind_due_to_sequence_gaps(&logs, all_log_sequences, &expected_sequences, &range); + return Ok(()); + } + + // Update the current indexing snapshot forward. + self.current_indexing_snapshot = TargetSnapshot { + sequence: self.current_indexing_snapshot.sequence + logs.len() as u32, + at_block: *range.end(), + }; + + // This means we indexed at least one log that builds on the last snapshot. + if let Some(highest_sequence_log) = logs.last() { + // Update the last indexed snapshot. + self.last_indexed_snapshot = LastIndexedSnapshot { + sequence: Some(highest_sequence_log.0.sequence()), + at_block: highest_sequence_log.1.block_number.try_into()?, + }; + } + + let Some(target_snapshot) = self.target_snapshot.as_ref() else { + warn!( + ?logs, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + target_snapshot=?self.target_snapshot, + "No target snapshot, cursor should not updated unless one is set", + ); + return Ok(()); + }; + + // If the end block is >= the target block and we haven't yet reached the target sequence, + // rewind to just after the last indexed snapshot. + if self + .last_indexed_snapshot + .sequence + .map(|last_indexed_sequence| last_indexed_sequence < target_snapshot.sequence) + .unwrap_or(true) + && *range.end() >= target_snapshot.at_block + { + warn!( + ?logs, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + target_snapshot=?self.target_snapshot, + "Reached the target block number but not the target sequence, rewinding to last snapshot", + ); + self.rewind(); + return Ok(()); + } + + Ok(()) + } + + /// Updates the cursor with the logs that were found in the range. + /// Only used in sequence mode. + /// Logs are expected to be sorted by sequence in ascending order and deduplicated. + /// + /// Behavior: + /// - The sequences of the logs must exactly match the range. + /// - If there are any gaps, the cursor rewinds and the range will be retried. + fn update_sequence_range( + &mut self, + logs: Vec<(T, LogMeta)>, + all_log_sequences: &HashSet, + range: RangeInclusive, + ) -> Result<()> { + // We require that the range starts at the current sequence. + // This should always be the case, but to be extra safe we handle this case. + if *range.start() != self.current_indexing_snapshot.sequence { + warn!( + ?logs, + ?range, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + target_snapshot=?self.target_snapshot, + "Expected range to start at the current sequence", + ); + self.rewind(); + return Ok(()); + } + + // We require that we've gotten all sequences in the range. + let expected_sequences = range.clone().collect::>(); + if all_log_sequences != &expected_sequences { + // If there are any missing sequences, rewind to just after the last snapshot. + self.rewind_due_to_sequence_gaps(&logs, all_log_sequences, &expected_sequences, &range); + return Ok(()); + } + + // If we've gotten here, it means we indexed the entire range. + // We update the last snapshot accordingly and set ourselves up for the next sequence. + let Some(highest_sequence_log) = logs.last() else { + // Sequence range indexing should never have empty ranges, + // but to be safe we handle this anyways. + warn!( + ?logs, + ?range, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + target_snapshot=?self.target_snapshot, + "Expected non-empty logs and range in sequence mode", + ); + return Ok(()); + }; + + // Update the last indexed snapshot. + self.last_indexed_snapshot = LastIndexedSnapshot { + sequence: Some(highest_sequence_log.0.sequence()), + at_block: highest_sequence_log.1.block_number.try_into()?, + }; + // Position the current snapshot to the next sequence. + self.current_indexing_snapshot = self.last_indexed_snapshot.next_target(); + + Ok(()) + } + + /// Rewinds the cursor to target immediately after the last indexed snapshot, + /// and logs the inconsistencies due to sequence gaps. + fn rewind_due_to_sequence_gaps( + &mut self, + logs: &Vec<(T, LogMeta)>, + all_log_sequences: &HashSet, + expected_sequences: &HashSet, + expected_sequence_range: &RangeInclusive, + ) { + warn!( + all_log_sequences=?all_log_sequences.iter().sorted().collect::>(), + expected_sequences=?expected_sequences.iter().sorted().collect::>(), + ?expected_sequence_range, + missing_expected_sequences=?expected_sequences.difference(all_log_sequences).sorted().collect::>(), + unexpected_sequences=?all_log_sequences.difference(expected_sequences).sorted().collect::>(), + ?logs, + current_indexing_snapshot=?self.current_indexing_snapshot, + last_indexed_snapshot=?self.last_indexed_snapshot, + target_snapshot=?self.target_snapshot, + "Log sequences don't exactly match the expected sequence range, rewinding to last indexed snapshot", + ); + // If there are any missing sequences, rewind to index immediately after the last snapshot. + self.rewind(); + } + + // Rewinds the cursor to target immediately after the last indexed snapshot. + fn rewind(&mut self) { + self.current_indexing_snapshot = self.last_indexed_snapshot.next_target(); + } +} + +#[async_trait] +impl ContractSyncCursor for ForwardSequenceAwareSyncCursor { + async fn next_action(&mut self) -> Result<(CursorAction, Duration)> { + // TODO: Fix ETA calculation + let eta = Duration::from_secs(0); + if let Some(range) = self.get_next_range().await? { + Ok((CursorAction::Query(range), eta)) + } else { + // TODO: Define the sleep time from interval flag + Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)) + } + } + + // TODO: revisit to establish a better heuristic for cursor / indexing health + fn latest_queried_block(&self) -> u32 { + self.current_indexing_snapshot.at_block + } + + /// Updates the cursor with the logs that were found in the range. + /// + /// Inconsistencies in the logs are not considered errors, instead they're handled by rewinding the cursor + /// to retry ranges. + /// + /// ## logs + /// The logs to ingest. If any logs are duplicated or their sequence is lower than the current indexing snapshot, + /// they are filtered out. See `update_sequence_range` and `update_block_range` for more details based + /// off the indexing mode. + /// + /// Note: + /// - Even if the logs include a gap, in practice these logs will have already been inserted into the DB. + /// This means that while gaps result in a rewind here, already known logs may be "fast forwarded" through, + /// and the cursor won't actually end up re-indexing already known logs. + async fn update(&mut self, logs: Vec<(T, LogMeta)>, range: RangeInclusive) -> Result<()> { + // Remove any sequence duplicates, filter out any logs preceding our current snapshot, + // and sort in ascending order. + let logs = logs + .into_iter() + .unique_by(|(log, _)| log.sequence()) + .filter(|(log, _)| log.sequence() >= self.current_indexing_snapshot.sequence) + .sorted_by(|(log_a, _), (log_b, _)| log_a.sequence().cmp(&log_b.sequence())) + .collect::>(); + + let all_log_sequences = logs + .iter() + .map(|(log, _)| log.sequence()) + .collect::>(); + + match &self.index_mode { + IndexMode::Block => self.update_block_range(logs, &all_log_sequences, range)?, + IndexMode::Sequence => self.update_sequence_range(logs, &all_log_sequences, range)?, + }; + Ok(()) + } +} + +#[cfg(test)] +pub(crate) mod test { + use derive_new::new; + use hyperlane_core::{ChainResult, HyperlaneLogStore, Indexer}; + + use super::*; + + #[derive(Debug, Clone)] + pub struct MockLatestSequenceQuerier { + pub latest_sequence_count: Option, + pub tip: u32, + } + + #[async_trait] + impl SequenceAwareIndexer for MockLatestSequenceQuerier + where + T: Sequenced + Debug, + { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + Ok((self.latest_sequence_count, self.tip)) + } + } + + #[async_trait] + impl Indexer for MockLatestSequenceQuerier + where + T: Sequenced + Debug, + { + async fn fetch_logs(&self, _range: RangeInclusive) -> ChainResult> { + Ok(vec![]) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Ok(self.tip) + } + } + + #[derive(Debug, Clone)] + pub struct MockHyperlaneSequenceAwareIndexerStore { + pub logs: Vec<(T, LogMeta)>, + } + + #[async_trait] + impl HyperlaneLogStore for MockHyperlaneSequenceAwareIndexerStore { + async fn store_logs(&self, logs: &[(T, LogMeta)]) -> eyre::Result { + Ok(logs.len() as u32) + } + } + + #[async_trait] + impl HyperlaneSequenceAwareIndexerStoreReader + for MockHyperlaneSequenceAwareIndexerStore + { + async fn retrieve_by_sequence(&self, sequence: u32) -> eyre::Result> { + Ok(self + .logs + .iter() + .find(|(log, _)| log.sequence() == sequence) + .map(|(log, _)| log.clone())) + } + + async fn retrieve_log_block_number_by_sequence( + &self, + sequence: u32, + ) -> eyre::Result> { + Ok(self + .logs + .iter() + .find(|(log, _)| log.sequence() == sequence) + .map(|(_, meta)| meta.block_number)) + } + } + + #[derive(Debug, Clone, new)] + pub struct MockSequencedData { + pub sequence: u32, + } + + impl Sequenced for MockSequencedData { + fn sequence(&self) -> u32 { + self.sequence + } + } + + pub fn log_meta_with_block(block_number: u64) -> LogMeta { + LogMeta { + address: Default::default(), + block_number, + block_hash: Default::default(), + transaction_id: Default::default(), + transaction_index: 0, + log_index: Default::default(), + } + } + + const INITIAL_CURRENT_INDEXING_SNAPSHOT: TargetSnapshot = TargetSnapshot { + sequence: 5, + at_block: 90, + }; + const INITIAL_LAST_INDEXED_SNAPSHOT: LastIndexedSnapshot = LastIndexedSnapshot { + sequence: Some(INITIAL_CURRENT_INDEXING_SNAPSHOT.sequence - 1), + at_block: INITIAL_CURRENT_INDEXING_SNAPSHOT.at_block, + }; + + /// Gets a cursor starting at INITIAL_CURRENT_INDEXING_SNAPSHOT. + async fn get_test_forward_sequence_aware_sync_cursor( + mode: IndexMode, + chunk_size: u32, + ) -> ForwardSequenceAwareSyncCursor { + let latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(5), + tip: 100, + }); + + let db = Arc::new(MockHyperlaneSequenceAwareIndexerStore { + logs: vec![ + (MockSequencedData::new(0), log_meta_with_block(50)), + (MockSequencedData::new(1), log_meta_with_block(60)), + (MockSequencedData::new(2), log_meta_with_block(70)), + (MockSequencedData::new(3), log_meta_with_block(80)), + ( + MockSequencedData::new(INITIAL_LAST_INDEXED_SNAPSHOT.sequence.unwrap()), + log_meta_with_block(INITIAL_LAST_INDEXED_SNAPSHOT.at_block.into()), + ), + ], + }); + + let mut cursor = ForwardSequenceAwareSyncCursor::new( + chunk_size, + latest_sequence_querier, + db, + // Start at sequence 3 and block 70 to illustrate fast forwarding + 3, + 70, + mode, + ); + + // Skip any already indexed logs and sanity check we start at the correct spot. + cursor.skip_indexed().await.unwrap(); + assert_eq!( + cursor.current_indexing_snapshot, + INITIAL_CURRENT_INDEXING_SNAPSHOT, + ); + assert_eq!(cursor.last_indexed_snapshot, INITIAL_LAST_INDEXED_SNAPSHOT); + + cursor + } + + mod block_range { + use super::*; + + const INDEX_MODE: IndexMode = IndexMode::Block; + const CHUNK_SIZE: u32 = 100; + + async fn get_cursor() -> ForwardSequenceAwareSyncCursor { + get_test_forward_sequence_aware_sync_cursor(INDEX_MODE, CHUNK_SIZE).await + } + + /// Tests successful fast forwarding & indexing where all ranges return logs. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_normal_indexing() { + let mut cursor = get_cursor().await; + + // As the latest sequence count is 5 and the current indexing snapshot is sequence 5, we should + // expect no range to index. + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + + // Update the tip, expect to still not index anything. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(5), + tip: 110, + }); + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + + // Update the latest sequence count to 6, now we expect to index. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(6), + tip: 120, + }); + + // Expect the range to be: + // (last polled block where the sequence had already been indexed, tip) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 110..=120; + assert_eq!(range, expected_range); + + // Expect the target snapshot to be set to the latest sequence and tip. + assert_eq!( + cursor.target_snapshot, + Some(TargetSnapshot { + sequence: 5, + at_block: 120, + }) + ); + + // Getting the range again without updating the cursor should yield the same range. + let range = cursor.get_next_range().await.unwrap().unwrap(); + assert_eq!(range, expected_range); + + // Update the cursor with the found log. + cursor + .update( + vec![(MockSequencedData::new(5), log_meta_with_block(115))], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the next sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 6, + at_block: 120, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(5), + at_block: 115, + } + ); + + // And now we should get no range to index. + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + } + + // Tests when the cursor is so behind the tip that it'll need to index multiple ranges (due to the + // chunk size) to catch up. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_multiple_ranges_till_target() { + let mut cursor = get_cursor().await; + + // Pretend like the tip is 200, and a message occurred at block 195. + + // Increase the latest sequence count, and with a tip that exceeds the chunk size. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(6), + tip: 200, + }); + + // Expect the range to be: + // (start, start + chunk_size) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 90..=190; + assert_eq!(range, expected_range); + + // Update the cursor. Update with no logs, because the log happened in block 195. + cursor.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have moved the current indexing snapshot's block number (but not sequence), + // and made no changes to the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 190, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + + // Expect the range to be: + // (start, tip) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 190..=200; + assert_eq!(range, expected_range); + + // Update the cursor with the found log. + cursor + .update( + vec![(MockSequencedData::new(5), log_meta_with_block(195))], + expected_range, + ) + .await + .unwrap(); + + // Expect the current indexing snapshot to have moved to the next sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 6, + at_block: 200, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(5), + at_block: 195, + } + ); + + // And now we should get no range to index. + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + } + + /// Tests when the cursor is so behind the tip that it'll need to index multiple ranges, but by the time + /// it gets to the target snapshot, it realizes it missed a log and needs to rewind. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_for_missed_target_sequence() { + let mut cursor = get_cursor().await; + + // Pretend like the tip is 200, and a message occurred at block 195, but we somehow miss it. + + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(6), + tip: 200, + }); + + // Expect the range to be: + // (start, start + chunk_size) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 90..=190; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. + cursor.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have moved the current indexing snapshot's block number (but not sequence), + // and made no changes to the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 190, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + + // Expect the range to be: + // (start, tip) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 190..=200; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. + cursor.update(vec![], expected_range).await.unwrap(); + + // Expect a rewind to occur back to the last indexed snapshot's block number. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 90, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + } + + /// Tests when the cursor is so behind the tip that it'll need to index multiple ranges. It successfully + /// finds a log in the second range, but missed log in the first range, showing a gap. It should rewind to the + /// last indexed snapshot. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_for_sequence_gaps() { + let mut cursor = get_cursor().await; + + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + // 3 new messages since we last indexed have come in! + latest_sequence_count: Some(7), + tip: 200, + }); + + async fn update_and_expect_rewind( + cur: &mut ForwardSequenceAwareSyncCursor, + logs: Vec<(MockSequencedData, LogMeta)>, + ) { + // For a more rigorous test case, first do a range where no logs are found, + // then in the next range there are issues, and we should rewind to the last indexed snapshot. + + // Expect the range to be: + // (start, start + chunk_size) + let range = cur.get_next_range().await.unwrap().unwrap(); + let expected_range = 90..=190; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. We should've found one here though! + cur.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have moved the current indexing snapshot's block number (but not sequence), + // and made no changes to the last indexed snapshot. + assert_eq!( + cur.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 190, + } + ); + assert_eq!( + cur.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + + // Expect the range to be: + // (start, tip) + let range = cur.get_next_range().await.unwrap().unwrap(); + let expected_range = 190..=200; + assert_eq!(range, expected_range); + + // Update the cursor, expecting a rewind now + cur.update(logs, expected_range).await.unwrap(); + + // Expect a rewind to occur back to the last indexed snapshot's block number. + assert_eq!( + cur.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 90, + } + ); + assert_eq!( + cur.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + } + + // We don't build upon the last sequence (5 missing) + update_and_expect_rewind( + &mut cursor, + vec![(MockSequencedData::new(6), log_meta_with_block(100))], + ) + .await; + + // There's a gap (sequence 6 missing) + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(5), log_meta_with_block(95)), + (MockSequencedData::new(7), log_meta_with_block(105)), + ], + ) + .await; + } + + /// Tests when the cursor is so behind the tip that it'll need to index multiple ranges, but by the time + /// it gets to the target snapshot, it realizes it missed a log and needs to rewind. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_handles_unexpected_logs() { + let mut cursor = get_cursor().await; + + // Pretend like the tip is 100, and a message occurred at block 95. + + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(6), + tip: 100, + }); + + // Expect the range to be: + // (start, start + chunk_size) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 90..=100; + assert_eq!(range, expected_range); + + // Update the cursor with some paritally bogus logs: + // - A log at sequence 4, which was already indexed and should be ignored + // - Three logs of sequence 5, i.e. duplicated + // - A log at sequence 6, which is unexpected, but tolerated nonetheless + cursor + .update( + vec![ + (MockSequencedData::new(4), log_meta_with_block(90)), + (MockSequencedData::new(5), log_meta_with_block(95)), + (MockSequencedData::new(5), log_meta_with_block(95)), + (MockSequencedData::new(6), log_meta_with_block(100)), + (MockSequencedData::new(5), log_meta_with_block(95)), + ], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the next sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 7, + at_block: 100, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(6), + at_block: 100, + } + ); + } + } + + mod sequence_range { + use super::*; + + const INDEX_MODE: IndexMode = IndexMode::Sequence; + const CHUNK_SIZE: u32 = 10; + + async fn get_cursor() -> ForwardSequenceAwareSyncCursor { + get_test_forward_sequence_aware_sync_cursor(INDEX_MODE, CHUNK_SIZE).await + } + + /// Tests successful fast forwarding & successful indexing with a correct sequence range. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_normal_indexing() { + let mut cursor = get_cursor().await; + + // As the latest sequence count is 5 and the current indexing snapshot is sequence 5, we should + // expect no range to index. + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + + // Update the tip, expect to still not index anything. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(5), + tip: 110, + }); + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + + // Update the latest sequence count to 6, now we expect to index. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(6), + tip: 120, + }); + + // Expect the range to be: + // (new sequence, new sequence) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 5..=5; + assert_eq!(range, expected_range); + + // Expect the target snapshot to be set to the latest sequence and tip. + assert_eq!( + cursor.target_snapshot, + Some(TargetSnapshot { + sequence: 5, + at_block: 120, + }) + ); + + // Getting the range again without updating the cursor should yield the same range. + let range = cursor.get_next_range().await.unwrap().unwrap(); + assert_eq!(range, expected_range); + + // Update the cursor with the found log. + cursor + .update( + vec![(MockSequencedData::new(5), log_meta_with_block(115))], + expected_range, + ) + .await + .unwrap(); + + // Expect the cursor to have moved to the next sequence and updated the last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 6, + at_block: 115, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(5), + at_block: 115, + } + ); + + // And now we should get no range to index. + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + + // Update the latest sequence count to 30 to test we use the chunk size. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(30), + tip: 150, + }); + + // Expect the range to be: + // (next sequence, next sequence + chunk size) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 6..=16; + assert_eq!(range, expected_range); + } + + /// Tests getting no logs when a sequence range is expected. + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_if_updated_with_no_logs() { + let mut cursor = get_cursor().await; + + // Update the latest sequence count to 6, expecting to index. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(6), + tip: 120, + }); + + // Expect the range to be: + // (new sequence, new sequence) + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = 5..=5; + assert_eq!(range, expected_range); + + // Update the cursor with no found logs. + cursor.update(vec![], expected_range).await.unwrap(); + + // Expect the cursor to have rewound to the last indexed snapshot - really this is + // the same as not updating current indexing snapshot / last indexed snapshot. + assert_eq!( + cursor.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 90, + } + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + } + + /// Tests getting a gap in the expected logs + #[tracing_test::traced_test] + #[tokio::test] + async fn test_rewinds_for_sequence_gaps() { + let mut cursor = get_cursor().await; + + // Update the latest sequence count to 8, expecting to index 3 messages. + cursor.latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(8), + tip: 120, + }); + + async fn update_and_expect_rewind( + cur: &mut ForwardSequenceAwareSyncCursor, + logs: Vec<(MockSequencedData, LogMeta)>, + ) { + // Expect the range to be: + // (new sequence, new sequence) + let range = cur.get_next_range().await.unwrap().unwrap(); + let expected_range = 5..=7; + assert_eq!(range, expected_range); + + // Update the cursor with sequence 5 and 7, but not 6. + cur.update(logs, expected_range.clone()).await.unwrap(); + + // Expect the cursor to have rewound to the last indexed snapshot - really this is + // the same as not updating current indexing snapshot / last indexed snapshot. + assert_eq!( + cur.current_indexing_snapshot, + TargetSnapshot { + sequence: 5, + at_block: 90, + } + ); + assert_eq!( + cur.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(4), + at_block: 90, + } + ); + } + + // Don't build upon the last sequence (5 missing) + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(6), log_meta_with_block(100)), + (MockSequencedData::new(7), log_meta_with_block(105)), + ], + ) + .await; + + // There's a gap (missing sequence 6) + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(5), log_meta_with_block(115)), + (MockSequencedData::new(7), log_meta_with_block(120)), + ], + ) + .await; + + // Final sequence is missing + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(5), log_meta_with_block(115)), + (MockSequencedData::new(6), log_meta_with_block(120)), + ], + ) + .await; + + // Correct sequences but also an unexpected sequence (8) + update_and_expect_rewind( + &mut cursor, + vec![ + (MockSequencedData::new(5), log_meta_with_block(115)), + (MockSequencedData::new(6), log_meta_with_block(115)), + (MockSequencedData::new(7), log_meta_with_block(120)), + (MockSequencedData::new(8), log_meta_with_block(125)), + ], + ) + .await; + } + } +} diff --git a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs new file mode 100644 index 0000000000..b99e00fff8 --- /dev/null +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs @@ -0,0 +1,132 @@ +use std::{fmt::Debug, sync::Arc, time::Duration}; + +use async_trait::async_trait; +use eyre::Result; +use hyperlane_core::{ + ChainCommunicationError, ContractSyncCursor, CursorAction, + HyperlaneSequenceAwareIndexerStoreReader, IndexMode, LogMeta, SequenceAwareIndexer, Sequenced, +}; +use std::ops::RangeInclusive; + +mod backward; +mod forward; + +pub(crate) use backward::BackwardSequenceAwareSyncCursor; +pub(crate) use forward::ForwardSequenceAwareSyncCursor; + +#[derive(Debug, Clone, PartialEq, Eq)] +struct LastIndexedSnapshot { + /// The last sequence that was indexed. + /// It's possible for this to be None if nothing has been indexed yet + /// e.g. upon first starting up or if no sequenced data exists yet. + pub sequence: Option, + /// The block number at which the last sequence was indexed. + /// If the sequence is None, this can be thought of as the starting block + /// number to index from. + pub at_block: u32, +} + +impl LastIndexedSnapshot { + fn next_target(&self) -> TargetSnapshot { + TargetSnapshot { + // If we haven't indexed anything yet, we start at 0, otherwise we increment. + sequence: self.sequence.map(|s| s + 1).unwrap_or(0), + at_block: self.at_block, + } + } + + fn previous_target(&self) -> Option { + match &self.sequence { + // A previous target doesn't exist if we're trying to go backward + // from sequence 0 or if nothing has been indexed yet. + Some(0) | None => None, + Some(s) => Some(TargetSnapshot { + sequence: s.saturating_sub(1), + at_block: self.at_block, + }), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct TargetSnapshot { + pub sequence: u32, + pub at_block: u32, +} + +#[derive(Debug)] +pub enum SyncDirection { + Forward, + Backward, +} + +/// A cursor that prefers to sync forward, but will sync backward if there is nothing to +/// sync forward. +pub(crate) struct ForwardBackwardSequenceAwareSyncCursor { + forward: ForwardSequenceAwareSyncCursor, + backward: BackwardSequenceAwareSyncCursor, + last_direction: SyncDirection, +} + +impl ForwardBackwardSequenceAwareSyncCursor { + /// Construct a new contract sync helper. + pub async fn new( + latest_sequence_querier: Arc>, + db: Arc>, + chunk_size: u32, + mode: IndexMode, + ) -> Result { + let (sequence_count, tip) = latest_sequence_querier + .latest_sequence_count_and_tip() + .await?; + let sequence_count = sequence_count.ok_or(ChainCommunicationError::from_other_str( + "Failed to query sequence", + ))?; + let forward_cursor = ForwardSequenceAwareSyncCursor::new( + chunk_size, + latest_sequence_querier.clone(), + db.clone(), + sequence_count, + tip, + mode, + ); + let backward_cursor = + BackwardSequenceAwareSyncCursor::new(chunk_size, db, sequence_count, tip, mode); + Ok(Self { + forward: forward_cursor, + backward: backward_cursor, + last_direction: SyncDirection::Forward, + }) + } +} + +#[async_trait] +impl ContractSyncCursor for ForwardBackwardSequenceAwareSyncCursor { + async fn next_action(&mut self) -> Result<(CursorAction, Duration)> { + // TODO: Proper ETA for backwards sync + let eta = Duration::from_secs(0); + // Prioritize forward syncing over backward syncing. + if let Some(forward_range) = self.forward.get_next_range().await? { + self.last_direction = SyncDirection::Forward; + return Ok((CursorAction::Query(forward_range), eta)); + } + + if let Some(backward_range) = self.backward.get_next_range().await? { + self.last_direction = SyncDirection::Backward; + return Ok((CursorAction::Query(backward_range), eta)); + } + // TODO: Define the sleep time from interval flag + return Ok((CursorAction::Sleep(Duration::from_secs(5)), eta)); + } + + fn latest_queried_block(&self) -> u32 { + self.forward.latest_queried_block() + } + + async fn update(&mut self, logs: Vec<(T, LogMeta)>, range: RangeInclusive) -> Result<()> { + match self.last_direction { + SyncDirection::Forward => self.forward.update(logs, range).await, + SyncDirection::Backward => self.backward.update(logs, range).await, + } + } +} diff --git a/rust/hyperlane-base/src/contract_sync/mod.rs b/rust/hyperlane-base/src/contract_sync/mod.rs index 3968ad9f57..d07aedf36d 100644 --- a/rust/hyperlane-base/src/contract_sync/mod.rs +++ b/rust/hyperlane-base/src/contract_sync/mod.rs @@ -1,22 +1,28 @@ -use std::{fmt::Debug, marker::PhantomData, sync::Arc}; +use std::{ + collections::HashSet, fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc, time::Duration, +}; -use cursor::*; +use cursors::*; use derive_new::new; use hyperlane_core::{ utils::fmt_sync_time, ContractSyncCursor, CursorAction, HyperlaneDomain, HyperlaneLogStore, - HyperlaneSequenceIndexerStore, HyperlaneWatermarkedLogStore, Indexer, SequenceIndexer, - Sequenced, + HyperlaneSequenceAwareIndexerStore, HyperlaneWatermarkedLogStore, Indexer, + SequenceAwareIndexer, Sequenced, }; pub use metrics::ContractSyncMetrics; use tokio::time::sleep; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; use crate::settings::IndexSettings; -mod cursor; +mod cursors; mod eta_calculator; mod metrics; +use cursors::{ForwardBackwardSequenceAwareSyncCursor, ForwardSequenceAwareSyncCursor}; + +const SLEEP_DURATION: Duration = Duration::from_secs(5); + /// Entity that drives the syncing of an agent's db with on-chain data. /// Extracts chain-specific data (emitted checkpoints, messages, etc) from an /// `indexer` and fills the agent's db with this data. @@ -31,7 +37,7 @@ pub struct ContractSync, I: Indexer> { impl ContractSync where - T: Debug + Send + Sync + Clone + 'static, + T: Debug + Send + Sync + Clone + Eq + Hash + 'static, D: HyperlaneLogStore + 'static, I: Indexer + Clone + 'static, { @@ -42,11 +48,7 @@ where /// Sync logs and write them to the LogStore #[tracing::instrument(name = "ContractSync", fields(domain=self.domain().name()), skip(self, cursor))] - pub async fn sync( - &self, - label: &'static str, - mut cursor: Box>, - ) -> eyre::Result<()> { + pub async fn sync(&self, label: &'static str, mut cursor: Box>) { let chain_name = self.domain.as_ref(); let indexed_height = self .metrics @@ -58,15 +60,32 @@ where .with_label_values(&[label, chain_name]); loop { - indexed_height.set(cursor.latest_block() as i64); - let Ok((action, eta)) = cursor.next_action().await else { - continue; + indexed_height.set(cursor.latest_queried_block() as i64); + + let (action, eta) = match cursor.next_action().await { + Ok((action, eta)) => (action, eta), + Err(err) => { + warn!(?err, "Error getting next action"); + sleep(SLEEP_DURATION).await; + continue; + } }; - match action { - CursorAction::Query(range) => { + let sleep_duration = match action { + // Use `loop` but always break - this allows for returning a value + // from the loop (the sleep duration) + #[allow(clippy::never_loop)] + CursorAction::Query(range) => loop { debug!(?range, "Looking for for events in index range"); - let logs = self.indexer.fetch_logs(range.clone()).await?; + let logs = match self.indexer.fetch_logs(range.clone()).await { + Ok(logs) => logs, + Err(err) => { + warn!(?err, "Error fetching logs"); + break SLEEP_DURATION; + } + }; + let deduped_logs = HashSet::<_>::from_iter(logs); + let logs = Vec::from_iter(deduped_logs); info!( ?range, @@ -75,23 +94,32 @@ where "Found log(s) in index range" ); // Store deliveries - let stored = self.db.store_logs(&logs).await?; + let stored = match self.db.store_logs(&logs).await { + Ok(stored) => stored, + Err(err) => { + warn!(?err, "Error storing logs in db"); + break SLEEP_DURATION; + } + }; // Report amount of deliveries stored into db stored_logs.inc_by(stored as u64); // Update cursor - cursor.update(logs).await?; - } - CursorAction::Sleep(duration) => { - sleep(duration).await; - } - } + if let Err(err) = cursor.update(logs, range).await { + warn!(?err, "Error updating cursor"); + break SLEEP_DURATION; + }; + break Default::default(); + }, + CursorAction::Sleep(duration) => duration, + }; + sleep(sleep_duration).await; } } } /// A ContractSync for syncing events using a RateLimitedContractSyncCursor pub type WatermarkContractSync = - ContractSync>, Arc>>; + ContractSync>, Arc>>; impl WatermarkContractSync where T: Debug + Send + Sync + Clone + 'static, @@ -122,23 +150,25 @@ where } /// A ContractSync for syncing messages using a SequenceSyncCursor -pub type SequencedDataContractSync = - ContractSync>, Arc>>; -impl SequencedDataContractSync { +pub type SequencedDataContractSync = ContractSync< + T, + Arc>, + Arc>, +>; +impl SequencedDataContractSync { /// Returns a new cursor to be used for syncing dispatched messages from the indexer pub async fn forward_message_sync_cursor( &self, index_settings: IndexSettings, next_nonce: u32, ) -> Box> { - Box::new(ForwardSequenceSyncCursor::new( - self.indexer.clone(), - self.db.clone(), + Box::new(ForwardSequenceAwareSyncCursor::new( index_settings.chunk_size, - index_settings.from, + self.indexer.clone(), + Arc::new(self.db.clone()), + next_nonce, index_settings.from, index_settings.mode, - next_nonce, )) } @@ -148,9 +178,9 @@ impl SequencedDataContractSync { index_settings: IndexSettings, ) -> Box> { Box::new( - ForwardBackwardSequenceSyncCursor::new( + ForwardBackwardSequenceAwareSyncCursor::new( self.indexer.clone(), - self.db.clone(), + Arc::new(self.db.clone()), index_settings.chunk_size, index_settings.mode, ) diff --git a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs index 807645beb0..acb73ff1fa 100644 --- a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs +++ b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs @@ -5,8 +5,9 @@ use tracing::{debug, instrument, trace}; use hyperlane_core::{ GasPaymentKey, HyperlaneDomain, HyperlaneLogStore, HyperlaneMessage, - HyperlaneSequenceIndexerStore, HyperlaneWatermarkedLogStore, InterchainGasExpenditure, - InterchainGasPayment, InterchainGasPaymentMeta, LogMeta, MerkleTreeInsertion, H256, + HyperlaneSequenceAwareIndexerStoreReader, HyperlaneWatermarkedLogStore, + InterchainGasExpenditure, InterchainGasPayment, InterchainGasPaymentMeta, LogMeta, + MerkleTreeInsertion, H256, }; use super::{ @@ -281,7 +282,7 @@ impl HyperlaneLogStore for HyperlaneRocksDB { } #[async_trait] -impl HyperlaneSequenceIndexerStore for HyperlaneRocksDB { +impl HyperlaneSequenceAwareIndexerStoreReader for HyperlaneRocksDB { /// Gets data by its sequence. async fn retrieve_by_sequence(&self, sequence: u32) -> Result> { let message = self.retrieve_message_by_nonce(sequence)?; @@ -289,14 +290,14 @@ impl HyperlaneSequenceIndexerStore for HyperlaneRocksDB { } /// Gets the block number at which the log occurred. - async fn retrieve_log_block_number(&self, sequence: u32) -> Result> { + async fn retrieve_log_block_number_by_sequence(&self, sequence: u32) -> Result> { let number = self.retrieve_dispatched_block_number_by_nonce(&sequence)?; Ok(number) } } #[async_trait] -impl HyperlaneSequenceIndexerStore for HyperlaneRocksDB { +impl HyperlaneSequenceAwareIndexerStoreReader for HyperlaneRocksDB { /// Gets data by its sequence. async fn retrieve_by_sequence(&self, sequence: u32) -> Result> { let insertion = self.retrieve_merkle_tree_insertion_by_leaf_index(&sequence)?; @@ -304,7 +305,7 @@ impl HyperlaneSequenceIndexerStore for HyperlaneRocksDB { } /// Gets the block number at which the log occurred. - async fn retrieve_log_block_number(&self, sequence: u32) -> Result> { + async fn retrieve_log_block_number_by_sequence(&self, sequence: u32) -> Result> { let number = self.retrieve_merkle_tree_insertion_block_number_by_leaf_index(&sequence)?; Ok(number) } diff --git a/rust/hyperlane-base/src/db/rocks/test_utils.rs b/rust/hyperlane-base/src/db/rocks/test_utils.rs index 5fbeb78df7..3b4639bbd1 100644 --- a/rust/hyperlane-base/src/db/rocks/test_utils.rs +++ b/rust/hyperlane-base/src/db/rocks/test_utils.rs @@ -49,7 +49,7 @@ mod test { let m = HyperlaneMessage { nonce: 100, - version: 0, + version: 3, origin: 10, sender: H256::from_low_u64_be(4), destination: 12, diff --git a/rust/hyperlane-base/src/lib.rs b/rust/hyperlane-base/src/lib.rs index eeb3e58c23..ce6843e583 100644 --- a/rust/hyperlane-base/src/lib.rs +++ b/rust/hyperlane-base/src/lib.rs @@ -12,9 +12,13 @@ pub mod settings; mod agent; pub use agent::*; -mod metrics; +pub mod metrics; pub use metrics::*; +/// Hyperlane server utils +pub mod server; +pub use server::*; + mod contract_sync; pub use contract_sync::*; diff --git a/rust/hyperlane-base/src/metrics/agent_metrics.rs b/rust/hyperlane-base/src/metrics/agent_metrics.rs new file mode 100644 index 0000000000..bc046dde1e --- /dev/null +++ b/rust/hyperlane-base/src/metrics/agent_metrics.rs @@ -0,0 +1,231 @@ +//! Metrics either related to the agents, or observed by them + +use std::sync::Arc; +use std::time::Duration; + +use derive_builder::Builder; +use eyre::Result; +use hyperlane_core::metrics::agent::decimals_by_protocol; +use hyperlane_core::metrics::agent::u256_as_scaled_f64; +use hyperlane_core::metrics::agent::METRICS_SCRAPE_INTERVAL; +use hyperlane_core::HyperlaneDomain; +use hyperlane_core::HyperlaneProvider; +use maplit::hashmap; +use prometheus::GaugeVec; +use prometheus::IntGaugeVec; +use tokio::{task::JoinHandle, time::MissedTickBehavior}; +use tracing::info_span; +use tracing::{debug, instrument::Instrumented, trace, warn, Instrument}; + +use crate::settings::ChainConf; +use crate::CoreMetrics; + +/// Expected label names for the `wallet_balance` metric. +pub const WALLET_BALANCE_LABELS: &[&str] = &[ + "chain", + "wallet_address", + "wallet_name", + "token_address", + "token_symbol", + "token_name", +]; +/// Help string for the metric. +pub const WALLET_BALANCE_HELP: &str = + "Current native token balance for the wallet addresses in the `wallets` set"; + +/// Expected label names for the `block_height` metric. +pub const BLOCK_HEIGHT_LABELS: &[&str] = &["chain"]; +/// Help string for the metric. +pub const BLOCK_HEIGHT_HELP: &str = "Tracks the current block height of the chain"; + +/// Expected label names for the `gas_price` metric. +pub const GAS_PRICE_LABELS: &[&str] = &["chain"]; +/// Help string for the metric. +pub const GAS_PRICE_HELP: &str = + "Tracks the current gas price of the chain, in the lowest denomination (e.g. wei)"; + +/// Agent-specific metrics +#[derive(Clone, Builder, Debug)] +pub struct AgentMetrics { + /// Current balance of native tokens for the + /// wallet address. + /// - `chain`: the chain name (or chain ID if the name is unknown) of the + /// chain the tx occurred on. + /// - `wallet_address`: Address of the wallet holding the funds. + /// - `wallet_name`: Name of the address holding the funds. + /// - `token_address`: Address of the token. + /// - `token_symbol`: Symbol of the token. + /// - `token_name`: Full name of the token. + #[builder(setter(into, strip_option), default)] + wallet_balance: Option, +} + +pub(crate) fn create_agent_metrics(metrics: &CoreMetrics) -> Result { + Ok(AgentMetricsBuilder::default() + .wallet_balance(metrics.new_gauge( + "wallet_balance", + WALLET_BALANCE_HELP, + WALLET_BALANCE_LABELS, + )?) + .build()?) +} + +/// Chain-specific metrics +#[derive(Clone, Builder, Debug)] +pub struct ChainMetrics { + /// Tracks the current block height of the chain. + /// - `chain`: the chain name (or ID if the name is unknown) of the chain + /// the block number refers to. + #[builder(setter(into))] + pub block_height: IntGaugeVec, + + /// Tracks the current gas price of the chain. Uses the base_fee_per_gas if + /// available or else sets this to none. + /// TODO: use the median of the transactions. + /// - `chain`: the chain name (or chain ID if the name is unknown) of the + /// chain the gas price refers to. + #[builder(setter(into, strip_option), default)] + pub gas_price: Option, +} + +pub(crate) fn create_chain_metrics(metrics: &CoreMetrics) -> Result { + Ok(ChainMetricsBuilder::default() + .block_height(metrics.new_int_gauge( + "block_height", + BLOCK_HEIGHT_HELP, + BLOCK_HEIGHT_LABELS, + )?) + .gas_price(metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?) + .build()?) +} + +/// Configuration for the prometheus middleware. This can be loaded via serde. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))] +pub struct AgentMetricsConf { + /// The account to track + #[cfg_attr(feature = "serde", serde(default))] + pub address: Option, + + /// Information about the chain this metric is for + pub domain: HyperlaneDomain, + + /// Name of the agent the metrics are about + pub name: String, +} + +/// Utility struct to update various metrics using a standalone tokio task +pub struct MetricsUpdater { + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, + conf: AgentMetricsConf, + provider: Box, +} + +impl MetricsUpdater { + /// Creates a new instance of the `MetricsUpdater` + pub async fn new( + chain_conf: &ChainConf, + core_metrics: Arc, + agent_metrics: AgentMetrics, + chain_metrics: ChainMetrics, + agent_name: String, + ) -> Result { + let agent_metrics_conf = chain_conf.agent_metrics_conf(agent_name).await?; + let provider = chain_conf.build_provider(&core_metrics).await?; + + Ok(Self { + agent_metrics, + chain_metrics, + conf: agent_metrics_conf, + provider, + }) + } + + async fn update_agent_metrics(&self) { + let Some(wallet_addr) = self.conf.address.clone() else { + return; + }; + let wallet_name = self.conf.name.clone(); + let Some(wallet_balance_metric) = self.agent_metrics.wallet_balance.clone() else { + return; + }; + let chain = self.conf.domain.name(); + + match self.provider.get_balance(wallet_addr.clone()).await { + Ok(balance) => { + let balance = u256_as_scaled_f64(balance, self.conf.domain.domain_protocol()); + trace!("Wallet {wallet_name} ({wallet_addr}) on chain {chain} balance is {balance} of the native currency"); + wallet_balance_metric + .with(&hashmap! { + "chain" => chain, + "wallet_address" => wallet_addr.as_str(), + "wallet_name" => wallet_name.as_str(), + "token_address" => "none", + // Note: Whatever this `chain`'s native currency is + "token_symbol" => "Native", + "token_name" => "Native" + }).set(balance) + }, + Err(e) => warn!("Metric update failed for wallet {wallet_name} ({wallet_addr}) on chain {chain} balance for native currency; {e}") + } + } + + async fn update_block_details(&self) { + let block_height = self.chain_metrics.block_height.clone(); + let gas_price = self.chain_metrics.gas_price.clone(); + if let HyperlaneDomain::Unknown { .. } = self.conf.domain { + return; + }; + let chain = self.conf.domain.name(); + debug!(?chain, "Updating metrics"); + let chain_metrics = match self.provider.get_chain_metrics().await { + Ok(Some(chain_metrics)) => chain_metrics, + Err(err) => { + trace!(?chain, ?err, "Failed to get chain metrics"); + return; + } + // This is the case hit by chains with an empty impl, no need to log an error + _ => return, + }; + + let height = chain_metrics.latest_block.number as i64; + trace!("Block height for chain {chain} is {height}"); + block_height + .with(&hashmap! { "chain" => chain }) + .set(height); + if let Some(gas_price) = gas_price { + let protocol = self.conf.domain.domain_protocol(); + let decimals_scale = 10f64.powf(decimals_by_protocol(protocol).into()); + let gas = u256_as_scaled_f64(chain_metrics.min_gas_price.unwrap_or_default(), protocol) + * decimals_scale; + trace!( + ?chain, + gas = format!("{gas:.2}"), + "Gas price updated for chain (using lowest denomination)" + ); + gas_price.with(&hashmap! { "chain" => chain }).set(gas); + } + } + + /// Periodically updates the metrics + pub async fn start_updating_on_interval(self, period: Duration) { + let mut interval = tokio::time::interval(period); + interval.set_missed_tick_behavior(MissedTickBehavior::Skip); + loop { + self.update_agent_metrics().await; + self.update_block_details().await; + interval.tick().await; + } + } + + /// Spawns a tokio task to update the metrics + pub fn spawn(self) -> Instrumented> { + tokio::spawn(async move { + self.start_updating_on_interval(METRICS_SCRAPE_INTERVAL) + .await; + }) + .instrument(info_span!("MetricsUpdater")) + } +} diff --git a/rust/hyperlane-base/src/metrics/core.rs b/rust/hyperlane-base/src/metrics/core.rs index a0c40d8a8e..447c6f2f45 100644 --- a/rust/hyperlane-base/src/metrics/core.rs +++ b/rust/hyperlane-base/src/metrics/core.rs @@ -1,16 +1,16 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Formatter}; -use std::sync::{Arc, OnceLock}; +use std::sync::OnceLock; use eyre::Result; +use hyperlane_core::{HyperlaneDomain, H160}; use prometheus::{ histogram_opts, labels, opts, register_counter_vec_with_registry, register_gauge_vec_with_registry, register_histogram_vec_with_registry, register_int_counter_vec_with_registry, register_int_gauge_vec_with_registry, CounterVec, Encoder, GaugeVec, HistogramVec, IntCounterVec, IntGaugeVec, Registry, }; -use tokio::task::JoinHandle; -use tracing::warn; +use tokio::sync::RwLock; use ethers_prometheus::{json_rpc_client::JsonRpcClientMetrics, middleware::MiddlewareMetrics}; @@ -37,7 +37,6 @@ pub struct CoreMetrics { span_counts: IntCounterVec, span_events: IntCounterVec, last_known_message_nonce: IntGaugeVec, - validator_checkpoint_index: IntGaugeVec, submitter_queue_length: IntGaugeVec, operations_processed_count: IntCounterVec, @@ -51,6 +50,9 @@ pub struct CoreMetrics { /// Set of provider-specific metrics. These only need to get created once. provider_metrics: OnceLock, + + /// Metrics that are used to observe validator sets. + pub validator_metrics: ValidatorObservabilityMetricManager, } impl CoreMetrics { @@ -109,13 +111,18 @@ impl CoreMetrics { registry )?; - let validator_checkpoint_index = register_int_gauge_vec_with_registry!( + let observed_validator_latest_index = register_int_gauge_vec_with_registry!( opts!( - namespaced!("validator_checkpoint_index"), - "Observed signed checkpoint indices per validator", + namespaced!("observed_validator_latest_index"), + "The latest observed latest signed checkpoint indices per validator, from the perspective of the relayer", const_labels_ref ), - &["origin", "validator"], + &[ + "origin", + "destination", + "validator", + "app_context", + ], registry )?; @@ -169,7 +176,6 @@ impl CoreMetrics { span_counts, span_events, last_known_message_nonce, - validator_checkpoint_index, submitter_queue_length, @@ -180,6 +186,10 @@ impl CoreMetrics { json_rpc_client_metrics: OnceLock::new(), provider_metrics: OnceLock::new(), + + validator_metrics: ValidatorObservabilityMetricManager::new( + observed_validator_latest_index.clone(), + ), }) } @@ -298,14 +308,6 @@ impl CoreMetrics { self.last_known_message_nonce.clone() } - /// Gauge for reporting the most recent validator checkpoint index - /// Labels: - /// - `origin`: Origin chain - /// - `validator`: Address of the validator - pub fn validator_checkpoint_index(&self) -> IntGaugeVec { - self.validator_checkpoint_index.clone() - } - /// Latest message nonce in the validator. /// /// Phase: @@ -411,41 +413,6 @@ impl CoreMetrics { Ok(out_buf) } - /// Run an HTTP server serving OpenMetrics format reports on `/metrics` - /// - /// This is compatible with Prometheus, which ought to be configured to - /// scrape me! - pub fn run_http_server(self: Arc) -> JoinHandle<()> { - use warp::Filter; - let port = self.listen_port; - tracing::info!(port, "starting prometheus server on 0.0.0.0"); - tokio::spawn(async move { - warp::serve( - warp::path!("metrics") - .map(move || { - warp::reply::with_header( - self.gather().expect("failed to encode metrics"), - "Content-Type", - // OpenMetrics specs demands "application/openmetrics-text; - // version=1.0.0; charset=utf-8" - // but the prometheus scraper itself doesn't seem to care? - // try text/plain to make web browsers happy. - "text/plain; charset=utf-8", - ) - }) - .or(warp::any().map(|| { - warp::reply::with_status( - "go look at /metrics", - warp::http::StatusCode::NOT_FOUND, - ) - })), - ) - .try_bind(([0, 0, 0, 0], port)) - .await; - warn!("Prometheus server could not be started or exited early"); - }) - } - /// Get the name of this agent, e.g. "relayer" pub fn agent_name(&self) -> &str { &self.agent_name @@ -457,6 +424,21 @@ impl CoreMetrics { .map(|(k, v)| (k.as_str(), v.as_str())) .collect() } + + /// Get the difference between the latest observed checkpoint and the latest signed checkpoint. + /// + /// This is useful for reporting the health of the validator and reporting it via EigenNodeAPI + pub fn get_latest_checkpoint_validator_delta(&self, origin_chain: HyperlaneDomain) -> i64 { + let observed_checkpoint = self + .latest_checkpoint() + .with_label_values(&["validator_observed", origin_chain.name()]) + .get(); + let signed_checkpoint = self + .latest_checkpoint() + .with_label_values(&["validator_processed", origin_chain.name()]) + .get(); + observed_checkpoint - signed_checkpoint + } } impl Debug for CoreMetrics { @@ -468,3 +450,97 @@ impl Debug for CoreMetrics { ) } } + +#[derive(Debug, Eq, PartialEq, Hash)] +struct AppContextKey { + origin: HyperlaneDomain, + destination: HyperlaneDomain, + app_context: String, +} + +/// Manages metrics for observing sets of validators. +pub struct ValidatorObservabilityMetricManager { + observed_validator_latest_index: IntGaugeVec, + + app_context_validators: RwLock>>, +} + +impl ValidatorObservabilityMetricManager { + fn new(observed_validator_latest_index: IntGaugeVec) -> Self { + Self { + observed_validator_latest_index, + app_context_validators: RwLock::new(HashMap::new()), + } + } + + /// Updates the metrics with the latest checkpoint index for each validator + /// in a given set. + pub async fn set_validator_latest_checkpoints( + &self, + origin: &HyperlaneDomain, + destination: &HyperlaneDomain, + app_context: String, + latest_checkpoints: &HashMap>, + ) { + let key = AppContextKey { + origin: origin.clone(), + destination: destination.clone(), + app_context: app_context.clone(), + }; + + let mut app_context_validators = self.app_context_validators.write().await; + + // First, clear out all previous metrics for the app context. + // This is necessary because the set of validators may have changed. + if let Some(prev_validators) = app_context_validators.get(&key) { + for validator in prev_validators { + // We unwrap because an error here occurs if the # of labels + // provided is incorrect, and we'd like to loudly fail in e2e if that + // happens. + self.observed_validator_latest_index + .remove_label_values(&[ + origin.as_ref(), + destination.as_ref(), + &format!("0x{:x}", validator).to_lowercase(), + &app_context, + ]) + .unwrap(); + } + } + + let mut set = HashSet::new(); + + // Then set the new metrics and update the cached set of validators. + for (validator, latest_checkpoint) in latest_checkpoints { + self.observed_validator_latest_index + .with_label_values(&[ + origin.as_ref(), + destination.as_ref(), + &format!("0x{:x}", validator).to_lowercase(), + &app_context, + ]) + // If the latest checkpoint is None, set to -1 to indicate that + // the validator did not provide a valid latest checkpoint index. + .set(latest_checkpoint.map(|i| i as i64).unwrap_or(-1)); + set.insert(*validator); + } + app_context_validators.insert(key, set); + } + + /// Gauge for reporting recently observed latest checkpoint indices for validator sets. + /// The entire set for an app context should be updated at once, and it should be updated + /// in a way that is robust to validator set changes. + /// Set to -1 to indicate a validator did not provide a valid latest checkpoint index. + /// Note that it's possible for an app to be using an aggregation ISM of more than one + /// validator set. If these sets are different, there is no label built into the metric + /// to distinguish them. + /// + /// Labels: + /// - `origin`: Origin chain + /// - `destination`: Destination chain + /// - `validator`: Address of the validator + /// - `app_context`: App context for the validator set + pub fn observed_validator_latest_index(&self) -> IntGaugeVec { + self.observed_validator_latest_index.clone() + } +} diff --git a/rust/hyperlane-base/src/metrics/mod.rs b/rust/hyperlane-base/src/metrics/mod.rs index ff30be6dc7..b2b1c6acd9 100644 --- a/rust/hyperlane-base/src/metrics/mod.rs +++ b/rust/hyperlane-base/src/metrics/mod.rs @@ -1,10 +1,14 @@ //! Useful metrics that all agents should track. +pub use self::core::*; + /// The metrics namespace prefix. All metric names will start with `{NAMESPACE}_`. pub const NAMESPACE: &str = "hyperlane"; mod core; -pub use self::core::*; +mod agent_metrics; mod json_rpc_client; mod provider; + +pub use self::agent_metrics::*; diff --git a/rust/hyperlane-base/src/metrics/provider.rs b/rust/hyperlane-base/src/metrics/provider.rs index 86a9fd5602..1694a0e2c9 100644 --- a/rust/hyperlane-base/src/metrics/provider.rs +++ b/rust/hyperlane-base/src/metrics/provider.rs @@ -6,16 +6,6 @@ use crate::CoreMetrics; pub(crate) fn create_provider_metrics(metrics: &CoreMetrics) -> Result { Ok(MiddlewareMetricsBuilder::default() - .block_height(metrics.new_int_gauge( - "block_height", - BLOCK_HEIGHT_HELP, - BLOCK_HEIGHT_LABELS, - )?) - .gas_price_gwei(metrics.new_gauge( - "gas_price_gwei", - GAS_PRICE_GWEI_HELP, - GAS_PRICE_GWEI_LABELS, - )?) .contract_call_duration_seconds(metrics.new_counter( "contract_call_duration_seconds", CONTRACT_CALL_DURATION_SECONDS_HELP, @@ -46,10 +36,5 @@ pub(crate) fn create_provider_metrics(metrics: &CoreMetrics) -> Result, +} + +impl Server { + /// Run an HTTP server serving agent-specific different routes + /// + /// routes: + /// - metrics - serving OpenMetrics format reports on `/metrics` + /// (this is compatible with Prometheus, which ought to be configured to scrape this endpoint) + /// - additional_routes - additional routes to be served by the server as per the specific agent + pub fn run(self: Arc, additional_routes: Vec<(&str, Router)>) -> JoinHandle<()> { + let port = self.listen_port; + tracing::info!(port, "starting server on 0.0.0.0"); + + let core_metrics_clone = self.core_metrics.clone(); + + let mut app = Router::new().route( + "/metrics", + get(move || Self::gather_metrics(core_metrics_clone)), + ); + + for (route, router) in additional_routes { + app = app.nest(route, router); + } + + tokio::spawn(async move { + let addr = SocketAddr::from(([0, 0, 0, 0], port)); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .expect("Failed to start server"); + }) + } + + /// Gather available metrics into an encoded (plaintext, OpenMetrics format) + /// report. + async fn gather_metrics(core_metrics: Arc) -> impl IntoResponse { + tracing::debug!("Traversing route for /metrics endpoint for serving Prometheus metrics"); + match core_metrics.gather() { + Ok(metrics) => { + let metrics = match String::from_utf8(metrics) { + Ok(metrics_string) => metrics_string, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal Server Error".into(), + ) + } + }; + (StatusCode::OK, metrics) + } + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to gather metrics".into(), + ), + } + } +} + +#[cfg(test)] +mod tests { + use prometheus::{Counter, Registry}; + use reqwest; + + use super::*; + + #[tokio::test] + async fn test_metrics_endpoint() { + let mock_registry = Registry::new(); + let counter = Counter::new("expected_metric_content", "test123").unwrap(); + mock_registry.register(Box::new(counter.clone())).unwrap(); + counter.inc(); + + let server = Server::new( + 8080, + Arc::new(CoreMetrics::new("test", 8080, mock_registry).unwrap()), + ); + let server = Arc::new(server); + // Run the server in the background + let _server_task = tokio::spawn(async move { + server.run(vec![]).await.unwrap(); + }); + + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + + let client = reqwest::Client::new(); + let response = client + .get("http://127.0.0.1:8080/metrics") + .send() + .await + .expect("Failed to send request"); + assert!(response.status().is_success()); + + let body = response.text().await.expect("Failed to read response body"); + assert!(body.contains("expected_metric_content")); + } +} diff --git a/rust/hyperlane-base/src/server/mod.rs b/rust/hyperlane-base/src/server/mod.rs new file mode 100644 index 0000000000..032093c794 --- /dev/null +++ b/rust/hyperlane-base/src/server/mod.rs @@ -0,0 +1,2 @@ +mod base_server; +pub use base_server::Server; diff --git a/rust/hyperlane-base/src/settings/base.rs b/rust/hyperlane-base/src/settings/base.rs index 135d80ea0b..2899b99a39 100644 --- a/rust/hyperlane-base/src/settings/base.rs +++ b/rust/hyperlane-base/src/settings/base.rs @@ -4,7 +4,7 @@ use eyre::{eyre, Context, Result}; use futures_util::future::try_join_all; use hyperlane_core::{ Delivery, HyperlaneChain, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, - HyperlaneSequenceIndexerStore, HyperlaneWatermarkedLogStore, InterchainGasPaymaster, + HyperlaneSequenceAwareIndexerStore, HyperlaneWatermarkedLogStore, InterchainGasPaymaster, InterchainGasPayment, Mailbox, MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, ValidatorAnnounce, H256, }; @@ -12,7 +12,7 @@ use hyperlane_core::{ use crate::{ settings::{chains::ChainConf, trace::TracingConfig}, ContractSync, ContractSyncMetrics, CoreMetrics, HyperlaneAgentCore, SequencedDataContractSync, - WatermarkContractSync, + Server, WatermarkContractSync, }; /// Settings. Usually this should be treated as a base config and used as @@ -95,6 +95,11 @@ impl Settings { )?)) } + /// Create the server from the settings given the name of the agent. + pub fn server(&self, core_metrics: Arc) -> Result> { + Ok(Arc::new(Server::new(self.metrics_port, core_metrics))) + } + /// Private to preserve linearity of AgentCore::from_settings -- creating an /// agent consumes the settings. fn clone(&self) -> Self { @@ -184,7 +189,7 @@ impl Settings { build_contract_fns!(build_validator_announce, build_validator_announces -> dyn ValidatorAnnounce); build_contract_fns!(build_provider, build_providers -> dyn HyperlaneProvider); build_indexer_fns!(build_delivery_indexer, build_delivery_indexers -> dyn HyperlaneWatermarkedLogStore, WatermarkContractSync); - build_indexer_fns!(build_message_indexer, build_message_indexers -> dyn HyperlaneSequenceIndexerStore, SequencedDataContractSync); + build_indexer_fns!(build_message_indexer, build_message_indexers -> dyn HyperlaneSequenceAwareIndexerStore, SequencedDataContractSync); build_indexer_fns!(build_interchain_gas_payment_indexer, build_interchain_gas_payment_indexers -> dyn HyperlaneWatermarkedLogStore, WatermarkContractSync); - build_indexer_fns!(build_merkle_tree_hook_indexer, build_merkle_tree_hook_indexers -> dyn HyperlaneSequenceIndexerStore, SequencedDataContractSync); + build_indexer_fns!(build_merkle_tree_hook_indexer, build_merkle_tree_hook_indexers -> dyn HyperlaneSequenceAwareIndexerStore, SequencedDataContractSync); } diff --git a/rust/hyperlane-base/src/settings/chains.rs b/rust/hyperlane-base/src/settings/chains.rs index e1010d5fd8..c8e66f66a6 100644 --- a/rust/hyperlane-base/src/settings/chains.rs +++ b/rust/hyperlane-base/src/settings/chains.rs @@ -1,16 +1,15 @@ use ethers::prelude::Selector; +use h_cosmos::CosmosProvider; use std::collections::HashMap; use eyre::{eyre, Context, Result}; -use ethers_prometheus::middleware::{ - ChainInfo, ContractInfo, PrometheusMiddlewareConf, WalletInfo, -}; +use ethers_prometheus::middleware::{ChainInfo, ContractInfo, PrometheusMiddlewareConf}; use hyperlane_core::{ AggregationIsm, CcipReadIsm, ContractLocator, HyperlaneAbi, HyperlaneDomain, - HyperlaneDomainProtocol, HyperlaneMessage, HyperlaneProvider, HyperlaneSigner, IndexMode, + HyperlaneDomainProtocol, HyperlaneMessage, HyperlaneProvider, IndexMode, InterchainGasPaymaster, InterchainGasPayment, InterchainSecurityModule, Mailbox, - MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, RoutingIsm, SequenceIndexer, + MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, RoutingIsm, SequenceAwareIndexer, ValidatorAnnounce, H256, }; use hyperlane_cosmos as h_cosmos; @@ -22,10 +21,13 @@ use hyperlane_fuel as h_fuel; use hyperlane_sealevel as h_sealevel; use crate::{ - settings::signers::{BuildableWithSignerConf, ChainSigner, SignerConf}, + metrics::AgentMetricsConf, + settings::signers::{BuildableWithSignerConf, SignerConf}, CoreMetrics, }; +use super::ChainSigner; + /// A chain setup is a domain ID, an address on that chain (where the mailbox is /// deployed) and details for connecting to the chain API. #[derive(Clone, Debug)] @@ -83,7 +85,7 @@ pub struct CoreContractAddresses { /// Address of the ValidatorAnnounce contract pub validator_announce: H256, /// Address of the MerkleTreeHook contract - pub merkle_tree_hook: Option, + pub merkle_tree_hook: H256, } /// Indexing settings @@ -109,15 +111,26 @@ impl ChainConf { metrics: &CoreMetrics, ) -> Result> { let ctx = "Building provider"; + let locator = self.locator(H256::zero()); match &self.connection { ChainConnectionConf::Ethereum(conf) => { - let locator = self.locator(H256::zero()); self.build_ethereum(conf, &locator, metrics, h_eth::HyperlaneProviderBuilder {}) .await } ChainConnectionConf::Fuel(_) => todo!(), - ChainConnectionConf::Sealevel(_) => todo!(), - ChainConnectionConf::Cosmos(_) => todo!(), + ChainConnectionConf::Sealevel(conf) => Ok(Box::new(h_sealevel::SealevelProvider::new( + locator.domain.clone(), + conf, + )) as Box), + ChainConnectionConf::Cosmos(conf) => { + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + None, + )?; + Ok(Box::new(provider) as Box) + } } .context(ctx) } @@ -160,13 +173,7 @@ impl ChainConf { metrics: &CoreMetrics, ) -> Result> { let ctx = "Building merkle tree hook"; - // TODO: if the merkle tree hook is set for sealevel, it's still a mailbox program - // that the connection is made to using the pda seeds, which will not be usable. - let address = self - .addresses - .merkle_tree_hook - .unwrap_or(self.addresses.mailbox); - let locator = self.locator(address); + let locator = self.locator(self.addresses.merkle_tree_hook); match &self.connection { ChainConnectionConf::Ethereum(conf) => { @@ -196,7 +203,7 @@ impl ChainConf { pub async fn build_message_indexer( &self, metrics: &CoreMetrics, - ) -> Result>> { + ) -> Result>> { let ctx = "Building delivery indexer"; let locator = self.locator(self.addresses.mailbox); @@ -215,7 +222,7 @@ impl ChainConf { ChainConnectionConf::Fuel(_) => todo!(), ChainConnectionConf::Sealevel(conf) => { let indexer = Box::new(h_sealevel::SealevelMailboxIndexer::new(conf, locator)?); - Ok(indexer as Box>) + Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; @@ -225,7 +232,7 @@ impl ChainConf { signer, self.reorg_period, )?); - Ok(indexer as Box>) + Ok(indexer as Box>) } } .context(ctx) @@ -235,7 +242,7 @@ impl ChainConf { pub async fn build_delivery_indexer( &self, metrics: &CoreMetrics, - ) -> Result>> { + ) -> Result>> { let ctx = "Building delivery indexer"; let locator = self.locator(self.addresses.mailbox); @@ -254,7 +261,7 @@ impl ChainConf { ChainConnectionConf::Fuel(_) => todo!(), ChainConnectionConf::Sealevel(conf) => { let indexer = Box::new(h_sealevel::SealevelMailboxIndexer::new(conf, locator)?); - Ok(indexer as Box>) + Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; @@ -264,7 +271,7 @@ impl ChainConf { signer, self.reorg_period, )?); - Ok(indexer as Box>) + Ok(indexer as Box>) } } .context(ctx) @@ -313,7 +320,7 @@ impl ChainConf { pub async fn build_interchain_gas_payment_indexer( &self, metrics: &CoreMetrics, - ) -> Result>> { + ) -> Result>> { let ctx = "Building IGP indexer"; let locator = self.locator(self.addresses.interchain_gas_paymaster); @@ -335,7 +342,7 @@ impl ChainConf { let indexer = Box::new( h_sealevel::SealevelInterchainGasPaymasterIndexer::new(conf, locator).await?, ); - Ok(indexer as Box>) + Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { let indexer = Box::new(h_cosmos::CosmosInterchainGasPaymasterIndexer::new( @@ -343,7 +350,7 @@ impl ChainConf { locator, self.reorg_period, )?); - Ok(indexer as Box>) + Ok(indexer as Box>) } } .context(ctx) @@ -353,13 +360,9 @@ impl ChainConf { pub async fn build_merkle_tree_hook_indexer( &self, metrics: &CoreMetrics, - ) -> Result>> { + ) -> Result>> { let ctx = "Building merkle tree hook indexer"; - let address = self - .addresses - .merkle_tree_hook - .unwrap_or(self.addresses.mailbox); - let locator = self.locator(address); + let locator = self.locator(self.addresses.merkle_tree_hook); match &self.connection { ChainConnectionConf::Ethereum(conf) => { @@ -374,9 +377,13 @@ impl ChainConf { .await } ChainConnectionConf::Fuel(_) => todo!(), - ChainConnectionConf::Sealevel(_) => { - let indexer = Box::new(h_sealevel::SealevelMerkleTreeHookIndexer::new()); - Ok(indexer as Box>) + ChainConnectionConf::Sealevel(conf) => { + let mailbox_indexer = + Box::new(h_sealevel::SealevelMailboxIndexer::new(conf, locator)?); + let indexer = Box::new(h_sealevel::SealevelMerkleTreeHookIndexer::new( + *mailbox_indexer, + )); + Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; @@ -387,7 +394,7 @@ impl ChainConf { signer, self.reorg_period, )?); - Ok(indexer as Box>) + Ok(indexer as Box>) } } .context(ctx) @@ -639,13 +646,19 @@ impl ChainConf { self.signer().await } + /// Try to build an agent metrics configuration from the chain config + pub async fn agent_metrics_conf(&self, agent_name: String) -> Result { + let chain_signer_address = self.chain_signer().await?.map(|s| s.address_string()); + Ok(AgentMetricsConf { + address: chain_signer_address, + domain: self.domain.clone(), + name: agent_name, + }) + } + /// Get a clone of the ethereum metrics conf with correctly configured /// contract information. - fn metrics_conf( - &self, - agent_name: &str, - signer: &Option, - ) -> PrometheusMiddlewareConf { + pub fn metrics_conf(&self) -> PrometheusMiddlewareConf { let mut cfg = self.metrics_conf.clone(); if cfg.chain.is_none() { @@ -654,14 +667,6 @@ impl ChainConf { }); } - if let Some(signer) = signer { - cfg.wallets - .entry(signer.eth_address().into()) - .or_insert_with(|| WalletInfo { - name: Some(agent_name.into()), - }); - } - let mut register_contract = |name: &str, address: H256, fns: HashMap, String>| { cfg.contracts .entry(address.into()) @@ -689,13 +694,11 @@ impl ChainConf { self.addresses.interchain_gas_paymaster, EthereumInterchainGasPaymasterAbi::fn_map_owned(), ); - if let Some(address) = self.addresses.merkle_tree_hook { - register_contract( - "merkle_tree_hook", - address, - EthereumInterchainGasPaymasterAbi::fn_map_owned(), - ); - } + register_contract( + "merkle_tree_hook", + self.addresses.merkle_tree_hook, + EthereumInterchainGasPaymasterAbi::fn_map_owned(), + ); cfg } @@ -718,7 +721,7 @@ impl ChainConf { B: BuildableWithProvider + Sync, { let signer = self.ethereum_signer().await?; - let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer); + let metrics_conf = self.metrics_conf(); let rpc_metrics = Some(metrics.json_rpc_client_metrics()); let middleware_metrics = Some((metrics.provider_metrics(), metrics_conf)); let res = builder diff --git a/rust/hyperlane-base/src/settings/checkpoint_syncer.rs b/rust/hyperlane-base/src/settings/checkpoint_syncer.rs index 342f44732c..cb4fa1c8d6 100644 --- a/rust/hyperlane-base/src/settings/checkpoint_syncer.rs +++ b/rust/hyperlane-base/src/settings/checkpoint_syncer.rs @@ -1,12 +1,13 @@ +use crate::{ + CheckpointSyncer, GcsStorageClientBuilder, LocalStorage, S3Storage, GCS_SERVICE_ACCOUNT_KEY, + GCS_USER_SECRET, +}; use core::str::FromStr; -use std::{collections::HashMap, path::PathBuf}; - use eyre::{eyre, Context, Report, Result}; -use hyperlane_core::H160; -use prometheus::{IntGauge, IntGaugeVec}; +use prometheus::IntGauge; use rusoto_core::Region; - -use crate::{CheckpointSyncer, LocalStorage, MultisigCheckpointSyncer, S3Storage}; +use std::{env, path::PathBuf}; +use ya_gcp::{AuthFlow, ServiceAccountAuth}; /// Checkpoint Syncer types #[derive(Debug, Clone)] @@ -25,6 +26,18 @@ pub enum CheckpointSyncerConf { /// S3 Region region: Region, }, + /// A checkpoint syncer on Google Cloud Storage + Gcs { + /// Bucket name + bucket: String, + /// Folder name inside bucket - defaults to the root of the bucket + folder: Option, + /// A path to the oauth service account key json file. + service_account_key: Option, + /// Path to oauth user secrets, like those created by + /// `gcloud auth application-default login` + user_secrets: Option, + }, } impl FromStr for CheckpointSyncerConf { @@ -55,6 +68,28 @@ impl FromStr for CheckpointSyncerConf { "file" => Ok(CheckpointSyncerConf::LocalStorage { path: suffix.into(), }), + // for google cloud both options (with or without folder) from str are for anonymous access only + // or env variables parsing + "gs" => { + let service_account_key = env::var(GCS_SERVICE_ACCOUNT_KEY).ok(); + let user_secrets = env::var(GCS_USER_SECRET).ok(); + if let Some(ind) = suffix.find('/') { + let (bucket, folder) = suffix.split_at(ind); + Ok(Self::Gcs { + bucket: bucket.into(), + folder: Some(folder.into()), + service_account_key, + user_secrets, + }) + } else { + Ok(Self::Gcs { + bucket: suffix.into(), + folder: None, + service_account_key, + user_secrets, + }) + } + } _ => Err(eyre!("Unknown storage location prefix `{prefix}`")), } } @@ -62,7 +97,7 @@ impl FromStr for CheckpointSyncerConf { impl CheckpointSyncerConf { /// Turn conf info a Checkpoint Syncer - pub fn build( + pub async fn build( &self, latest_index_gauge: Option, ) -> Result, Report> { @@ -80,34 +115,27 @@ impl CheckpointSyncerConf { region.clone(), latest_index_gauge, )), - }) - } -} - -/// Config for a MultisigCheckpointSyncer -#[derive(Debug, Clone)] -pub struct MultisigCheckpointSyncerConf { - /// The checkpoint syncer for each valid validator signer address - checkpointsyncers: HashMap, -} + CheckpointSyncerConf::Gcs { + bucket, + folder, + service_account_key, + user_secrets, + } => { + let auth = if let Some(path) = service_account_key { + AuthFlow::ServiceAccount(ServiceAccountAuth::Path(path.into())) + } else if let Some(path) = user_secrets { + AuthFlow::UserAccount(path.into()) + } else { + // Public data access only - no `insert` + AuthFlow::NoAuth + }; -impl MultisigCheckpointSyncerConf { - /// Get a MultisigCheckpointSyncer from the config - pub fn build( - &self, - origin: &str, - validator_checkpoint_index: IntGaugeVec, - ) -> Result { - let mut checkpoint_syncers = HashMap::new(); - for (key, value) in self.checkpointsyncers.iter() { - let gauge = - validator_checkpoint_index.with_label_values(&[origin, &key.to_lowercase()]); - if let Ok(conf) = value.build(Some(gauge)) { - checkpoint_syncers.insert(H160::from_str(key)?, conf.into()); - } else { - continue; + Box::new( + GcsStorageClientBuilder::new(auth) + .build(bucket, folder.to_owned()) + .await?, + ) } - } - Ok(MultisigCheckpointSyncer::new(checkpoint_syncers)) + }) } } diff --git a/rust/hyperlane-base/src/settings/mod.rs b/rust/hyperlane-base/src/settings/mod.rs index b999b8a323..e809444328 100644 --- a/rust/hyperlane-base/src/settings/mod.rs +++ b/rust/hyperlane-base/src/settings/mod.rs @@ -14,8 +14,7 @@ //! each configured chain. //! //! All agents share the [`Settings`] struct in this crate, and then define any -//! additional `Settings` in their own crate. By convention this is done in -//! `settings.rs` using the [`decl_settings!`] macro. +//! additional `Settings` in their own crate. //! //! ### Configuration //! diff --git a/rust/hyperlane-base/src/settings/parser/connection_parser.rs b/rust/hyperlane-base/src/settings/parser/connection_parser.rs index b7a0a1244d..0d47d6eca9 100644 --- a/rust/hyperlane-base/src/settings/parser/connection_parser.rs +++ b/rust/hyperlane-base/src/settings/parser/connection_parser.rs @@ -6,7 +6,7 @@ use url::Url; use crate::settings::envs::*; use crate::settings::ChainConnectionConf; -use super::ValueParser; +use super::{parse_base_and_override_urls, parse_cosmos_gas_price, ValueParser}; pub fn build_ethereum_connection_conf( rpcs: &[Url], @@ -43,19 +43,8 @@ pub fn build_cosmos_connection_conf( err: &mut ConfigParsingError, ) -> Option { let mut local_err = ConfigParsingError::default(); - - let grpc_url = chain - .chain(&mut local_err) - .get_key("grpcUrl") - .parse_string() - .end() - .or_else(|| { - local_err.push( - &chain.cwp + "grpc_url", - eyre!("Missing grpc definitions for chain"), - ); - None - }); + let grpcs = + parse_base_and_override_urls(chain, "grpcUrls", "customGrpcUrls", "http", &mut local_err); let chain_id = chain .chain(&mut local_err) @@ -69,11 +58,14 @@ pub fn build_cosmos_connection_conf( let prefix = chain .chain(err) - .get_key("prefix") + .get_key("bech32Prefix") .parse_string() .end() .or_else(|| { - local_err.push(&chain.cwp + "prefix", eyre!("Missing prefix for chain")); + local_err.push( + &chain.cwp + "bech32Prefix", + eyre!("Missing bech32 prefix for chain"), + ); None }); @@ -94,16 +86,30 @@ pub fn build_cosmos_connection_conf( None }; + let gas_price = chain + .chain(err) + .get_opt_key("gasPrice") + .and_then(parse_cosmos_gas_price) + .end(); + + let contract_address_bytes = chain + .chain(err) + .get_opt_key("contractAddressBytes") + .parse_u64() + .end(); + if !local_err.is_ok() { err.merge(local_err); None } else { Some(ChainConnectionConf::Cosmos(h_cosmos::ConnectionConf::new( - grpc_url.unwrap().to_string(), + grpcs, rpcs.first().unwrap().to_string(), chain_id.unwrap().to_string(), prefix.unwrap().to_string(), canonical_asset.unwrap(), + gas_price.unwrap(), + contract_address_bytes.unwrap().try_into().unwrap(), ))) } } diff --git a/rust/hyperlane-base/src/settings/parser/mod.rs b/rust/hyperlane-base/src/settings/parser/mod.rs index 76ed1d5944..3bfb52f91f 100644 --- a/rust/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/hyperlane-base/src/settings/parser/mod.rs @@ -11,12 +11,14 @@ use std::{ use convert_case::{Case, Casing}; use eyre::{eyre, Context}; +use h_cosmos::RawCosmosAmount; use hyperlane_core::{ cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol, IndexMode, }; use itertools::Itertools; use serde::Deserialize; use serde_json::Value; +use url::Url; pub use self::json_value_parser::ValueParser; pub use super::envs::*; @@ -133,47 +135,7 @@ fn parse_chain( .parse_u32() .unwrap_or(1); - let rpcs_base = chain - .chain(&mut err) - .get_key("rpcUrls") - .into_array_iter() - .map(|urls| { - urls.filter_map(|v| { - v.chain(&mut err) - .get_key("http") - .parse_from_str("Invalid http url") - .end() - }) - .collect_vec() - }) - .unwrap_or_default(); - - let rpc_overrides = chain - .chain(&mut err) - .get_opt_key("customRpcUrls") - .parse_string() - .end() - .map(|urls| { - urls.split(',') - .filter_map(|url| { - url.parse() - .take_err(&mut err, || &chain.cwp + "customRpcUrls") - }) - .collect_vec() - }); - - let rpcs = rpc_overrides.unwrap_or(rpcs_base); - - if rpcs.is_empty() { - err.push( - &chain.cwp + "rpc_urls", - eyre!("Missing base rpc definitions for chain"), - ); - err.push( - &chain.cwp + "custom_rpc_urls", - eyre!("Also missing rpc overrides for chain"), - ); - } + let rpcs = parse_base_and_override_urls(&chain, "rpcUrls", "customRpcUrls", "http", &mut err); let from = chain .chain(&mut err) @@ -220,7 +182,7 @@ fn parse_chain( .end(); let merkle_tree_hook = chain .chain(&mut err) - .get_opt_key("merkleTreeHook") + .get_key("merkleTreeHook") .parse_address_hash() .end(); @@ -233,7 +195,7 @@ fn parse_chain( default_rpc_consensus_type, ); - cfg_unwrap_all!(&chain.cwp, err: [connection, mailbox, interchain_gas_paymaster, validator_announce]); + cfg_unwrap_all!(&chain.cwp, err: [connection, mailbox, interchain_gas_paymaster, validator_announce, merkle_tree_hook]); err.into_result(ChainConf { domain, signer, @@ -398,3 +360,85 @@ pub fn recase_json_value(mut val: Value, case: Case) -> Value { } val } + +/// Expects AgentSigner. +fn parse_cosmos_gas_price(gas_price: ValueParser) -> ConfigResult { + let mut err = ConfigParsingError::default(); + + let amount = gas_price + .chain(&mut err) + .get_opt_key("amount") + .parse_string() + .end(); + + let denom = gas_price + .chain(&mut err) + .get_opt_key("denom") + .parse_string() + .end(); + cfg_unwrap_all!(&gas_price.cwp, err: [denom, amount]); + err.into_result(RawCosmosAmount::new(denom.to_owned(), amount.to_owned())) +} + +fn parse_urls( + chain: &ValueParser, + key: &str, + protocol: &str, + err: &mut ConfigParsingError, +) -> Vec { + chain + .chain(err) + .get_key(key) + .into_array_iter() + .map(|urls| { + urls.filter_map(|v| { + v.chain(err) + .get_key(protocol) + .parse_from_str("Invalid url") + .end() + }) + .collect_vec() + }) + .unwrap_or_default() +} + +fn parse_custom_urls( + chain: &ValueParser, + key: &str, + err: &mut ConfigParsingError, +) -> Option> { + chain + .chain(err) + .get_opt_key(key) + .parse_string() + .end() + .map(|urls| { + urls.split(',') + .filter_map(|url| url.parse().take_err(err, || &chain.cwp + "customGrpcUrls")) + .collect_vec() + }) +} + +fn parse_base_and_override_urls( + chain: &ValueParser, + base_key: &str, + override_key: &str, + protocol: &str, + err: &mut ConfigParsingError, +) -> Vec { + let base = parse_urls(chain, base_key, protocol, err); + let overrides = parse_custom_urls(chain, override_key, err); + let combined = overrides.unwrap_or(base); + + if combined.is_empty() { + err.push( + &chain.cwp + "rpc_urls", + eyre!("Missing base rpc definitions for chain"), + ); + err.push( + &chain.cwp + "custom_rpc_urls", + eyre!("Also missing rpc overrides for chain"), + ); + } + combined +} diff --git a/rust/hyperlane-base/src/settings/signers.rs b/rust/hyperlane-base/src/settings/signers.rs index 2979488544..d0642434f5 100644 --- a/rust/hyperlane-base/src/settings/signers.rs +++ b/rust/hyperlane-base/src/settings/signers.rs @@ -1,16 +1,16 @@ -use std::time::Duration; - use async_trait::async_trait; use ed25519_dalek::SecretKey; use ethers::prelude::{AwsSigner, LocalWallet}; +use ethers::utils::hex::ToHex; use eyre::{bail, Context, Report}; use hyperlane_core::H256; use hyperlane_sealevel::Keypair; -use rusoto_core::{HttpClient, HttpConfig, Region}; +use rusoto_core::Region; use rusoto_kms::KmsClient; use tracing::instrument; use super::aws_credentials::AwsChainCredentialsProvider; +use crate::types::utils; /// Signer types #[derive(Default, Debug, Clone)] @@ -72,13 +72,10 @@ impl BuildableWithSignerConf for hyperlane_ethereum::Signers { ), )), SignerConf::Aws { id, region } => { - let mut config = HttpConfig::new(); - // see https://github.com/hyperium/hyper/issues/2136#issuecomment-589345238 - config.pool_idle_timeout(Duration::from_secs(20)); let client = KmsClient::new_with_client( rusoto_core::Client::new_with( AwsChainCredentialsProvider::new(), - HttpClient::new_with_config(config).unwrap(), + utils::http_client_with_timeout().unwrap(), ), region.clone(), ); @@ -96,7 +93,7 @@ impl BuildableWithSignerConf for hyperlane_ethereum::Signers { impl ChainSigner for hyperlane_ethereum::Signers { fn address_string(&self) -> String { - ethers::abi::AbiEncode::encode_hex(ethers::signers::Signer::address(self)) + ethers::signers::Signer::address(self).encode_hex() } } diff --git a/rust/hyperlane-base/src/types/gcs_storage.rs b/rust/hyperlane-base/src/types/gcs_storage.rs new file mode 100644 index 0000000000..ebd1589a4f --- /dev/null +++ b/rust/hyperlane-base/src/types/gcs_storage.rs @@ -0,0 +1,204 @@ +use crate::CheckpointSyncer; +use async_trait::async_trait; +use derive_new::new; +use eyre::{bail, Result}; +use hyperlane_core::{SignedAnnouncement, SignedCheckpointWithMessageId}; +use std::fmt; +use ya_gcp::{storage::StorageClient, AuthFlow, ClientBuilder, ClientBuilderConfig}; + +const LATEST_INDEX_KEY: &str = "gcsLatestIndexKey"; +const ANNOUNCEMENT_KEY: &str = "gcsAnnouncementKey"; +/// Path to GCS users_secret file +pub const GCS_USER_SECRET: &str = "GCS_USER_SECRET"; +/// Path to GCS Service account key +pub const GCS_SERVICE_ACCOUNT_KEY: &str = "GCS_SERVICE_ACCOUNT_KEY"; + +/// Google Cloud Storage client builder +/// Provide `AuthFlow::NoAuth` for no-auth access to public bucket +/// # Example 1 - anonymous client with access to public bucket +/// ``` +/// use hyperlane_base::GcsStorageClientBuilder; +/// use ya_gcp::AuthFlow; +/// # #[tokio::main] +/// # async fn main() { +/// let client = GcsStorageClientBuilder::new(AuthFlow::NoAuth) +/// .build("HyperlaneBucket", None) +/// .await.expect("failed to instantiate anonymous client"); +/// # } +///``` +/// +/// For authenticated write access to bucket proper file path must be provided. +/// # WARN: panic-s if file path is incorrect or data in it as faulty +/// +/// # Example 2 - service account key +/// ```should_panic +/// use hyperlane_base::GcsStorageClientBuilder; +/// use ya_gcp::{AuthFlow, ServiceAccountAuth}; +/// # #[tokio::main] +/// # async fn main() { +/// let auth = +/// AuthFlow::ServiceAccount(ServiceAccountAuth::Path("path/to/sac.json".into())); +/// +/// let client = GcsStorageClientBuilder::new(auth) +/// .build("HyperlaneBucket", None) +/// .await.expect("failed to instantiate anonymous client"); +/// # } +///``` +/// # Example 3 - user secret access +/// ```should_panic +/// use hyperlane_base::GcsStorageClientBuilder; +/// use ya_gcp::AuthFlow; +/// # #[tokio::main] +/// # async fn main() { +/// let auth = +/// AuthFlow::UserAccount("path/to/user_secret.json".into()); +/// +/// let client = GcsStorageClientBuilder::new(auth) +/// .build("HyperlaneBucket", None) +/// .await.expect("failed to instantiate anonymous client"); +/// # } +///``` +#[derive(Debug, new)] +pub struct GcsStorageClientBuilder { + auth: AuthFlow, +} + +/// Google Cloud Storage client +/// Enables use of any of service account key OR user secrets to authenticate +/// For anonymous access to public data provide `(None, None)` to Builder +pub struct GcsStorageClient { + // GCS storage client + // # Details: + inner: StorageClient, + // bucket name of this client's storage + bucket: String, +} + +impl GcsStorageClientBuilder { + /// Instantiates `ya_gcp:StorageClient` based on provided auth method + /// # Param + /// * `baucket_name` - String name of target bucket to work with, will be used by all store and get ops + pub async fn build( + self, + bucket_name: impl Into, + folder: Option, + ) -> Result { + let inner = ClientBuilder::new(ClientBuilderConfig::new().auth_flow(self.auth)) + .await? + .build_storage_client(); + let bucket = if let Some(folder) = folder { + format! {"{}/{}", bucket_name.into(), folder} + } else { + bucket_name.into() + }; + + Ok(GcsStorageClient { inner, bucket }) + } +} + +impl GcsStorageClient { + // convinience formatter + fn get_checkpoint_key(index: u32) -> String { + format!("checkpoint_{index}_with_id.json") + } + // #test only method[s] + #[cfg(test)] + pub(crate) async fn get_by_path(&self, path: impl AsRef) -> Result<()> { + self.inner.get_object(&self.bucket, path).await?; + Ok(()) + } +} + +// required by `CheckpointSyncer` +impl fmt::Debug for GcsStorageClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("S3Storage") + .field("bucket", &self.bucket) + .finish() + } +} + +#[async_trait] +impl CheckpointSyncer for GcsStorageClient { + /// Read the highest index of this Syncer + async fn latest_index(&self) -> Result> { + match self.inner.get_object(&self.bucket, LATEST_INDEX_KEY).await { + Ok(data) => Ok(Some(serde_json::from_slice(data.as_ref())?)), + Err(e) => match e { + // never written before to this bucket + ya_gcp::storage::ObjectError::InvalidName(_) => Ok(None), + _ => bail!(e), + }, + } + } + + /// Writes the highest index of this Syncer + async fn write_latest_index(&self, index: u32) -> Result<()> { + let d = serde_json::to_vec(&index)?; + self.inner + .insert_object(&self.bucket, LATEST_INDEX_KEY, d) + .await?; + Ok(()) + } + + /// Update the latest index of this syncer if necessary + async fn update_latest_index(&self, index: u32) -> Result<()> { + let curr = self.latest_index().await?.unwrap_or(0); + if index > curr { + self.write_latest_index(index).await?; + } + Ok(()) + } + + /// Attempt to fetch the signed (checkpoint, messageId) tuple at this index + async fn fetch_checkpoint(&self, index: u32) -> Result> { + let res = self + .inner + .get_object(&self.bucket, GcsStorageClient::get_checkpoint_key(index)) + .await?; + Ok(Some(serde_json::from_slice(res.as_ref())?)) + } + + /// Write the signed (checkpoint, messageId) tuple to this syncer + async fn write_checkpoint( + &self, + signed_checkpoint: &SignedCheckpointWithMessageId, + ) -> Result<()> { + self.inner + .insert_object( + &self.bucket, + GcsStorageClient::get_checkpoint_key(signed_checkpoint.value.index), + serde_json::to_vec(signed_checkpoint)?, + ) + .await?; + Ok(()) + } + + /// Write the signed announcement to this syncer + async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()> { + self.inner + .insert_object( + &self.bucket, + ANNOUNCEMENT_KEY, + serde_json::to_string(signed_announcement)?, + ) + .await?; + Ok(()) + } + + /// Return the announcement storage location for this syncer + fn announcement_location(&self) -> String { + format!("gs://{}/{}", &self.bucket, ANNOUNCEMENT_KEY) + } +} + +#[tokio::test] +async fn public_landset_no_auth_works_test() { + const LANDSAT_BUCKET: &str = "gcp-public-data-landsat"; + const LANDSAT_KEY: &str = "LC08/01/001/003/LC08_L1GT_001003_20140812_20170420_01_T2/LC08_L1GT_001003_20140812_20170420_01_T2_B3.TIF"; + let client = GcsStorageClientBuilder::new(AuthFlow::NoAuth) + .build(LANDSAT_BUCKET, None) + .await + .unwrap(); + assert!(client.get_by_path(LANDSAT_KEY).await.is_ok()); +} diff --git a/rust/hyperlane-base/src/types/mod.rs b/rust/hyperlane-base/src/types/mod.rs index f1e23c773c..1f6b3b792f 100644 --- a/rust/hyperlane-base/src/types/mod.rs +++ b/rust/hyperlane-base/src/types/mod.rs @@ -1,7 +1,12 @@ +mod gcs_storage; mod local_storage; mod multisig; mod s3_storage; +/// Reusable logic for working with storage backends. +pub mod utils; + +pub use gcs_storage::*; pub use local_storage::*; pub use multisig::*; pub use s3_storage::*; diff --git a/rust/hyperlane-base/src/types/multisig.rs b/rust/hyperlane-base/src/types/multisig.rs index 4fd60f7985..dc7878af39 100644 --- a/rust/hyperlane-base/src/types/multisig.rs +++ b/rust/hyperlane-base/src/types/multisig.rs @@ -5,19 +5,76 @@ use derive_new::new; use eyre::Result; use tracing::{debug, instrument}; -use hyperlane_core::{MultisigSignedCheckpoint, SignedCheckpointWithMessageId, H160, H256}; +use hyperlane_core::{ + HyperlaneDomain, MultisigSignedCheckpoint, SignedCheckpointWithMessageId, H160, H256, +}; -use crate::CheckpointSyncer; +use crate::{CheckpointSyncer, CoreMetrics}; -/// Fetches signed checkpoints from multiple validators to create -/// MultisigSignedCheckpoints +/// For a particular validator set, fetches signed checkpoints from multiple +/// validators to create MultisigSignedCheckpoints. #[derive(Clone, Debug, new)] pub struct MultisigCheckpointSyncer { /// The checkpoint syncer for each valid validator signer address checkpoint_syncers: HashMap>, + metrics: Arc, + app_context: Option, } impl MultisigCheckpointSyncer { + /// Gets the latest checkpoint index from each validator's checkpoint syncer. + /// Returns a vector of the latest indices, in an unspecified order, and does + /// not contain indices for validators that did not provide a latest index. + /// Also updates the validator latest checkpoint metrics. + pub async fn get_validator_latest_checkpoints_and_update_metrics( + &self, + validators: &[H256], + origin: &HyperlaneDomain, + destination: &HyperlaneDomain, + ) -> Vec { + // Get the latest_index from each validator's checkpoint syncer. + // If a validator does not return a latest index, None is recorded so + // this can be surfaced in the metrics. + let mut latest_indices: HashMap> = + HashMap::with_capacity(validators.len()); + + for validator in validators { + let address = H160::from(*validator); + if let Some(checkpoint_syncer) = self.checkpoint_syncers.get(&address) { + // Gracefully handle errors getting the latest_index + match checkpoint_syncer.latest_index().await { + Ok(Some(index)) => { + debug!(?address, ?index, "Validator returned latest index"); + latest_indices.insert(H160::from(*validator), Some(index)); + } + result => { + debug!( + ?address, + ?result, + "Failed to get latest index from validator" + ); + latest_indices.insert(H160::from(*validator), None); + } + } + } + } + + if let Some(app_context) = &self.app_context { + self.metrics + .validator_metrics + .set_validator_latest_checkpoints( + origin, + destination, + app_context.clone(), + &latest_indices, + ) + .await; + } + + // Filter out any validators that did not return a latest index + latest_indices.values().copied().flatten().collect() + } + /// Attempts to get the latest checkpoint with a quorum of signatures among /// validators. /// @@ -37,24 +94,13 @@ impl MultisigCheckpointSyncer { threshold: usize, minimum_index: u32, maximum_index: u32, + origin: &HyperlaneDomain, + destination: &HyperlaneDomain, ) -> Result> { - // Get the latest_index from each validator's checkpoint syncer. - let mut latest_indices = Vec::with_capacity(validators.len()); - for validator in validators { - let address = H160::from(*validator); - if let Some(checkpoint_syncer) = self.checkpoint_syncers.get(&address) { - // Gracefully handle errors getting the latest_index - match checkpoint_syncer.latest_index().await { - Ok(Some(index)) => { - debug!(?address, ?index, "Validator returned latest index"); - latest_indices.push(index); - } - err => { - debug!(?address, ?err, "Failed to get latest index from validator"); - } - } - } - } + let mut latest_indices = self + .get_validator_latest_checkpoints_and_update_metrics(validators, origin, destination) + .await; + debug!( ?latest_indices, "Fetched latest indices from checkpoint syncers" diff --git a/rust/hyperlane-base/src/types/s3_storage.rs b/rust/hyperlane-base/src/types/s3_storage.rs index 2d09a18a86..4db179ad4d 100644 --- a/rust/hyperlane-base/src/types/s3_storage.rs +++ b/rust/hyperlane-base/src/types/s3_storage.rs @@ -8,11 +8,12 @@ use hyperlane_core::{SignedAnnouncement, SignedCheckpointWithMessageId}; use prometheus::IntGauge; use rusoto_core::{ credential::{Anonymous, AwsCredentials, StaticProvider}, - HttpClient, Region, RusotoError, + Region, RusotoError, }; use rusoto_s3::{GetObjectError, GetObjectRequest, PutObjectRequest, S3Client, S3}; use tokio::time::timeout; +use crate::types::utils; use crate::{settings::aws_credentials::AwsChainCredentialsProvider, CheckpointSyncer}; /// The timeout for S3 requests. Rusoto doesn't offer timeout configuration @@ -93,7 +94,7 @@ impl S3Storage { fn authenticated_client(&self) -> &S3Client { self.authenticated_client.get_or_init(|| { S3Client::new_with( - HttpClient::new().unwrap(), + utils::http_client_with_timeout().unwrap(), AwsChainCredentialsProvider::new(), self.region.clone(), ) @@ -113,7 +114,7 @@ impl S3Storage { assert!(credentials.is_anonymous(), "AWS credentials not anonymous"); S3Client::new_with( - HttpClient::new().unwrap(), + utils::http_client_with_timeout().unwrap(), StaticProvider::from(credentials), self.region.clone(), ) diff --git a/rust/hyperlane-base/src/types/utils.rs b/rust/hyperlane-base/src/types/utils.rs new file mode 100644 index 0000000000..a84c55d5ad --- /dev/null +++ b/rust/hyperlane-base/src/types/utils.rs @@ -0,0 +1,15 @@ +use std::time::Duration; + +use eyre::Result; +use rusoto_core::{HttpClient, HttpConfig}; + +/// See https://github.com/hyperium/hyper/issues/2136#issuecomment-589488526 +pub const HYPER_POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(15); + +/// Create a new HTTP client with a timeout for the connection pool. +/// This is a workaround for https://github.com/hyperium/hyper/issues/2136#issuecomment-589345238 +pub fn http_client_with_timeout() -> Result { + let mut config = HttpConfig::new(); + config.pool_idle_timeout(HYPER_POOL_IDLE_TIMEOUT); + Ok(HttpClient::new_with_config(config)?) +} diff --git a/rust/hyperlane-core/Cargo.toml b/rust/hyperlane-core/Cargo.toml index bbb0835b8c..40468bef6c 100644 --- a/rust/hyperlane-core/Cargo.toml +++ b/rust/hyperlane-core/Cargo.toml @@ -11,7 +11,9 @@ version = { workspace = true } [dependencies] async-trait.workspace = true +async-rwlock.workspace = true auto_impl.workspace = true +bigdecimal.workspace = true borsh.workspace = true bs58.workspace = true bytes = { workspace = true, features = ["serde"] } @@ -35,6 +37,8 @@ serde_json = { workspace = true } sha3 = { workspace = true } strum = { workspace = true, optional = true, features = ["derive"] } thiserror = { workspace = true } +tokio = { workspace = true, optional = true, features = ["rt", "time"] } +tracing.workspace = true primitive-types = { workspace = true, optional = true } solana-sdk = { workspace = true, optional = true } tiny-keccak = { workspace = true, features = ["keccak"]} @@ -51,3 +55,4 @@ agent = ["ethers", "strum"] strum = ["dep:strum"] ethers = ["dep:ethers-core", "dep:ethers-contract", "dep:ethers-providers", "dep:primitive-types"] solana = ["dep:solana-sdk"] +fallback-provider = ["tokio"] diff --git a/rust/hyperlane-core/src/chain.rs b/rust/hyperlane-core/src/chain.rs index 94c2c5c44e..b8e7077e09 100644 --- a/rust/hyperlane-core/src/chain.rs +++ b/rust/hyperlane-core/src/chain.rs @@ -5,6 +5,7 @@ use std::{ hash::{Hash, Hasher}, }; +use derive_new::new; use num_derive::FromPrimitive; use num_traits::FromPrimitive; #[cfg(feature = "strum")] @@ -18,7 +19,7 @@ pub struct Address(pub bytes::Bytes); #[derive(Debug, Clone)] pub struct Balance(pub num::BigInt); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, new)] pub struct ContractLocator<'a> { pub domain: &'a HyperlaneDomain, pub address: H256, @@ -79,6 +80,12 @@ pub enum KnownHyperlaneDomain { Gnosis = 100, Chiado = 10200, + MantaPacific = 169, + + Neutron = 1853125230, + + Injective = 6909546, + // -- Local test chains -- /// Test1 local chain Test1 = 13371, @@ -97,12 +104,11 @@ pub enum KnownHyperlaneDomain { // -- v3 testnets -- LineaGoerli = 59140, - BaseGoerli = 84531, ScrollSepolia = 534351, /// Cosmos local chains - CosmosTest26657 = 26657, - CosmosTest26658 = 26658, + CosmosTest99990 = 99990, + CosmosTest99991 = 99991, } #[derive(Clone)] @@ -194,13 +200,13 @@ impl KnownHyperlaneDomain { many_to_one!(match self { Mainnet: [ Ethereum, Avalanche, Arbitrum, Polygon, Optimism, BinanceSmartChain, Celo, - Moonbeam, Gnosis + Moonbeam, Gnosis, MantaPacific, Neutron, Injective ], Testnet: [ Goerli, Mumbai, Fuji, ArbitrumGoerli, OptimismGoerli, BinanceSmartChainTestnet, - Alfajores, MoonbaseAlpha, Sepolia, PolygonZkEvmTestnet, LineaGoerli, BaseGoerli, ScrollSepolia, Chiado + Alfajores, MoonbaseAlpha, Sepolia, PolygonZkEvmTestnet, LineaGoerli, ScrollSepolia, Chiado ], - LocalTestChain: [Test1, Test2, Test3, FuelTest1, SealevelTest1, SealevelTest2, CosmosTest26657, CosmosTest26658], + LocalTestChain: [Test1, Test2, Test3, FuelTest1, SealevelTest1, SealevelTest2, CosmosTest99990, CosmosTest99991], }) } @@ -211,11 +217,12 @@ impl KnownHyperlaneDomain { HyperlaneDomainProtocol::Ethereum: [ Ethereum, Goerli, Sepolia, Polygon, Mumbai, Avalanche, Fuji, Arbitrum, ArbitrumGoerli, Optimism, OptimismGoerli, BinanceSmartChain, BinanceSmartChainTestnet, Celo, Gnosis, - Alfajores, Moonbeam, MoonbaseAlpha, PolygonZkEvmTestnet, LineaGoerli, BaseGoerli, ScrollSepolia, Chiado, Test1, Test2, Test3 + Alfajores, Moonbeam, MoonbaseAlpha, PolygonZkEvmTestnet, LineaGoerli, ScrollSepolia, + Chiado, MantaPacific, Test1, Test2, Test3 ], HyperlaneDomainProtocol::Fuel: [FuelTest1], HyperlaneDomainProtocol::Sealevel: [SealevelTest1, SealevelTest2], - HyperlaneDomainProtocol::Cosmos: [CosmosTest26657, CosmosTest26658], + HyperlaneDomainProtocol::Cosmos: [CosmosTest99990, CosmosTest99991, Neutron, Injective], }) } } @@ -287,6 +294,12 @@ impl Debug for HyperlaneDomain { } } +impl From for HyperlaneDomain { + fn from(domain: KnownHyperlaneDomain) -> Self { + HyperlaneDomain::Known(domain) + } +} + #[derive(thiserror::Error, Debug)] pub enum HyperlaneDomainConfigError { #[error("Domain name (`{0}`) does not match the name of a known domain id; the name is probably misspelled.")] @@ -362,7 +375,7 @@ impl HyperlaneDomain { } } - pub fn is_arbitrum_nitro(&self) -> bool { + pub const fn is_arbitrum_nitro(&self) -> bool { matches!( self, HyperlaneDomain::Known( @@ -371,11 +384,15 @@ impl HyperlaneDomain { ) } + pub const fn is_injective(&self) -> bool { + matches!(self, Self::Known(KnownHyperlaneDomain::Injective)) + } + pub const fn index_mode(&self) -> IndexMode { use HyperlaneDomainProtocol::*; let protocol = self.domain_protocol(); many_to_one!(match protocol { - IndexMode::Block: [Ethereum, Cosmos], // TODO: Is cosmos index-mode is correct? + IndexMode::Block: [Ethereum, Cosmos], IndexMode::Sequence : [Sealevel, Fuel], }) } diff --git a/rust/hyperlane-core/src/error.rs b/rust/hyperlane-core/src/error.rs index ae7c5f8b54..2f3c880793 100644 --- a/rust/hyperlane-core/src/error.rs +++ b/rust/hyperlane-core/src/error.rs @@ -3,7 +3,11 @@ use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; +use bigdecimal::ParseBigDecimalError; +use derive_new::new; + use crate::config::StrOrIntParseError; +use crate::rpc_clients::RpcClientError; use std::string::FromUtf8Error; use crate::Error as PrimitiveTypeError; @@ -21,6 +25,7 @@ impl HyperlaneCustomError for E {} /// Thin wrapper around a boxed HyperlaneCustomError; required to satisfy /// AsDynError implementations. Basically a trait-object adaptor. #[repr(transparent)] +#[derive(new)] pub struct HyperlaneCustomErrorWrapper(Box); impl Debug for HyperlaneCustomErrorWrapper { @@ -123,6 +128,12 @@ pub enum ChainCommunicationError { /// Primitive type error #[error(transparent)] PrimitiveTypeError(#[from] PrimitiveTypeError), + /// Big decimal parsing error + #[error(transparent)] + ParseBigDecimalError(#[from] ParseBigDecimalError), + /// Rpc client error + #[error(transparent)] + RpcClientError(#[from] RpcClientError), } impl ChainCommunicationError { diff --git a/rust/hyperlane-core/src/lib.rs b/rust/hyperlane-core/src/lib.rs index 0e8349186b..610fa3d81b 100644 --- a/rust/hyperlane-core/src/lib.rs +++ b/rust/hyperlane-core/src/lib.rs @@ -8,6 +8,7 @@ extern crate core; pub use chain::*; +pub use error::*; pub use error::{ChainCommunicationError, ChainResult, HyperlaneProtocolError}; pub use identifiers::HyperlaneIdentifier; pub use traits::*; @@ -26,6 +27,8 @@ pub mod utils; pub mod test_utils; pub mod config; +/// Prometheus metrics traits / utilities +pub mod metrics; /// Core hyperlane system data structures mod types; @@ -33,6 +36,9 @@ mod types; mod chain; mod error; +/// Implementations of custom rpc client logic (e.g. fallback) +pub mod rpc_clients; + /// Enum for validity of a list of messages #[derive(Debug)] pub enum ListValidity { diff --git a/rust/hyperlane-core/src/metrics/agent.rs b/rust/hyperlane-core/src/metrics/agent.rs new file mode 100644 index 0000000000..c6c138ca33 --- /dev/null +++ b/rust/hyperlane-core/src/metrics/agent.rs @@ -0,0 +1,30 @@ +use crate::HyperlaneDomainProtocol; +use std::time::Duration; + +#[cfg(feature = "float")] +use crate::U256; + +const ETHEREUM_DECIMALS: u8 = 18; +const COSMOS_DECIMALS: u8 = 6; +const SOLANA_DECIMALS: u8 = 9; + +/// Interval for querying the prometheus metrics endpoint. +/// This should be whatever the prometheus scrape interval is +pub const METRICS_SCRAPE_INTERVAL: Duration = Duration::from_secs(60); + +/// Convert a u256 scaled integer value into the corresponding f64 value. +#[cfg(feature = "float")] +pub fn u256_as_scaled_f64(value: U256, domain: HyperlaneDomainProtocol) -> f64 { + let decimals = decimals_by_protocol(domain); + value.to_f64_lossy() / (10u64.pow(decimals as u32) as f64) +} + +/// Get the decimals each protocol typically uses for its lowest denomination +/// of the native token +pub fn decimals_by_protocol(protocol: HyperlaneDomainProtocol) -> u8 { + match protocol { + HyperlaneDomainProtocol::Cosmos => COSMOS_DECIMALS, + HyperlaneDomainProtocol::Sealevel => SOLANA_DECIMALS, + _ => ETHEREUM_DECIMALS, + } +} diff --git a/rust/hyperlane-core/src/metrics/mod.rs b/rust/hyperlane-core/src/metrics/mod.rs new file mode 100644 index 0000000000..4c82be31d7 --- /dev/null +++ b/rust/hyperlane-core/src/metrics/mod.rs @@ -0,0 +1,2 @@ +/// Agent metrics utils +pub mod agent; diff --git a/rust/hyperlane-core/src/rpc_clients/error.rs b/rust/hyperlane-core/src/rpc_clients/error.rs new file mode 100644 index 0000000000..f896fc4983 --- /dev/null +++ b/rust/hyperlane-core/src/rpc_clients/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +use crate::ChainCommunicationError; + +/// Errors specific to fallback provider. +#[derive(Error, Debug)] +pub enum RpcClientError { + /// Fallback providers failed + #[error("All fallback providers failed. (Errors: {0:?})")] + FallbackProvidersFailed(Vec), +} diff --git a/rust/hyperlane-core/src/rpc_clients/fallback.rs b/rust/hyperlane-core/src/rpc_clients/fallback.rs new file mode 100644 index 0000000000..6f75cbc4c8 --- /dev/null +++ b/rust/hyperlane-core/src/rpc_clients/fallback.rs @@ -0,0 +1,334 @@ +use async_rwlock::RwLock; +use async_trait::async_trait; +use derive_new::new; +use itertools::Itertools; +use std::{ + fmt::{Debug, Formatter}, + future::Future, + marker::PhantomData, + pin::Pin, + sync::Arc, + time::{Duration, Instant}, +}; +use tokio; +use tracing::{info, trace, warn_span}; + +use crate::ChainCommunicationError; + +use super::RpcClientError; + +/// Read the current block number from a chain. +#[async_trait] +pub trait BlockNumberGetter: Send + Sync + Debug { + /// Latest block number getter + async fn get_block_number(&self) -> Result; +} + +const MAX_BLOCK_TIME: Duration = Duration::from_secs(2 * 60); + +/// Information about a provider in `PrioritizedProviders` + +#[derive(Clone, Copy, new)] +pub struct PrioritizedProviderInner { + /// Index into the `providers` field of `PrioritizedProviders` + pub index: usize, + /// Tuple of the block number and the time when it was queried + #[new(value = "(0, Instant::now())")] + last_block_height: (u64, Instant), +} + +impl PrioritizedProviderInner { + fn from_block_height(index: usize, block_height: u64) -> Self { + Self { + index, + last_block_height: (block_height, Instant::now()), + } + } +} +/// Sub-providers and priority information +pub struct PrioritizedProviders { + /// Unsorted list of providers this provider calls + pub providers: Vec, + /// Sorted list of providers this provider calls, in descending order or reliability + pub priorities: RwLock>, +} + +/// A provider that bundles multiple providers and attempts to call the first, +/// then the second, and so on until a response is received. +/// +/// Although no trait bounds are used in the struct definition, the intended purpose of `B` +/// is to be bound by `BlockNumberGetter` and have `T` be convertible to `B`. That is, +/// inner providers should be able to get the current block number, or be convertible into +/// something that is. +pub struct FallbackProvider { + /// The sub-providers called by this provider + pub inner: Arc>, + max_block_time: Duration, + _phantom: PhantomData, +} + +impl Clone for FallbackProvider { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + max_block_time: self.max_block_time, + _phantom: PhantomData, + } + } +} + +impl Debug for FallbackProvider +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // iterate the inner providers and write them to the formatter + f.debug_struct("FallbackProvider") + .field( + "providers", + &self + .inner + .providers + .iter() + .map(|v| format!("{:?}", v)) + .join(", "), + ) + .finish() + } +} + +impl FallbackProvider +where + T: Into + Debug + Clone, + B: BlockNumberGetter, +{ + /// Convenience method for creating a `FallbackProviderBuilder` with same + /// `JsonRpcClient` types + pub fn builder() -> FallbackProviderBuilder { + FallbackProviderBuilder::default() + } + + /// Create a new fallback provider + pub fn new(providers: impl IntoIterator) -> Self { + Self::builder().add_providers(providers).build() + } + + async fn deprioritize_provider(&self, priority: PrioritizedProviderInner) { + // De-prioritize the current provider by moving it to the end of the queue + let mut priorities = self.inner.priorities.write().await; + priorities.retain(|&p| p.index != priority.index); + priorities.push(priority); + } + + async fn update_last_seen_block(&self, provider_index: usize, current_block_height: u64) { + let mut priorities = self.inner.priorities.write().await; + // Get provider position in the up-to-date priorities vec + if let Some(position) = priorities.iter().position(|p| p.index == provider_index) { + priorities[position] = + PrioritizedProviderInner::from_block_height(provider_index, current_block_height); + } + } + + /// Used to iterate the providers in a non-blocking way + pub async fn take_priorities_snapshot(&self) -> Vec { + let read_lock = self.inner.priorities.read().await; + (*read_lock).clone() + } + + /// De-prioritize a provider that has either timed out or returned a bad response + pub async fn handle_stalled_provider(&self, priority: &PrioritizedProviderInner, provider: &T) { + let now = Instant::now(); + if now + .duration_since(priority.last_block_height.1) + .le(&self.max_block_time) + { + // Do nothing, it's too early to tell if the provider has stalled + return; + } + + let block_getter: B = provider.clone().into(); + let current_block_height = block_getter + .get_block_number() + .await + .unwrap_or(priority.last_block_height.0); + if current_block_height <= priority.last_block_height.0 { + // The `max_block_time` elapsed but the block number returned by the provider has not increased + self.deprioritize_provider(*priority).await; + info!( + provider_index=%priority.index, + provider=?self.inner.providers[priority.index], + "Deprioritizing an inner provider in FallbackProvider", + ); + } else { + self.update_last_seen_block(priority.index, current_block_height) + .await; + } + } + + /// Call the first provider, then the second, and so on (in order of priority) until a response is received. + /// If all providers fail, return an error. + pub async fn call( + &self, + mut f: impl FnMut(T) -> Pin> + Send>>, + ) -> Result { + let mut errors = vec![]; + // make sure we do at least 4 total retries. + while errors.len() <= 3 { + if !errors.is_empty() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + let priorities_snapshot = self.take_priorities_snapshot().await; + for (idx, priority) in priorities_snapshot.iter().enumerate() { + let provider = &self.inner.providers[priority.index]; + let resp = f(provider.clone()).await; + self.handle_stalled_provider(priority, provider).await; + let _span = + warn_span!("FallbackProvider::call", fallback_count=%idx, provider_index=%priority.index, ?provider).entered(); + match resp { + Ok(v) => return Ok(v), + Err(e) => { + trace!( + error=?e, + "Got error from inner fallback provider", + ); + errors.push(e) + } + } + } + } + + Err(RpcClientError::FallbackProvidersFailed(errors).into()) + } +} + +/// Builder to create a new fallback provider. +#[derive(Debug, Clone)] +pub struct FallbackProviderBuilder { + providers: Vec, + max_block_time: Duration, + _phantom: PhantomData, +} + +impl Default for FallbackProviderBuilder { + fn default() -> Self { + Self { + providers: Vec::new(), + max_block_time: MAX_BLOCK_TIME, + _phantom: PhantomData, + } + } +} + +impl FallbackProviderBuilder { + /// Add a new provider to the set. Each new provider will be a lower + /// priority than the previous. + pub fn add_provider(mut self, provider: T) -> Self { + self.providers.push(provider); + self + } + + /// Add many providers sorted by highest priority to lowest. + pub fn add_providers(mut self, providers: impl IntoIterator) -> Self { + self.providers.extend(providers); + self + } + + /// Only used for testing purposes. + /// TODO: Move tests into this crate to control the visiblity with conditional compilation. + pub fn with_max_block_time(mut self, max_block_time: Duration) -> Self { + self.max_block_time = max_block_time; + self + } + + /// Create a fallback provider. + pub fn build(self) -> FallbackProvider { + let provider_count = self.providers.len(); + let prioritized_providers = PrioritizedProviders { + providers: self.providers, + // The order of `self.providers` gives the initial priority. + priorities: RwLock::new( + (0..provider_count) + .map(PrioritizedProviderInner::new) + .collect(), + ), + }; + FallbackProvider { + inner: Arc::new(prioritized_providers), + max_block_time: self.max_block_time, + _phantom: PhantomData, + } + } +} + +/// Utilities to import when testing chain-specific fallback providers +pub mod test { + use super::*; + use std::{ + ops::Deref, + sync::{Arc, Mutex}, + }; + + /// Provider that stores requests and optionally sleeps before returning a dummy value + #[derive(Debug, Clone)] + pub struct ProviderMock { + // Store requests as tuples of (method, params) + // Even if the tests were single-threaded, need the arc-mutex + // for interior mutability in `JsonRpcClient::request` + requests: Arc>>, + request_sleep: Option, + } + + impl Default for ProviderMock { + fn default() -> Self { + Self { + requests: Arc::new(Mutex::new(vec![])), + request_sleep: None, + } + } + } + + impl ProviderMock { + /// Create a new provider + pub fn new(request_sleep: Option) -> Self { + Self { + request_sleep, + ..Default::default() + } + } + + /// Push a request to the internal store for later inspection + pub fn push(&self, method: &str, params: T) { + self.requests + .lock() + .unwrap() + .push((method.to_owned(), format!("{:?}", params))); + } + + /// Get the stored requests + pub fn requests(&self) -> Vec<(String, String)> { + self.requests.lock().unwrap().clone() + } + + /// Set the sleep duration + pub fn request_sleep(&self) -> Option { + self.request_sleep + } + + /// Get how many times each provider was called + pub async fn get_call_counts, B>( + fallback_provider: &FallbackProvider, + ) -> Vec { + fallback_provider + .inner + .priorities + .read() + .await + .iter() + .map(|p| { + let provider = &fallback_provider.inner.providers[p.index]; + provider.requests().len() + }) + .collect() + } + } +} diff --git a/rust/hyperlane-core/src/rpc_clients/mod.rs b/rust/hyperlane-core/src/rpc_clients/mod.rs new file mode 100644 index 0000000000..02aaae99f5 --- /dev/null +++ b/rust/hyperlane-core/src/rpc_clients/mod.rs @@ -0,0 +1,8 @@ +pub use self::error::*; + +#[cfg(feature = "fallback-provider")] +pub use self::fallback::*; + +mod error; +#[cfg(feature = "fallback-provider")] +mod fallback; diff --git a/rust/hyperlane-core/src/traits/cursor.rs b/rust/hyperlane-core/src/traits/cursor.rs index 4deb063fde..1d052c4fc7 100644 --- a/rust/hyperlane-core/src/traits/cursor.rs +++ b/rust/hyperlane-core/src/traits/cursor.rs @@ -2,22 +2,29 @@ use std::{fmt, ops::RangeInclusive, time::Duration}; use async_trait::async_trait; use auto_impl::auto_impl; +use eyre::Result; -use crate::{ChainResult, LogMeta}; +use crate::LogMeta; /// A cursor governs event indexing for a contract. #[async_trait] #[auto_impl(Box)] pub trait ContractSyncCursor: Send + Sync + 'static { /// The next block range that should be queried. - async fn next_action(&mut self) -> ChainResult<(CursorAction, Duration)>; + /// This method should be tolerant to being called multiple times in a row + /// without any updates in between. + async fn next_action(&mut self) -> Result<(CursorAction, Duration)>; - /// The latest block that has been queried - fn latest_block(&self) -> u32; + /// The latest block that has been queried, used as a proxy for health. + /// TODO: consider a better way to assess health + fn latest_queried_block(&self) -> u32; - /// Ingests the logs that were fetched from the chain, and adjusts the cursor - /// accordingly. - async fn update(&mut self, logs: Vec<(T, LogMeta)>) -> eyre::Result<()>; + /// Ingests the logs that were fetched from the chain and the range that was queried, + /// and adjusts the cursor accordingly. + /// This is called after the logs have been written to the store, + /// however may require logs to meet certain criteria (e.g. no gaps), that if + /// not met, should result in internal state changes (e.g. rewinding) and not an Err. + async fn update(&mut self, logs: Vec<(T, LogMeta)>, range: RangeInclusive) -> Result<()>; } /// The action that should be taken by the contract sync loop diff --git a/rust/hyperlane-core/src/traits/db.rs b/rust/hyperlane-core/src/traits/db.rs index 3fbebf52db..080a182b45 100644 --- a/rust/hyperlane-core/src/traits/db.rs +++ b/rust/hyperlane-core/src/traits/db.rs @@ -23,18 +23,28 @@ pub trait Sequenced: 'static + Send + Sync { fn sequence(&self) -> u32; } -/// Extension of HyperlaneLogStore trait that supports indexed sequenced data. +/// A read-only interface for a sequence-aware indexer store. #[async_trait] #[auto_impl(&, Box, Arc)] -pub trait HyperlaneSequenceIndexerStore: HyperlaneLogStore -where - T: Send + Sync, -{ +pub trait HyperlaneSequenceAwareIndexerStoreReader: Send + Sync + Debug { /// Gets data by its sequence. async fn retrieve_by_sequence(&self, sequence: u32) -> Result>; /// Gets the block number at which the log occurred. - async fn retrieve_log_block_number(&self, nonce: u32) -> Result>; + async fn retrieve_log_block_number_by_sequence(&self, sequence: u32) -> Result>; +} + +/// Extension of HyperlaneLogStore trait for sequence-aware indexer stores. +#[async_trait] +pub trait HyperlaneSequenceAwareIndexerStore: + HyperlaneLogStore + HyperlaneSequenceAwareIndexerStoreReader +{ +} + +/// Auto-impl for HyperlaneSequenceAwareIndexerStore +impl HyperlaneSequenceAwareIndexerStore for U where + U: HyperlaneLogStore + HyperlaneSequenceAwareIndexerStoreReader + Send + Sync + Debug +{ } /// Extension of HyperlaneLogStore trait that supports a high watermark for the highest indexed block number. diff --git a/rust/hyperlane-core/src/traits/indexer.rs b/rust/hyperlane-core/src/traits/indexer.rs index a9a3752bfa..db93dedf88 100644 --- a/rust/hyperlane-core/src/traits/indexer.rs +++ b/rust/hyperlane-core/src/traits/indexer.rs @@ -38,7 +38,7 @@ pub trait Indexer: Send + Sync + Debug { /// Interface for indexing data in sequence. #[async_trait] #[auto_impl(&, Box, Arc)] -pub trait SequenceIndexer: Indexer { +pub trait SequenceAwareIndexer: Indexer { /// Return the latest finalized sequence (if any) and block number - async fn sequence_and_tip(&self) -> ChainResult<(Option, u32)>; + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)>; } diff --git a/rust/hyperlane-core/src/traits/mod.rs b/rust/hyperlane-core/src/traits/mod.rs index ec2c9e04f4..e85b04f4a6 100644 --- a/rust/hyperlane-core/src/traits/mod.rs +++ b/rust/hyperlane-core/src/traits/mod.rs @@ -15,6 +15,8 @@ pub use routing_ism::*; pub use signing::*; pub use validator_announce::*; +use crate::{FixedPointNumber, H512, U256}; + mod aggregation_ism; mod ccip_read_ism; mod cursor; @@ -33,16 +35,16 @@ mod signing; mod validator_announce; /// The result of a transaction -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct TxOutcome { /// The transaction identifier/hash - pub transaction_id: crate::H512, + pub transaction_id: H512, /// True if executed, false otherwise (reverted, etc.) pub executed: bool, /// Amount of gas used on this transaction. - pub gas_used: crate::U256, + pub gas_used: U256, /// Price paid for the gas - pub gas_price: crate::U256, + pub gas_price: FixedPointNumber, // TODO: more? What can be abstracted across all chains? } @@ -52,11 +54,11 @@ impl From for TxOutcome { Self { transaction_id: t.transaction_hash.into(), executed: t.status.unwrap().low_u32() == 1, - gas_used: t.gas_used.map(Into::into).unwrap_or(crate::U256::zero()), + gas_used: t.gas_used.map(Into::into).unwrap_or(U256::zero()), gas_price: t .effective_gas_price - .map(Into::into) - .unwrap_or(crate::U256::zero()), + .and_then(|price| U256::from(price).try_into().ok()) + .unwrap_or(FixedPointNumber::zero()), } } } diff --git a/rust/hyperlane-core/src/traits/provider.rs b/rust/hyperlane-core/src/traits/provider.rs index 3f00e7a4ac..654e80218f 100644 --- a/rust/hyperlane-core/src/traits/provider.rs +++ b/rust/hyperlane-core/src/traits/provider.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use auto_impl::auto_impl; use thiserror::Error; -use crate::{BlockInfo, ChainResult, HyperlaneChain, TxnInfo, H256}; +use crate::{BlockInfo, ChainInfo, ChainResult, HyperlaneChain, TxnInfo, H256, U256}; /// Interface for a provider. Allows abstraction over different provider types /// for different chains. @@ -24,6 +24,12 @@ pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { /// Returns whether a contract exists at the provided address async fn is_contract(&self, address: &H256) -> ChainResult; + + /// Fetch the balance of the wallet address associated with the chain provider. + async fn get_balance(&self, address: String) -> ChainResult; + + /// Fetch metrics related to this chain + async fn get_chain_metrics(&self) -> ChainResult>; } /// Errors when querying for provider information. diff --git a/rust/hyperlane-core/src/traits/signing.rs b/rust/hyperlane-core/src/traits/signing.rs index 9fe8c89151..34e5676c42 100644 --- a/rust/hyperlane-core/src/traits/signing.rs +++ b/rust/hyperlane-core/src/traits/signing.rs @@ -6,7 +6,7 @@ use serde::{ }; use std::fmt::{Debug, Formatter}; -use crate::utils::fmt_bytes; +use crate::utils::bytes_to_hex; use crate::{Signature, H160, H256}; /// An error incurred by a signer @@ -99,7 +99,7 @@ impl Serialize for SignedType { state.serialize_field("value", &self.value)?; state.serialize_field("signature", &self.signature)?; let sig: [u8; 65] = self.signature.into(); - state.serialize_field("serialized_signature", &fmt_bytes(&sig))?; + state.serialize_field("serialized_signature", &bytes_to_hex(&sig))?; state.end() } } diff --git a/rust/hyperlane-core/src/types/chain_data.rs b/rust/hyperlane-core/src/types/chain_data.rs index b4ecec9208..5f5ecb2e3b 100644 --- a/rust/hyperlane-core/src/types/chain_data.rs +++ b/rust/hyperlane-core/src/types/chain_data.rs @@ -1,7 +1,9 @@ +use derive_new::new; + use crate::{H256, U256}; /// Info about a given block in the chain. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct BlockInfo { /// Hash of this block pub hash: H256, @@ -11,6 +13,16 @@ pub struct BlockInfo { pub number: u64, } +/// Metrics about the chain. +#[derive(Debug, Clone, Default, new)] +pub struct ChainInfo { + /// Information about the latest block + pub latest_block: BlockInfo, + /// The current gas price, in the lowest denomination (e.g. wei) + /// Unless the chain implements an EIP-1559 style tx fee mechanism, this field will be `None` + pub min_gas_price: Option, +} + /// Information about a given transaction in the chain. #[derive(Debug, Clone)] pub struct TxnInfo { diff --git a/rust/hyperlane-core/src/types/log_metadata.rs b/rust/hyperlane-core/src/types/log_metadata.rs index bdb4b043fe..e85824dab9 100644 --- a/rust/hyperlane-core/src/types/log_metadata.rs +++ b/rust/hyperlane-core/src/types/log_metadata.rs @@ -10,7 +10,7 @@ use crate::{H256, H512, U256}; /// A close clone of the Ethereum `LogMeta`, this is designed to be a more /// generic metadata that we can use for other blockchains later. Some changes /// may be required in the future. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] pub struct LogMeta { /// Address from which this log originated pub address: H256, diff --git a/rust/hyperlane-core/src/types/merkle_tree.rs b/rust/hyperlane-core/src/types/merkle_tree.rs index 96ea78ff1c..5d4e20a9fc 100644 --- a/rust/hyperlane-core/src/types/merkle_tree.rs +++ b/rust/hyperlane-core/src/types/merkle_tree.rs @@ -4,7 +4,7 @@ use std::io::{Read, Write}; use crate::{Decode, Encode, HyperlaneProtocolError, Sequenced, H256}; /// Merkle Tree Hook insertion event -#[derive(Debug, Copy, Clone, new, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, new, Eq, PartialEq, Hash)] pub struct MerkleTreeInsertion { leaf_index: u32, message_id: H256, diff --git a/rust/hyperlane-core/src/types/message.rs b/rust/hyperlane-core/src/types/message.rs index 0291d33fb2..99fb2fef45 100644 --- a/rust/hyperlane-core/src/types/message.rs +++ b/rust/hyperlane-core/src/types/message.rs @@ -21,7 +21,7 @@ impl From<&HyperlaneMessage> for RawHyperlaneMessage { } /// A full Hyperlane message between chains -#[derive(Default, Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq, Hash)] pub struct HyperlaneMessage { /// 1 Hyperlane version number pub version: u8, @@ -39,6 +39,21 @@ pub struct HyperlaneMessage { pub body: Vec, } +impl Default for HyperlaneMessage { + fn default() -> Self { + Self { + // Use version 3 now that Hyperlane V3 is the default + version: 3, + nonce: 0, + origin: 0, + sender: H256::zero(), + destination: 0, + recipient: H256::zero(), + body: vec![], + } + } +} + impl Sequenced for HyperlaneMessage { fn sequence(&self) -> u32 { self.nonce diff --git a/rust/hyperlane-core/src/types/mod.rs b/rust/hyperlane-core/src/types/mod.rs index c1e39e491b..9d56cb7f08 100644 --- a/rust/hyperlane-core/src/types/mod.rs +++ b/rust/hyperlane-core/src/types/mod.rs @@ -115,7 +115,7 @@ pub struct GasPaymentKey { } /// A payment of a message's gas costs. -#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] pub struct InterchainGasPayment { /// Id of the message pub message_id: H256, @@ -224,13 +224,13 @@ pub struct TxCostEstimate { /// The gas limit for the transaction. pub gas_limit: U256, /// The gas price for the transaction. - pub gas_price: U256, + pub gas_price: FixedPointNumber, /// The amount of L2 gas for the transaction. /// If Some, `gas_limit` is the sum of the gas limit /// covering L1 costs and the L2 gas limit. /// Only present for Arbitrum Nitro chains, where the gas amount /// is used to cover L1 and L2 costs. For details: - /// https://medium.com/offchainlabs/understanding-arbitrum-2-dimensional-fees-fd1d582596c9 + /// `` pub l2_gas_limit: Option, } diff --git a/rust/hyperlane-core/src/types/primitive_types.rs b/rust/hyperlane-core/src/types/primitive_types.rs index 2b0f5112e6..acc649113b 100644 --- a/rust/hyperlane-core/src/types/primitive_types.rs +++ b/rust/hyperlane-core/src/types/primitive_types.rs @@ -3,11 +3,15 @@ #![allow(clippy::assign_op_pattern)] #![allow(clippy::reversed_empty_ranges)] +use std::{ops::Mul, str::FromStr}; + +use bigdecimal::BigDecimal; use borsh::{BorshDeserialize, BorshSerialize}; use fixed_hash::impl_fixed_hash_conversions; +use num_traits::Zero; use uint::construct_uint; -use crate::types::serialize; +use crate::{types::serialize, ChainCommunicationError}; /// Error type for conversion. #[derive(Debug, PartialEq, Eq, thiserror::Error)] @@ -337,3 +341,90 @@ impl From for H512 { H512(sig.into()) } } + +/// Wrapper type around `BigDecimal` to implement various traits on it +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FixedPointNumber(BigDecimal); + +impl FixedPointNumber { + /// Zero + pub fn zero() -> Self { + Self(BigDecimal::zero()) + } + + /// Round up to the nearest integer + pub fn ceil_to_integer(&self) -> Self { + Self(self.0.with_scale(0)) + } + + /// Ceil + pub fn ceil(&self, fractional_digit_count: i64) -> Self { + Self( + self.0 + .with_scale_round(fractional_digit_count, bigdecimal::RoundingMode::Ceiling), + ) + } +} + +impl Default for FixedPointNumber { + fn default() -> Self { + Self::zero() + } +} + +impl TryFrom for FixedPointNumber { + type Error = ChainCommunicationError; + fn try_from(val: U256) -> Result { + let u256_string = val.to_string(); + Ok(Self(BigDecimal::from_str(&u256_string)?)) + } +} + +impl TryInto for FixedPointNumber { + type Error = ChainCommunicationError; + + fn try_into(self) -> Result { + // Remove all decimals + let big_integer_string = self.0.with_scale(0).to_string(); + let value = U256::from_dec_str(&big_integer_string)?; + Ok(value) + } +} + +impl TryInto for FixedPointNumber { + type Error = ChainCommunicationError; + + fn try_into(self) -> Result { + let u256: U256 = self.try_into()?; + Ok(u256.as_u128()) + } +} + +impl From for FixedPointNumber +where + T: Into, +{ + fn from(val: T) -> Self { + Self(val.into()) + } +} + +impl Mul for FixedPointNumber +where + T: Into, +{ + type Output = FixedPointNumber; + + fn mul(self, rhs: T) -> Self::Output { + let rhs = rhs.into(); + Self(self.0 * rhs.0) + } +} + +impl FromStr for FixedPointNumber { + type Err = ChainCommunicationError; + + fn from_str(s: &str) -> Result { + Ok(Self(BigDecimal::from_str(s)?)) + } +} diff --git a/rust/hyperlane-core/src/utils.rs b/rust/hyperlane-core/src/utils.rs index 56a3e4c9b3..59b9afd26e 100644 --- a/rust/hyperlane-core/src/utils.rs +++ b/rust/hyperlane-core/src/utils.rs @@ -57,8 +57,8 @@ pub fn fmt_address_for_domain(domain: u32, addr: H256) -> String { .unwrap_or_else(|_| format!("{addr:?}")) } -/// Pretty print a byte slice for logging -pub fn fmt_bytes(bytes: &[u8]) -> String { +/// Pretty print a byte slice, including a hex prefix +pub fn bytes_to_hex(bytes: &[u8]) -> String { format!("0x{}", hex::encode(bytes)) } diff --git a/rust/sealevel/.gitignore b/rust/sealevel/.gitignore index b21c3b0132..b8a7e200a5 100644 --- a/rust/sealevel/.gitignore +++ b/rust/sealevel/.gitignore @@ -1,2 +1,3 @@ /target -environments/**/deploy-logs.txt \ No newline at end of file +environments/**/deploy-logs.txt +**/**/keys \ No newline at end of file diff --git a/rust/sealevel/client/Cargo.toml b/rust/sealevel/client/Cargo.toml index 976bf11632..8c14541fac 100644 --- a/rust/sealevel/client/Cargo.toml +++ b/rust/sealevel/client/Cargo.toml @@ -10,6 +10,7 @@ borsh.workspace = true bs58.workspace = true bincode.workspace = true clap = { workspace = true, features = ["derive"] } +ethers.workspace = true hex.workspace = true pretty_env_logger.workspace = true serde.workspace = true diff --git a/rust/sealevel/client/src/artifacts.rs b/rust/sealevel/client/src/artifacts.rs index 7caf4806f0..d6e39beeec 100644 --- a/rust/sealevel/client/src/artifacts.rs +++ b/rust/sealevel/client/src/artifacts.rs @@ -54,6 +54,13 @@ pub(crate) fn read_json(path: &Path) -> T where T: DeserializeOwned, { - let file = File::open(path).expect("Failed to open JSON file"); - serde_json::from_reader(file).expect("Failed to read JSON file") + try_read_json(path).expect("Failed to read JSON from file") +} + +pub(crate) fn try_read_json(path: &Path) -> std::io::Result +where + T: DeserializeOwned, +{ + let file = File::open(path)?; + Ok(serde_json::from_reader(file)?) } diff --git a/rust/sealevel/client/src/context.rs b/rust/sealevel/client/src/context.rs index 80b0ea9e2b..5cb9ba383b 100644 --- a/rust/sealevel/client/src/context.rs +++ b/rust/sealevel/client/src/context.rs @@ -239,14 +239,17 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> { fn wait_for_user_confirmation() { println!("Continue? [y/n] then press Enter"); let mut input = [0u8; 1]; - std::io::stdin().read_exact(&mut input).unwrap(); - match input[0] { - b'y' => { - println!("Continuing..."); + loop { + std::io::stdin().read_exact(&mut input).unwrap(); + match input[0] { + b'y' => { + println!("Continuing..."); + break; + } + b'n' => { + panic!("User requested exit"); + } + _ => {} } - b'n' => { - panic!("User requested exit"); - } - _ => {} } } diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index 8aaf51adb1..783fd3a5f4 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -18,7 +18,8 @@ use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE}; pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { match cmd.cmd { CoreSubCmd::Deploy(core) => { - let environments_dir = create_new_directory(&core.environments_dir, &core.environment); + let environments_dir = + create_new_directory(&core.env_args.environments_dir, &core.env_args.environment); let chain_dir = create_new_directory(&environments_dir, &core.chain); let core_dir = create_new_directory(&chain_dir, "core"); let key_dir = create_new_directory(&core_dir, "keys"); diff --git a/rust/sealevel/client/src/helloworld.rs b/rust/sealevel/client/src/helloworld.rs index 6226b48282..999e623f7d 100644 --- a/rust/sealevel/client/src/helloworld.rs +++ b/rust/sealevel/client/src/helloworld.rs @@ -10,6 +10,7 @@ use hyperlane_sealevel_hello_world::{ }, program_storage_pda_seeds, }; +use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType; use serde::{Deserialize, Serialize}; use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; @@ -180,6 +181,26 @@ impl ConnectionClient for HelloWorldDeployer { set_interchain_security_module_instruction(*program_id, storage.owner.unwrap(), ism) .unwrap() } + + fn get_interchain_gas_paymaster( + &self, + client: &RpcClient, + program_id: &Pubkey, + ) -> Option<(Pubkey, InterchainGasPaymasterType)> { + let storage = self.get_storage(client, program_id); + + storage.igp + } + + fn set_interchain_gas_paymaster_instruction( + &self, + _client: &RpcClient, + _program_id: &Pubkey, + _igp_config: Option<(Pubkey, InterchainGasPaymasterType)>, + ) -> Option { + // There is no way to set the IGP on HelloWorld + None + } } fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) { @@ -190,8 +211,8 @@ fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) { &deploy.context, deploy.config_file, deploy.chain_config_file, - deploy.environments_dir, - &deploy.environment, + deploy.env_args.environments_dir, + &deploy.env_args.environment, deploy.built_so_dir, ) } diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs new file mode 100644 index 0000000000..f41ddad2fb --- /dev/null +++ b/rust/sealevel/client/src/igp.rs @@ -0,0 +1,610 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::{ + artifacts::{read_json, try_read_json, write_json, SingularProgramIdArtifact}, + cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program}, + read_core_program_ids, + router::ChainMetadata, + Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd, +}; + +use std::{ + fs::File, + path::{Path, PathBuf}, + str::FromStr, +}; + +use solana_sdk::{ + pubkey::Pubkey, + signature::{Keypair, Signer as _}, +}; + +use hyperlane_core::H256; + +use hyperlane_sealevel_igp::{ + accounts::{ + GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, + ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, + }, + igp_program_data_pda_seeds, + instruction::{GasOracleConfig, GasOverheadConfig}, +}; + +#[derive(Debug, Serialize, Deserialize, Default)] +struct IgpAccountsArtifacts { + salt: H256, + #[serde(default)] + #[serde(with = "crate::serde::serde_option_pubkey")] + igp_account: Option, + #[serde(default)] + #[serde(with = "crate::serde::serde_option_pubkey")] + overhead_igp_account: Option, +} + +fn get_context_salt(context: Option<&String>) -> H256 { + context + .map(|c| { + if c == "default" { + H256::zero() + } else { + ethers::utils::keccak256(c.as_bytes()).into() + } + }) + .unwrap_or_else(H256::zero) +} + +fn get_context_dir_name(context: Option<&String>) -> &str { + context.map(|c| c.as_str()).unwrap_or("default") +} + +pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { + match cmd.cmd { + IgpSubCmd::DeployProgram(deploy) => { + let environments_dir = create_new_directory( + &deploy.env_args.environments_dir, + &deploy.env_args.environment, + ); + let ism_dir = create_new_directory(&environments_dir, "igp"); + let chain_dir = create_new_directory(&ism_dir, &deploy.chain); + let key_dir = create_new_directory(&chain_dir, "keys"); + + let program_id = deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir); + + write_json::( + &chain_dir.join("program-ids.json"), + program_id.into(), + ); + } + IgpSubCmd::InitIgpAccount(init) => { + let environments_dir = + create_new_directory(&init.env_args.environments_dir, &init.env_args.environment); + let ism_dir = create_new_directory(&environments_dir, "igp"); + let chain_dir = create_new_directory(&ism_dir, &init.chain); + let context_dir = + create_new_directory(&chain_dir, get_context_dir_name(init.context.as_ref())); + + let artifacts_path = context_dir.join("igp-accounts.json"); + + let existing_artifacts = try_read_json::(&artifacts_path).ok(); + + let salt = get_context_salt(init.context.as_ref()); + + let chain_configs = + read_json::>(&init.chain_config_file); + + let igp_account = init_and_configure_igp_account( + &mut ctx, + init.program_id, + chain_configs.get(&init.chain).unwrap().domain_id(), + salt, + init.gas_oracle_config_file, + ); + + let artifacts = IgpAccountsArtifacts { + salt, + igp_account: Some(igp_account), + overhead_igp_account: existing_artifacts.and_then(|a| a.overhead_igp_account), + }; + + write_json(&artifacts_path, artifacts); + } + IgpSubCmd::InitOverheadIgpAccount(init) => { + let environments_dir = + create_new_directory(&init.env_args.environments_dir, &init.env_args.environment); + let ism_dir = create_new_directory(&environments_dir, "igp"); + let chain_dir = create_new_directory(&ism_dir, &init.chain); + let context_dir = + create_new_directory(&chain_dir, get_context_dir_name(init.context.as_ref())); + + let artifacts_path = context_dir.join("igp-accounts.json"); + + let existing_artifacts = try_read_json::(&artifacts_path).ok(); + + let salt = get_context_salt(init.context.as_ref()); + + let chain_configs = + read_json::>(&init.chain_config_file); + + let overhead_igp_account = init_and_configure_overhead_igp_account( + &mut ctx, + init.program_id, + init.inner_igp_account, + chain_configs.get(&init.chain).unwrap().domain_id(), + salt, + init.overhead_config_file, + ); + + let artifacts = IgpAccountsArtifacts { + salt, + igp_account: existing_artifacts.and_then(|a| a.igp_account), + overhead_igp_account: Some(overhead_igp_account), + }; + + write_json(&artifacts_path, artifacts); + } + IgpSubCmd::Query(query) => { + let (program_data_account_pda, _program_data_account_bump) = + Pubkey::find_program_address(igp_program_data_pda_seeds!(), &query.program_id); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment( + &[program_data_account_pda, query.igp_account], + ctx.commitment, + ) + .unwrap() + .value; + + let igp_program_data = + IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..]) + .unwrap() + .into_inner(); + + println!("IGP program data: {:?}", igp_program_data); + + let igp = IgpAccount::fetch(&mut &accounts[1].as_ref().unwrap().data[..]) + .unwrap() + .into_inner(); + + println!("IGP account: {:?}", igp); + + if let Some(gas_payment_account_pubkey) = query.gas_payment_account { + let account = ctx + .client + .get_account_with_commitment(&gas_payment_account_pubkey, ctx.commitment) + .unwrap() + .value + .unwrap(); + let gas_payment_account = GasPaymentAccount::fetch(&mut &account.data[..]) + .unwrap() + .into_inner(); + println!("Gas payment account: {:?}", gas_payment_account); + } + } + IgpSubCmd::PayForGas(payment_details) => { + let unique_gas_payment_keypair = Keypair::new(); + let salt = H256::zero(); + let (igp_account, _igp_account_bump) = Pubkey::find_program_address( + hyperlane_sealevel_igp::igp_pda_seeds!(salt), + &payment_details.program_id, + ); + + let (overhead_igp_account, _) = Pubkey::find_program_address( + hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), + &payment_details.program_id, + ); + let (ixn, gas_payment_data_account) = + hyperlane_sealevel_igp::instruction::pay_for_gas_instruction( + payment_details.program_id, + ctx.payer_pubkey, + igp_account, + Some(overhead_igp_account), + unique_gas_payment_keypair.pubkey(), + H256::from_str(&payment_details.message_id).unwrap(), + payment_details.destination_domain, + payment_details.gas, + ) + .unwrap(); + + ctx.new_txn() + .add(ixn) + .send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]); + + println!( + "Made a payment for message {} with gas payment data account {}", + payment_details.message_id, gas_payment_data_account + ); + } + IgpSubCmd::Claim(claim) => { + let igp_account = ctx + .client + .get_account_with_commitment(&claim.igp_account, ctx.commitment) + .unwrap() + .value + .unwrap(); + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + let ixn = hyperlane_sealevel_igp::instruction::claim_instruction( + claim.program_id, + claim.igp_account, + igp_account.beneficiary, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + ixn, + format!( + "Claiming from IGP account {} to beneficiary {}", + claim.igp_account, igp_account.beneficiary + ), + ) + .send_with_payer(); + } + IgpSubCmd::SetIgpBeneficiary(set_beneficiary) => { + let igp_account = ctx + .client + .get_account_with_commitment(&set_beneficiary.igp_account, ctx.commitment) + .unwrap() + .value + .unwrap(); + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + let ixn = hyperlane_sealevel_igp::instruction::set_beneficiary_instruction( + set_beneficiary.program_id, + set_beneficiary.igp_account, + igp_account.owner.unwrap(), + set_beneficiary.new_beneficiary, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + ixn, + format!( + "Change beneficiary of IGP account {} to beneficiary {}", + set_beneficiary.igp_account, set_beneficiary.new_beneficiary + ), + ) + .send_with_payer(); + } + IgpSubCmd::GasOracleConfig(args) => { + let core_program_ids = read_core_program_ids( + &args.env_args.environments_dir, + &args.env_args.environment, + &args.chain_name, + ); + match args.cmd { + GetSetCmd::Set(set_args) => { + let remote_gas_data = RemoteGasData { + token_exchange_rate: set_args.token_exchange_rate, + gas_price: set_args.gas_price, + token_decimals: set_args.token_decimals, + }; + let gas_oracle_config = GasOracleConfig { + domain: args.remote_domain, + gas_oracle: Some(GasOracle::RemoteGasData(remote_gas_data)), + }; + let instruction = + hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + core_program_ids.igp_program_id, + core_program_ids.igp_account, + ctx.payer_pubkey, + vec![gas_oracle_config], + ) + .unwrap(); + ctx.new_txn().add(instruction).send_with_payer(); + println!("Set gas oracle for remote domain {:?}", args.remote_domain); + } + GetSetCmd::Get(_) => { + let igp_account = ctx + .client + .get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment) + .unwrap() + .value + .expect( + "IGP account not found. Make sure you are connected to the right RPC.", + ); + + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + println!( + "IGP account gas oracle: {:#?}", + igp_account.gas_oracles.get(&args.remote_domain) + ); + } + } + } + IgpSubCmd::DestinationGasOverhead(args) => { + let core_program_ids = read_core_program_ids( + &args.env_args.environments_dir, + &args.env_args.environment, + &args.chain_name, + ); + match args.cmd { + GasOverheadSubCmd::Get => { + // Read the gas overhead config + let overhead_igp_account = ctx + .client + .get_account_with_commitment( + &core_program_ids.overhead_igp_account, + ctx.commitment, + ) + .unwrap() + .value + .expect("Overhead IGP account not found. Make sure you are connected to the right RPC."); + let overhead_igp_account = + OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) + .unwrap() + .into_inner(); + println!( + "Overhead IGP account gas oracle: {:#?}", + overhead_igp_account.gas_overheads.get(&args.remote_domain) + ); + } + GasOverheadSubCmd::Set(set_args) => { + let overhead_config = GasOverheadConfig { + destination_domain: args.remote_domain, + gas_overhead: Some(set_args.gas_overhead), + }; + // Set the gas overhead config + let instruction = + hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + core_program_ids.igp_program_id, + core_program_ids.overhead_igp_account, + ctx.payer_pubkey, + vec![overhead_config], + ) + .unwrap(); + ctx.new_txn().add(instruction).send_with_payer(); + println!( + "Set gas overheads for remote domain {:?}", + args.remote_domain + ) + } + } + } + IgpSubCmd::TransferIgpOwnership(ref transfer_ownership) + | IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => { + let igp_account_type = match cmd.cmd { + IgpSubCmd::TransferIgpOwnership(_) => { + InterchainGasPaymasterType::Igp(transfer_ownership.igp_account) + } + IgpSubCmd::TransferOverheadIgpOwnership(_) => { + InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account) + } + _ => unreachable!(), + }; + let instruction = + hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction( + transfer_ownership.program_id, + igp_account_type.clone(), + ctx.payer_pubkey, + Some(transfer_ownership.new_owner), + ) + .unwrap(); + ctx.new_txn() + .add_with_description( + instruction, + format!( + "Transfer ownership of {:?} to {}", + igp_account_type, transfer_ownership.new_owner + ), + ) + .send_with_payer(); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn deploy_igp_program( + ctx: &mut Context, + built_so_dir: &Path, + use_existing_keys: bool, + key_dir: &Path, +) -> Pubkey { + let (keypair, keypair_path) = create_and_write_keypair( + key_dir, + "hyperlane_sealevel_igp-keypair.json", + use_existing_keys, + ); + let program_id = keypair.pubkey(); + + deploy_program( + ctx.payer_keypair_path(), + keypair_path.to_str().unwrap(), + built_so_dir + .join("hyperlane_sealevel_igp.so") + .to_str() + .unwrap(), + &ctx.client.url(), + ); + + println!("Deployed IGP at program ID {}", program_id); + + let (program_data_account, _program_data_bump) = Pubkey::find_program_address( + hyperlane_sealevel_igp::igp_program_data_pda_seeds!(), + &program_id, + ); + + // Initialize the program data + let instruction = + hyperlane_sealevel_igp::instruction::init_instruction(program_id, ctx.payer_pubkey) + .unwrap(); + + ctx.new_txn() + .add_with_description( + instruction, + format!("Initializing IGP program data {}", program_data_account), + ) + .send_with_payer(); + + program_id +} + +fn init_and_configure_igp_account( + ctx: &mut Context, + program_id: Pubkey, + local_domain: u32, + salt: H256, + gas_oracle_config_file: Option, +) -> Pubkey { + let gas_oracle_configs = gas_oracle_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open oracle config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse oracle config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.domain != local_domain) + .collect::>(); + + // Initialize IGP with the given salt + let (igp_account_pda, _igp_account_bump) = + Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id); + + if ctx + .client + .get_account_with_commitment(&igp_account_pda, ctx.commitment) + .unwrap() + .value + .is_none() + { + let instruction = hyperlane_sealevel_igp::instruction::init_igp_instruction( + program_id, + ctx.payer_pubkey, + salt, + Some(ctx.payer_pubkey), + ctx.payer_pubkey, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + instruction, + format!("Initializing IGP account {}", igp_account_pda), + ) + .send_with_payer(); + } else { + println!( + "IGP account {} already exists, not creating", + igp_account_pda + ); + } + + if !gas_oracle_configs.is_empty() { + // TODO: idempotency + + let domains = gas_oracle_configs + .iter() + .map(|c| c.domain) + .collect::>(); + let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + program_id, + igp_account_pda, + ctx.payer_pubkey, + gas_oracle_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas oracle for remote domains {domains:?}",); + } else { + println!("Skipping settings gas oracle config"); + } + + igp_account_pda +} + +fn init_and_configure_overhead_igp_account( + ctx: &mut Context, + program_id: Pubkey, + inner_igp_account: Pubkey, + local_domain: u32, + salt: H256, + overhead_config_file: Option, +) -> Pubkey { + let overhead_configs = overhead_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open overhead config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse overhead config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.destination_domain != local_domain) + .map(|c| (c.destination_domain, c)) + .collect::>() // dedup + .into_values() + .collect::>(); + + let (overhead_igp_account, _) = Pubkey::find_program_address( + hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), + &program_id, + ); + + if ctx + .client + .get_account_with_commitment(&overhead_igp_account, ctx.commitment) + .unwrap() + .value + .is_none() + { + let instruction = hyperlane_sealevel_igp::instruction::init_overhead_igp_instruction( + program_id, + ctx.payer_pubkey, + salt, + Some(ctx.payer_pubkey), + inner_igp_account, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + instruction, + format!("Initializing overhead IGP account {}", overhead_igp_account), + ) + .send_with_payer(); + } else { + println!( + "Overhead IGP account {} already exists, not creating", + overhead_igp_account + ); + } + + if !overhead_configs.is_empty() { + // TODO: idempotency + + let domains = overhead_configs + .iter() + .map(|c| c.destination_domain) + .collect::>(); + + let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + program_id, + overhead_igp_account, + ctx.payer_pubkey, + overhead_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas overheads for remote domains {domains:?}",) + } else { + println!("Skipping setting gas overheads"); + } + + overhead_igp_account +} diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index bef6cc2095..74e3de7203 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -23,12 +23,8 @@ use account_utils::DiscriminatorEncode; use hyperlane_core::{H160, H256}; use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; use hyperlane_sealevel_igp::{ - accounts::{ - GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, - ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, - }, + accounts::{InterchainGasPaymasterType, OverheadIgpAccount}, igp_gas_payment_pda_seeds, igp_program_data_pda_seeds, - instruction::{GasOracleConfig, GasOverheadConfig}, }; use hyperlane_sealevel_mailbox::{ accounts::{InboxAccount, OutboxAccount}, @@ -67,12 +63,14 @@ mod cmd_utils; mod context; mod r#core; mod helloworld; +mod igp; mod multisig_ism; mod router; mod serde; mod warp_route; use crate::helloworld::process_helloworld_cmd; +use crate::igp::process_igp_cmd; use crate::multisig_ism::process_multisig_ism_message_id_cmd; use crate::warp_route::process_warp_route_cmd; pub(crate) use crate::{context::*, core::*}; @@ -115,6 +113,14 @@ enum HyperlaneSealevelCmd { HelloWorld(HelloWorldCmd), } +#[derive(Args)] +struct EnvironmentArgs { + #[arg(long)] + environment: String, + #[arg(long)] + environments_dir: PathBuf, +} + #[derive(Args)] pub(crate) struct WarpRouteCmd { #[command(subcommand)] @@ -129,10 +135,8 @@ pub(crate) enum WarpRouteSubCmd { #[derive(Args)] pub(crate) struct WarpRouteDeploy { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] built_so_dir: PathBuf, #[arg(long)] @@ -168,8 +172,8 @@ enum CoreSubCmd { struct CoreDeploy { #[arg(long)] local_domain: u32, - #[arg(long)] - environment: String, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] gas_oracle_config_file: Option, #[arg(long)] @@ -178,8 +182,6 @@ struct CoreDeploy { chain: String, #[arg(long)] use_existing_keys: bool, - #[arg(long)] - environments_dir: PathBuf, #[arg(long, num_args = 1.., value_delimiter = ',')] remote_domains: Vec, #[arg(long)] @@ -381,14 +383,63 @@ struct IgpCmd { #[derive(Subcommand)] enum IgpSubCmd { + DeployProgram(IgpDeployProgramArgs), + InitIgpAccount(InitIgpAccountArgs), + InitOverheadIgpAccount(InitOverheadIgpAccountArgs), Query(IgpQueryArgs), PayForGas(PayForGasArgs), + Claim(ClaimArgs), + SetIgpBeneficiary(SetIgpBeneficiaryArgs), GasOracleConfig(GasOracleConfigArgs), DestinationGasOverhead(DestinationGasOverheadArgs), TransferIgpOwnership(TransferIgpOwnership), TransferOverheadIgpOwnership(TransferIgpOwnership), } +#[derive(Args)] +struct IgpDeployProgramArgs { + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + chain: String, + #[arg(long)] + built_so_dir: PathBuf, +} + +#[derive(Args)] +struct InitIgpAccountArgs { + #[arg(long)] + program_id: Pubkey, + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + chain: String, + #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] + context: Option, + #[arg(long)] + gas_oracle_config_file: Option, +} + +#[derive(Args)] +struct InitOverheadIgpAccountArgs { + #[arg(long)] + program_id: Pubkey, + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + chain: String, + #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] + inner_igp_account: Pubkey, + #[arg(long)] + context: Option, + #[arg(long)] + overhead_config_file: Option, +} + #[derive(Args)] struct IgpQueryArgs { #[arg(long)] @@ -423,11 +474,26 @@ struct PayForGasArgs { } #[derive(Args)] -struct GasOracleConfigArgs { +struct ClaimArgs { #[arg(long)] - environment: String, + program_id: Pubkey, #[arg(long)] - environments_dir: PathBuf, + igp_account: Pubkey, +} + +#[derive(Args)] +struct SetIgpBeneficiaryArgs { + #[arg(long)] + program_id: Pubkey, + #[arg(long)] + igp_account: Pubkey, + new_beneficiary: Pubkey, +} + +#[derive(Args)] +struct GasOracleConfigArgs { + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] chain_name: String, #[arg(long)] @@ -451,10 +517,8 @@ struct GetGasOracleArgs; #[derive(Args)] struct DestinationGasOverheadArgs { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] chain_name: String, #[arg(long)] @@ -535,10 +599,8 @@ enum MultisigIsmMessageIdSubCmd { #[derive(Args)] struct MultisigIsmMessageIdDeploy { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] built_so_dir: PathBuf, #[arg(long)] @@ -597,10 +659,8 @@ pub(crate) enum HelloWorldSubCmd { #[derive(Args)] pub(crate) struct HelloWorldDeploy { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] built_so_dir: PathBuf, #[arg(long)] @@ -1313,202 +1373,3 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) { } } } - -fn process_igp_cmd(ctx: Context, cmd: IgpCmd) { - match cmd.cmd { - IgpSubCmd::Query(query) => { - let (program_data_account_pda, _program_data_account_bump) = - Pubkey::find_program_address(igp_program_data_pda_seeds!(), &query.program_id); - - let accounts = ctx - .client - .get_multiple_accounts_with_commitment( - &[program_data_account_pda, query.igp_account], - ctx.commitment, - ) - .unwrap() - .value; - - let igp_program_data = - IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..]) - .unwrap() - .into_inner(); - - println!("IGP program data: {:?}", igp_program_data); - - let igp = IgpAccount::fetch(&mut &accounts[1].as_ref().unwrap().data[..]) - .unwrap() - .into_inner(); - - println!("IGP account: {:?}", igp); - - if let Some(gas_payment_account_pubkey) = query.gas_payment_account { - let account = ctx - .client - .get_account_with_commitment(&gas_payment_account_pubkey, ctx.commitment) - .unwrap() - .value - .unwrap(); - let gas_payment_account = GasPaymentAccount::fetch(&mut &account.data[..]) - .unwrap() - .into_inner(); - println!("Gas payment account: {:?}", gas_payment_account); - } - } - IgpSubCmd::PayForGas(payment_details) => { - let unique_gas_payment_keypair = Keypair::new(); - let salt = H256::zero(); - let (igp_account, _igp_account_bump) = Pubkey::find_program_address( - hyperlane_sealevel_igp::igp_pda_seeds!(salt), - &payment_details.program_id, - ); - - let (overhead_igp_account, _) = Pubkey::find_program_address( - hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), - &payment_details.program_id, - ); - let (ixn, gas_payment_data_account) = - hyperlane_sealevel_igp::instruction::pay_for_gas_instruction( - payment_details.program_id, - ctx.payer_pubkey, - igp_account, - Some(overhead_igp_account), - unique_gas_payment_keypair.pubkey(), - H256::from_str(&payment_details.message_id).unwrap(), - payment_details.destination_domain, - payment_details.gas, - ) - .unwrap(); - - ctx.new_txn() - .add(ixn) - .send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]); - - println!( - "Made a payment for message {} with gas payment data account {}", - payment_details.message_id, gas_payment_data_account - ); - } - IgpSubCmd::GasOracleConfig(args) => { - let core_program_ids = - read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name); - match args.cmd { - GetSetCmd::Set(set_args) => { - let remote_gas_data = RemoteGasData { - token_exchange_rate: set_args.token_exchange_rate, - gas_price: set_args.gas_price, - token_decimals: set_args.token_decimals, - }; - let gas_oracle_config = GasOracleConfig { - domain: args.remote_domain, - gas_oracle: Some(GasOracle::RemoteGasData(remote_gas_data)), - }; - let instruction = - hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( - core_program_ids.igp_program_id, - core_program_ids.igp_account, - ctx.payer_pubkey, - vec![gas_oracle_config], - ) - .unwrap(); - ctx.new_txn().add(instruction).send_with_payer(); - println!("Set gas oracle for remote domain {:?}", args.remote_domain); - } - GetSetCmd::Get(_) => { - let igp_account = ctx - .client - .get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment) - .unwrap() - .value - .expect( - "IGP account not found. Make sure you are connected to the right RPC.", - ); - - let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) - .unwrap() - .into_inner(); - - println!( - "IGP account gas oracle: {:#?}", - igp_account.gas_oracles.get(&args.remote_domain) - ); - } - } - } - IgpSubCmd::DestinationGasOverhead(args) => { - let core_program_ids = - read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name); - match args.cmd { - GasOverheadSubCmd::Get => { - // Read the gas overhead config - let overhead_igp_account = ctx - .client - .get_account_with_commitment( - &core_program_ids.overhead_igp_account, - ctx.commitment, - ) - .unwrap() - .value - .expect("Overhead IGP account not found. Make sure you are connected to the right RPC."); - let overhead_igp_account = - OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) - .unwrap() - .into_inner(); - println!( - "Overhead IGP account gas oracle: {:#?}", - overhead_igp_account.gas_overheads.get(&args.remote_domain) - ); - } - GasOverheadSubCmd::Set(set_args) => { - let overhead_config = GasOverheadConfig { - destination_domain: args.remote_domain, - gas_overhead: Some(set_args.gas_overhead), - }; - // Set the gas overhead config - let instruction = - hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( - core_program_ids.igp_program_id, - core_program_ids.overhead_igp_account, - ctx.payer_pubkey, - vec![overhead_config], - ) - .unwrap(); - ctx.new_txn().add(instruction).send_with_payer(); - println!( - "Set gas overheads for remote domain {:?}", - args.remote_domain - ) - } - } - } - IgpSubCmd::TransferIgpOwnership(ref transfer_ownership) - | IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => { - let igp_account_type = match cmd.cmd { - IgpSubCmd::TransferIgpOwnership(_) => { - InterchainGasPaymasterType::Igp(transfer_ownership.igp_account) - } - IgpSubCmd::TransferOverheadIgpOwnership(_) => { - InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account) - } - _ => unreachable!(), - }; - let instruction = - hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction( - transfer_ownership.program_id, - igp_account_type.clone(), - ctx.payer_pubkey, - Some(transfer_ownership.new_owner), - ) - .unwrap(); - ctx.new_txn() - .add_with_description( - instruction, - format!( - "Transfer ownership of {:?} to {}", - igp_account_type, transfer_ownership.new_owner - ), - ) - .send_with_payer(); - } - } -} diff --git a/rust/sealevel/client/src/multisig_ism.rs b/rust/sealevel/client/src/multisig_ism.rs index d5676b2815..e354f39bb8 100644 --- a/rust/sealevel/client/src/multisig_ism.rs +++ b/rust/sealevel/client/src/multisig_ism.rs @@ -45,8 +45,10 @@ impl From for ValidatorsAndThreshold { pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: MultisigIsmMessageIdCmd) { match cmd.cmd { MultisigIsmMessageIdSubCmd::Deploy(deploy) => { - let environments_dir = - create_new_directory(&deploy.environments_dir, &deploy.environment); + let environments_dir = create_new_directory( + &deploy.env_args.environments_dir, + &deploy.env_args.environment, + ); let ism_dir = create_new_directory(&environments_dir, "multisig-ism-message-id"); let chain_dir = create_new_directory(&ism_dir, &deploy.chain); let context_dir = create_new_directory(&chain_dir, &deploy.context); diff --git a/rust/sealevel/client/src/router.rs b/rust/sealevel/client/src/router.rs index 4eaf4cae78..ae20abc5bd 100644 --- a/rust/sealevel/client/src/router.rs +++ b/rust/sealevel/client/src/router.rs @@ -268,6 +268,21 @@ pub(crate) trait ConnectionClient: Ownable { program_id: &Pubkey, ism: Option, ) -> Instruction; + + /// Gets the IGP configured on-chain. + fn get_interchain_gas_paymaster( + &self, + client: &RpcClient, + program_id: &Pubkey, + ) -> Option<(Pubkey, InterchainGasPaymasterType)>; + + /// Gets an instruction to set the IGP. + fn set_interchain_gas_paymaster_instruction( + &self, + client: &RpcClient, + program_id: &Pubkey, + igp_config: Option<(Pubkey, InterchainGasPaymasterType)>, + ) -> Option; } /// Idempotently deploys routers on multiple Sealevel chains and enrolls all routers (including @@ -426,8 +441,6 @@ fn configure_connection_client( router_config: &RouterConfig, chain_config: &ChainMetadata, ) { - // Just ISM for now - let client = chain_config.client(); let actual_ism = deployer.get_interchain_security_module(&client, program_id); @@ -451,6 +464,35 @@ fn configure_connection_client( .with_client(&client) .send_with_payer(); } + + let actual_igp = deployer.get_interchain_gas_paymaster(&client, program_id); + let expected_igp = router_config + .connection_client + .interchain_gas_paymaster_config(&client); + + if actual_igp != expected_igp { + let instruction = deployer.set_interchain_gas_paymaster_instruction( + &client, + program_id, + expected_igp.clone(), + ); + if let Some(instruction) = instruction { + ctx.new_txn() + .add_with_description( + instruction, + format!( + "Setting IGP for chain: {} ({}) to {:?}", + chain_config.name, + chain_config.domain_id(), + expected_igp + ), + ) + .with_client(&client) + .send_with_payer(); + } else { + println!("WARNING: Invalid configured IGP {:?}, expected {:?} for chain {} ({}), but cannot craft instruction to change it", actual_igp, expected_igp, chain_config.name, chain_config.domain_id()); + } + } } // Idempotent. @@ -541,17 +583,18 @@ fn enroll_all_remote_routers< .collect::>(); if !router_configs.is_empty() { - println!( - "Enrolling routers for chain: {}, program_id {}, routers: {:?}", - chain_name, program_id, router_configs, - ); - ctx.new_txn() - .add(deployer.enroll_remote_routers_instruction( - program_id, - ctx.payer_pubkey, - router_configs, - )) + .add_with_description( + deployer.enroll_remote_routers_instruction( + program_id, + ctx.payer_pubkey, + router_configs.clone(), + ), + format!( + "Enrolling routers for chain: {}, program_id {}, routers: {:?}", + chain_name, program_id, router_configs, + ), + ) .with_client(&chain_config.client()) .send_with_payer(); } else { diff --git a/rust/sealevel/client/src/warp_route.rs b/rust/sealevel/client/src/warp_route.rs index 5e67fcf379..28ef3766ca 100644 --- a/rust/sealevel/client/src/warp_route.rs +++ b/rust/sealevel/client/src/warp_route.rs @@ -20,7 +20,7 @@ use hyperlane_sealevel_token_lib::{ accounts::{HyperlaneToken, HyperlaneTokenAccount}, hyperlane_token_pda_seeds, instruction::{ - enroll_remote_routers_instruction, set_destination_gas_configs, + enroll_remote_routers_instruction, set_destination_gas_configs, set_igp_instruction, set_interchain_security_module_instruction, transfer_ownership_instruction, Init, }, }; @@ -125,8 +125,8 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) { &deploy.warp_route_name, deploy.token_config_file, deploy.chain_config_file, - deploy.environments_dir, - &deploy.environment, + deploy.env_args.environments_dir, + &deploy.env_args.environment, deploy.built_so_dir, ); } @@ -206,7 +206,7 @@ impl RouterDeployer for WarpRouteDeployer { let domain_id = chain_config.domain_id(); // TODO: consider pulling the setting of defaults into router.rs, - // and possibly have a more distinct connection client abstration. + // and possibly have a more distinct connection client abstraction. let mailbox = app_config .router_config() @@ -286,7 +286,7 @@ impl RouterDeployer for WarpRouteDeployer { collateral_info .spl_token_program .as_ref() - .expect("Cannot initalize collateral warp route without SPL token program") + .expect("Cannot initialize collateral warp route without SPL token program") .program_id(), collateral_info.mint.parse().expect("Invalid mint address"), ) @@ -434,6 +434,27 @@ impl ConnectionClient for WarpRouteDeployer { set_interchain_security_module_instruction(*program_id, token_data.owner.unwrap(), ism) .unwrap() } + + fn get_interchain_gas_paymaster( + &self, + client: &RpcClient, + program_id: &Pubkey, + ) -> Option<(Pubkey, InterchainGasPaymasterType)> { + let token_data = get_token_data::<()>(client, program_id); + + token_data.interchain_gas_paymaster + } + + fn set_interchain_gas_paymaster_instruction( + &self, + client: &RpcClient, + program_id: &Pubkey, + igp_config: Option<(Pubkey, InterchainGasPaymasterType)>, + ) -> Option { + let token_data = get_token_data::<()>(client, program_id); + + Some(set_igp_instruction(*program_id, token_data.owner.unwrap(), igp_config).unwrap()) + } } fn get_token_data(client: &RpcClient, program_id: &Pubkey) -> HyperlaneToken diff --git a/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json b/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json index ba62748efe..c5e945eae3 100644 --- a/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json +++ b/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json @@ -1,10 +1,10 @@ { - "sealeveltest1": { - "hex": "0xa77b4e2ed231894cc8cb8eee21adcc705d8489bccc6b2fcf40a358de23e60b7b", - "base58": "CGn8yNtSD3aTTqJfYhUb6s1aVTN75NzwtsFKo1e83aga" - }, "sealeveltest2": { "hex": "0x2317f9615d4ebc2419ad4b88580e2a80a03b2c7a60bc960de7d6934dbc37a87e", "base58": "3MzUPjP5LEkiHH82nEAe28Xtz9ztuMqWc8UmuKxrpVQH" + }, + "sealeveltest1": { + "hex": "0xa77b4e2ed231894cc8cb8eee21adcc705d8489bccc6b2fcf40a358de23e60b7b", + "base58": "CGn8yNtSD3aTTqJfYhUb6s1aVTN75NzwtsFKo1e83aga" } } \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/chain-config.json b/rust/sealevel/environments/testnet4/chain-config.json new file mode 100644 index 0000000000..efb4a36c69 --- /dev/null +++ b/rust/sealevel/environments/testnet4/chain-config.json @@ -0,0 +1,431 @@ +{ + "alfajores": { + "blockExplorers": [ + { + "apiUrl": "https://api-alfajores.celoscan.io/api", + "family": "etherscan", + "name": "CeloScan", + "url": "https://alfajores.celoscan.io" + }, + { + "apiUrl": "https://explorer.celo.org/alfajores/api", + "family": "blockscout", + "name": "Blockscout", + "url": "https://explorer.celo.org/alfajores" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 0 + }, + "chainId": 44787, + "displayName": "Alfajores", + "domainId": 44787, + "isTestnet": true, + "name": "alfajores", + "nativeToken": { + "decimals": 18, + "name": "CELO", + "symbol": "CELO" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://alfajores-forno.celo-testnet.org" + } + ] + }, + "fuji": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.snowtrace.io/api", + "family": "etherscan", + "name": "SnowTrace", + "url": "https://testnet.snowtrace.io" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 2, + "reorgPeriod": 3 + }, + "chainId": 43113, + "displayName": "Fuji", + "domainId": 43113, + "isTestnet": true, + "name": "fuji", + "nativeToken": { + "decimals": 18, + "name": "Avalanche", + "symbol": "AVAX" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://api.avax-test.network/ext/bc/C/rpc", + "pagination": { + "maxBlockRange": 2048 + } + } + ] + }, + "mumbai": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://mumbai.polygonscan.com" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 5, + "reorgPeriod": 32 + }, + "chainId": 80001, + "displayName": "Mumbai", + "domainId": 80001, + "isTestnet": true, + "name": "mumbai", + "nativeToken": { + "decimals": 18, + "name": "MATIC", + "symbol": "MATIC" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.ankr.com/polygon_mumbai", + "pagination": { + "maxBlockRange": 10000, + "minBlockNumber": 22900000 + } + } + ], + "transactionOverrides": { + "maxFeePerGas": 150000000000, + "maxPriorityFeePerGas": 40000000000 + } + }, + "bsctestnet": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.bscscan.com/api", + "family": "etherscan", + "name": "BscScan", + "url": "https://testnet.bscscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 9 + }, + "chainId": 97, + "displayName": "BSC Testnet", + "domainId": 97, + "isTestnet": true, + "name": "bsctestnet", + "nativeToken": { + "decimals": 18, + "name": "BNB", + "symbol": "BNB" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://bsc-testnet.publicnode.com" + }, + { + "http": "https://bsc-testnet.blockpi.network/v1/rpc/public" + } + ], + "transactionOverrides": { + "gasPrice": 80000000000 + } + }, + "goerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://goerli.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, + "chainId": 5, + "displayName": "Goerli", + "domainId": 5, + "isTestnet": true, + "name": "goerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" + }, + { + "http": "https://rpc.ankr.com/eth_goerli" + } + ] + }, + "scrollsepolia": { + "blockExplorers": [ + { + "apiUrl": "https://api-sepolia.scrollscan.com/api", + "family": "etherscan", + "name": "Scroll Explorer", + "url": "https://sepolia.scrollscan.dev/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 534351, + "displayName": "Scroll Sepolia", + "domainId": 534351, + "isTestnet": true, + "name": "scrollsepolia", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://sepolia-rpc.scroll.io" + } + ] + }, + "sepolia": { + "blockExplorers": [ + { + "apiUrl": "https://api-sepolia.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://sepolia.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, + "chainId": 11155111, + "displayName": "Sepolia", + "domainId": 11155111, + "isTestnet": true, + "name": "sepolia", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public" + }, + { + "http": "https://rpc.sepolia.org" + } + ] + }, + "moonbasealpha": { + "blockExplorers": [ + { + "apiUrl": "https://api-moonbase.moonscan.io/api", + "family": "etherscan", + "name": "MoonScan", + "url": "https://moonbase.moonscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 12, + "reorgPeriod": 1 + }, + "chainId": 1287, + "displayName": "Moonbase Alpha", + "displayNameShort": "Moonbase", + "domainId": 1287, + "isTestnet": true, + "name": "moonbasealpha", + "nativeToken": { + "decimals": 18, + "name": "DEV", + "symbol": "DEV" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.api.moonbase.moonbeam.network" + } + ] + }, + "optimismgoerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli-optimism.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://goerli-optimism.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 420, + "displayName": "Optimism Goerli", + "displayNameShort": "Opt. Goerli", + "domainId": 420, + "isTestnet": true, + "name": "optimismgoerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://goerli.optimism.io" + } + ] + }, + "arbitrumgoerli": { + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 421613, + "displayName": "Arbitrum Goerli", + "displayNameShort": "Arb. Goerli", + "domainId": 421613, + "isTestnet": true, + "name": "arbitrumgoerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://goerli-rollup.arbitrum.io/rpc" + } + ] + }, + "polygonzkevmtestnet": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet-zkevm.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://testnet-zkevm.polygonscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 1442, + "displayName": "Polygon zkEVM Testnet", + "displayNameShort": "ZkEvm Testnet", + "domainId": 1442, + "isTestnet": true, + "name": "polygonzkevmtestnet", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.public.zkevm-test.net" + } + ] + }, + "solanatestnet": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.solana.com", + "family": "other", + "name": "Solana Explorer", + "url": "https://explorer.solana.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 0.4, + "reorgPeriod": 0 + }, + "chainId": 1399811150, + "displayName": "Solana Testnet", + "displayNameShort": "Sol Testnet", + "domainId": 1399811150, + "isTestnet": true, + "name": "solanatestnet", + "nativeToken": { + "decimals": 9, + "name": "Sol", + "symbol": "SOL" + }, + "protocol": "sealevel", + "rpcUrls": [ + { + "http": "https://api.testnet.solana.com" + } + ] + }, + "eclipsetestnet": { + "blockExplorers": [ + { + "apiUrl": "https://testnet.dev2.eclipsenetwork.xyz", + "family": "other", + "name": "Eclipse Testnet Explorer", + "url": "https://explorer.dev.eclipsenetwork.xyz/?cluster=testnet" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 0.4, + "reorgPeriod": 0 + }, + "chainId": 239092742, + "displayName": "Eclipse Testnet", + "domainId": 239092742, + "isTestnet": true, + "name": "eclipsetestnet", + "nativeToken": { + "decimals": 9, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "sealevel", + "rpcUrls": [ + { + "http": "https://testnet.dev2.eclipsenetwork.xyz" + } + ] + } +} diff --git a/rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json b/rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json new file mode 100644 index 0000000000..095ae366cf --- /dev/null +++ b/rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json @@ -0,0 +1,8 @@ +{ + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "validator_announce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "multisig_ism_message_id": "4GHxwWyKB9exhKG4fdyU2hfLgfFzhHp2WcsSKc2uNR1k", + "igp_program_id": "5p7Hii6CJL4xGBYYTGEQmH9LnUSZteFJUu9AVLDExZX2", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/gas-oracle-configs.json b/rust/sealevel/environments/testnet4/gas-oracle-configs.json new file mode 100644 index 0000000000..9e1cb73138 --- /dev/null +++ b/rust/sealevel/environments/testnet4/gas-oracle-configs.json @@ -0,0 +1,29 @@ +[ + { + "domain": 11155111, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "15000000000", + "tokenDecimals": 18 + } + }, + { + "domain": 1399811150, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } + }, + { + "domain": 239092742, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } + } +] \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json b/rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json new file mode 100644 index 0000000000..9d06cd936c --- /dev/null +++ b/rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json @@ -0,0 +1,5 @@ +{ + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json b/rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json new file mode 100644 index 0000000000..9d06cd936c --- /dev/null +++ b/rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json @@ -0,0 +1,5 @@ +{ + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json b/rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json new file mode 100644 index 0000000000..6f746e5b8a --- /dev/null +++ b/rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json @@ -0,0 +1,108 @@ +{ + "alfajores": { + "threshold": 2, + "validators": [ + "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", + "0xba279f965489d90f90490e3c49e860e0b43c2ae6", + "0x86485dcec5f7bb8478dd251676372d054dea6653" + ], + "type": 3 + }, + "arbitrumgoerli": { + "threshold": 2, + "validators": [ + "0x071c8d135845ae5a2cb73f98d681d519014c0a8b", + "0x1bcf03360989f15cbeb174c188288f2c6d2760d7", + "0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d" + ], + "type": 3 + }, + "bsctestnet": { + "threshold": 2, + "validators": [ + "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", + "0xf620f5e3d25a3ae848fec74bccae5de3edcd8796", + "0x1f030345963c54ff8229720dd3a711c15c554aeb" + ], + "type": 3 + }, + "fuji": { + "threshold": 2, + "validators": [ + "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", + "0x895ae30bc83ff1493b9cf7781b0b813d23659857", + "0x43e915573d9f1383cbf482049e4a012290759e7f" + ], + "type": 3 + }, + "goerli": { + "threshold": 2, + "validators": [ + "0x05a9b5efe9f61f9142453d8e9f61565f333c6768", + "0x43a96c7dfbd8187c95013d6ee8665650cbdb2673", + "0x7940a12c050e24e1839c21ecb12f65afd84e8c5b" + ], + "type": 3 + }, + "moonbasealpha": { + "threshold": 2, + "validators": [ + "0x521877064bd7ac7500d300f162c8c47c256a2f9c", + "0xbc1c70f58ae0459d4b8a013245420a893837d568", + "0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c" + ], + "type": 3 + }, + "mumbai": { + "threshold": 2, + "validators": [ + "0xebc301013b6cd2548e347c28d2dc43ec20c068f2", + "0x315db9868fc8813b221b1694f8760ece39f45447", + "0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5" + ], + "type": 3 + }, + "optimismgoerli": { + "threshold": 2, + "validators": [ + "0x79e58546e2faca865c6732ad5f6c4951051c4d67", + "0x7bbfe1bb7146aad7df309c637987d856179ebbc1", + "0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40" + ], + "type": 3 + }, + "polygonzkevmtestnet": { + "threshold": 2, + "validators": [ + "0x3f06b725bc9648917eb11c414e9f8d76fd959550", + "0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636", + "0xd476548222f43206d0abaa30e46e28670aa7859c" + ], + "type": 3 + }, + "scrollsepolia": { + "threshold": 2, + "validators": [ + "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", + "0x9a11ed23ae962974018ab45bc133caabff7b3271", + "0x7867bea3c9761fe64e6d124b171f91fd5dd79644" + ], + "type": 3 + }, + "sepolia": { + "threshold": 2, + "validators": [ + "0xb22b65f202558adf86a8bb2847b76ae1036686a5", + "0x469f0940684d147defc44f3647146cb90dd0bc8e", + "0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83" + ], + "type": 3 + }, + "solanatestnet": { + "threshold": 1, + "validators": [ + "0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5" + ], + "type": 3 + } +} diff --git a/rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json b/rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json new file mode 100644 index 0000000000..b35110fdc4 --- /dev/null +++ b/rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json @@ -0,0 +1,108 @@ +{ + "alfajores": { + "threshold": 2, + "validators": [ + "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", + "0xba279f965489d90f90490e3c49e860e0b43c2ae6", + "0x86485dcec5f7bb8478dd251676372d054dea6653" + ], + "type": 3 + }, + "arbitrumgoerli": { + "threshold": 2, + "validators": [ + "0x071c8d135845ae5a2cb73f98d681d519014c0a8b", + "0x1bcf03360989f15cbeb174c188288f2c6d2760d7", + "0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d" + ], + "type": 3 + }, + "bsctestnet": { + "threshold": 2, + "validators": [ + "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", + "0xf620f5e3d25a3ae848fec74bccae5de3edcd8796", + "0x1f030345963c54ff8229720dd3a711c15c554aeb" + ], + "type": 3 + }, + "fuji": { + "threshold": 2, + "validators": [ + "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", + "0x895ae30bc83ff1493b9cf7781b0b813d23659857", + "0x43e915573d9f1383cbf482049e4a012290759e7f" + ], + "type": 3 + }, + "goerli": { + "threshold": 2, + "validators": [ + "0x05a9b5efe9f61f9142453d8e9f61565f333c6768", + "0x43a96c7dfbd8187c95013d6ee8665650cbdb2673", + "0x7940a12c050e24e1839c21ecb12f65afd84e8c5b" + ], + "type": 3 + }, + "moonbasealpha": { + "threshold": 2, + "validators": [ + "0x521877064bd7ac7500d300f162c8c47c256a2f9c", + "0xbc1c70f58ae0459d4b8a013245420a893837d568", + "0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c" + ], + "type": 3 + }, + "mumbai": { + "threshold": 2, + "validators": [ + "0xebc301013b6cd2548e347c28d2dc43ec20c068f2", + "0x315db9868fc8813b221b1694f8760ece39f45447", + "0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5" + ], + "type": 3 + }, + "optimismgoerli": { + "threshold": 2, + "validators": [ + "0x79e58546e2faca865c6732ad5f6c4951051c4d67", + "0x7bbfe1bb7146aad7df309c637987d856179ebbc1", + "0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40" + ], + "type": 3 + }, + "polygonzkevmtestnet": { + "threshold": 2, + "validators": [ + "0x3f06b725bc9648917eb11c414e9f8d76fd959550", + "0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636", + "0xd476548222f43206d0abaa30e46e28670aa7859c" + ], + "type": 3 + }, + "scrollsepolia": { + "threshold": 2, + "validators": [ + "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", + "0x9a11ed23ae962974018ab45bc133caabff7b3271", + "0x7867bea3c9761fe64e6d124b171f91fd5dd79644" + ], + "type": 3 + }, + "sepolia": { + "threshold": 2, + "validators": [ + "0xb22b65f202558adf86a8bb2847b76ae1036686a5", + "0x469f0940684d147defc44f3647146cb90dd0bc8e", + "0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83" + ], + "type": 3 + }, + "eclipsetestnet": { + "threshold": 1, + "validators": [ + "0xf344f34abca9a444545b5295066348a0ae22dda3" + ], + "type": 3 + } +} diff --git a/rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json b/rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json new file mode 100644 index 0000000000..095ae366cf --- /dev/null +++ b/rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json @@ -0,0 +1,8 @@ +{ + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "validator_announce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "multisig_ism_message_id": "4GHxwWyKB9exhKG4fdyU2hfLgfFzhHp2WcsSKc2uNR1k", + "igp_program_id": "5p7Hii6CJL4xGBYYTGEQmH9LnUSZteFJUu9AVLDExZX2", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json new file mode 100644 index 0000000000..2b614fb946 --- /dev/null +++ b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json @@ -0,0 +1,10 @@ +{ + "solanatestnet": { + "hex": "0xb5eb96475b2d58f7b9a9ff2f60f46cf5ac1be244e557574971885337522b59c9", + "base58": "DF99ZiHj8aN4ETbKmpMoMsRTSbm7gtL5j6sKKLC5mkTS" + }, + "eclipsetestnet": { + "hex": "0x6dcd836e4ed228f42f3b2d6e2c2723b431b8e8b0bc9dded26bdff16ffaf818bd", + "base58": "8PdCNrJqkDfSMjg9BvXrDr3Hooo7WZ6cN7Qn9mHL1GZr" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json new file mode 100644 index 0000000000..f51cda6452 --- /dev/null +++ b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json @@ -0,0 +1,14 @@ +{ + "solanatestnet": { + "type": "native", + "decimals": 9, + "interchainGasPaymaster": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" + }, + "eclipsetestnet": { + "type": "synthetic", + "decimals": 9, + "name": "Solana (solanatestnet)", + "symbol": "SOL", + "interchainGasPaymaster": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" + } +} diff --git a/rust/sealevel/libraries/account-utils/src/lib.rs b/rust/sealevel/libraries/account-utils/src/lib.rs index aa7fa69744..78af429748 100644 --- a/rust/sealevel/libraries/account-utils/src/lib.rs +++ b/rust/sealevel/libraries/account-utils/src/lib.rs @@ -193,7 +193,7 @@ where /// Creates associated token account using Program Derived Address for the given seeds. /// Required to allow PDAs to be created even if they already have a lamport balance. /// -/// Borrowed from https://github.com/solana-labs/solana-program-library/blob/cf77ed0c187d1becd0db56edff4491c28f18dfc8/associated-token-account/program/src/tools/account.rs#L18 +/// Borrowed from `` pub fn create_pda_account<'a>( payer: &AccountInfo<'a>, rent: &Rent, diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs b/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs index ae1276a226..2193ea355d 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs @@ -89,11 +89,11 @@ pub fn init_instruction( let ixn = Instruction::Init(init); // Accounts: - // 0. [executable] The system program. - // 1. [writable] The token PDA account. - // 2. [writable] The dispatch authority PDA account. - // 3. [signer] The payer and access control owner. - // 4..N [??..??] Plugin-specific accounts. + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer and access control owner. + // 4..N `[??..??]` Plugin-specific accounts. let accounts = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(token_key, false), @@ -123,9 +123,9 @@ pub fn enroll_remote_routers_instruction( let ixn = Instruction::EnrollRemoteRouters(configs); // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The token PDA account. - // 2. [signer] The owner. + // 0. `[executable]` The system program. + // 1. `[writeable]` The token PDA account. + // 2. `[signer]` The owner. let accounts = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(token_key, false), @@ -154,9 +154,9 @@ pub fn set_destination_gas_configs( let ixn = Instruction::SetDestinationGasConfigs(configs); // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The token PDA account. - // 2. [signer] The owner. + // 0. `[executable]` The system program. + // 1. `[writeable]` The token PDA account. + // 2. `[signer]` The owner. let accounts = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(token_key, false), @@ -185,8 +185,8 @@ pub fn transfer_ownership_instruction( let ixn = Instruction::TransferOwnership(new_owner); // Accounts: - // 0. [writeable] The token PDA account. - // 1. [signer] The current owner. + // 0. `[writeable]` The token PDA account. + // 1. `[signer]` The current owner. let accounts = vec![ AccountMeta::new(token_key, false), AccountMeta::new_readonly(owner_payer, true), @@ -214,8 +214,8 @@ pub fn set_interchain_security_module_instruction( let ixn = Instruction::SetInterchainSecurityModule(new_interchain_security_module); // Accounts: - // 0. [writeable] The token PDA account. - // 1. [signer] The current owner. + // 0. `[writeable]` The token PDA account. + // 1. `[signer]` The current owner. let accounts = vec![ AccountMeta::new(token_key, false), AccountMeta::new_readonly(owner_payer, true), @@ -243,8 +243,8 @@ pub fn set_igp_instruction( let ixn = Instruction::SetInterchainGasPaymaster(igp_program_and_account); // Accounts: - // 0. [writeable] The token PDA account. - // 1. [signer] The current owner. + // 0. `[writeable]` The token PDA account. + // 1. `[signer]` The current owner. let accounts = vec![ AccountMeta::new(token_key, false), AccountMeta::new_readonly(owner_payer, true), diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs index c104828572..801f9df4f6 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs @@ -138,11 +138,11 @@ where /// Initializes the program. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [writable] The token PDA account. - /// 2. [writable] The dispatch authority PDA account. - /// 3. [signer] The payer and access control owner. - /// 4..N [??..??] Plugin-specific accounts. + /// 0. `[executable]` The system program. + /// 1. `[writable]` The token PDA account. + /// 2. `[writable]` The dispatch authority PDA account. + /// 3. `[signer]` The payer and access control owner. + /// 4..N `[??..??]` Plugin-specific accounts. pub fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -255,23 +255,23 @@ where /// then dispatches a message to the remote recipient. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [executable] The spl_noop program. - /// 2. [] The token PDA account. - /// 3. [executable] The mailbox program. - /// 4. [writeable] The mailbox outbox account. - /// 5. [] Message dispatch authority. - /// 6. [signer] The token sender and mailbox payer. - /// 7. [signer] Unique message / gas payment account. - /// 8. [writeable] Message storage PDA. + /// 0. `[executable]` The system program. + /// 1. `[executable]` The spl_noop program. + /// 2. `[]` The token PDA account. + /// 3. `[executable]` The mailbox program. + /// 4. `[writeable]` The mailbox outbox account. + /// 5. `[]` Message dispatch authority. + /// 6. `[signer]` The token sender and mailbox payer. + /// 7. `[signer]` Unique message / gas payment account. + /// 8. `[writeable]` Message storage PDA. /// ---- If using an IGP ---- - /// 9. [executable] The IGP program. - /// 10. [writeable] The IGP program data. - /// 11. [writeable] Gas payment PDA. - /// 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. - /// 13. [writeable] The IGP account. + /// 9. `[executable]` The IGP program. + /// 10. `[writeable]` The IGP program data. + /// 11. `[writeable]` Gas payment PDA. + /// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + /// 13. `[writeable]` The IGP account. /// ---- End if ---- - /// 14..N [??..??] Plugin-specific accounts. + /// 14..N `[??..??]` Plugin-specific accounts. pub fn transfer_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -362,13 +362,13 @@ where // Accounts expected by the IGP's `PayForGas` instruction: // - // 0. [executable] The system program. - // 1. [signer] The payer. - // 2. [writeable] The IGP program data. - // 3. [signer] Unique gas payment account. - // 4. [writeable] Gas payment PDA. - // 5. [writeable] The IGP account. - // 6. [] Overhead IGP account (optional). + // 0. `[executable]` The system program. + // 1. `[signer]` The payer. + // 2. `[writeable]` The IGP program data. + // 3. `[signer]` Unique gas payment account. + // 4. `[writeable]` Gas payment PDA. + // 5. `[writeable]` The IGP account. + // 6. `[]` Overhead IGP account (optional). let mut igp_payment_account_metas = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), @@ -491,11 +491,11 @@ where } /// Accounts: - /// 0. [signer] Mailbox processor authority specific to this program. - /// 1. [executable] system_program - /// 2. [] hyperlane_token storage + /// 0. `[signer]` Mailbox processor authority specific to this program. + /// 1. `[executable]` system_program + /// 2. `[]` hyperlane_token storage /// 3. [depends on plugin] recipient wallet address - /// 4..N [??..??] Plugin-specific accounts. + /// 4..N `[??..??]` Plugin-specific accounts. pub fn transfer_from_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -575,7 +575,7 @@ where /// serializes them, and sets them as return data. /// /// Accounts: - /// 0. [] The token PDA, which is the PDA with the seeds `HANDLE_ACCOUNT_METAS_PDA_SEEDS`. + /// 0. `[]` The token PDA, which is the PDA with the seeds `HANDLE_ACCOUNT_METAS_PDA_SEEDS`. pub fn transfer_from_remote_account_metas( program_id: &Pubkey, accounts: &[AccountInfo], @@ -621,9 +621,9 @@ where /// Enrolls a remote router. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [writeable] The token PDA account. - /// 2. [signer] The owner. + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The token PDA account. + /// 2. `[signer]` The owner. pub fn enroll_remote_router( program_id: &Pubkey, accounts: &[AccountInfo], @@ -670,9 +670,9 @@ where /// Enrolls remote routers. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [writeable] The token PDA account. - /// 2. [signer] The owner. + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The token PDA account. + /// 2. `[signer]` The owner. pub fn enroll_remote_routers( program_id: &Pubkey, accounts: &[AccountInfo], @@ -719,8 +719,8 @@ where /// Transfers ownership. /// /// Accounts: - /// 0. [writeable] The token PDA account. - /// 1. [signer] The current owner. + /// 0. `[writeable]` The token PDA account. + /// 1. `[signer]` The current owner. pub fn transfer_ownership( program_id: &Pubkey, accounts: &[AccountInfo], @@ -747,7 +747,7 @@ where /// Gets the interchain security module. /// /// Accounts: - /// 0. [] The token PDA account. + /// 0. `[]` The token PDA account. pub fn interchain_security_module( program_id: &Pubkey, accounts: &[AccountInfo], @@ -792,8 +792,8 @@ where /// Lets the owner set the interchain security module. /// /// Accounts: - /// 0. [writeable] The token PDA account. - /// 1. [signer] The access control owner. + /// 0. `[writeable]` The token PDA account. + /// 1. `[signer]` The access control owner. pub fn set_interchain_security_module( program_id: &Pubkey, accounts: &[AccountInfo], @@ -820,9 +820,9 @@ where /// Lets the owner set destination gas configs. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [writeable] The token PDA account. - /// 2. [signer] The access control owner. + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The token PDA account. + /// 2. `[signer]` The access control owner. pub fn set_destination_gas_configs( program_id: &Pubkey, accounts: &[AccountInfo], @@ -860,8 +860,8 @@ where /// Lets the owner set the interchain gas paymaster. /// /// Accounts: - /// 0. [writeable] The token PDA account. - /// 1. [signer] The access control owner. + /// 0. `[writeable]` The token PDA account. + /// 1. `[signer]` The access control owner. pub fn set_interchain_gas_paymaster( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/libraries/multisig-ism/src/test_data.rs b/rust/sealevel/libraries/multisig-ism/src/test_data.rs index 78ca51b35e..952bde14b9 100644 --- a/rust/sealevel/libraries/multisig-ism/src/test_data.rs +++ b/rust/sealevel/libraries/multisig-ism/src/test_data.rs @@ -17,7 +17,7 @@ const DESTINATION_DOMAIN: u32 = 4321u32; pub fn get_multisig_ism_test_data() -> MultisigIsmTestData { let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 69, origin: ORIGIN_DOMAIN, sender: H256::from_str( @@ -49,31 +49,31 @@ pub fn get_multisig_ism_test_data() -> MultisigIsmTestData { }; // checkpoint.signing_hash() is equal to: - // 0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332 + // 0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a // Validator 0: // Address: 0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D // Private Key: 0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091 let validator_0 = H160::from_str("0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D").unwrap(); - // > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332')) - // '0x3a06cc01fef07025ee5ae9e29ae783338fe11f5c21af383fb8cc5878a2ea3616125c230ec07b059eaebb842af0a51040ad3214f9050cccef36b5c21c9c9cc4ba1b' - let signature_0 = hex::decode("3a06cc01fef07025ee5ae9e29ae783338fe11f5c21af383fb8cc5878a2ea3616125c230ec07b059eaebb842af0a51040ad3214f9050cccef36b5c21c9c9cc4ba1b").unwrap(); + // > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a')) + // '0xb8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b' + let signature_0 = hex::decode("b8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b").unwrap(); // Validator 1: // Address: 0xb25206874C24733F05CC0dD11924724A8E7175bd // Private Key: 0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e let validator_1 = H160::from_str("0xb25206874C24733F05CC0dD11924724A8E7175bd").unwrap(); - // > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332')) - // '0xfd34aac152ec85a79211c990f308c7e719145e2e67e48f2d10db4347d3a9102131254eccbcd0fe389afad96b88d368192b33649336893dfe1bbad43901d1bef71b' - let signature_1 = hex::decode("fd34aac152ec85a79211c990f308c7e719145e2e67e48f2d10db4347d3a9102131254eccbcd0fe389afad96b88d368192b33649336893dfe1bbad43901d1bef71b").unwrap(); + // > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a')) + // '0xfa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c' + let signature_1 = hex::decode("fa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c").unwrap(); // Validator 2: // Address: 0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3 // Private Key: 0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515 let validator_2 = H160::from_str("0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3").unwrap(); - // > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332')) - // '0x85992e471002c40730d2b91831ba40cd8ffcebf4905646c25b7b6abb7575f25d19395045466e833b7700e233bfa5836f0a459da05bf817efd6cb4f55bcaec4b51c' - let signature_2 = hex::decode("85992e471002c40730d2b91831ba40cd8ffcebf4905646c25b7b6abb7575f25d19395045466e833b7700e233bfa5836f0a459da05bf817efd6cb4f55bcaec4b51c").unwrap(); + // > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a')) + // '0x6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b' + let signature_2 = hex::decode("6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b").unwrap(); MultisigIsmTestData { message, diff --git a/rust/sealevel/libraries/serializable-account-meta/src/lib.rs b/rust/sealevel/libraries/serializable-account-meta/src/lib.rs index 1153f9975f..5d2df76447 100644 --- a/rust/sealevel/libraries/serializable-account-meta/src/lib.rs +++ b/rust/sealevel/libraries/serializable-account-meta/src/lib.rs @@ -29,7 +29,7 @@ impl From for AccountMeta { } } -/// A ridiculous workaround for https://github.com/solana-labs/solana/issues/31391, +/// A ridiculous workaround for ``, /// which is a bug where if a simulated transaction's return data ends with zero byte(s), /// they end up being incorrectly truncated. /// As a workaround, we can (de)serialize data with a trailing non-zero byte. diff --git a/rust/sealevel/libraries/test-utils/src/igp.rs b/rust/sealevel/libraries/test-utils/src/igp.rs index 688928e9dc..70e5100067 100644 --- a/rust/sealevel/libraries/test-utils/src/igp.rs +++ b/rust/sealevel/libraries/test-utils/src/igp.rs @@ -74,9 +74,9 @@ pub async fn initialize_igp_program( Pubkey::find_program_address(igp_program_data_pda_seeds!(), &program_id); // Accounts: - // 0. [executable] The system program. - // 1. [signer] The payer account. - // 2. [writeable] The program data account. + // 0. `[executable]` The system program. + // 1. `[signer]` The payer account. + // 2. `[writeable]` The program data account. let init_instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::Init, @@ -104,9 +104,9 @@ pub async fn initialize_igp( let (igp_key, igp_bump_seed) = Pubkey::find_program_address(igp_pda_seeds!(salt), &program_id); // Accounts: - // 0. [executable] The system program. - // 1. [signer] The payer account. - // 2. [writeable] The IGP account to initialize. + // 0. `[executable]` The system program. + // 1. `[signer]` The payer account. + // 2. `[writeable]` The IGP account to initialize. let init_instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::InitIgp(InitIgp { @@ -139,9 +139,9 @@ pub async fn initialize_overhead_igp( Pubkey::find_program_address(overhead_igp_pda_seeds!(salt), &program_id); // Accounts: - // 0. [executable] The system program. - // 1. [signer] The payer account. - // 2. [writeable] The Overhead IGP account to initialize. + // 0. `[executable]` The system program. + // 1. `[signer]` The payer account. + // 2. `[writeable]` The Overhead IGP account to initialize. let init_instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::InitOverheadIgp(InitOverheadIgp { salt, owner, inner }), diff --git a/rust/sealevel/libraries/test-utils/src/lib.rs b/rust/sealevel/libraries/test-utils/src/lib.rs index e518163b9c..48a15d2dc8 100644 --- a/rust/sealevel/libraries/test-utils/src/lib.rs +++ b/rust/sealevel/libraries/test-utils/src/lib.rs @@ -105,7 +105,7 @@ async fn initialize_test_ism( /// Simulates an instruction, and attempts to deserialize it into a T. /// If no return data at all was returned, returns Ok(None). -/// If some return data was returned but deserialization was unsuccesful, +/// If some return data was returned but deserialization was unsuccessful, /// an Err is returned. pub async fn simulate_instruction( banks_client: &mut BanksClient, diff --git a/rust/sealevel/programs/helloworld/src/instruction.rs b/rust/sealevel/programs/helloworld/src/instruction.rs index 5c0a42edac..4b3bbb6596 100644 --- a/rust/sealevel/programs/helloworld/src/instruction.rs +++ b/rust/sealevel/programs/helloworld/src/instruction.rs @@ -71,9 +71,9 @@ pub fn init_instruction( }; // Accounts: - // 0. [executable] System program. - // 1. [signer] Payer. - // 2. [writeable] Storage PDA. + // 0. `[executable]` System program. + // 1. `[signer]` Payer. + // 2. `[writeable]` Storage PDA. let accounts = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(payer, true), @@ -100,9 +100,9 @@ pub fn enroll_remote_routers_instruction( .ok_or(ProgramError::InvalidSeeds)?; // Accounts: - // 0. [executable] System program. - // 1. [signer] Payer. - // 2. [writeable] Storage PDA. + // 0. `[executable]` System program. + // 1. `[signer]` Payer. + // 2. `[writeable]` Storage PDA. let accounts = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(program_storage_account, false), @@ -129,8 +129,8 @@ pub fn set_interchain_security_module_instruction( .ok_or(ProgramError::InvalidSeeds)?; // Accounts: - // 0. [writeable] Storage PDA account. - // 1. [signer] Owner. + // 0. `[writeable]` Storage PDA account. + // 1. `[signer]` Owner. let accounts = vec![ AccountMeta::new(program_storage_account, false), AccountMeta::new(owner, true), diff --git a/rust/sealevel/programs/helloworld/src/processor.rs b/rust/sealevel/programs/helloworld/src/processor.rs index 4e1fdcd924..48ca4119fb 100644 --- a/rust/sealevel/programs/helloworld/src/processor.rs +++ b/rust/sealevel/programs/helloworld/src/processor.rs @@ -103,9 +103,9 @@ pub fn process_instruction( /// Creates the storage PDA. /// /// Accounts: -/// 0. [executable] System program. -/// 1. [signer] Payer. -/// 2. [writeable] Storage PDA. +/// 0. `[executable]` System program. +/// 1. `[signer]` Payer. +/// 2. `[writeable]` Storage PDA. fn init(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -155,22 +155,22 @@ fn init(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramRes /// Dispatches a message using the dispatch authority. /// /// Accounts: -/// 0. [writeable] Program storage. -/// 1. [executable] The Mailbox program. -/// 2. [writeable] Outbox PDA. -/// 3. [] This program's dispatch authority. -/// 4. [executable] System program. -/// 5. [executable] SPL Noop program. -/// 6. [signer] Payer. -/// 7. [signer] Unique message account. -/// 8. [writeable] Dispatched message PDA. An empty message PDA relating to the seeds +/// 0. `[writeable]` Program storage. +/// 1. `[executable]` The Mailbox program. +/// 2. `[writeable]` Outbox PDA. +/// 3. `[]` This program's dispatch authority. +/// 4. `[executable]` System program. +/// 5. `[executable]` SPL Noop program. +/// 6. `[signer]` Payer. +/// 7. `[signer]` Unique message account. +/// 8. `[writeable]` Dispatched message PDA. An empty message PDA relating to the seeds /// `mailbox_dispatched_message_pda_seeds` where the message contents will be stored. /// ---- if an IGP is configured ---- -/// 9. [executable] The IGP program. -/// 10. [writeable] The IGP program data. -/// 11. [writeable] The gas payment PDA. -/// 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. -/// 13. [writeable] The IGP account. +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` The gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. /// ---- end if an IGP is configured ---- fn send_hello_world( program_id: &Pubkey, @@ -261,13 +261,13 @@ fn send_hello_world( // Accounts expected by the IGP's `PayForGas` instruction: // - // 0. [executable] The system program. - // 1. [signer] The payer. - // 2. [writeable] The IGP program data. - // 3. [signer] Unique gas payment account. - // 4. [writeable] Gas payment PDA. - // 5. [writeable] The IGP account. - // 6. [] Overhead IGP account (optional). + // 0. `[executable]` The system program. + // 1. `[signer]` The payer. + // 2. `[writeable]` The IGP program data. + // 3. `[signer]` Unique gas payment account. + // 4. `[writeable]` Gas payment PDA. + // 5. `[writeable]` The IGP account. + // 6. `[]` Overhead IGP account (optional). let mut igp_payment_account_metas = vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), @@ -355,8 +355,8 @@ fn send_hello_world( /// Handles a message. /// /// Accounts: -/// 0. [writeable] Process authority specific to this program. -/// 1. [] Storage PDA account. +/// 0. `[writeable]` Process authority specific to this program. +/// 1. `[]` Storage PDA account. pub fn handle( program_id: &Pubkey, accounts: &[AccountInfo], @@ -412,8 +412,8 @@ pub fn handle( } /// Accounts: -/// 0. [writeable] Storage PDA account. -/// 1. [signer] Owner. +/// 0. `[writeable]` Storage PDA account. +/// 1. `[signer]` Owner. fn set_interchain_security_module( program_id: &Pubkey, accounts: &[AccountInfo], @@ -445,7 +445,7 @@ fn set_interchain_security_module( } /// Accounts: -/// 0. [] Storage PDA account. +/// 0. `[]` Storage PDA account. fn get_interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -491,9 +491,9 @@ fn set_account_meta_return_data(program_id: &Pubkey) -> ProgramResult { /// Enrolls remote routers. /// /// Accounts: -/// 0. [executable] System program. -/// 1. [writeable] Storage PDA account. -/// 2. [signer] Owner. +/// 0. `[executable]` System program. +/// 1. `[writeable]` Storage PDA account. +/// 2. `[signer]` Owner. fn enroll_remote_routers( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp-test/src/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-igp-test/src/functional.rs index 0a3e6c962a..a7820019ac 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp-test/src/functional.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-igp-test/src/functional.rs @@ -66,9 +66,9 @@ async fn initialize( Pubkey::find_program_address(igp_program_data_pda_seeds!(), &program_id); // Accounts: - // 0. [executable] The system program. - // 1. [signer] The payer account. - // 2. [writeable] The program data account. + // 0. `[executable]` The system program. + // 1. `[signer]` The payer account. + // 2. `[writeable]` The program data account. let init_instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::Init, @@ -96,9 +96,9 @@ async fn initialize_igp( let (igp_key, igp_bump_seed) = Pubkey::find_program_address(igp_pda_seeds!(salt), &program_id); // Accounts: - // 0. [executable] The system program. - // 1. [signer] The payer account. - // 2. [writeable] The IGP account to initialize. + // 0. `[executable]` The system program. + // 1. `[signer]` The payer account. + // 2. `[writeable]` The IGP account to initialize. let init_instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::InitIgp(InitIgp { @@ -131,9 +131,9 @@ async fn initialize_overhead_igp( Pubkey::find_program_address(overhead_igp_pda_seeds!(salt), &program_id); // Accounts: - // 0. [executable] The system program. - // 1. [signer] The payer account. - // 2. [writeable] The Overhead IGP account to initialize. + // 0. `[executable]` The system program. + // 1. `[signer]` The payer account. + // 2. `[writeable]` The Overhead IGP account to initialize. let init_instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::InitOverheadIgp(InitOverheadIgp { salt, owner, inner }), @@ -438,9 +438,9 @@ async fn test_set_gas_oracle_configs() { ]; // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The IGP. - // 2. [signer] The IGP owner. + // 0. `[executable]` The system program. + // 1. `[writeable]` The IGP. + // 2. `[signer]` The IGP owner. let instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::SetGasOracleConfigs(configs.clone()), @@ -536,9 +536,9 @@ async fn test_set_gas_oracle_configs_errors_if_owner_not_signer() { }]; // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The IGP. - // 2. [signer] The IGP owner. + // 0. `[executable]` The system program. + // 1. `[writeable]` The IGP. + // 2. `[signer]` The IGP owner. // Try with the correct owner passed in, but it's not a signer let instruction = Instruction::new_with_borsh( @@ -600,9 +600,9 @@ async fn test_set_destination_gas_overheads() { ]; // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The Overhead IGP. - // 2. [signer] The Overhead IGP owner. + // 0. `[executable]` The system program. + // 1. `[writeable]` The Overhead IGP. + // 2. `[signer]` The Overhead IGP owner. let instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::SetDestinationGasOverheads(configs.clone()), @@ -698,9 +698,9 @@ async fn test_set_destination_gas_overheads_errors_if_owner_not_signer() { }]; // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The Overhead IGP. - // 2. [signer] The Overhead IGP owner. + // 0. `[executable]` The system program. + // 1. `[writeable]` The Overhead IGP. + // 2. `[signer]` The Overhead IGP owner. // Try with the correct owner passed in, but it's not a signer let instruction = Instruction::new_with_borsh( @@ -986,13 +986,13 @@ async fn pay_for_gas( &program_id, ); - // 0. [executable] The system program. - // 1. [signer] The payer. - // 2. [writeable] The IGP program data. - // 3. [signer] Unique gas payment account. - // 4. [writeable] Gas payment PDA. - // 5. [writeable] The IGP account. - // 6. [] Overhead IGP account (optional). + // 0. `[executable]` The system program. + // 1. `[signer]` The payer. + // 2. `[writeable]` The IGP program data. + // 3. `[signer]` Unique gas payment account. + // 4. `[writeable]` Gas payment PDA. + // 5. `[writeable]` The IGP account. + // 6. `[]` Overhead IGP account (optional). let mut accounts = vec![ AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new(payer.pubkey(), true), @@ -1301,9 +1301,9 @@ async fn test_claim() { let beneficiary_balance_before = banks_client.get_balance(payer.pubkey()).await.unwrap(); // Accounts: - // 0. [executable] The system program. - // 1. [writeable] The IGP. - // 2. [writeable] The IGP beneficiary. + // 0. `[executable]` The system program. + // 1. `[writeable]` The IGP. + // 2. `[writeable]` The IGP beneficiary. process_instruction( &mut banks_client, Instruction::new_with_borsh( @@ -1357,8 +1357,8 @@ async fn test_set_igp_beneficiary() { let new_beneficiary = Pubkey::new_unique(); // Accounts: - // 0. [] The IGP. - // 1. [signer] The owner of the IGP account. + // 0. `[]` The IGP. + // 1. `[signer]` The owner of the IGP account. let instruction = Instruction::new_with_borsh( program_id, &IgpInstruction::SetIgpBeneficiary(new_beneficiary), @@ -1403,8 +1403,8 @@ async fn test_set_igp_beneficiary_errors_if_owner_not_signer() { .unwrap(); // Accounts: - // 0. [] The IGP. - // 1. [signer] The owner of the IGP account. + // 0. `[]` The IGP. + // 1. `[signer]` The owner of the IGP account. // Try with the right owner passed in, but it's not a signer let instruction = Instruction::new_with_borsh( @@ -1448,8 +1448,8 @@ async fn run_transfer_ownership_tests Result { + let ixn = Instruction::Claim; + + // Accounts: + // 0. `[executable]` The system program. + // 1. `[writeable]` The IGP. + // 2. `[writeable]` The IGP beneficiary. + let accounts = vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(igp, false), + AccountMeta::new(beneficiary, false), + ]; + + let instruction = SolanaInstruction { + program_id, + data: ixn.try_to_vec()?, + accounts, + }; + + Ok(instruction) +} + +/// Gets an instruction to claim funds from an IGP to the beneficiary. +pub fn set_beneficiary_instruction( + program_id: Pubkey, + igp: Pubkey, + igp_owner: Pubkey, + new_beneficiary: Pubkey, +) -> Result { + let ixn = Instruction::SetIgpBeneficiary(new_beneficiary); + + // Accounts: + // 0. `[]` The IGP. + // 1. `[signer]` The owner of the IGP account. + let accounts = vec![ + AccountMeta::new(igp, false), + AccountMeta::new(igp_owner, true), + ]; + + let instruction = SolanaInstruction { + program_id, + data: ixn.try_to_vec()?, + accounts, + }; + + Ok(instruction) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-igp/src/processor.rs index 044ce42fc1..35546264d4 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp/src/processor.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-igp/src/processor.rs @@ -89,9 +89,9 @@ pub fn process_instruction( /// Initializes the program. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [signer] The payer account. -/// 2. [writeable] The program data PDA account. +/// 0. `[executable]` The system program. +/// 1. `[signer]` The payer account. +/// 2. `[writeable]` The program data PDA account. fn init(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -147,9 +147,9 @@ fn init(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { /// Initialize a new IGP account. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [signer] The payer account. -/// 2. [writeable] The IGP account to initialize. +/// 0. `[executable]` The system program. +/// 1. `[signer]` The payer account. +/// 2. `[writeable]` The IGP account to initialize. fn init_igp(program_id: &Pubkey, accounts: &[AccountInfo], data: InitIgp) -> ProgramResult { let igp_key = init_igp_variant( program_id, @@ -175,9 +175,9 @@ fn init_igp(program_id: &Pubkey, accounts: &[AccountInfo], data: InitIgp) -> Pro /// Initialize a new overhead IGP account. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [signer] The payer account. -/// 2. [writeable] The Overhead IGP account to initialize. +/// 0. `[executable]` The system program. +/// 1. `[signer]` The payer account. +/// 2. `[writeable]` The Overhead IGP account to initialize. fn init_overhead_igp( program_id: &Pubkey, accounts: &[AccountInfo], @@ -258,13 +258,13 @@ fn init_igp_variant( /// Pay for gas. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [signer] The payer. -/// 2. [writeable] The IGP program data. -/// 3. [signer] Unique gas payment account. -/// 4. [writeable] Gas payment PDA. -/// 5. [writeable] The IGP account. -/// 6. [] Overhead IGP account (optional). +/// 0. `[executable]` The system program. +/// 1. `[signer]` The payer. +/// 2. `[writeable]` The IGP program data. +/// 3. `[signer]` Unique gas payment account. +/// 4. `[writeable]` Gas payment PDA. +/// 5. `[writeable]` The IGP account. +/// 6. `[]` Overhead IGP account (optional). fn pay_for_gas(program_id: &Pubkey, accounts: &[AccountInfo], payment: PayForGas) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -407,9 +407,9 @@ fn pay_for_gas(program_id: &Pubkey, accounts: &[AccountInfo], payment: PayForGas /// Quotes the required payment for a given gas amount and destination domain. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [] The IGP account. -/// 2. [] The overhead IGP account (optional). +/// 0. `[executable]` The system program. +/// 1. `[]` The IGP account. +/// 2. `[]` The overhead IGP account (optional). fn quote_gas_payment( program_id: &Pubkey, accounts: &[AccountInfo], @@ -463,8 +463,8 @@ fn quote_gas_payment( /// Sets the beneficiary of an IGP. /// /// Accounts: -/// 0. [] The IGP. -/// 1. [signer] The owner of the IGP account. +/// 0. `[]` The IGP. +/// 1. `[signer]` The owner of the IGP account. fn set_igp_beneficiary( program_id: &Pubkey, accounts: &[AccountInfo], @@ -485,8 +485,8 @@ fn set_igp_beneficiary( /// Transfers ownership of an IGP variant. /// /// Accounts: -/// 0. [writeable] The IGP or OverheadIGP. -/// 1. [signer] The owner of the IGP account. +/// 0. `[writeable]` The IGP or OverheadIGP. +/// 1. `[signer]` The owner of the IGP account. fn transfer_igp_variant_ownership( program_id: &Pubkey, accounts: &[AccountInfo], @@ -506,8 +506,8 @@ fn transfer_igp_variant_ownership ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -583,9 +583,9 @@ fn claim(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { /// Sets destination gas overheads for an OverheadIGP. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The OverheadIGP. -/// 2. [signer] The OverheadIGP owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The OverheadIGP. +/// 2. `[signer]` The OverheadIGP owner. fn set_destination_gas_overheads( program_id: &Pubkey, accounts: &[AccountInfo], @@ -629,9 +629,9 @@ fn set_destination_gas_overheads( /// Sets gas oracle configs for an IGP. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The IGP. -/// 2. [signer] The IGP owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The IGP. +/// 2. `[signer]` The IGP owner. fn set_gas_oracle_configs( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/instruction.rs index df0d434fc8..3f3d0af86d 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/instruction.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/instruction.rs @@ -23,11 +23,11 @@ pub fn init_instruction( let mut instruction = lib_init_instruction(program_id, payer, init)?; // Add additional account metas: - // 0. [executable] The SPL token program for the mint, i.e. either SPL token program or the 2022 version. - // 1. [] The mint. - // 2. [executable] The Rent sysvar program. - // 3. [writable] The escrow PDA account. - // 4. [writable] The ATA payer PDA account. + // 0. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. + // 1. `[]` The mint. + // 2. `[executable]` The Rent sysvar program. + // 3. `[writable]` The escrow PDA account. + // 4. `[writable]` The ATA payer PDA account. let (escrow_key, _escrow_bump) = Pubkey::find_program_address(hyperlane_token_escrow_pda_seeds!(), &program_id); diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs index 81b867b904..0fc224667e 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs @@ -100,11 +100,11 @@ impl HyperlaneSealevelTokenPlugin for CollateralPlugin { /// Initializes the plugin. /// /// Accounts: - /// 0. [executable] The SPL token program for the mint, i.e. either SPL token program or the 2022 version. - /// 1. [] The mint. - /// 2. [executable] The Rent sysvar program. - /// 3. [writable] The escrow PDA account. - /// 4. [writable] The ATA payer PDA account. + /// 0. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. + /// 1. `[]` The mint. + /// 2. `[executable]` The Rent sysvar program. + /// 3. `[writable]` The escrow PDA account. + /// 4. `[writable]` The ATA payer PDA account. fn initialize<'a, 'b>( program_id: &Pubkey, system_program: &'a AccountInfo<'b>, @@ -232,10 +232,10 @@ impl HyperlaneSealevelTokenPlugin for CollateralPlugin { /// Burns the tokens from the sender's associated token account. /// /// Accounts: - /// 0. [executable] The SPL token program for the mint. - /// 1. [writeable] The mint. - /// 2. [writeable] The token sender's associated token account, from which tokens will be sent. - /// 3. [writeable] The escrow PDA account. + /// 0. `[executable]` The SPL token program for the mint. + /// 1. `[writeable]` The mint. + /// 2. `[writeable]` The token sender's associated token account, from which tokens will be sent. + /// 3. `[writeable]` The escrow PDA account. fn transfer_in<'a, 'b>( _program_id: &Pubkey, token: &HyperlaneToken, @@ -308,12 +308,12 @@ impl HyperlaneSealevelTokenPlugin for CollateralPlugin { /// result of a transfer to this chain from a remote chain. /// /// Accounts: - /// 0. [executable] SPL token for the mint. - /// 1. [executable] SPL associated token account. - /// 2. [writeable] Mint account. - /// 3. [writeable] Recipient associated token account. - /// 4. [writeable] ATA payer PDA account. - /// 5. [writeable] Escrow account. + /// 0. `[executable]` SPL token for the mint. + /// 1. `[executable]` SPL associated token account. + /// 2. `[writeable]` Mint account. + /// 3. `[writeable]` Recipient associated token account. + /// 4. `[writeable]` ATA payer PDA account. + /// 5. `[writeable]` Escrow account. fn transfer_out<'a, 'b>( program_id: &Pubkey, token: &HyperlaneToken, diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/processor.rs index 09546028b8..e9dff90ee0 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/processor.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/processor.rs @@ -89,15 +89,15 @@ pub fn process_instruction( /// Initializes the program. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writable] The token PDA account. -/// 2. [writable] The dispatch authority PDA account. -/// 3. [signer] The payer and access control owner of the program. -/// 4. [executable] The SPL token program for the mint, i.e. either SPL token program or the 2022 version. -/// 5. [] The mint. -/// 6. [executable] The Rent sysvar program. -/// 7. [writable] The escrow PDA account. -/// 8. [writable] The ATA payer PDA account. +/// 0. `[executable]` The system program. +/// 1. `[writable]` The token PDA account. +/// 2. `[writable]` The dispatch authority PDA account. +/// 3. `[signer]` The payer and access control owner of the program. +/// 4. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. +/// 5. `[]` The mint. +/// 6. `[executable]` The Rent sysvar program. +/// 7. `[writable]` The escrow PDA account. +/// 8. `[writable]` The ATA payer PDA account. fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { HyperlaneSealevelToken::::initialize(program_id, accounts, init) } @@ -107,26 +107,26 @@ fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> Prog /// then dispatches a message to the remote recipient. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [executable] The spl_noop program. -/// 2. [] The token PDA account. -/// 3. [executable] The mailbox program. -/// 4. [writeable] The mailbox outbox account. -/// 5. [] Message dispatch authority. -/// 6. [signer] The token sender and mailbox payer. -/// 7. [signer] Unique message / gas payment account. -/// 8. [writeable] Message storage PDA. +/// 0. `[executable]` The system program. +/// 1. `[executable]` The spl_noop program. +/// 2. `[]` The token PDA account. +/// 3. `[executable]` The mailbox program. +/// 4. `[writeable]` The mailbox outbox account. +/// 5. `[]` Message dispatch authority. +/// 6. `[signer]` The token sender and mailbox payer. +/// 7. `[signer]` Unique message / gas payment account. +/// 8. `[writeable]` Message storage PDA. /// ---- If using an IGP ---- -/// 9. [executable] The IGP program. -/// 10. [writeable] The IGP program data. -/// 11. [writeable] Gas payment PDA. -/// 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. -/// 13. [writeable] The IGP account. +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` Gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. /// ---- End if ---- -/// 14. [executable] The SPL token program for the mint. -/// 15. [writeable] The mint. -/// 16. [writeable] The token sender's associated token account, from which tokens will be sent. -/// 17. [writeable] The escrow PDA account. +/// 14. `[executable]` The SPL token program for the mint. +/// 15. `[writeable]` The mint. +/// 16. `[writeable]` The token sender's associated token account, from which tokens will be sent. +/// 17. `[writeable]` The escrow PDA account. fn transfer_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -136,16 +136,16 @@ fn transfer_remote( } // Accounts: -// 0. [signer] Mailbox process authority specific to this program. -// 1. [executable] system_program -// 2. [] hyperlane_token storage -// 3. [] recipient wallet address -// 4. [executable] SPL token 2022 program. -// 5. [executable] SPL associated token account. -// 6. [writeable] Mint account. -// 7. [writeable] Recipient associated token account. -// 8. [writeable] ATA payer PDA account. -// 9. [writeable] Escrow account. +// 0. `[signer]` Mailbox process authority specific to this program. +// 1. `[executable]` system_program +// 2. `[]` hyperlane_token storage +// 3. `[]` recipient wallet address +// 4. `[executable]` SPL token 2022 program. +// 5. `[executable]` SPL associated token account. +// 6. `[writeable]` Mint account. +// 7. `[writeable]` Recipient associated token account. +// 8. `[writeable]` ATA payer PDA account. +// 9. `[writeable]` Escrow account. fn transfer_from_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -171,9 +171,9 @@ fn transfer_from_remote_account_metas( /// Enrolls a remote router. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn enroll_remote_router( program_id: &Pubkey, accounts: &[AccountInfo], @@ -185,9 +185,9 @@ fn enroll_remote_router( /// Enrolls remote routers. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn enroll_remote_routers( program_id: &Pubkey, accounts: &[AccountInfo], @@ -199,9 +199,9 @@ fn enroll_remote_routers( /// Sets the destination gas configs. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn set_destination_gas_configs( program_id: &Pubkey, accounts: &[AccountInfo], @@ -215,8 +215,8 @@ fn set_destination_gas_configs( /// Transfers ownership. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The current owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The current owner. fn transfer_ownership( program_id: &Pubkey, accounts: &[AccountInfo], @@ -228,7 +228,7 @@ fn transfer_ownership( /// Gets the interchain security module, returning it as a serialized Option. /// /// Accounts: -/// 0. [] The token PDA account. +/// 0. `[]` The token PDA account. fn interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { HyperlaneSealevelToken::::interchain_security_module(program_id, accounts) } @@ -244,8 +244,8 @@ fn interchain_security_module_account_metas(program_id: &Pubkey) -> ProgramResul /// Lets the owner set the interchain security module. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The access control owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. fn set_interchain_security_module( program_id: &Pubkey, accounts: &[AccountInfo], @@ -259,8 +259,8 @@ fn set_interchain_security_module( /// Lets the owner set the interchain gas paymaster. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The access control owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. fn set_interchain_gas_paymaster( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/tests/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/tests/functional.rs index 7ac4bd8e20..0f5e33f4df 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/tests/functional.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/tests/functional.rs @@ -299,15 +299,15 @@ async fn initialize_hyperlane_token( .encode() .unwrap(), vec![ - // 0. [executable] The system program. - // 1. [writable] The token PDA account. - // 2. [writable] The dispatch authority PDA account. - // 3. [signer] The payer. - // 4. [executable] The SPL token program for the mint, i.e. either SPL token program or the 2022 version. - // 5. [] The mint. - // 6. [executable] The Rent sysvar program. - // 7. [writable] The escrow PDA account. - // 8. [writable] The ATA payer PDA account. + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer. + // 4. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. + // 5. `[]` The mint. + // 6. `[executable]` The Rent sysvar program. + // 7. `[writable]` The escrow PDA account. + // 8. `[writable]` The ATA payer PDA account. AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(token_account_key, false), AccountMeta::new(dispatch_authority_key, false), @@ -642,26 +642,26 @@ async fn test_transfer_remote(spl_token_program_id: Pubkey) { }) .encode() .unwrap(), - // 0. [executable] The system program. - // 1. [executable] The spl_noop program. - // 2. [] The token PDA account. - // 3. [executable] The mailbox program. - // 4. [writeable] The mailbox outbox account. - // 5. [] Message dispatch authority. - // 6. [signer] The token sender and mailbox payer. - // 7. [signer] Unique message account. - // 8. [writeable] Message storage PDA. + // 0. `[executable]` The system program. + // 1. `[executable]` The spl_noop program. + // 2. `[]` The token PDA account. + // 3. `[executable]` The mailbox program. + // 4. `[writeable]` The mailbox outbox account. + // 5. `[]` Message dispatch authority. + // 6. `[signer]` The token sender and mailbox payer. + // 7. `[signer]` Unique message account. + // 8. `[writeable]` Message storage PDA. // ---- If using an IGP ---- - // 9. [executable] The IGP program. - // 10. [writeable] The IGP program data. - // 11. [writeable] Gas payment PDA. - // 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. - // 13. [writeable] The IGP account. + // 9. `[executable]` The IGP program. + // 10. `[writeable]` The IGP program data. + // 11. `[writeable]` Gas payment PDA. + // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. `[writeable]` The IGP account. // ---- End if ---- - // 14. [executable] The spl_token_2022 program. - // 15. [writeable] The mint. - // 16. [writeable] The token sender's associated token account, from which tokens will be sent. - // 17. [writeable] The escrow PDA account. + // 14. `[executable]` The spl_token_2022 program. + // 15. `[writeable]` The mint. + // 16. `[writeable]` The token sender's associated token account, from which tokens will be sent. + // 17. `[writeable]` The escrow PDA account. vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(spl_noop::id(), false), @@ -725,7 +725,7 @@ async fn test_transfer_remote(spl_token_program_id: Pubkey) { .unwrap(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: LOCAL_DOMAIN, sender: program_id.to_bytes().into(), @@ -869,7 +869,7 @@ async fn transfer_from_remote( ); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: origin_override.unwrap_or(REMOTE_DOMAIN), // Default to the remote router as the sender @@ -1084,16 +1084,16 @@ async fn test_transfer_from_remote_errors_if_process_authority_not_signer() { .unwrap(), vec![ // Recipient.handle accounts - // 0. [signer] Mailbox process authority - // 1. [executable] system_program - // 2. [] hyperlane_token storage - // 3. [] recipient wallet address - // 4. [executable] SPL token 2022 program. - // 5. [executable] SPL associated token account. - // 6. [writeable] Mint account. - // 7. [writeable] Recipient associated token account. - // 8. [writeable] ATA payer PDA account. - // 9. [writeable] Escrow account. + // 0. `[signer]` Mailbox process authority + // 1. `[executable]` system_program + // 2. `[]` hyperlane_token storage + // 3. `[]` recipient wallet address + // 4. `[executable]` SPL token 2022 program. + // 5. `[executable]` SPL associated token account. + // 6. `[writeable]` Mint account. + // 7. `[writeable]` Recipient associated token account. + // 8. `[writeable]` ATA payer PDA account. + // 9. `[writeable]` Escrow account. AccountMeta::new_readonly( hyperlane_token_accounts.mailbox_process_authority, false, diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native/src/instruction.rs index 356d176b09..2cc12149f7 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-native/src/instruction.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native/src/instruction.rs @@ -19,7 +19,7 @@ pub fn init_instruction( let mut instruction = lib_init_instruction(program_id, payer, init)?; // Add additional account metas: - // 0. [writable] The native collateral PDA account. + // 0. `[writable]` The native collateral PDA account. let (native_collateral_key, _native_collatera_bump) = Pubkey::find_program_address(hyperlane_token_native_collateral_pda_seeds!(), &program_id); diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native/src/plugin.rs index 855fa6dcb3..8c553058c0 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-native/src/plugin.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native/src/plugin.rs @@ -76,7 +76,7 @@ impl HyperlaneSealevelTokenPlugin for NativePlugin { /// Initializes the plugin. /// /// Accounts: - /// 0. [writable] The native collateral PDA account. + /// 0. `[writable]` The native collateral PDA account. fn initialize<'a, 'b>( program_id: &Pubkey, system_program: &'a AccountInfo<'b>, @@ -115,8 +115,8 @@ impl HyperlaneSealevelTokenPlugin for NativePlugin { /// Burns the tokens from the sender's associated token account. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [writeable] The native token collateral PDA account. + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The native token collateral PDA account. fn transfer_in<'a, 'b>( program_id: &Pubkey, token: &HyperlaneToken, @@ -145,8 +145,8 @@ impl HyperlaneSealevelTokenPlugin for NativePlugin { /// result of a transfer to this chain from a remote chain. /// /// Accounts: - /// 0. [executable] The system program. - /// 1. [writeable] The native token collateral PDA account. + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The native token collateral PDA account. fn transfer_out<'a, 'b>( program_id: &Pubkey, token: &HyperlaneToken, diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native/src/processor.rs index ab3121b537..1cbfd072dd 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-native/src/processor.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native/src/processor.rs @@ -89,11 +89,11 @@ pub fn process_instruction( /// Initializes the program. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writable] The token PDA account. -/// 2. [writable] The dispatch authority PDA account. -/// 3. [signer] The payer and mailbox payer. -/// 4. [writable] The native collateral PDA account. +/// 0. `[executable]` The system program. +/// 1. `[writable]` The token PDA account. +/// 2. `[writable]` The dispatch authority PDA account. +/// 3. `[signer]` The payer and mailbox payer. +/// 4. `[writable]` The native collateral PDA account. fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { HyperlaneSealevelToken::::initialize(program_id, accounts, init) } @@ -103,24 +103,24 @@ fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> Prog /// then dispatches a message to the remote recipient. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [executable] The spl_noop program. -/// 2. [] The token PDA account. -/// 3. [executable] The mailbox program. -/// 4. [writeable] The mailbox outbox account. -/// 5. [] Message dispatch authority. -/// 6. [signer] The token sender and mailbox payer. -/// 7. [signer] Unique message / gas payment account. -/// 8. [writeable] Message storage PDA. +/// 0. `[executable]` The system program. +/// 1. `[executable]` The spl_noop program. +/// 2. `[]` The token PDA account. +/// 3. `[executable]` The mailbox program. +/// 4. `[writeable]` The mailbox outbox account. +/// 5. `[]` Message dispatch authority. +/// 6. `[signer]` The token sender and mailbox payer. +/// 7. `[signer]` Unique message / gas payment account. +/// 8. `[writeable]` Message storage PDA. /// ---- If using an IGP ---- -/// 9. [executable] The IGP program. -/// 10. [writeable] The IGP program data. -/// 11. [writeable] Gas payment PDA. -/// 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. -/// 13. [writeable] The IGP account. +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` Gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. /// ---- End if ---- -/// 14. [executable] The system program. -/// 15. [writeable] The native token collateral PDA account. +/// 14. `[executable]` The system program. +/// 15. `[writeable]` The native token collateral PDA account. fn transfer_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -130,12 +130,12 @@ fn transfer_remote( } /// Accounts: -/// 0. [signer] Mailbox processor authority specific to this program. -/// 1. [executable] system_program -/// 2. [] hyperlane_token storage -/// 3. [writeable] recipient wallet address -/// 4. [executable] The system program. -/// 5. [writeable] The native token collateral PDA account. +/// 0. `[signer]` Mailbox processor authority specific to this program. +/// 1. `[executable]` system_program +/// 2. `[]` hyperlane_token storage +/// 3. `[writeable]` recipient wallet address +/// 4. `[executable]` The system program. +/// 5. `[writeable]` The native token collateral PDA account. fn transfer_from_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -161,9 +161,9 @@ fn transfer_from_remote_account_metas( /// Enrolls a remote router. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn enroll_remote_router( program_id: &Pubkey, accounts: &[AccountInfo], @@ -175,9 +175,9 @@ fn enroll_remote_router( /// Enrolls remote routers. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn enroll_remote_routers( program_id: &Pubkey, accounts: &[AccountInfo], @@ -189,9 +189,9 @@ fn enroll_remote_routers( /// Sets the destination gas configs. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn set_destination_gas_configs( program_id: &Pubkey, accounts: &[AccountInfo], @@ -205,8 +205,8 @@ fn set_destination_gas_configs( /// Transfers ownership. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The current owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The current owner. fn transfer_ownership( program_id: &Pubkey, accounts: &[AccountInfo], @@ -218,7 +218,7 @@ fn transfer_ownership( /// Gets the interchain security module, returning it as a serialized Option. /// /// Accounts: -/// 0. [] The token PDA account. +/// 0. `[]` The token PDA account. fn interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { HyperlaneSealevelToken::::interchain_security_module(program_id, accounts) } @@ -234,8 +234,8 @@ fn interchain_security_module_account_metas(program_id: &Pubkey) -> ProgramResul /// Lets the owner set the interchain security module. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The access control owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. fn set_interchain_security_module( program_id: &Pubkey, accounts: &[AccountInfo], @@ -249,8 +249,8 @@ fn set_interchain_security_module( /// Lets the owner set the interchain gas paymaster. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The access control owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. fn set_interchain_gas_paymaster( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native/tests/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native/tests/functional.rs index ec5f6e438d..1863f5b14a 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-native/tests/functional.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native/tests/functional.rs @@ -148,11 +148,11 @@ async fn initialize_hyperlane_token( .encode() .unwrap(), vec![ - // 0. [executable] The system program. - // 1. [writable] The token PDA account. - // 2. [writable] The dispatch authority PDA account. - // 3. [signer] The payer and mailbox payer. - // 4. [writable] The native collateral PDA account. + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer and mailbox payer. + // 4. `[writable]` The native collateral PDA account. AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(token_account_key, false), AccountMeta::new(dispatch_authority_key, false), @@ -416,24 +416,24 @@ async fn test_transfer_remote() { }) .encode() .unwrap(), - // 0. [executable] The system program. - // 1. [executable] The spl_noop program. - // 2. [] The token PDA account. - // 3. [executable] The mailbox program. - // 4. [writeable] The mailbox outbox account. - // 5. [] Message dispatch authority. - // 6. [signer] The token sender and mailbox payer. - // 7. [signer] Unique message / gas payment account. - // 8. [writeable] Message storage PDA. + // 0. `[executable]` The system program. + // 1. `[executable]` The spl_noop program. + // 2. `[]` The token PDA account. + // 3. `[executable]` The mailbox program. + // 4. `[writeable]` The mailbox outbox account. + // 5. `[]` Message dispatch authority. + // 6. `[signer]` The token sender and mailbox payer. + // 7. `[signer]` Unique message / gas payment account. + // 8. `[writeable]` Message storage PDA. // ---- If using an IGP ---- - // 9. [executable] The IGP program. - // 10. [writeable] The IGP program data. - // 11. [writeable] Gas payment PDA. - // 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. - // 13. [writeable] The IGP account. + // 9. `[executable]` The IGP program. + // 10. `[writeable]` The IGP program data. + // 11. `[writeable]` Gas payment PDA. + // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. `[writeable]` The IGP account. // ---- End if ---- - // 14. [executable] The system program. - // 15. [writeable] The native token collateral PDA account. + // 14. `[executable]` The system program. + // 15. `[writeable]` The native token collateral PDA account. vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(spl_noop::id(), false), @@ -510,7 +510,7 @@ async fn test_transfer_remote() { .unwrap(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: LOCAL_DOMAIN, sender: program_id.to_bytes().into(), @@ -616,7 +616,7 @@ async fn transfer_from_remote( let recipient: H256 = recipient_pubkey.to_bytes().into(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: origin_override.unwrap_or(REMOTE_DOMAIN), // Default to the remote router as the sender @@ -762,12 +762,12 @@ async fn test_transfer_from_remote_errors_if_process_authority_not_signer() { .unwrap(), vec![ // Recipient.handle accounts - // 0. [signer] Mailbox processor authority specific to this program. - // 1. [executable] system_program - // 2. [] hyperlane_token storage - // 3. [writeable] recipient wallet address - // 4. [executable] The system program. - // 5. [writeable] The native token collateral PDA account. + // 0. `[signer]` Mailbox processor authority specific to this program. + // 1. `[executable]` system_program + // 2. `[]` hyperlane_token storage + // 3. `[writeable]` recipient wallet address + // 4. `[executable]` The system program. + // 5. `[writeable]` The native token collateral PDA account. AccountMeta::new_readonly( hyperlane_token_accounts.mailbox_process_authority, false, diff --git a/rust/sealevel/programs/hyperlane-sealevel-token/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-token/src/instruction.rs index 594e5836e5..0bfab1f334 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token/src/instruction.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token/src/instruction.rs @@ -19,8 +19,8 @@ pub fn init_instruction( let mut instruction = lib_init_instruction(program_id, payer, init)?; // Add additional account metas: - // 0. [writable] The mint / mint authority PDA account. - // 1. [writable] The ATA payer PDA account. + // 0. `[writable]` The mint / mint authority PDA account. + // 1. `[writable]` The ATA payer PDA account. let (mint_key, _mint_bump) = Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); diff --git a/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs index 60928790ee..c5029f9423 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs @@ -121,8 +121,8 @@ impl HyperlaneSealevelTokenPlugin for SyntheticPlugin { /// instruction initializing the mint with the SPL token 2022 program. /// /// Accounts: - /// 0. [writable] The mint / mint authority PDA account. - /// 1. [writable] The ATA payer PDA account. + /// 0. `[writable]` The mint / mint authority PDA account. + /// 1. `[writable]` The ATA payer PDA account. fn initialize<'a, 'b>( program_id: &Pubkey, system_program: &'a AccountInfo<'b>, @@ -187,9 +187,9 @@ impl HyperlaneSealevelTokenPlugin for SyntheticPlugin { /// Burns the tokens from the sender's associated token account. /// /// Accounts: - /// 0. [executable] The spl_token_2022 program. - /// 1. [writeable] The mint / mint authority PDA account. - /// 2. [writeable] The token sender's associated token account, from which tokens will be burned. + /// 0. `[executable]` The spl_token_2022 program. + /// 1. `[writeable]` The mint / mint authority PDA account. + /// 2. `[writeable]` The token sender's associated token account, from which tokens will be burned. fn transfer_in<'a, 'b>( program_id: &Pubkey, token: &HyperlaneToken, @@ -244,11 +244,11 @@ impl HyperlaneSealevelTokenPlugin for SyntheticPlugin { /// result of a transfer to this chain from a remote chain. /// /// Accounts: - /// 0. [executable] SPL token 2022 program - /// 1. [executable] SPL associated token account - /// 2. [writeable] Mint account - /// 3. [writeable] Recipient associated token account - /// 4. [writeable] ATA payer PDA account. + /// 0. `[executable]` SPL token 2022 program + /// 1. `[executable]` SPL associated token account + /// 2. `[writeable]` Mint account + /// 3. `[writeable]` Recipient associated token account + /// 4. `[writeable]` ATA payer PDA account. fn transfer_out<'a, 'b>( program_id: &Pubkey, token: &HyperlaneToken, diff --git a/rust/sealevel/programs/hyperlane-sealevel-token/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token/src/processor.rs index 50990fe4a7..f203a1e4e6 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token/src/processor.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token/src/processor.rs @@ -89,12 +89,12 @@ pub fn process_instruction( /// Initializes the program. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writable] The token PDA account. -/// 2. [writable] The dispatch authority PDA account. -/// 3. [signer] The payer. -/// 4. [writable] The mint / mint authority PDA account. -/// 5. [writable] The ATA payer PDA account. +/// 0. `[executable]` The system program. +/// 1. `[writable]` The token PDA account. +/// 2. `[writable]` The dispatch authority PDA account. +/// 3. `[signer]` The payer. +/// 4. `[writable]` The mint / mint authority PDA account. +/// 5. `[writable]` The ATA payer PDA account. fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { HyperlaneSealevelToken::::initialize(program_id, accounts, init) } @@ -104,26 +104,26 @@ fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> Prog /// then dispatches a message to the remote recipient. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [executable] The spl_noop program. -/// 2. [] The token PDA account. -/// 3. [executable] The mailbox program. -/// 4. [writeable] The mailbox outbox account. -/// 5. [] Message dispatch authority. -/// 6. [signer] The token sender and mailbox payer. -/// 7. [signer] Unique message / gas payment account. -/// 8. [writeable] Message storage PDA. +/// 0. `[executable]` The system program. +/// 1. `[executable]` The spl_noop program. +/// 2. `[]` The token PDA account. +/// 3. `[executable]` The mailbox program. +/// 4. `[writeable]` The mailbox outbox account. +/// 5. `[]` Message dispatch authority. +/// 6. `[signer]` The token sender and mailbox payer. +/// 7. `[signer]` Unique message / gas payment account. +/// 8. `[writeable]` Message storage PDA. /// ---- If using an IGP ---- -/// 9. [executable] The IGP program. -/// 10. [writeable] The IGP program data. -/// 11. [writeable] Gas payment PDA. -/// 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. -/// 13. [writeable] The IGP account. +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` Gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. /// ---- End if ---- -/// 14. [signer] The token sender. -/// 15. [executable] The spl_token_2022 program. -/// 16. [writeable] The mint / mint authority PDA account. -/// 17. [writeable] The token sender's associated token account, from which tokens will be burned. +/// 14. `[signer]` The token sender. +/// 15. `[executable]` The spl_token_2022 program. +/// 16. `[writeable]` The mint / mint authority PDA account. +/// 17. `[writeable]` The token sender's associated token account, from which tokens will be burned. fn transfer_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -133,15 +133,15 @@ fn transfer_remote( } // Accounts: -// 0. [signer] Mailbox process authority specific to this program. -// 1. [executable] system_program -// 2. [] hyperlane_token storage -// 3. [] recipient wallet address -// 4. [executable] SPL token 2022 program -// 5. [executable] SPL associated token account -// 6. [writeable] Mint account -// 7. [writeable] Recipient associated token account -// 8. [writeable] ATA payer PDA account. +// 0. `[signer]` Mailbox process authority specific to this program. +// 1. `[executable]` system_program +// 2. `[]` hyperlane_token storage +// 3. `[]` recipient wallet address +// 4. `[executable]` SPL token 2022 program +// 5. `[executable]` SPL associated token account +// 6. `[writeable]` Mint account +// 7. `[writeable]` Recipient associated token account +// 8. `[writeable]` ATA payer PDA account. fn transfer_from_remote( program_id: &Pubkey, accounts: &[AccountInfo], @@ -164,9 +164,9 @@ fn transfer_from_remote_account_metas( /// Enrolls a remote router. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn enroll_remote_router( program_id: &Pubkey, accounts: &[AccountInfo], @@ -178,9 +178,9 @@ fn enroll_remote_router( /// Enrolls remote routers. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn enroll_remote_routers( program_id: &Pubkey, accounts: &[AccountInfo], @@ -192,9 +192,9 @@ fn enroll_remote_routers( /// Sets the destination gas configs. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [writeable] The token PDA account. -/// 2. [signer] The owner. +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. fn set_destination_gas_configs( program_id: &Pubkey, accounts: &[AccountInfo], @@ -208,8 +208,8 @@ fn set_destination_gas_configs( /// Transfers ownership. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The current owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The current owner. fn transfer_ownership( program_id: &Pubkey, accounts: &[AccountInfo], @@ -221,7 +221,7 @@ fn transfer_ownership( /// Gets the interchain security module, returning it as a serialized Option. /// /// Accounts: -/// 0. [] The token PDA account. +/// 0. `[]` The token PDA account. fn interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { HyperlaneSealevelToken::::interchain_security_module(program_id, accounts) } @@ -237,8 +237,8 @@ fn interchain_security_module_account_metas(program_id: &Pubkey) -> ProgramResul /// Lets the owner set the interchain security module. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The access control owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. fn set_interchain_security_module( program_id: &Pubkey, accounts: &[AccountInfo], @@ -252,8 +252,8 @@ fn set_interchain_security_module( /// Lets the owner set the interchain gas paymaster. /// /// Accounts: -/// 0. [writeable] The token PDA account. -/// 1. [signer] The access control owner. +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. fn set_interchain_gas_paymaster( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/hyperlane-sealevel-token/tests/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-token/tests/functional.rs index 7cac39aee5..73324efde2 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token/tests/functional.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token/tests/functional.rs @@ -165,12 +165,12 @@ async fn initialize_hyperlane_token( .encode() .unwrap(), vec![ - // 0. [executable] The system program. - // 1. [writable] The token PDA account. - // 2. [writable] The dispatch authority PDA account. - // 3. [signer] The payer. - // 4. [writable] The mint / mint authority PDA account. - // 5. [writable] The ATA payer PDA account. + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer. + // 4. `[writable]` The mint / mint authority PDA account. + // 5. `[writable]` The ATA payer PDA account. AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new(token_account_key, false), AccountMeta::new(dispatch_authority_key, false), @@ -451,7 +451,7 @@ async fn transfer_from_remote( ); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: origin_override.unwrap_or(REMOTE_DOMAIN), // Default to the remote router as the sender @@ -592,15 +592,15 @@ async fn test_transfer_from_remote_errors_if_process_authority_not_signer() { .unwrap(), vec![ // Recipient.handle accounts - // 0. [signer] Mailbox process authority specific to this program. - // 1. [executable] system_program - // 2. [] hyperlane_token storage - // 3. [] recipient wallet address - // 4. [executable] SPL token 2022 program - // 5. [executable] SPL associated token account - // 6. [writeable] Mint account - // 7. [writeable] Recipient associated token account - // 8. [writeable] ATA payer PDA account. + // 0. `[signer]` Mailbox process authority specific to this program. + // 1. `[executable]` system_program + // 2. `[]` hyperlane_token storage + // 3. `[]` recipient wallet address + // 4. `[executable]` SPL token 2022 program + // 5. `[executable]` SPL associated token account + // 6. `[writeable]` Mint account + // 7. `[writeable]` Recipient associated token account + // 8. `[writeable]` ATA payer PDA account. AccountMeta::new_readonly( hyperlane_token_accounts.mailbox_process_authority, false, @@ -710,25 +710,25 @@ async fn test_transfer_remote() { }) .encode() .unwrap(), - // 0. [executable] The system program. - // 1. [executable] The spl_noop program. - // 2. [] The token PDA account. - // 3. [executable] The mailbox program. - // 4. [writeable] The mailbox outbox account. - // 5. [] Message dispatch authority. - // 6. [signer] The token sender and mailbox payer. - // 7. [signer] Unique message account. - // 8. [writeable] Message storage PDA. + // 0. `[executable]` The system program. + // 1. `[executable]` The spl_noop program. + // 2. `[]` The token PDA account. + // 3. `[executable]` The mailbox program. + // 4. `[writeable]` The mailbox outbox account. + // 5. `[]` Message dispatch authority. + // 6. `[signer]` The token sender and mailbox payer. + // 7. `[signer]` Unique message account. + // 8. `[writeable]` Message storage PDA. // ---- If using an IGP ---- - // 9. [executable] The IGP program. - // 10. [writeable] The IGP program data. - // 11. [writeable] Gas payment PDA. - // 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. - // 13. [writeable] The IGP account. + // 9. `[executable]` The IGP program. + // 10. `[writeable]` The IGP program data. + // 11. `[writeable]` Gas payment PDA. + // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. `[writeable]` The IGP account. // ---- End if ---- - // 14. [executable] The spl_token_2022 program. - // 15. [writeable] The mint / mint authority PDA account. - // 16. [writeable] The token sender's associated token account, from which tokens will be burned. + // 14. `[executable]` The spl_token_2022 program. + // 15. `[writeable]` The mint / mint authority PDA account. + // 16. `[writeable]` The token sender's associated token account, from which tokens will be burned. vec![ AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(spl_noop::id(), false), @@ -783,7 +783,7 @@ async fn test_transfer_remote() { .unwrap(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: LOCAL_DOMAIN, sender: program_id.to_bytes().into(), diff --git a/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml b/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml index 8a35c3b96f..86b543fcd9 100644 --- a/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml +++ b/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml @@ -27,10 +27,13 @@ serializable-account-meta = { path = "../../../libraries/serializable-account-me [dev-dependencies] hyperlane-sealevel-multisig-ism-message-id = { path = "../multisig-ism-message-id" } hyperlane-test-utils = { path = "../../../libraries/test-utils" } +multisig-ism = { path = "../../../libraries/multisig-ism", features = ["test-data"] } solana-program-test.workspace = true solana-sdk.workspace = true hex.workspace = true -multisig-ism = { path = "../../../libraries/multisig-ism", features = ["test-data"] } +# Can't have as a workspace dep, because this is already in the dep tree twice: once as +# an older solana one, once as a newer one used more generally. +rand = "0.8.5" [lib] crate-type = ["cdylib", "lib"] diff --git a/rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs b/rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs index 50b3e2c64b..dd1bd1b15f 100644 --- a/rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs +++ b/rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs @@ -7,12 +7,14 @@ use crate::error::Error; pub struct MultisigIsmMessageIdMetadata { pub origin_merkle_tree_hook: H256, pub merkle_root: H256, + pub merkle_index: u32, pub validator_signatures: Vec, } const ORIGIN_MAILBOX_OFFSET: usize = 0; const MERKLE_ROOT_OFFSET: usize = 32; -const SIGNATURES_OFFSET: usize = 64; +const MERKLE_INDEX_OFFSET: usize = 64; +const SIGNATURES_OFFSET: usize = 68; const SIGNATURE_LENGTH: usize = 65; /// Format of metadata: @@ -32,7 +34,12 @@ impl TryFrom> for MultisigIsmMessageIdMetadata { } let origin_mailbox = H256::from_slice(&bytes[ORIGIN_MAILBOX_OFFSET..MERKLE_ROOT_OFFSET]); - let merkle_root = H256::from_slice(&bytes[MERKLE_ROOT_OFFSET..SIGNATURES_OFFSET]); + let merkle_root = H256::from_slice(&bytes[MERKLE_ROOT_OFFSET..MERKLE_INDEX_OFFSET]); + // This cannot panic since SIGNATURES_OFFSET - MERKLE_INDEX_OFFSET is 4. + let merkle_index_bytes: [u8; 4] = bytes[MERKLE_INDEX_OFFSET..SIGNATURES_OFFSET] + .try_into() + .map_err(|_| Error::InvalidMetadata)?; + let merkle_index = u32::from_be_bytes(merkle_index_bytes); let signature_bytes_len = bytes_len - SIGNATURES_OFFSET; // Require the signature bytes to be a multiple of the signature length. @@ -55,6 +62,7 @@ impl TryFrom> for MultisigIsmMessageIdMetadata { Ok(Self { origin_merkle_tree_hook: origin_mailbox, merkle_root, + merkle_index, validator_signatures, }) } @@ -68,6 +76,7 @@ impl Encode for MultisigIsmMessageIdMetadata { let mut bytes_written = 0; bytes_written += writer.write(self.origin_merkle_tree_hook.as_ref())?; bytes_written += writer.write(self.merkle_root.as_ref())?; + bytes_written += writer.write(&self.merkle_index.to_be_bytes())?; for signature in &self.validator_signatures { bytes_written += writer.write(&signature.as_fixed_bytes()[..])?; } @@ -78,11 +87,21 @@ impl Encode for MultisigIsmMessageIdMetadata { #[cfg(test)] mod test { use super::*; + use rand::Rng; + + // Provide a default test implementation + fn dummy_metadata_with_sigs(sigs: Vec) -> MultisigIsmMessageIdMetadata { + let mut rng = rand::thread_rng(); + MultisigIsmMessageIdMetadata { + origin_merkle_tree_hook: H256::random(), + merkle_root: H256::random(), + merkle_index: rng.gen(), + validator_signatures: sigs, + } + } #[test] fn test_decode_correctly_formatted_metadata() { - let origin_mailbox = H256::random(); - let merkle_root = H256::random(); let validator_signatures = vec![ EcdsaSignature { serialized_rs: [11u8; 64], @@ -97,43 +116,49 @@ mod test { recovery_id: 0, }, ]; - let mut metadata_bytes = origin_mailbox.as_bytes().to_vec(); - metadata_bytes.extend_from_slice(merkle_root.as_bytes()); - for signature in &validator_signatures { - metadata_bytes.extend_from_slice(&signature.as_fixed_bytes()[..]); - } - - let metadata = MultisigIsmMessageIdMetadata::try_from(metadata_bytes).unwrap(); - assert_eq!(metadata.origin_merkle_tree_hook, origin_mailbox); - assert_eq!(metadata.merkle_root, merkle_root); - assert_eq!(metadata.validator_signatures, validator_signatures); + let test_meta = dummy_metadata_with_sigs(validator_signatures); + let encoded_meta = test_meta.to_vec(); + let metadata = MultisigIsmMessageIdMetadata::try_from(encoded_meta.clone()).unwrap(); + assert_eq!( + metadata.origin_merkle_tree_hook, + test_meta.origin_merkle_tree_hook + ); + assert_eq!(metadata.merkle_root, test_meta.merkle_root); + assert_eq!(metadata.merkle_index, test_meta.merkle_index); + assert_eq!( + metadata.validator_signatures, + test_meta.validator_signatures + ); } #[test] fn test_decode_no_signatures_is_err() { - let origin_mailbox = H256::random(); - let merkle_root = H256::random(); - let metadata_bytes = origin_mailbox - .as_bytes() - .iter() - .chain(merkle_root.as_bytes().iter()) - .cloned() - .collect::>(); - - let result = MultisigIsmMessageIdMetadata::try_from(metadata_bytes); + let test_meta = dummy_metadata_with_sigs(vec![]); + let encoded_meta = test_meta.to_vec(); + let result = MultisigIsmMessageIdMetadata::try_from(encoded_meta); assert!(result.unwrap_err() == Error::InvalidMetadata); } #[test] fn test_decode_incorrect_signature_length_is_err() { - let origin_mailbox = H256::random(); - let merkle_root = H256::random(); - let mut metadata_bytes = origin_mailbox.as_bytes().to_vec(); - metadata_bytes.extend_from_slice(merkle_root.as_bytes()); - // 64 byte signature instead of 65. - metadata_bytes.extend_from_slice(&[1u8; 64]); - - let result = MultisigIsmMessageIdMetadata::try_from(metadata_bytes); + let sigs = vec![EcdsaSignature { + serialized_rs: [1u8; 64], + recovery_id: 0, + }]; + let test_meta = dummy_metadata_with_sigs(sigs); + let encoded_meta = test_meta.to_vec(); + // remove the last byte from the encoded signature + let faulty_encoded_meta = encoded_meta[..encoded_meta.len() - 1].to_vec(); + let result = MultisigIsmMessageIdMetadata::try_from(faulty_encoded_meta); assert!(result.unwrap_err() == Error::InvalidMetadata); + MultisigIsmMessageIdMetadata::try_from(encoded_meta).expect("Decoding should succeed"); + } + + #[test] + fn test_decode_real_meta() { + // multisig ism message id metadata from this tx: + // https://arbiscan.io//tx/0xe558f04ad446b1d9ec4d4a1284661869b73daff38ec9fb7e809be652732fff30#txninfo + let bytes = hex::decode("000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f112fb91807ccda2db543bfbd013242643553bc1238f891ae9d0abb3b8b46c5a89990000017addc429c97ca8bcd6ad86ef4461379374b0d545308a1f47db246a6c028f74d7af521dd9355afd2f2a02565a24f22ac7b7e388cbd1f2a931acc97ce689be5456851b4d22f1aece05d293e574e38edcda9f2db64f1dc5b69a89a6a5989e7aaa4f443c137e593bb794eb211de719ed0f466a0778c4d204cc275f54c0936eee918ae1651c").unwrap(); + MultisigIsmMessageIdMetadata::try_from(bytes).expect("Decoding should succeed"); } } diff --git a/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs b/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs index 400cfdc7b2..c479b69a94 100644 --- a/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs +++ b/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs @@ -601,6 +601,7 @@ pub mod test { metadata: MultisigIsmMessageIdMetadata { origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address, merkle_root: checkpoint.root, + merkle_index: checkpoint.index, validator_signatures: vec![ EcdsaSignature::from_bytes(&signatures[0]).unwrap(), EcdsaSignature::from_bytes(&signatures[1]).unwrap(), @@ -626,6 +627,7 @@ pub mod test { metadata: MultisigIsmMessageIdMetadata { origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address, merkle_root: checkpoint.root, + merkle_index: checkpoint.index, validator_signatures: vec![ EcdsaSignature::from_bytes(&signatures[1]).unwrap(), EcdsaSignature::from_bytes(&signatures[0]).unwrap(), @@ -654,6 +656,7 @@ pub mod test { metadata: MultisigIsmMessageIdMetadata { origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address, merkle_root: checkpoint.root, + merkle_index: checkpoint.index, validator_signatures: vec![ EcdsaSignature::from_bytes(&signatures[0]).unwrap(), EcdsaSignature::from_bytes(&signatures[2]).unwrap(), @@ -678,6 +681,7 @@ pub mod test { metadata: MultisigIsmMessageIdMetadata { origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address, merkle_root: checkpoint.root, + merkle_index: checkpoint.index, validator_signatures: vec![ EcdsaSignature::from_bytes(&signatures[0]).unwrap(), EcdsaSignature::from_bytes(&signatures[1]).unwrap(), diff --git a/rust/sealevel/programs/ism/multisig-ism-message-id/tests/functional.rs b/rust/sealevel/programs/ism/multisig-ism-message-id/tests/functional.rs index b7820649fc..e40cd3a7aa 100644 --- a/rust/sealevel/programs/ism/multisig-ism-message-id/tests/functional.rs +++ b/rust/sealevel/programs/ism/multisig-ism-message-id/tests/functional.rs @@ -299,7 +299,7 @@ async fn test_set_validators_and_threshold_creates_pda_account() { // to fetch the account metas required for the instruction. let test_message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: domain, sender: H256::random(), @@ -421,6 +421,7 @@ async fn test_ism_verify() { metadata: MultisigIsmMessageIdMetadata { origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address, merkle_root: checkpoint.root, + merkle_index: checkpoint.index, validator_signatures: vec![ EcdsaSignature::from_bytes(&signatures[0]).unwrap(), EcdsaSignature::from_bytes(&signatures[1]).unwrap(), diff --git a/rust/sealevel/programs/ism/test-ism/src/program.rs b/rust/sealevel/programs/ism/test-ism/src/program.rs index 4b4c87638e..ef4914e650 100644 --- a/rust/sealevel/programs/ism/test-ism/src/program.rs +++ b/rust/sealevel/programs/ism/test-ism/src/program.rs @@ -101,9 +101,9 @@ pub fn process_instruction( /// Creates the storage PDA. /// /// Accounts: -/// 0. [executable] System program. -/// 1. [signer] Payer. -/// 2. [writeable] Storage PDA. +/// 0. `[executable]` System program. +/// 1. `[signer]` Payer. +/// 2. `[writeable]` Storage PDA. fn init(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -144,7 +144,7 @@ fn init(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { } /// Accounts: -/// 0. [writeable] Storage PDA. +/// 0. `[writeable]` Storage PDA. fn set_accept(_program_id: &Pubkey, accounts: &[AccountInfo], accept: bool) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -160,7 +160,7 @@ fn set_accept(_program_id: &Pubkey, accounts: &[AccountInfo], accept: bool) -> P } /// Accounts: -/// 0. [] Storage PDA. +/// 0. `[]` Storage PDA. fn verify(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); diff --git a/rust/sealevel/programs/ism/test-ism/src/test_client.rs b/rust/sealevel/programs/ism/test-ism/src/test_client.rs index f2bf0f6af5..d756dd41dc 100644 --- a/rust/sealevel/programs/ism/test-ism/src/test_client.rs +++ b/rust/sealevel/programs/ism/test-ism/src/test_client.rs @@ -38,9 +38,9 @@ impl TestIsmTestClient { program_id, data: TestIsmInstruction::Init.try_to_vec().unwrap(), accounts: vec![ - // 0. [executable] System program. - // 1. [signer] Payer. - // 2. [writeable] Storage PDA. + // 0. `[executable]` System program. + // 1. `[signer]` Payer. + // 2. `[writeable]` Storage PDA. AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new(payer_pubkey, true), AccountMeta::new(Self::get_storage_pda_key(), false), @@ -66,7 +66,7 @@ impl TestIsmTestClient { program_id, data: TestIsmInstruction::SetAccept(accept).try_to_vec().unwrap(), accounts: vec![ - // 0. [writeable] Storage PDA. + // 0. `[writeable]` Storage PDA. AccountMeta::new(Self::get_storage_pda_key(), false), ], }; diff --git a/rust/sealevel/programs/mailbox-test/src/functional.rs b/rust/sealevel/programs/mailbox-test/src/functional.rs index c826b6ed0c..088dc7da74 100644 --- a/rust/sealevel/programs/mailbox-test/src/functional.rs +++ b/rust/sealevel/programs/mailbox-test/src/functional.rs @@ -183,7 +183,7 @@ async fn test_dispatch_from_eoa() { .unwrap(); let expected_message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: LOCAL_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -238,7 +238,7 @@ async fn test_dispatch_from_eoa() { .unwrap(); let expected_message = HyperlaneMessage { - version: 0, + version: 3, nonce: 1, origin: LOCAL_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -300,7 +300,7 @@ async fn test_dispatch_from_program() { .unwrap(); let expected_message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: LOCAL_DOMAIN, // The sender should be the program ID because its dispatch authority signed @@ -389,7 +389,7 @@ async fn test_dispatch_returns_message_id() { message_body: message_body.clone(), }; let expected_message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: LOCAL_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -411,13 +411,13 @@ async fn test_dispatch_returns_message_id() { .into_instruction_data() .unwrap(), accounts: vec![ - // 0. [writeable] Outbox PDA. - // 1. [signer] Message sender signer. - // 2. [executable] System program. - // 3. [executable] SPL Noop program. - // 4. [signer] Payer. - // 5. [signer] Unique message account. - // 6. [writeable] Dispatched message PDA. An empty message PDA relating to the seeds + // 0. `[writeable]` Outbox PDA. + // 1. `[signer]` Message sender signer. + // 2. `[executable]` System program. + // 3. `[executable]` SPL Noop program. + // 4. `[signer]` Payer. + // 5. `[signer]` Unique message account. + // 6. `[writeable]` Dispatched message PDA. An empty message PDA relating to the seeds // `mailbox_dispatched_message_pda_seeds` where the message contents will be stored. AccountMeta::new(mailbox_accounts.outbox, false), AccountMeta::new(payer.pubkey(), true), @@ -574,7 +574,7 @@ async fn test_process_successful_verify_and_handle() { let recipient_id = hyperlane_sealevel_test_send_receiver::id(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -605,7 +605,7 @@ async fn test_process_successful_verify_and_handle() { // Send another to illustrate that the sequence is incremented let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -647,7 +647,7 @@ async fn test_process_errors_if_message_already_processed() { let recipient_id = hyperlane_sealevel_test_send_receiver::id(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -697,7 +697,7 @@ async fn test_process_errors_if_ism_verify_fails() { test_ism.set_accept(false).await.unwrap(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -743,7 +743,7 @@ async fn test_process_errors_if_recipient_handle_fails() { .unwrap(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -784,7 +784,7 @@ async fn test_process_errors_if_incorrect_destination_domain() { let recipient_id = hyperlane_sealevel_test_send_receiver::id(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -905,7 +905,7 @@ async fn test_process_errors_if_reentrant() { let recipient_id = hyperlane_sealevel_test_send_receiver::id(); let message = HyperlaneMessage { - version: 0, + version: 3, nonce: 0, origin: REMOTE_DOMAIN, sender: payer.pubkey().to_bytes().into(), @@ -964,9 +964,9 @@ async fn test_inbox_set_default_ism() { .into_instruction_data() .unwrap(), accounts: vec![ - // 0. [writeable] - The Inbox PDA account. - // 1. [] - The Outbox PDA account. - // 2. [signer] - The owner of the Mailbox. + // 0. `[writeable]` - The Inbox PDA account. + // 1. `[]` - The Outbox PDA account. + // 2. `[signer]` - The owner of the Mailbox. AccountMeta::new(mailbox_accounts.inbox, false), AccountMeta::new_readonly(mailbox_accounts.outbox, false), AccountMeta::new(payer.pubkey(), true), @@ -1011,9 +1011,9 @@ async fn test_inbox_set_default_ism_errors_if_owner_not_signer() { .into_instruction_data() .unwrap(), accounts: vec![ - // 0. [writeable] - The Inbox PDA account. - // 1. [] - The Outbox PDA account. - // 2. [signer] - The owner of the Mailbox. + // 0. `[writeable]` - The Inbox PDA account. + // 1. `[]` - The Outbox PDA account. + // 2. `[signer]` - The owner of the Mailbox. AccountMeta::new(mailbox_accounts.inbox, false), AccountMeta::new_readonly(mailbox_accounts.outbox, false), AccountMeta::new_readonly(non_owner.pubkey(), true), @@ -1033,9 +1033,9 @@ async fn test_inbox_set_default_ism_errors_if_owner_not_signer() { .into_instruction_data() .unwrap(), accounts: vec![ - // 0. [writeable] - The Inbox PDA account. - // 1. [] - The Outbox PDA account. - // 2. [signer] - The owner of the Mailbox. + // 0. `[writeable]` - The Inbox PDA account. + // 1. `[]` - The Outbox PDA account. + // 2. `[signer]` - The owner of the Mailbox. AccountMeta::new(mailbox_accounts.inbox, false), AccountMeta::new_readonly(mailbox_accounts.outbox, false), AccountMeta::new_readonly(payer.pubkey(), false), diff --git a/rust/sealevel/programs/mailbox-test/src/utils.rs b/rust/sealevel/programs/mailbox-test/src/utils.rs index 434dce00bb..b4e2076668 100644 --- a/rust/sealevel/programs/mailbox-test/src/utils.rs +++ b/rust/sealevel/programs/mailbox-test/src/utils.rs @@ -42,13 +42,13 @@ pub async fn dispatch_from_payer( .into_instruction_data() .unwrap(), accounts: vec![ - // 0. [writeable] Outbox PDA. - // 1. [signer] Message sender signer. - // 2. [executable] System program. - // 3. [executable] SPL Noop program. - // 4. [signer] Payer. - // 5. [signer] Unique message account. - // 6. [writeable] Dispatched message PDA. An empty message PDA relating to the seeds + // 0. `[writeable]` Outbox PDA. + // 1. `[signer]` Message sender signer. + // 2. `[executable]` System program. + // 3. `[executable]` SPL Noop program. + // 4. `[signer]` Payer. + // 5. `[signer]` Unique message account. + // 6. `[writeable]` Dispatched message PDA. An empty message PDA relating to the seeds // `mailbox_dispatched_message_pda_seeds` where the message contents will be stored. AccountMeta::new(mailbox_accounts.outbox, false), AccountMeta::new(payer.pubkey(), true), diff --git a/rust/sealevel/programs/mailbox/src/instruction.rs b/rust/sealevel/programs/mailbox/src/instruction.rs index 78a553f92d..74aac05f97 100644 --- a/rust/sealevel/programs/mailbox/src/instruction.rs +++ b/rust/sealevel/programs/mailbox/src/instruction.rs @@ -11,7 +11,7 @@ use solana_program::{ use crate::{mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds}; /// The current message version. -pub const VERSION: u8 = 0; +pub const VERSION: u8 = 3; /// Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin). pub const MAX_MESSAGE_BODY_BYTES: usize = 2 * 2_usize.pow(10); @@ -156,9 +156,9 @@ pub fn set_default_ism_instruction( Pubkey::try_find_program_address(mailbox_outbox_pda_seeds!(), &program_id) .ok_or(ProgramError::InvalidSeeds)?; - // 0. [writeable] - The Inbox PDA account. - // 1. [] - The Outbox PDA account. - // 2. [signer] - The owner of the Mailbox. + // 0. `[writeable]` - The Inbox PDA account. + // 1. `[]` - The Outbox PDA account. + // 2. `[signer]` - The owner of the Mailbox. let instruction = SolanaInstruction { program_id, data: Instruction::InboxSetDefaultIsm(default_ism).into_instruction_data()?, diff --git a/rust/sealevel/programs/mailbox/src/processor.rs b/rust/sealevel/programs/mailbox/src/processor.rs index c0efa6272a..9d4e133960 100644 --- a/rust/sealevel/programs/mailbox/src/processor.rs +++ b/rust/sealevel/programs/mailbox/src/processor.rs @@ -79,10 +79,10 @@ pub fn process_instruction( /// Initializes the Mailbox. /// /// Accounts: -/// 0. [executable] The system program. -/// 1. [signer, writable] The payer account and owner of the Mailbox. -/// 2. [writable] The inbox PDA account. -/// 3. [writable] The outbox PDA account. +/// 0. `[executable]` The system program. +/// 1. `[signer, writable]` The payer account and owner of the Mailbox. +/// 2. `[writable]` The inbox PDA account. +/// 3. `[writable]` The outbox PDA account. fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -164,16 +164,16 @@ fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> Prog /// Process a message. Non-reentrant through the use of a RefMut. /// // Accounts: -// 0. [signer] Payer account. This pays for the creation of the processed message PDA. -// 1. [executable] The system program. -// 2. [writable] Inbox PDA account. -// 3. [] Mailbox process authority specific to the message recipient. -// 4. [writable] Processed message PDA. +// 0. `[signer]` Payer account. This pays for the creation of the processed message PDA. +// 1. `[executable]` The system program. +// 2. `[writable]` Inbox PDA account. +// 3. `[]` Mailbox process authority specific to the message recipient. +// 4. `[writable]` Processed message PDA. // 5..N [??] Accounts required to invoke the recipient's InterchainSecurityModule instruction. -// N+1. [executable] SPL noop -// N+2. [executable] ISM +// N+1. `[executable]` SPL noop +// N+2. `[executable]` ISM // N+2..M. [??] Accounts required to invoke the ISM's Verify instruction. -// M+1. [executable] Recipient program. +// M+1. `[executable]` Recipient program. // M+2..K. [??] Accounts required to invoke the recipient's Handle instruction. fn inbox_process( program_id: &Pubkey, @@ -414,8 +414,8 @@ fn inbox_process( /// Gets the ISM to use for a recipient program and sets it as return data. /// /// Accounts: -/// 0. [] - The Inbox PDA. -/// 1. [] - The recipient program. +/// 0. `[]` - The Inbox PDA. +/// 1. `[]` - The recipient program. /// 2..N. [??] - The accounts required to make the CPI into the recipient program. /// These can be retrieved from the recipient using the /// `MessageRecipientInstruction::InterchainSecurityModuleAccountMetas` instruction. @@ -505,9 +505,9 @@ fn get_recipient_ism( /// Sets the default ISM. /// /// Accounts: -/// 0. [writeable] - The Inbox PDA account. -/// 1. [] - The Outbox PDA account. -/// 2. [signer] - The owner of the Mailbox. +/// 0. `[writeable]` - The Inbox PDA account. +/// 1. `[]` - The Outbox PDA account. +/// 2. `[signer]` - The owner of the Mailbox. fn inbox_set_default_ism( program_id: &Pubkey, accounts: &[AccountInfo], @@ -549,13 +549,13 @@ fn inbox_set_default_ism( /// Sets the ID of the message as return data. /// /// Accounts: -/// 0. [writeable] Outbox PDA. -/// 1. [signer] Message sender signer. -/// 2. [executable] System program. -/// 3. [executable] SPL Noop program. -/// 4. [signer] Payer. -/// 5. [signer] Unique message account. -/// 6. [writeable] Dispatched message PDA. An empty message PDA relating to the seeds +/// 0. `[writeable]` Outbox PDA. +/// 1. `[signer]` Message sender signer. +/// 2. `[executable]` System program. +/// 3. `[executable]` SPL Noop program. +/// 4. `[signer]` Payer. +/// 5. `[signer]` Unique message account. +/// 6. `[writeable]` Dispatched message PDA. An empty message PDA relating to the seeds /// `mailbox_dispatched_message_pda_seeds` where the message contents will be stored. fn outbox_dispatch( program_id: &Pubkey, @@ -712,7 +712,7 @@ fn outbox_dispatch( /// Gets the number of dispatched messages as little endian encoded return data. /// /// Accounts: -/// 0. [] Outbox PDA account. +/// 0. `[]` Outbox PDA account. fn outbox_get_count(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -743,7 +743,7 @@ fn outbox_get_count(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramRes /// Gets the latest checkpoint as return data. /// /// Accounts: -/// 0. [] Outbox PDA account. +/// 0. `[]` Outbox PDA account. fn outbox_get_latest_checkpoint(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -779,7 +779,7 @@ fn outbox_get_latest_checkpoint(program_id: &Pubkey, accounts: &[AccountInfo]) - /// Gets the root as return data. /// /// Accounts: -/// 0. [] Outbox PDA account. +/// 0. `[]` Outbox PDA account. fn outbox_get_root(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); diff --git a/rust/sealevel/programs/test-send-receiver/src/program.rs b/rust/sealevel/programs/test-send-receiver/src/program.rs index 0f2bbc37af..46884ea400 100644 --- a/rust/sealevel/programs/test-send-receiver/src/program.rs +++ b/rust/sealevel/programs/test-send-receiver/src/program.rs @@ -37,7 +37,7 @@ pub enum TestSendReceiverError { /// that the Mailbox can handle different ISM getter return data. #[derive(BorshSerialize, BorshDeserialize, Debug)] pub enum IsmReturnDataMode { - /// Encodes the ISM as an Option. + /// Encodes the ISM as an `Option`. EncodeOption, /// Returns no data. ReturnNothing, @@ -161,9 +161,9 @@ pub fn process_instruction( /// Creates the storage PDA. /// /// Accounts: -/// 0. [executable] System program. -/// 1. [signer] Payer. -/// 2. [writeable] Storage PDA. +/// 0. `[executable]` System program. +/// 1. `[signer]` Payer. +/// 2. `[writeable]` Storage PDA. fn init(program_id: &Pubkey, accounts: &[AccountInfo], mailbox: Pubkey) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -211,15 +211,15 @@ fn init(program_id: &Pubkey, accounts: &[AccountInfo], mailbox: Pubkey) -> Progr /// Dispatches a message using the dispatch authority. /// /// Accounts: -/// 0. [executable] The Mailbox program. +/// 0. `[executable]` The Mailbox program. /// And now the accounts expected by the Mailbox's OutboxDispatch instruction: -/// 2. [writeable] Outbox PDA. -/// 3. [] This program's dispatch authority. -/// 4. [executable] System program. -/// 5. [executable] SPL Noop program. -/// 6. [signer] Payer. -/// 7. [signer] Unique message account. -/// 8. [writeable] Dispatched message PDA. An empty message PDA relating to the seeds +/// 2. `[writeable]` Outbox PDA. +/// 3. `[]` This program's dispatch authority. +/// 4. `[executable]` System program. +/// 5. `[executable]` SPL Noop program. +/// 6. `[signer]` Payer. +/// 7. `[signer]` Unique message account. +/// 8. `[writeable]` Dispatched message PDA. An empty message PDA relating to the seeds /// `mailbox_dispatched_message_pda_seeds` where the message contents will be stored. fn dispatch( program_id: &Pubkey, @@ -291,8 +291,8 @@ fn dispatch( /// Handles a message. /// /// Accounts: -/// 0. [writeable] Process authority specific to this program. -/// 1. [] Storage PDA account. +/// 0. `[writeable]` Process authority specific to this program. +/// 1. `[]` Storage PDA account. pub fn handle( program_id: &Pubkey, accounts: &[AccountInfo], @@ -367,7 +367,7 @@ pub fn handle( } /// Accounts: -/// 0. [writeable] Storage PDA account. +/// 0. `[writeable]` Storage PDA account. fn set_interchain_security_module( _program_id: &Pubkey, accounts: &[AccountInfo], @@ -392,7 +392,7 @@ fn set_interchain_security_module( } /// Accounts: -/// 0. [] Storage PDA account. +/// 0. `[]` Storage PDA account. fn get_interchain_security_module(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -424,7 +424,7 @@ fn get_interchain_security_module(_program_id: &Pubkey, accounts: &[AccountInfo] } /// Accounts: -/// 0. [writeable] Storage PDA account. +/// 0. `[writeable]` Storage PDA account. fn set_handle_mode( _program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/test-send-receiver/src/test_client.rs b/rust/sealevel/programs/test-send-receiver/src/test_client.rs index 3096c10f7b..2f1539d9fe 100644 --- a/rust/sealevel/programs/test-send-receiver/src/test_client.rs +++ b/rust/sealevel/programs/test-send-receiver/src/test_client.rs @@ -48,9 +48,9 @@ impl TestSendReceiverTestClient { .try_to_vec() .unwrap(), accounts: vec![ - // 0. [executable] System program. - // 1. [signer] Payer. - // 2. [writeable] Storage PDA. + // 0. `[executable]` System program. + // 1. `[signer]` Payer. + // 2. `[writeable]` Storage PDA. AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new(payer_pubkey, true), AccountMeta::new(Self::get_storage_pda_key(), false), @@ -85,7 +85,7 @@ impl TestSendReceiverTestClient { .try_to_vec() .unwrap(), accounts: vec![ - // 0. [writeable] Storage PDA. + // 0. `[writeable]` Storage PDA. AccountMeta::new(Self::get_storage_pda_key(), false), ], }; @@ -114,7 +114,7 @@ impl TestSendReceiverTestClient { .try_to_vec() .unwrap(), accounts: vec![ - // 0. [writeable] Storage PDA. + // 0. `[writeable]` Storage PDA. AccountMeta::new(storage_pda_key, false), ], }; @@ -155,15 +155,15 @@ impl TestSendReceiverTestClient { .try_to_vec() .unwrap(), accounts: vec![ - // 0. [executable] The Mailbox program. + // 0. `[executable]` The Mailbox program. // And now the accounts expected by the Mailbox's OutboxDispatch instruction: - // 1. [writeable] Outbox PDA. - // 2. [] This program's dispatch authority. - // 3. [executable] System program. - // 4. [executable] SPL Noop program. - // 5. [signer] Payer. - // 6. [signer] Unique message account. - // 7. [writeable] Dispatched message PDA. An empty message PDA relating to the seeds + // 1. `[writeable]` Outbox PDA. + // 2. `[]` This program's dispatch authority. + // 3. `[executable]` System program. + // 4. `[executable]` SPL Noop program. + // 5. `[signer]` Payer. + // 6. `[signer]` Unique message account. + // 7. `[writeable]` Dispatched message PDA. An empty message PDA relating to the seeds // `mailbox_dispatched_message_pda_seeds` where the message contents will be stored. AccountMeta::new_readonly(mailbox_accounts.program, false), AccountMeta::new(mailbox_accounts.outbox, false), diff --git a/rust/sealevel/programs/validator-announce/src/instruction.rs b/rust/sealevel/programs/validator-announce/src/instruction.rs index 6501bbd398..a94898fe3d 100644 --- a/rust/sealevel/programs/validator-announce/src/instruction.rs +++ b/rust/sealevel/programs/validator-announce/src/instruction.rs @@ -80,9 +80,9 @@ pub fn init_instruction( }); // Accounts: - // 0. [signer] The payer. - // 1. [executable] The system program. - // 2. [writable] The ValidatorAnnounce PDA account. + // 0. `[signer]` The payer. + // 1. `[executable]` The system program. + // 2. `[writable]` The ValidatorAnnounce PDA account. let accounts = vec![ AccountMeta::new_readonly(payer, true), AccountMeta::new_readonly(solana_program::system_program::id(), false), diff --git a/rust/sealevel/programs/validator-announce/src/processor.rs b/rust/sealevel/programs/validator-announce/src/processor.rs index 253c58d569..409dc29e78 100644 --- a/rust/sealevel/programs/validator-announce/src/processor.rs +++ b/rust/sealevel/programs/validator-announce/src/processor.rs @@ -49,9 +49,9 @@ pub fn process_instruction( /// Initializes the program. /// /// Accounts: -/// 0. [signer] The payer. -/// 1. [executable] The system program. -/// 2. [writable] The ValidatorAnnounce PDA account. +/// 0. `[signer]` The payer. +/// 1. `[executable]` The system program. +/// 2. `[writable]` The ValidatorAnnounce PDA account. pub fn process_init( program_id: &Pubkey, accounts: &[AccountInfo], @@ -115,11 +115,11 @@ pub fn process_init( /// Announces a validator. /// /// Accounts: -/// 0. [signer] The payer. -/// 1. [executable] The system program. -/// 2. [] The ValidatorAnnounce PDA account. -/// 3. [writeable] The validator-specific ValidatorStorageLocationsAccount PDA account. -/// 4. [writeable] The ReplayProtection PDA account specific to the announcement being made. +/// 0. `[signer]` The payer. +/// 1. `[executable]` The system program. +/// 2. `[]` The ValidatorAnnounce PDA account. +/// 3. `[writeable]` The validator-specific ValidatorStorageLocationsAccount PDA account. +/// 4. `[writeable]` The ReplayProtection PDA account specific to the announcement being made. fn process_announce( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/rust/sealevel/programs/validator-announce/tests/functional.rs b/rust/sealevel/programs/validator-announce/tests/functional.rs index 68ee589f9d..cf1b5f0f55 100644 --- a/rust/sealevel/programs/validator-announce/tests/functional.rs +++ b/rust/sealevel/programs/validator-announce/tests/functional.rs @@ -88,9 +88,9 @@ async fn initialize( Pubkey::find_program_address(validator_announce_pda_seeds!(), &program_id); // Accounts: - // 0. [signer] The payer. - // 1. [executable] The system program. - // 2. [writable] The ValidatorAnnounce PDA account. + // 0. `[signer]` The payer. + // 1. `[executable]` The system program. + // 2. `[writable]` The ValidatorAnnounce PDA account. let init_instruction = Instruction::new_with_borsh( program_id, &ValidatorAnnounceInstruction::Init(InitInstruction { @@ -194,11 +194,11 @@ async fn announce( Pubkey::find_program_address(replay_protection_pda_seeds!(replay_id), &program_id); // Accounts: - // 0. [signer] The payer. - // 1. [executable] The system program. - // 2. [] The ValidatorAnnounce PDA account. - // 3. [writeable] The validator-specific ValidatorStorageLocationsAccount PDA account. - // 4. [writeable] The ReplayProtection PDA account specific to the announcement being made. + // 0. `[signer]` The payer. + // 1. `[executable]` The system program. + // 2. `[]` The ValidatorAnnounce PDA account. + // 3. `[writeable]` The validator-specific ValidatorStorageLocationsAccount PDA account. + // 4. `[writeable]` The ReplayProtection PDA account specific to the announcement being made. let announce_instruction = Instruction::new_with_borsh( program_id, &ValidatorAnnounceInstruction::Announce(announce_instruction), diff --git a/rust/terraform/.gitignore b/rust/terraform/.gitignore new file mode 100644 index 0000000000..282617e472 --- /dev/null +++ b/rust/terraform/.gitignore @@ -0,0 +1,2 @@ +.terraform +.terraform.lock.* diff --git a/rust/terraform/README.md b/rust/terraform/README.md new file mode 100644 index 0000000000..67e1f7330c --- /dev/null +++ b/rust/terraform/README.md @@ -0,0 +1,7 @@ +# Terraform Module for Hyperlane Validator + +This Terraform module is designed to set up the necessary infrastructure for a Hyperlane validator on AWS. It automates the creation of resources such as ECS clusters, VPCs, subnets, and security groups required for running a validator node. + +> **Note:** This module is intended to be an example of running a validator for a core supported network. You may have to modify the validator module to support more advanced configurations. It is recommended to test thoroughly before using in a production environment. + +For more information, read the [Deploy with Terraform](https://hyp-v3-docs-git-feat-aws-agent-guide-abacus-works.vercel.app/docs/operate/deploy-with-terraform) documentation. diff --git a/rust/terraform/globals.tf b/rust/terraform/globals.tf new file mode 100644 index 0000000000..92e6549404 --- /dev/null +++ b/rust/terraform/globals.tf @@ -0,0 +1,100 @@ +provider "aws" { + region = var.aws_region # Set the AWS region for the provider +} + +resource "aws_ecs_cluster" "validator_cluster" { + name = "hyperlane-validator-cluster" # Name of the ECS cluster for the validator +} + +resource "aws_vpc" "validator_vpc" { + cidr_block = "10.0.0.0/16" # Define the IP range for the VPC + enable_dns_support = true # Enable DNS support in the VPC + enable_dns_hostnames = true # Enable DNS hostnames in the VPC +} + +data "aws_availability_zones" "available" {} # Fetch the list of available AZs + +resource "aws_subnet" "public_subnet" { + vpc_id = aws_vpc.validator_vpc.id # Associate with the VPC + cidr_block = "10.0.2.0/24" # Define the IP range for the public subnet + availability_zone = data.aws_availability_zones.available.names[0] # Use the first available AZ + map_public_ip_on_launch = true # Automatically assign public IP on instance launch +} + +resource "aws_subnet" "validator_subnet" { + vpc_id = aws_vpc.validator_vpc.id # Associate with the VPC + cidr_block = "10.0.1.0/24" # Define the IP range for the validator subnet + availability_zone = data.aws_availability_zones.available.names[0] # Use the first available AZ + map_public_ip_on_launch = false # Do not assign public IP on instance launch +} + +resource "aws_internet_gateway" "vpc_igw" { + vpc_id = aws_vpc.validator_vpc.id # Attach the internet gateway to the VPC +} + +resource "aws_eip" "nat_gateway_eip" { + domain = "vpc" # Allocate an Elastic IP in the VPC domain +} + +resource "aws_nat_gateway" "validator_nat_gateway" { + allocation_id = aws_eip.nat_gateway_eip.id # Associate the EIP with the NAT gateway + subnet_id = aws_subnet.public_subnet.id # Place the NAT gateway in the public subnet + depends_on = [aws_internet_gateway.vpc_igw] # Ensure IGW is created before the NAT gateway +} + +resource "aws_route_table" "public_route_table" { + vpc_id = aws_vpc.validator_vpc.id # Associate the route table with the VPC + + route { + cidr_block = "0.0.0.0/0" # Route all traffic + gateway_id = aws_internet_gateway.vpc_igw.id # Through the internet gateway + } +} + +resource "aws_route_table" "private_route_table" { + vpc_id = aws_vpc.validator_vpc.id # Associate the route table with the VPC + + route { + cidr_block = "0.0.0.0/0" # Route all traffic + nat_gateway_id = aws_nat_gateway.validator_nat_gateway.id # Through the NAT gateway + } +} + +resource "aws_route_table_association" "public_subnet_association" { + subnet_id = aws_subnet.public_subnet.id # Associate the public subnet + route_table_id = aws_route_table.public_route_table.id # With the public route table +} + +resource "aws_route_table_association" "private_subnet_association" { + subnet_id = aws_subnet.validator_subnet.id # Associate the validator subnet + route_table_id = aws_route_table.private_route_table.id # With the private route table +} + +resource "aws_security_group" "validator_sg" { + name = "validator-sg" # Name of the security group for the validator + vpc_id = aws_vpc.validator_vpc.id # Associate with the VPC + + # prometheus + ingress { + from_port = 9090 # Prometheus metrics port + to_port = 9090 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Allow traffic from any IP + } + + # efs mounting + ingress { + from_port = 2049 # NFS port for EFS + to_port = 2049 + protocol = "tcp" + cidr_blocks = [aws_subnet.validator_subnet.cidr_block] # Allow traffic from the validator subnet + } + + # all egress + egress { + from_port = 0 # Allow all outbound traffic + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] # To any IP + } +} diff --git a/rust/terraform/main.tf b/rust/terraform/main.tf new file mode 100644 index 0000000000..e67fe80b78 --- /dev/null +++ b/rust/terraform/main.tf @@ -0,0 +1,20 @@ +# Configure a Hyperlane Validator +# Replaces https://docs.hyperlane.xyz/docs/operate/validators/run-validators +module "your_validator_name" { + source = "./modules/validator" + + validator_name = "your-validator-name" + origin_chain_name = "originChainName" + + aws_region = var.aws_region + validator_cluster_id = aws_ecs_cluster.validator_cluster.id + validator_subnet_id = aws_subnet.validator_subnet.id + validator_sg_id = aws_security_group.validator_sg.id + validator_nat_gateway_id = aws_nat_gateway.validator_nat_gateway.id + + # Disabling the validator task allows you to set up all the required infrastructure + # without running the actual validator yet. This is useful when setting up a validator for + # the first time, so that you can find out the validator address and fund it before it + # performs the announcement transaction. + # validator_task_disabled = true +} diff --git a/rust/terraform/modules/efs/main.tf b/rust/terraform/modules/efs/main.tf new file mode 100644 index 0000000000..ba3c499e66 --- /dev/null +++ b/rust/terraform/modules/efs/main.tf @@ -0,0 +1,42 @@ +# This resource defines an EFS file system that acts as persistent storage for the validator. +# The `creation_token` is used to ensure idempotent creation of the file system. +resource "aws_efs_file_system" "validator_fs" { + creation_token = var.creation_token # Unique token to guarantee the idempotence of the resource + + # Tags are key-value pairs that help with the organization and identification of AWS resources. + tags = { + Name = var.creation_token # Name tag using the creation token for easy identification + } +} + +# The EFS access point serves as a custom entry point into the file system. +# It enforces the specified POSIX user and group, and the root directory settings. +resource "aws_efs_access_point" "validator_ap" { + file_system_id = aws_efs_file_system.validator_fs.id # Associates the access point with the file system + + # The POSIX user configuration sets the owner's user and group IDs for all file system requests. + posix_user { + gid = var.posix_user_gid # POSIX group ID + uid = var.posix_user_uid # POSIX user ID + } + + # The root directory configuration specifies the path and creation settings within the EFS. + root_directory { + path = var.root_directory_path # The path where the root directory is mounted + + # The creation info sets the ownership and permissions for the root directory upon creation. + creation_info { + owner_gid = var.posix_user_gid # Group ID of the directory owner + owner_uid = var.posix_user_uid # User ID of the directory owner + permissions = var.root_directory_permissions # Permissions for the root directory + } + } +} + +# This resource creates a mount target within a specific subnet, allowing EC2 instances to access the EFS file system. +# The mount target is secured by associating it with one or more security groups. +resource "aws_efs_mount_target" "validator_mt" { + file_system_id = aws_efs_file_system.validator_fs.id # Associates the mount target with the file system + subnet_id = var.subnet_id # The subnet ID where the mount target is placed + security_groups = var.security_group_ids # Security groups that define the access rules for the mount target +} diff --git a/rust/terraform/modules/efs/outputs.tf b/rust/terraform/modules/efs/outputs.tf new file mode 100644 index 0000000000..8fb002fd29 --- /dev/null +++ b/rust/terraform/modules/efs/outputs.tf @@ -0,0 +1,19 @@ +output "file_system_id" { + description = "The ID of the EFS file system" + value = aws_efs_file_system.validator_fs.id +} + +output "access_point_id" { + description = "The ID of the EFS access point" + value = aws_efs_access_point.validator_ap.id +} + +output "mount_target_id" { + description = "The ID of the EFS mount target" + value = aws_efs_mount_target.validator_mt.id +} + +output "access_point_arn" { + description = "The ARN of the EFS access point" + value = aws_efs_access_point.validator_ap.arn +} diff --git a/rust/terraform/modules/efs/variables.tf b/rust/terraform/modules/efs/variables.tf new file mode 100644 index 0000000000..43803193fb --- /dev/null +++ b/rust/terraform/modules/efs/variables.tf @@ -0,0 +1,38 @@ +variable "creation_token" { + description = "Unique string to ensure the idempotent creation of the file system" + type = string +} + +variable "subnet_id" { + description = "The ID of the subnet to create the mount target in" + type = string +} + +variable "security_group_ids" { + description = "A list of security group IDs to associate with the mount target" + type = list(string) +} + +variable "posix_user_gid" { + description = "The POSIX group ID for the EFS access point" + type = number + default = 1000 +} + +variable "posix_user_uid" { + description = "The POSIX user ID for the EFS access point" + type = number + default = 1000 +} + +variable "root_directory_path" { + description = "Path to the root directory on the EFS volume" + type = string + default = "/hyperlane_db" +} + +variable "root_directory_permissions" { + description = "Permissions to apply to the root directory on the EFS volume" + type = string + default = "700" +} diff --git a/rust/terraform/modules/iam_kms/main.tf b/rust/terraform/modules/iam_kms/main.tf new file mode 100644 index 0000000000..c6e49c2c12 --- /dev/null +++ b/rust/terraform/modules/iam_kms/main.tf @@ -0,0 +1,179 @@ +# Creates an IAM user for the validator to interact with AWS services +resource "aws_iam_user" "ecs_user" { + name = "${var.validator_name}-exec-user" # The name of the IAM user is derived from the validator's name +} + +# Creates a KMS key for the validator to sign transactions securely +resource "aws_kms_key" "validator_signer_key" { + description = "KMS Key for Hyperlane Validator Signing" + key_usage = "SIGN_VERIFY" # Specifies that the key is used for signing and verification + customer_master_key_spec = "ECC_SECG_P256K1" # Specifies the type of key to be used +} + +# Creates an alias for the KMS key to make it easier to reference +resource "aws_kms_alias" "validator_signer_key_alias" { + name = "alias/${var.validator_name}" # The alias name includes the validator's name for easy identification + target_key_id = aws_kms_key.validator_signer_key.key_id # Associates the alias with the created KMS key +} + +# Defines an IAM policy that grants permissions to use the KMS key for signing operations +resource "aws_iam_policy" "validator_user_kms_policy" { + name = "${var.validator_name}-user-kms-policy" + description = "Allow ECS tasks to use the KMS key for signing" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "kms:GetPublicKey", # Allows retrieval of the public key + "kms:Sign", # Allows signing operations + "kms:Verify" # Allows verification of signatures + ], + Resource = aws_kms_key.validator_signer_key.arn # Specifies the KMS key resource + } + ] + }) +} + +# Attaches the KMS policy to the IAM user, granting it the defined permissions +resource "aws_iam_user_policy_attachment" "validator_user_kms_policy_attachment" { + user = aws_iam_user.ecs_user.name # The IAM user to attach the policy to + policy_arn = aws_iam_policy.validator_user_kms_policy.arn # The ARN of the policy to attach +} + +# Generates an access key for the IAM user to authenticate with AWS services +resource "aws_iam_access_key" "ecs_user_key" { + user = aws_iam_user.ecs_user.name # The IAM user for which to create the access key +} + +# Stores the access key ID in SSM Parameter Store for secure retrieval +resource "aws_ssm_parameter" "key_id" { + name = "/ecs/${var.validator_name}/access-key-id" # The parameter name includes the validator's name + type = "String" # The type of the parameter is a simple string + value = aws_iam_access_key.ecs_user_key.id # The value is the access key ID +} + +# Stores the access key secret in SSM Parameter Store for secure retrieval +resource "aws_ssm_parameter" "key_secret" { + name = "/ecs/${var.validator_name}/secret-access-key" # The parameter name includes the validator's name + type = "String" # The type of the parameter is a simple string + value = aws_iam_access_key.ecs_user_key.secret # The value is the access key secret +} + +# Creates an IAM role for ECS tasks to assume during execution +resource "aws_iam_role" "ecs_execution_role" { + name = "${var.validator_name}-exec-role" # The name of the role includes the validator's name + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" # Specifies that ECS tasks can assume this role + } + } + ] + }) +} + +# Attaches the AmazonECSTaskExecutionRolePolicy to the ECS execution role +resource "aws_iam_role_policy_attachment" "ecs_execution_policy" { + role = aws_iam_role.ecs_execution_role.name # The ECS execution role to attach the policy to + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" # The ARN of the Amazon-managed policy +} + +# Defines an IAM policy to allow ECS tasks to write logs to CloudWatch +resource "aws_iam_policy" "cloudwatch_logs_policy" { + name = "${var.validator_name}-cloudwatch-logs-policy" + description = "IAM policy for ECS tasks to interact with CloudWatch Logs" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "logs:CreateLogStream", # Allows creation of log streams + "logs:PutLogEvents" # Allows putting log events into log streams + ], + Resource = "arn:aws:logs:${var.aws_region}:*:log-group:/aws/ecs/${var.aws_log_group}:log-stream:*" # Specifies the log group resource + } + ] + }) +} + +# Attaches the CloudWatch logs policy to the ECS execution role +resource "aws_iam_role_policy_attachment" "cloudwatch_logs_policy_attachment" { + role = aws_iam_role.ecs_execution_role.name # The ECS execution role to attach the policy to + policy_arn = aws_iam_policy.cloudwatch_logs_policy.arn # The ARN of the CloudWatch logs policy +} + +# Defines an IAM policy to allow ECS tasks to read SSM parameters for access keys +resource "aws_iam_policy" "ssm_read_policy" { + name = "${var.validator_name}-ssm-read-policy" + description = "Allow ECS tasks to read parameters" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = ["ssm:GetParameters"], # Allows retrieval of SSM parameters + Resource = [ + aws_ssm_parameter.key_id.arn, # The ARN of the access key ID parameter + aws_ssm_parameter.key_secret.arn # The ARN of the access key secret parameter + ] + } + ] + }) +} + +# Attaches the SSM read policy to the ECS execution role +resource "aws_iam_role_policy_attachment" "ssm_read_policy_execution_attachment" { + role = aws_iam_role.ecs_execution_role.name # The ECS execution role to attach the policy to + policy_arn = aws_iam_policy.ssm_read_policy.arn # The ARN of the SSM read policy +} + +# Creates an IAM role for ECS tasks to perform specific actions +resource "aws_iam_role" "ecs_task_role" { + name = "${var.validator_name}-task-role" # The name of the task role includes the validator's name + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" # Specifies that ECS tasks can assume this role + } + } + ] + }) +} + +# Defines an IAM policy to allow ECS tasks to perform actions on the EFS file system +resource "aws_iam_policy" "ecs_task_policy" { + name = "${var.validator_name}-task-policy" # The name of the policy includes the validator's name + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = "elasticfilesystem:*", # Allows all actions on the EFS file system + Resource = var.efs_access_point_arn # Specifies the EFS access point resource + } + ] + }) +} + +# Attaches the EFS policy to the ECS task role +resource "aws_iam_role_policy_attachment" "ecs_task_policy_attachment" { + role = aws_iam_role.ecs_task_role.name # The ECS task role to attach the policy to + policy_arn = aws_iam_policy.ecs_task_policy.arn # The ARN of the EFS policy +} diff --git a/rust/terraform/modules/iam_kms/outputs.tf b/rust/terraform/modules/iam_kms/outputs.tf new file mode 100644 index 0000000000..81b18a625c --- /dev/null +++ b/rust/terraform/modules/iam_kms/outputs.tf @@ -0,0 +1,35 @@ +output "ecs_user_arn" { + value = aws_iam_user.ecs_user.arn +} + +output "ecs_user_access_key_id_arn" { + value = aws_ssm_parameter.key_id.arn +} + +output "ecs_user_secret_access_key_arn" { + value = aws_ssm_parameter.key_secret.arn +} + +output "validator_signer_key_arn" { + value = aws_kms_key.validator_signer_key.arn +} + +output "validator_signer_key_alias" { + value = aws_kms_alias.validator_signer_key_alias.name +} + +output "validator_execution_role_arn" { + value = aws_iam_role.ecs_execution_role.arn +} + +output "validator_task_role_arn" { + value = aws_iam_role.ecs_task_role.arn +} + +output "aws_access_key_id" { + value = aws_iam_access_key.ecs_user_key.id +} + +output "aws_secret_access_key" { + value = aws_iam_access_key.ecs_user_key.secret +} diff --git a/rust/terraform/modules/iam_kms/variables.tf b/rust/terraform/modules/iam_kms/variables.tf new file mode 100644 index 0000000000..7f6568d42f --- /dev/null +++ b/rust/terraform/modules/iam_kms/variables.tf @@ -0,0 +1,19 @@ +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "validator_name" { + description = "The name of the validator" + type = string +} + +variable "aws_log_group" { + description = "The name of the log group to write to" + type = string +} + +variable "efs_access_point_arn" { + description = "The ARN of the EFS access point" + type = string +} diff --git a/rust/terraform/modules/s3/main.tf b/rust/terraform/modules/s3/main.tf new file mode 100644 index 0000000000..784bea146a --- /dev/null +++ b/rust/terraform/modules/s3/main.tf @@ -0,0 +1,65 @@ +# This resource creates an S3 bucket used to store validator signatures. +# The `force_destroy` attribute is set to true to allow the bucket to be destroyed even if it contains objects. +resource "aws_s3_bucket" "validator_bucket" { + bucket = "${var.validator_name}-signatures" + force_destroy = true # Enables deletion of non-empty bucket during destroy operation +} + +# This resource applies a public access block configuration to the validator signatures bucket. +# It prevents public ACLs from being applied to the bucket and ignores any public ACLs already on the bucket. +resource "aws_s3_bucket_public_access_block" "validator_bucket_public_access_block" { + bucket = aws_s3_bucket.validator_bucket.id + + block_public_acls = true # Blocks public ACLs from being added to the bucket + ignore_public_acls = true # Ignores any public ACLs currently associated with the bucket + block_public_policy = false # Allows public bucket policies (not recommended for sensitive data) + restrict_public_buckets = false # Allows unrestricted public access to the bucket (not recommended for sensitive data) +} + +# This resource defines a bucket policy that allows public read access to the bucket and its objects. +# It also grants additional permissions to a specific IAM role to delete and put objects in the bucket. +resource "aws_s3_bucket_policy" "validator_bucket_policy" { + bucket = aws_s3_bucket.validator_bucket.id + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = "*", + Action = [ + "s3:GetObject", # Allows retrieval of objects from the bucket + "s3:ListBucket" # Allows listing of the objects within the bucket + ], + Resource = [ + "${aws_s3_bucket.validator_bucket.arn}", # Bucket ARN + "${aws_s3_bucket.validator_bucket.arn}/*" # All objects within the bucket + ] + }, + { + Effect = "Allow", + Principal = { + AWS = var.validator_iam_user_arn # IAM user ARN of validator + }, + Action = [ + "s3:PutObject", # Allows uploading of new objects to the bucket + "s3:GetObject", # Allows retrieval of objects from the bucket + "s3:ListBucket", # Allows listing of the objects within the bucket + "s3:DeleteObject", # Allows deletion of objects within the bucket + ], + Resource = [ + "${aws_s3_bucket.validator_bucket.arn}", # Bucket ARN + "${aws_s3_bucket.validator_bucket.arn}/*" # All objects within the bucket + ] + } + ] + }) +} + +# This resource enables versioning for the S3 bucket to keep multiple versions of an object in the same bucket. +# Versioning is useful for data retention and recovery, as it allows you to recover from unintended user actions and application failures. +resource "aws_s3_bucket_versioning" "validator_bucket_versioning" { + bucket = aws_s3_bucket.validator_bucket.id + versioning_configuration { + status = "Enabled" # Enables versioning for the specified bucket + } +} diff --git a/rust/terraform/modules/s3/outputs.tf b/rust/terraform/modules/s3/outputs.tf new file mode 100644 index 0000000000..b51bb31375 --- /dev/null +++ b/rust/terraform/modules/s3/outputs.tf @@ -0,0 +1,7 @@ +output "validator_bucket_id" { + value = aws_s3_bucket.validator_bucket.id +} + +output "validator_bucket_arn" { + value = aws_s3_bucket.validator_bucket.arn +} diff --git a/rust/terraform/modules/s3/variables.tf b/rust/terraform/modules/s3/variables.tf new file mode 100644 index 0000000000..8740623102 --- /dev/null +++ b/rust/terraform/modules/s3/variables.tf @@ -0,0 +1,9 @@ +variable "validator_name" { + description = "The name of the validator" + type = string +} + +variable "validator_iam_user_arn" { + description = "The ARN of the IAM user that will write to the S3 bucket" + type = string +} diff --git a/rust/terraform/modules/validator/main.tf b/rust/terraform/modules/validator/main.tf new file mode 100644 index 0000000000..4667de95c3 --- /dev/null +++ b/rust/terraform/modules/validator/main.tf @@ -0,0 +1,138 @@ +# Sets up roles, permissions and KMS key +# Replaces https://docs.hyperlane.xyz/docs/operate/set-up-agent-keys +module "iam_kms" { + source = "../iam_kms" + + aws_region = var.aws_region + aws_log_group = var.aws_log_group + validator_name = var.validator_name + efs_access_point_arn = module.efs.access_point_arn +} + +# Creates bucket for posting validator signatures +# Replaces https://docs.hyperlane.xyz/docs/operate/validators/validator-aws +module "s3" { + source = "../s3" + + validator_name = var.validator_name + validator_iam_user_arn = module.iam_kms.ecs_user_arn +} + +# Creates file system and mounting point for the validator task +module "efs" { + source = "../efs" + + creation_token = "${var.validator_name}-db-fs" + subnet_id = var.validator_subnet_id + security_group_ids = [var.validator_sg_id] +} + +# A template for running the validator task +resource "aws_ecs_task_definition" "validator" { + family = var.validator_name + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.validator_cpu + memory = var.validator_memory + execution_role_arn = module.iam_kms.validator_execution_role_arn + task_role_arn = module.iam_kms.validator_task_role_arn + + container_definitions = jsonencode([ + { + name = "validator", + image = "gcr.io/abacus-labs-dev/hyperlane-agent:${var.validator_image_version}", + user = "1000:1000", + secrets = [ + { + name = "AWS_ACCESS_KEY_ID", + valueFrom = module.iam_kms.ecs_user_access_key_id_arn + }, + { + name = "AWS_SECRET_ACCESS_KEY", + valueFrom = module.iam_kms.ecs_user_secret_access_key_arn + } + ], + mountPoints = [ + { + sourceVolume = "hyperlane_db", + containerPath = "/hyperlane_db" + }, + ], + portMappings = [ + { + containerPort = 9090, # Prometheus metrics port + hostPort = 9090 + } + ], + command = [ + "./validator", + "--db", + "/hyperlane_db", + "--originChainName", + var.origin_chain_name, + "--validator.type", + "aws", + "--validator.id", + module.iam_kms.validator_signer_key_alias, + "--chains.${var.origin_chain_name}.type", + "aws", + "--chains.${var.origin_chain_name}.id", + module.iam_kms.validator_signer_key_alias, + "--checkpointSyncer.type", + "s3", + "--checkpointSyncer.bucket", + module.s3.validator_bucket_id, + "--checkpointSyncer.region", + var.aws_region, + "--validator.region", + var.aws_region + ], + logConfiguration = { + logDriver = "awslogs", + options = { + "awslogs-group" = var.aws_log_group, + "awslogs-region" = var.aws_region, + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + + volume { + name = "hyperlane_db" + + efs_volume_configuration { + file_system_id = module.efs.file_system_id + transit_encryption = "ENABLED" + + authorization_config { + access_point_id = module.efs.access_point_id + iam = "ENABLED" + } + } + } +} + +# An ECS service for running the validator ECS task +resource "aws_ecs_service" "validator_service" { + name = var.validator_name + cluster = var.validator_cluster_id + task_definition = aws_ecs_task_definition.validator.arn + launch_type = "FARGATE" + + # avoid rolling deployments to not lock agent db + deployment_maximum_percent = 100 + deployment_minimum_healthy_percent = 0 + + network_configuration { + subnets = [var.validator_subnet_id] + security_groups = [var.validator_sg_id] + } + + desired_count = var.validator_task_disabled ? 0 : 1 + + # implicit dependency on nat gateway existing + tags = { + NatGatewayID = var.validator_nat_gateway_id + } +} diff --git a/rust/terraform/modules/validator/outputs.tf b/rust/terraform/modules/validator/outputs.tf new file mode 100644 index 0000000000..f13b9463c4 --- /dev/null +++ b/rust/terraform/modules/validator/outputs.tf @@ -0,0 +1,9 @@ +output "validator_info" { + value = { + aws_access_key_id = module.iam_kms.aws_access_key_id, + aws_secret_access_key = module.iam_kms.aws_secret_access_key, + aws_kms_alias = module.iam_kms.validator_signer_key_alias, + aws_s3_bucket_id = module.s3.validator_bucket_id, + aws_region = var.aws_region, + } +} diff --git a/rust/terraform/modules/validator/variables.tf b/rust/terraform/modules/validator/variables.tf new file mode 100644 index 0000000000..46b8fc51f8 --- /dev/null +++ b/rust/terraform/modules/validator/variables.tf @@ -0,0 +1,64 @@ +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "validator_cluster_id" { + description = "ID of the validator cluster" + type = string +} + +variable "validator_subnet_id" { + description = "ID of the validator subnet" + type = string +} + +variable "validator_sg_id" { + description = "ID of the validator security group" + type = string +} + +variable "validator_nat_gateway_id" { + description = "ID of the validator NAT gateway" + type = string +} + +variable "validator_name" { + description = "The name of the validator" + type = string +} + +variable "origin_chain_name" { + description = "The origin chain of the validator" + type = string +} + +variable "validator_cpu" { + description = "CPU units used by the validator. Default 1 vCPU." + type = string + default = "1024" +} + +variable "validator_memory" { + description = "Memory units used by the validator. Default 6GB." + type = string + default = "6144" +} + +variable "aws_log_group" { + description = "The name of the log group to write to" + type = string + default = "DefaultLogGroup" +} + +variable "validator_image_version" { + description = "The name of the log group to write to" + type = string + default = "f44589e-20231130-114734" +} + +variable "validator_task_disabled" { + description = "Whether to run the validator in addition to auxiliary setup" + type = bool + default = false +} diff --git a/rust/terraform/outputs.tf b/rust/terraform/outputs.tf new file mode 100644 index 0000000000..a3f84a13cb --- /dev/null +++ b/rust/terraform/outputs.tf @@ -0,0 +1,4 @@ +output "your_validator_name" { + value = module.your_validator_name.validator_info + sensitive = true +} diff --git a/rust/terraform/variables.tf b/rust/terraform/variables.tf new file mode 100644 index 0000000000..3af94fb041 --- /dev/null +++ b/rust/terraform/variables.tf @@ -0,0 +1,5 @@ +variable "aws_region" { + description = "AWS region" + type = string + default = "us-east-1" +} diff --git a/rust/utils/abigen/src/lib.rs b/rust/utils/abigen/src/lib.rs index 2e8d5ca081..b4b7970fdc 100644 --- a/rust/utils/abigen/src/lib.rs +++ b/rust/utils/abigen/src/lib.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "fuels")] use fuels_code_gen::ProgramType; use std::collections::BTreeSet; use std::ffi::OsStr; diff --git a/rust/utils/run-locally/Cargo.toml b/rust/utils/run-locally/Cargo.toml index 1f59fb2fb3..bf57138f4e 100644 --- a/rust/utils/run-locally/Cargo.toml +++ b/rust/utils/run-locally/Cargo.toml @@ -10,9 +10,11 @@ publish.workspace = true version.workspace = true [dependencies] -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["float"]} +hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos"} toml_edit.workspace = true k256.workspace = true +jobserver.workspace = true ripemd.workspace = true sha2.workspace = true serde.workspace = true @@ -27,5 +29,8 @@ ureq = { workspace = true, default-features = false } which.workspace = true macro_rules_attribute.workspace = true regex.workspace = true -hpl-interface.workspace = true +hyperlane-cosmwasm-interface.workspace = true cosmwasm-schema.workspace = true + +[features] +cosmos = [] \ No newline at end of file diff --git a/rust/utils/run-locally/src/cosmos/cli.rs b/rust/utils/run-locally/src/cosmos/cli.rs index b221c9f499..4258f149c2 100644 --- a/rust/utils/run-locally/src/cosmos/cli.rs +++ b/rust/utils/run-locally/src/cosmos/cli.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, io::Write, path::PathBuf, process::Stdio}; +use hyperlane_cosmos::RawCosmosAmount; use k256::ecdsa::SigningKey; use crate::{ @@ -9,7 +10,7 @@ use crate::{ use super::{ crypto::KeyPair, default_keys, modify_toml, sed, types::BalanceResponse, wait_for_node, Codes, - Coin, TxResponse, + TxResponse, }; const GENESIS_FUND: u128 = 1000000000000; @@ -253,7 +254,7 @@ impl OsmosisCLI { sender: &str, contract: &str, execute_msg: T, - funds: Vec, + funds: Vec, ) -> TxResponse { let mut cmd = self .cli() diff --git a/rust/utils/run-locally/src/cosmos/crypto.rs b/rust/utils/run-locally/src/cosmos/crypto.rs index e4520cbcbf..75924df69a 100644 --- a/rust/utils/run-locally/src/cosmos/crypto.rs +++ b/rust/utils/run-locally/src/cosmos/crypto.rs @@ -1,6 +1,4 @@ -// TODO: this file can be removed if `CosmosAddress` can be imported from `hyperlane-cosmos`. -// However, adding a hyperlane-cosmos dep creates a dep cycle. -// Look into how this can be fixed. +// TODO: this file can be removed by replacing `KeyPair` uses with `CosmosAddress` use k256::ecdsa::{SigningKey, VerifyingKey}; use ripemd::Ripemd160; @@ -26,7 +24,7 @@ pub fn pub_to_addr(pub_key: &[u8], prefix: &str) -> String { let sha_hash = sha256_digest(pub_key); let rip_hash = ripemd160_digest(sha_hash); - let addr = hpl_interface::types::bech32_encode(prefix, &rip_hash).unwrap(); + let addr = hyperlane_cosmwasm_interface::types::bech32_encode(prefix, &rip_hash).unwrap(); addr.to_string() } diff --git a/rust/utils/run-locally/src/cosmos/deploy.rs b/rust/utils/run-locally/src/cosmos/deploy.rs index 859f9f05dd..4da016d865 100644 --- a/rust/utils/run-locally/src/cosmos/deploy.rs +++ b/rust/utils/run-locally/src/cosmos/deploy.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use hpl_interface::{core, hook, igp, ism}; +use hyperlane_cosmwasm_interface::{core, hook, igp, ism}; use macro_rules_attribute::apply; use crate::utils::as_task; @@ -9,25 +9,24 @@ use super::{ types::{Codes, Deployments}, }; -#[cw_serde] -pub struct IsmMultisigInstantiateMsg { - pub owner: String, -} - #[cw_serde] pub struct TestMockMsgReceiverInstantiateMsg { pub hrp: String, } #[cw_serde] -pub struct IGPOracleInstantiateMsg { +struct IgpInstantiateMsg { + pub hrp: String, pub owner: String, + pub gas_token: String, + pub beneficiary: String, + pub default_gas_usage: String, // u128 doesnt work with cw_serde } #[cw_serde] pub struct EmptyMsg {} -const PREFIX: &str = "osmo"; +const BECH32_PREFIX: &str = "osmo"; #[apply(as_task)] pub fn deploy_cw_hyperlane( @@ -46,29 +45,19 @@ pub fn deploy_cw_hyperlane( codes.hpl_mailbox, core::mailbox::InstantiateMsg { owner: deployer_addr.to_string(), - hrp: PREFIX.to_string(), + hrp: BECH32_PREFIX.to_string(), domain, }, "hpl_mailbox", ); - // deploy igp set - #[cw_serde] - pub struct GasOracleInitMsg { - pub hrp: String, - pub owner: String, - pub gas_token: String, - pub beneficiary: String, - pub default_gas_usage: String, - } - let igp = cli.wasm_init( &endpoint, &deployer, Some(deployer_addr), codes.hpl_igp, - GasOracleInitMsg { - hrp: PREFIX.to_string(), + IgpInstantiateMsg { + hrp: BECH32_PREFIX.to_string(), owner: deployer_addr.clone(), gas_token: "uosmo".to_string(), beneficiary: deployer_addr.clone(), @@ -107,12 +96,39 @@ pub fn deploy_cw_hyperlane( &deployer, Some(deployer_addr), codes.hpl_ism_multisig, - IsmMultisigInstantiateMsg { + ism::multisig::InstantiateMsg { owner: deployer_addr.clone(), }, "hpl_ism_multisig", ); + // deploy pausable ism + let ism_pausable = cli.wasm_init( + &endpoint, + &deployer, + Some(deployer_addr), + codes.hpl_ism_pausable, + ism::pausable::InstantiateMsg { + owner: deployer_addr.clone(), + paused: false, + }, + "hpl_ism_pausable", + ); + + // deploy ism - aggregation + let ism_aggregate = cli.wasm_init( + &endpoint, + &deployer, + Some(deployer_addr), + codes.hpl_ism_aggregate, + ism::aggregate::InstantiateMsg { + owner: deployer_addr.clone(), + threshold: 2, + isms: vec![ism_multisig.clone(), ism_pausable.clone()], + }, + "hpl_ism_aggregate", + ); + // deploy merkle hook let hook_merkle = cli.wasm_init( &endpoint, @@ -120,7 +136,6 @@ pub fn deploy_cw_hyperlane( Some(deployer_addr), codes.hpl_hook_merkle, hook::merkle::InstantiateMsg { - owner: deployer_addr.clone(), mailbox: mailbox.to_string(), }, "hpl_hook_merkle", @@ -145,7 +160,7 @@ pub fn deploy_cw_hyperlane( Some(deployer_addr), codes.hpl_validator_announce, core::va::InstantiateMsg { - hrp: PREFIX.to_string(), + hrp: BECH32_PREFIX.to_string(), mailbox: mailbox.to_string(), }, "hpl_validator_announce", @@ -159,7 +174,7 @@ pub fn deploy_cw_hyperlane( Some(deployer_addr), codes.hpl_test_mock_msg_receiver, TestMockMsgReceiverInstantiateMsg { - hrp: PREFIX.to_string(), + hrp: BECH32_PREFIX.to_string(), }, "hpl_test_mock_msg_receiver", ); @@ -188,6 +203,7 @@ pub fn deploy_cw_hyperlane( hook_routing, igp, igp_oracle, + ism_aggregate, ism_routing, ism_multisig, mailbox, diff --git a/rust/utils/run-locally/src/cosmos/link.rs b/rust/utils/run-locally/src/cosmos/link.rs index 1e65aa4b25..ebf53bd36c 100644 --- a/rust/utils/run-locally/src/cosmos/link.rs +++ b/rust/utils/run-locally/src/cosmos/link.rs @@ -1,7 +1,7 @@ use std::path::Path; use cosmwasm_schema::cw_serde; -use hpl_interface::{ +use hyperlane_cosmwasm_interface::{ core, ism::{self}, }; @@ -69,18 +69,14 @@ pub struct MockQuoteDispatch { #[cw_serde] pub struct GeneralIsmValidatorMessage { - pub enroll_validator: EnrollValidatorMsg, + pub set_validators: SetValidatorsMsg, } #[cw_serde] -pub struct EnrollValidatorMsg { - pub set: EnrollValidatorSet, -} - -#[cw_serde] -pub struct EnrollValidatorSet { +pub struct SetValidatorsMsg { pub domain: u32, - pub validator: String, + pub threshold: u8, + pub validators: Vec, } fn link_network( @@ -93,10 +89,10 @@ fn link_network( ) { let validator_addr = validator.addr(hrp); - let dest_domain = if network.domain == 26657 { - 26658 + let dest_domain = if network.domain == 99990 { + 99991 } else { - 26657 + 99990 }; // hook routing @@ -105,7 +101,7 @@ fn link_network( let public_key = validator.priv_key.verifying_key().to_encoded_point(false); let public_key = public_key.as_bytes(); - let hash = hpl_interface::types::keccak256_hash(&public_key[1..]); + let hash = hyperlane_cosmwasm_interface::types::keccak256_hash(&public_key[1..]); let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash.as_slice()[12..]); @@ -115,24 +111,10 @@ fn link_network( linker, &network.deployments.ism_multisig, GeneralIsmValidatorMessage { - enroll_validator: EnrollValidatorMsg { - set: EnrollValidatorSet { - domain: target_domain, - validator: hex::encode(bytes).to_string(), - }, - }, - }, - vec![], - ); - - cli.wasm_execute( - &network.launch_resp.endpoint, - linker, - &network.deployments.ism_multisig, - ism::multisig::ExecuteMsg::SetThreshold { - set: ism::multisig::ThresholdSet { - domain: target_domain, + set_validators: SetValidatorsMsg { threshold: 1, + domain: target_domain, + validators: vec![hex::encode(bytes).to_string()], }, }, vec![], @@ -162,7 +144,7 @@ fn link_network( ism::routing::ExecuteMsg::Set { ism: ism::routing::IsmSet { domain: target_domain, - address: network.deployments.ism_multisig.clone(), + address: network.deployments.ism_aggregate.clone(), }, }, vec![], diff --git a/rust/utils/run-locally/src/cosmos/mod.rs b/rust/utils/run-locally/src/cosmos/mod.rs index 1ecb26dc0c..b1b2b4dd47 100644 --- a/rust/utils/run-locally/src/cosmos/mod.rs +++ b/rust/utils/run-locally/src/cosmos/mod.rs @@ -5,8 +5,10 @@ use std::time::{Duration, Instant}; use std::{env, fs}; use cosmwasm_schema::cw_serde; -use hpl_interface::types::bech32_decode; +use hyperlane_cosmos::RawCosmosAmount; +use hyperlane_cosmwasm_interface::types::bech32_decode; use macro_rules_attribute::apply; +use maplit::hashmap; use tempfile::tempdir; mod cli; @@ -24,16 +26,17 @@ use utils::*; use crate::cosmos::link::link_networks; use crate::logging::log; +use crate::metrics::agent_balance_sum; use crate::program::Program; use crate::utils::{as_task, concat_path, stop_child, AgentHandles, TaskHandle}; -use crate::AGENT_BIN_PATH; +use crate::{fetch_metric, AGENT_BIN_PATH}; use cli::{OsmosisCLI, OsmosisEndpoint}; use self::deploy::deploy_cw_hyperlane; use self::source::{CLISource, CodeSource}; const OSMOSIS_CLI_GIT: &str = "https://github.com/osmosis-labs/osmosis"; -const OSMOSIS_CLI_VERSION: &str = "19.0.0"; +const OSMOSIS_CLI_VERSION: &str = "20.5.0"; const KEY_HPL_VALIDATOR: (&str,&str) = ("hpl-validator", "guard evolve region sentence danger sort despair eye deputy brave trim actor left recipe debate document upgrade sustain bus cage afford half demand pigeon"); const KEY_HPL_RELAYER: (&str,&str) = ("hpl-relayer", "moral item damp melt gloom vendor notice head assume balance doctor retire fashion trim find biology saddle undo switch fault cattle toast drip empty"); @@ -54,8 +57,8 @@ fn default_keys<'a>() -> [(&'a str, &'a str); 6] { ] } -const CW_HYPERLANE_GIT: &str = "https://github.com/many-things/cw-hyperlane"; -const CW_HYPERLANE_VERSION: &str = "0.0.6-rc6"; +const CW_HYPERLANE_GIT: &str = "https://github.com/hyperlane-xyz/cosmwasm"; +const CW_HYPERLANE_VERSION: &str = "v0.0.6"; fn make_target() -> String { let os = if cfg!(target_os = "linux") { @@ -98,19 +101,22 @@ pub fn install_codes(dir: Option, local: bool) -> BTreeMap path map fs::read_dir(dir_path) @@ -173,6 +179,7 @@ pub struct CosmosNetwork { pub launch_resp: CosmosResp, pub deployments: Deployments, pub chain_id: String, + pub metrics_port: u32, pub domain: u32, } @@ -182,17 +189,17 @@ impl Drop for CosmosNetwork { } } -impl From<(CosmosResp, Deployments, String, u32)> for CosmosNetwork { - fn from(v: (CosmosResp, Deployments, String, u32)) -> Self { +impl From<(CosmosResp, Deployments, String, u32, u32)> for CosmosNetwork { + fn from(v: (CosmosResp, Deployments, String, u32, u32)) -> Self { Self { launch_resp: v.0, deployments: v.1, chain_id: v.2, - domain: v.3, + metrics_port: v.3, + domain: v.4, } } } - pub struct CosmosHyperlaneStack { pub validators: Vec, pub relayer: AgentHandles, @@ -256,9 +263,8 @@ fn launch_cosmos_validator( .hyp_env("CHECKPOINTSYNCER_PATH", checkpoint_path.to_str().unwrap()) .hyp_env("CHECKPOINTSYNCER_TYPE", "localStorage") .hyp_env("ORIGINCHAINNAME", agent_config.name) - .hyp_env("REORGPERIOD", "100") .hyp_env("DB", validator_base_db.to_str().unwrap()) - .hyp_env("METRICSPORT", agent_config.domain_id.to_string()) + .hyp_env("METRICSPORT", agent_config.metrics_port.to_string()) .hyp_env("VALIDATOR_SIGNER_TYPE", agent_config.signer.typ) .hyp_env("VALIDATOR_KEY", agent_config.signer.key.clone()) .hyp_env("VALIDATOR_PREFIX", "osmo") @@ -274,6 +280,7 @@ fn launch_cosmos_validator( fn launch_cosmos_relayer( agent_config_path: PathBuf, relay_chains: Vec, + metrics: u32, debug: bool, ) -> AgentHandles { let relayer_bin = concat_path(format!("../../{AGENT_BIN_PATH}"), "relayer"); @@ -285,12 +292,11 @@ fn launch_cosmos_relayer( .env("CONFIG_FILES", agent_config_path.to_str().unwrap()) .env("RUST_BACKTRACE", "1") .hyp_env("RELAYCHAINS", relay_chains.join(",")) - .hyp_env("REORGPERIOD", "100") .hyp_env("DB", relayer_base.as_ref().to_str().unwrap()) .hyp_env("ALLOWLOCALCHECKPOINTSYNCERS", "true") .hyp_env("TRACING_LEVEL", if debug { "debug" } else { "info" }) .hyp_env("GASPAYMENTENFORCEMENT", "[{\"type\": \"none\"}]") - .hyp_env("METRICSPORT", 9093.to_string()) + .hyp_env("METRICSPORT", metrics.to_string()) .spawn("RLY"); relayer @@ -347,7 +353,8 @@ fn run_locally() { }; let port_start = 26600u32; - let domain_start = 26657u32; + let metrics_port_start = 9090u32; + let domain_start = 99990u32; let node_count = 2; let nodes = (0..node_count) @@ -355,10 +362,11 @@ fn run_locally() { ( launch_cosmos_node(CosmosConfig { node_port_base: port_start + (i * 10), - chain_id: format!("cosmos-test-{}", i + 26657), + chain_id: format!("cosmos-test-{}", i + domain_start), ..default_config.clone() }), - format!("cosmos-test-{}", i + 26657), + format!("cosmos-test-{}", i + domain_start), + metrics_port_start + i, domain_start + i, ) }) @@ -371,8 +379,8 @@ fn run_locally() { let nodes = nodes .into_iter() - .map(|v| (v.0.join(), v.1, v.2)) - .map(|(launch_resp, chain_id, domain)| { + .map(|v| (v.0.join(), v.1, v.2, v.3)) + .map(|(launch_resp, chain_id, metrics_port, domain)| { let deployments = deploy_cw_hyperlane( launch_resp.cli(&osmosisd), launch_resp.endpoint.clone(), @@ -381,14 +389,14 @@ fn run_locally() { domain, ); - (launch_resp, deployments, chain_id, domain) + (launch_resp, deployments, chain_id, metrics_port, domain) }) .collect::>(); // nodes with base deployments let nodes = nodes .into_iter() - .map(|v| (v.0, v.1.join(), v.2, v.3)) + .map(|v| (v.0, v.1.join(), v.2, v.3, v.4)) .map(|v| v.into()) .collect::>(); @@ -448,12 +456,19 @@ fn run_locally() { .into_values() .map(|agent_config| launch_cosmos_validator(agent_config, agent_config_path.clone(), debug)) .collect::>(); + let hpl_rly_metrics_port = metrics_port_start + node_count + 1u32; let hpl_rly = launch_cosmos_relayer( agent_config_path, agent_config_out.chains.into_keys().collect::>(), + hpl_rly_metrics_port, debug, ); + // give things a chance to fully start. + sleep(Duration::from_secs(10)); + + let starting_relayer_balance: f64 = agent_balance_sum(hpl_rly_metrics_port).unwrap(); + // dispatch messages let mut dispatched_messages = 0; @@ -495,7 +510,7 @@ fn run_locally() { metadata: "".to_string(), }, }, - vec![Coin { + vec![RawCosmosAmount { denom: "uosmo".to_string(), amount: 25_000_000.to_string(), }], @@ -511,12 +526,16 @@ fn run_locally() { // Mostly copy-pasta from `rust/utils/run-locally/src/main.rs` // TODO: refactor to share code let loop_start = Instant::now(); - // give things a chance to fully start. - sleep(Duration::from_secs(5)); let mut failure_occurred = false; loop { // look for the end condition. - if termination_invariants_met(dispatched_messages).unwrap_or(false) { + if termination_invariants_met( + hpl_rly_metrics_port, + dispatched_messages, + starting_relayer_balance, + ) + .unwrap_or(false) + { // end condition reached successfully break; } else if (Instant::now() - loop_start).as_secs() > TIMEOUT_SECS { @@ -536,47 +555,65 @@ fn run_locally() { } } -fn termination_invariants_met(_messages_expected: u32) -> eyre::Result { +fn termination_invariants_met( + relayer_metrics_port: u32, + messages_expected: u32, + starting_relayer_balance: f64, +) -> eyre::Result { + let gas_payments_scraped = fetch_metric( + &relayer_metrics_port.to_string(), + "hyperlane_contract_sync_stored_events", + &hashmap! {"data_type" => "gas_payment"}, + )? + .iter() + .sum::(); + let expected_gas_payments = messages_expected; + if gas_payments_scraped != expected_gas_payments { + log!( + "Scraper has scraped {} gas payments, expected {}", + gas_payments_scraped, + expected_gas_payments + ); + return Ok(false); + } + + let delivered_messages_scraped = fetch_metric( + &relayer_metrics_port.to_string(), + "hyperlane_operations_processed_count", + &hashmap! {"phase" => "confirmed"}, + )? + .iter() + .sum::(); + if delivered_messages_scraped != messages_expected { + log!( + "Relayer confirmed {} submitted messages, expected {}", + delivered_messages_scraped, + messages_expected + ); + return Ok(false); + } + + let ending_relayer_balance: f64 = agent_balance_sum(relayer_metrics_port).unwrap(); + + // Make sure the balance was correctly updated in the metrics. + // Ideally, make sure that the difference is >= gas_per_tx * gas_cost, set here: + // https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/c2288eb31734ba1f2f997e2c6ecb30176427bc2c/rust/utils/run-locally/src/cosmos/cli.rs#L55 + // What's stopping this is that the format returned by the `uosmo` balance query is a surprisingly low number (0.000003999999995184) + // but then maybe the gas_per_tx is just very low - how can we check that? (maybe by simulating said tx) + if starting_relayer_balance <= ending_relayer_balance { + log!( + "Expected starting relayer balance to be greater than ending relayer balance, but got {} <= {}", + starting_relayer_balance, + ending_relayer_balance + ); + return Ok(false); + } + + log!("Termination invariants have been meet"); Ok(true) - // TODO: uncomment once CI passes consistently on Ubuntu - // let gas_payments_scraped = fetch_metric( - // "9093", - // "hyperlane_contract_sync_stored_events", - // &hashmap! {"data_type" => "gas_payment"}, - // )? - // .iter() - // .sum::(); - // let expected_gas_payments = messages_expected; - // if gas_payments_scraped != expected_gas_payments { - // log!( - // "Scraper has scraped {} gas payments, expected {}", - // gas_payments_scraped, - // expected_gas_payments - // ); - // return Ok(false); - // } - - // let delivered_messages_scraped = fetch_metric( - // "9093", - // "hyperlane_operations_processed_count", - // &hashmap! {"phase" => "confirmed"}, - // )? - // .iter() - // .sum::(); - // if delivered_messages_scraped != messages_expected { - // log!( - // "Relayer confirmed {} submitted messages, expected {}", - // delivered_messages_scraped, - // messages_expected - // ); - // return Ok(false); - // } - - // log!("Termination invariants have been meet"); - // Ok(true) } -#[cfg(test)] +#[cfg(feature = "cosmos")] mod test { use super::*; diff --git a/rust/utils/run-locally/src/cosmos/types.rs b/rust/utils/run-locally/src/cosmos/types.rs index 8632687b15..120ed05afd 100644 --- a/rust/utils/run-locally/src/cosmos/types.rs +++ b/rust/utils/run-locally/src/cosmos/types.rs @@ -1,6 +1,7 @@ use std::{collections::BTreeMap, path::PathBuf}; -use hpl_interface::types::bech32_decode; +use hyperlane_cosmos::RawCosmosAmount; +use hyperlane_cosmwasm_interface::types::bech32_decode; use super::{cli::OsmosisCLI, CosmosNetwork}; @@ -35,19 +36,15 @@ pub struct TxResponse { pub logs: Vec, } -#[derive(serde::Serialize, serde::Deserialize)] -pub struct Coin { - pub denom: String, - pub amount: String, -} - #[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct Codes { pub hpl_hook_merkle: u64, pub hpl_hook_routing: u64, pub hpl_igp: u64, pub hpl_igp_oracle: u64, + pub hpl_ism_aggregate: u64, pub hpl_ism_multisig: u64, + pub hpl_ism_pausable: u64, pub hpl_ism_routing: u64, pub hpl_test_mock_ism: u64, pub hpl_test_mock_hook: u64, @@ -62,6 +59,7 @@ pub struct Deployments { pub hook_routing: String, pub igp: String, pub igp_oracle: String, + pub ism_aggregate: String, pub ism_routing: String, pub ism_multisig: String, pub mailbox: String, @@ -73,7 +71,7 @@ pub struct Deployments { #[derive(serde::Serialize, serde::Deserialize)] pub struct BalanceResponse { - pub balances: Vec, + pub balances: Vec, } #[derive(serde::Serialize, serde::Deserialize)] @@ -113,18 +111,20 @@ pub struct AgentUrl { pub struct AgentConfig { pub name: String, pub domain_id: u32, + pub metrics_port: u32, pub mailbox: String, pub interchain_gas_paymaster: String, pub validator_announce: String, pub merkle_tree_hook: String, pub protocol: String, - pub finality_blocks: u32, pub chain_id: String, pub rpc_urls: Vec, - pub grpc_url: String, - pub prefix: String, + pub grpc_urls: Vec, + pub bech32_prefix: String, pub signer: AgentConfigSigner, pub index: AgentConfigIndex, + pub gas_price: RawCosmosAmount, + pub contract_address_bytes: usize, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] @@ -144,12 +144,12 @@ impl AgentConfig { AgentConfig { name: format!("cosmostest{}", network.domain), domain_id: network.domain, + metrics_port: network.metrics_port, mailbox: to_hex_addr(&network.deployments.mailbox), interchain_gas_paymaster: to_hex_addr(&network.deployments.igp), validator_announce: to_hex_addr(&network.deployments.va), merkle_tree_hook: to_hex_addr(&network.deployments.hook_merkle), protocol: "cosmos".to_string(), - finality_blocks: 1, chain_id: format!("cosmos-test-{}", network.domain), rpc_urls: vec![AgentUrl { http: format!( @@ -157,13 +157,26 @@ impl AgentConfig { network.launch_resp.endpoint.rpc_addr.replace("tcp://", "") ), }], - grpc_url: format!("http://{}", network.launch_resp.endpoint.grpc_addr), - prefix: "osmo".to_string(), + grpc_urls: vec![ + // The first url points to a nonexistent node, but is used for checking fallback provider logic + AgentUrl { + http: "localhost:1337".to_string(), + }, + AgentUrl { + http: format!("http://{}", network.launch_resp.endpoint.grpc_addr), + }, + ], + bech32_prefix: "osmo".to_string(), signer: AgentConfigSigner { typ: "cosmosKey".to_string(), key: format!("0x{}", hex::encode(validator.priv_key.to_bytes())), prefix: "osmo".to_string(), }, + gas_price: RawCosmosAmount { + denom: "uosmo".to_string(), + amount: "0.05".to_string(), + }, + contract_address_bytes: 32, index: AgentConfigIndex { from: 1, chunk: 100, diff --git a/rust/utils/run-locally/src/invariants.rs b/rust/utils/run-locally/src/invariants.rs index f1fb725959..6900210469 100644 --- a/rust/utils/run-locally/src/invariants.rs +++ b/rust/utils/run-locally/src/invariants.rs @@ -1,22 +1,26 @@ // use std::path::Path; +use std::path::Path; + use crate::config::Config; +use crate::metrics::agent_balance_sum; use maplit::hashmap; use crate::logging::log; +use crate::solana::solana_termination_invariants_met; use crate::{fetch_metric, ZERO_MERKLE_INSERTION_KATHY_MESSAGES}; -// use crate::solana::solana_termination_invariants_met; // This number should be even, so the messages can be split into two equal halves // sent before and after the relayer spins up, to avoid rounding errors. -pub const SOL_MESSAGES_EXPECTED: u32 = 0; +pub const SOL_MESSAGES_EXPECTED: u32 = 20; /// Use the metrics to check if the relayer queues are empty and the expected /// number of messages have been sent. pub fn termination_invariants_met( config: &Config, - // solana_cli_tools_path: &Path, - // solana_config_path: &Path, + starting_relayer_balance: f64, + solana_cli_tools_path: &Path, + solana_config_path: &Path, ) -> eyre::Result { let eth_messages_expected = (config.kathy_messages / 2) as u32 * 2; let total_messages_expected = eth_messages_expected + SOL_MESSAGES_EXPECTED; @@ -72,10 +76,10 @@ pub fn termination_invariants_met( return Ok(false); } - // if !solana_termination_invariants_met(solana_cli_tools_path, solana_config_path) { - // log!("Solana termination invariants not met"); - // return Ok(false); - // } + if !solana_termination_invariants_met(solana_cli_tools_path, solana_config_path) { + log!("Solana termination invariants not met"); + return Ok(false); + } let dispatched_messages_scraped = fetch_metric( "9093", @@ -129,6 +133,17 @@ pub fn termination_invariants_met( return Ok(false); } + let ending_relayer_balance: f64 = agent_balance_sum(9092).unwrap(); + // Make sure the balance was correctly updated in the metrics. + if starting_relayer_balance <= ending_relayer_balance { + log!( + "Expected starting relayer balance to be greater than ending relayer balance, but got {} <= {}", + starting_relayer_balance, + ending_relayer_balance + ); + return Ok(false); + } + log!("Termination invariants have been meet"); Ok(true) } diff --git a/rust/utils/run-locally/src/main.rs b/rust/utils/run-locally/src/main.rs index 0ee63b02d4..b4efc6115f 100644 --- a/rust/utils/run-locally/src/main.rs +++ b/rust/utils/run-locally/src/main.rs @@ -29,7 +29,8 @@ use tempfile::tempdir; use crate::{ config::Config, ethereum::start_anvil, - invariants::termination_invariants_met, + invariants::{termination_invariants_met, SOL_MESSAGES_EXPECTED}, + metrics::agent_balance_sum, solana::*, utils::{concat_path, make_static, stop_child, AgentHandles, ArbitraryData, TaskHandle}, }; @@ -53,9 +54,9 @@ const RELAYER_KEYS: &[&str] = &[ // test3 "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", // sealeveltest1 - // "0x892bf6949af4233e62f854cb3618bc1a3ee3341dc71ada08c4d5deca239acf4f", - // // sealeveltest2 - // "0x892bf6949af4233e62f854cb3618bc1a3ee3341dc71ada08c4d5deca239acf4f", + "0x892bf6949af4233e62f854cb3618bc1a3ee3341dc71ada08c4d5deca239acf4f", + // sealeveltest2 + "0x892bf6949af4233e62f854cb3618bc1a3ee3341dc71ada08c4d5deca239acf4f", ]; /// These private keys are from hardhat/anvil's testing accounts. /// These must be consistent with the ISM config for the test. @@ -65,15 +66,13 @@ const VALIDATOR_KEYS: &[&str] = &[ "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", // sealevel - // "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", ]; -const VALIDATOR_ORIGIN_CHAINS: &[&str] = &["test1", "test2", "test3"]; -// const VALIDATOR_ORIGIN_CHAINS: &[&str] = &["test1", "test2", "test3", "sealeveltest1"]; +const VALIDATOR_ORIGIN_CHAINS: &[&str] = &["test1", "test2", "test3", "sealeveltest1"]; const AGENT_BIN_PATH: &str = "target/debug"; const INFRA_PATH: &str = "../typescript/infra"; -// const TS_SDK_PATH: &str = "../typescript/sdk"; const MONOREPO_ROOT_PATH: &str = "../"; const ZERO_MERKLE_INSERTION_KATHY_MESSAGES: u32 = 10; @@ -91,6 +90,7 @@ struct State { watchers: Vec>>, data: Vec>, } + impl State { fn push_agent(&mut self, handles: AgentHandles) { self.agents.push((handles.0, handles.1)); @@ -99,6 +99,7 @@ impl State { self.data.push(handles.4); } } + impl Drop for State { fn drop(&mut self) { SHUTDOWN.store(true, Ordering::Relaxed); @@ -136,11 +137,11 @@ fn main() -> ExitCode { let config = Config::load(); - // let solana_checkpoint_path = Path::new(SOLANA_CHECKPOINT_LOCATION); - // fs::remove_dir_all(solana_checkpoint_path).unwrap_or_default(); - let checkpoints_dirs: Vec = (0..VALIDATOR_COUNT) + let solana_checkpoint_path = Path::new(SOLANA_CHECKPOINT_LOCATION); + fs::remove_dir_all(solana_checkpoint_path).unwrap_or_default(); + let checkpoints_dirs: Vec = (0..VALIDATOR_COUNT - 1) .map(|_| Box::new(tempdir().unwrap()) as DynPath) - // .chain([Box::new(solana_checkpoint_path) as DynPath]) + .chain([Box::new(solana_checkpoint_path) as DynPath]) .collect(); let rocks_db_dir = tempdir().unwrap(); let relayer_db = concat_path(&rocks_db_dir, "relayer"); @@ -172,8 +173,8 @@ fn main() -> ExitCode { .hyp_env("DB", relayer_db.to_str().unwrap()) .hyp_env("CHAINS_TEST1_SIGNER_KEY", RELAYER_KEYS[0]) .hyp_env("CHAINS_TEST2_SIGNER_KEY", RELAYER_KEYS[1]) - // .hyp_env("CHAINS_SEALEVELTEST1_SIGNER_KEY", RELAYER_KEYS[3]) - // .hyp_env("CHAINS_SEALEVELTEST2_SIGNER_KEY", RELAYER_KEYS[4]) + .hyp_env("CHAINS_SEALEVELTEST1_SIGNER_KEY", RELAYER_KEYS[3]) + .hyp_env("CHAINS_SEALEVELTEST2_SIGNER_KEY", RELAYER_KEYS[4]) .hyp_env("RELAYCHAINS", "invalidchain,otherinvalid") .hyp_env("ALLOWLOCALCHECKPOINTSYNCERS", "true") .hyp_env( @@ -200,8 +201,7 @@ fn main() -> ExitCode { .arg("defaultSigner.key", RELAYER_KEYS[2]) .arg( "relayChains", - "test1,test2,test3", - // "test1,test2,test3,sealeveltest1,sealeveltest2", + "test1,test2,test3,sealeveltest1,sealeveltest2", ); let base_validator_env = common_agent_env @@ -273,9 +273,9 @@ fn main() -> ExitCode { // Ready to run... // - // let (solana_path, solana_path_tempdir) = install_solana_cli_tools().join(); - // state.data.push(Box::new(solana_path_tempdir)); - // let solana_program_builder = build_solana_programs(solana_path.clone()); + let (solana_path, solana_path_tempdir) = install_solana_cli_tools().join(); + state.data.push(Box::new(solana_path_tempdir)); + let solana_program_builder = build_solana_programs(solana_path.clone()); // this task takes a long time in the CI so run it in parallel log!("Building rust..."); @@ -286,13 +286,13 @@ fn main() -> ExitCode { .arg("bin", "validator") .arg("bin", "scraper") .arg("bin", "init-db") - // .arg("bin", "hyperlane-sealevel-client") + .arg("bin", "hyperlane-sealevel-client") .filter_logs(|l| !l.contains("workspace-inheritance")) .run(); let start_anvil = start_anvil(config.clone()); - // let solana_program_path = solana_program_builder.join(); + let solana_program_path = solana_program_builder.join(); log!("Running postgres db..."); let postgres = Program::new("docker") @@ -307,15 +307,15 @@ fn main() -> ExitCode { build_rust.join(); - // let solana_ledger_dir = tempdir().unwrap(); - // let start_solana_validator = start_solana_test_validator( - // solana_path.clone(), - // solana_program_path, - // solana_ledger_dir.as_ref().to_path_buf(), - // ); + let solana_ledger_dir = tempdir().unwrap(); + let start_solana_validator = start_solana_test_validator( + solana_path.clone(), + solana_program_path, + solana_ledger_dir.as_ref().to_path_buf(), + ); - // let (_solana_config_path, solana_validator) = start_solana_validator.join(); - // state.push_agent(solana_validator); + let (solana_config_path, solana_validator) = start_solana_validator.join(); + state.push_agent(solana_validator); state.push_agent(start_anvil.join()); // spawn 1st validator before any messages have been sent to test empty mailbox @@ -361,9 +361,9 @@ fn main() -> ExitCode { kathy_env_double_insertion.clone().run().join(); // Send some sealevel messages before spinning up the agents, to test the backward indexing cursor - // for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { - // initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()).join(); - // } + for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { + initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()).join(); + } // spawn the rest of the validators for (i, validator_env) in validator_envs.into_iter().enumerate().skip(1) { @@ -374,9 +374,9 @@ fn main() -> ExitCode { state.push_agent(relayer_env.spawn("RLY")); // Send some sealevel messages after spinning up the relayer, to test the forward indexing cursor - // for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { - // initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()).join(); - // } + for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { + initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()).join(); + } log!("Setup complete! Agents running in background..."); log!("Ctrl+C to end execution..."); @@ -388,14 +388,20 @@ fn main() -> ExitCode { let loop_start = Instant::now(); // give things a chance to fully start. - sleep(Duration::from_secs(5)); + sleep(Duration::from_secs(10)); let mut failure_occurred = false; + let starting_relayer_balance: f64 = agent_balance_sum(9092).unwrap(); while !SHUTDOWN.load(Ordering::Relaxed) { if config.ci_mode { // for CI we have to look for the end condition. - if termination_invariants_met(&config) - // if termination_invariants_met(&config, &solana_path, &solana_config_path) - .unwrap_or(false) + // if termination_invariants_met(&config, starting_relayer_balance) + if termination_invariants_met( + &config, + starting_relayer_balance, + &solana_path, + &solana_config_path, + ) + .unwrap_or(false) { // end condition reached successfully break; diff --git a/rust/utils/run-locally/src/metrics.rs b/rust/utils/run-locally/src/metrics.rs index 3ee1f17146..aad0f626d9 100644 --- a/rust/utils/run-locally/src/metrics.rs +++ b/rust/utils/run-locally/src/metrics.rs @@ -1,8 +1,14 @@ -use std::collections::HashMap; +use std::{collections::HashMap, error::Error as StdError, str::FromStr}; -use eyre::{eyre, Result}; +use eyre::{eyre, ErrReport, Result}; +use maplit::hashmap; -pub fn fetch_metric(port: &str, metric: &str, labels: &HashMap<&str, &str>) -> Result> { +/// Fetch a prometheus format metric, filtering by labels. +pub fn fetch_metric(port: &str, metric: &str, labels: &HashMap<&str, &str>) -> Result> +where + T: FromStr, + E: Into + StdError + Send + Sync + 'static, +{ let resp = ureq::get(&format!("http://127.0.0.1:{}/metrics", port)); resp.call()? .into_string()? @@ -16,10 +22,19 @@ pub fn fetch_metric(port: &str, metric: &str, labels: &HashMap<&str, &str>) -> R .all(|(k, v)| l.contains(&format!("{k}=\"{v}"))) }) .map(|l| { - Ok(l.rsplit_once(' ') - .ok_or(eyre!("Unknown metric format"))? - .1 - .parse::()?) + let value = l.rsplit_once(' ').ok_or(eyre!("Unknown metric format"))?.1; + Ok(value.parse::()?) }) .collect() } + +pub fn agent_balance_sum(metrics_port: u32) -> eyre::Result { + let balance = fetch_metric( + &metrics_port.to_string(), + "hyperlane_wallet_balance", + &hashmap! {}, + )? + .iter() + .sum(); + Ok(balance) +} diff --git a/rust/utils/run-locally/src/solana.rs b/rust/utils/run-locally/src/solana.rs index 1d2ccea623..94dbd926f5 100644 --- a/rust/utils/run-locally/src/solana.rs +++ b/rust/utils/run-locally/src/solana.rs @@ -282,10 +282,10 @@ pub fn start_solana_test_validator( } #[apply(as_task)] -pub fn _initiate_solana_hyperlane_transfer( +pub fn initiate_solana_hyperlane_transfer( solana_cli_tools_path: PathBuf, solana_config_path: PathBuf, -) { +) -> String { let sender = Program::new(concat_path(&solana_cli_tools_path, "solana")) .arg("config", solana_config_path.to_str().unwrap()) .arg("keypair", SOLANA_KEYPAIR) @@ -309,21 +309,20 @@ pub fn _initiate_solana_hyperlane_transfer( .run_with_output() .join(); - let message_id = _get_message_id_from_logs(output); - if let Some(message_id) = message_id { - sealevel_client(&solana_cli_tools_path, &solana_config_path) - .cmd("igp") - .cmd("pay-for-gas") - .arg("program-id", "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U") - .arg("message-id", message_id) - .arg("destination-domain", SOLANA_REMOTE_CHAIN_ID) - .arg("gas", "100000") - .run() - .join(); - } + let message_id = get_message_id_from_logs(output).expect("failed to get message id from logs"); + sealevel_client(&solana_cli_tools_path, &solana_config_path) + .cmd("igp") + .cmd("pay-for-gas") + .arg("program-id", "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U") + .arg("message-id", message_id.clone()) + .arg("destination-domain", SOLANA_REMOTE_CHAIN_ID) + .arg("gas", "100000") + .run() + .join(); + message_id } -fn _get_message_id_from_logs(logs: Vec) -> Option { +fn get_message_id_from_logs(logs: Vec) -> Option { let message_id_regex = Regex::new(r"Dispatched message to \d+, ID 0x([0-9a-fA-F]+)").unwrap(); for log in logs { // Use the regular expression to capture the ID @@ -337,7 +336,7 @@ fn _get_message_id_from_logs(logs: Vec) -> Option { None } -pub fn _solana_termination_invariants_met( +pub fn solana_termination_invariants_met( solana_cli_tools_path: &Path, solana_config_path: &Path, ) -> bool { @@ -349,7 +348,7 @@ pub fn _solana_termination_invariants_met( // This value was gotten by observing the relayer logs. // TODO: get the actual message-id so we don't have to hardcode it "message-id", - "0x7b8ba684e5ce44f898c5fa81785c83a00e32b5bef3412e648eb7a17bec497685", + "0x89c76191bd40b1858b7957e35bf3455122826e4737c5540b9dc5a555370d78c5", ) .arg("program-id", "9tCUWNjpqcf3NUSrtp7vquYVCwbEByvLjZUrhG5dgvhj") .run_with_output() diff --git a/solidity/.gitignore b/solidity/.gitignore index 11e2c7c8d1..c3e1139887 100644 --- a/solidity/.gitignore +++ b/solidity/.gitignore @@ -5,6 +5,7 @@ types/ dist/ coverage/ coverage.json +storage/ .env *lcov.info # Foundry @@ -12,3 +13,4 @@ out forge-cache docs flattened/ +buildArtifact.json diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index 0ec7753074..d0461163e6 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,70 @@ # @hyperlane-xyz/core +## 3.7.0 + +### Patch Changes + +- @hyperlane-xyz/utils@3.7.0 + +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/utils@3.6.2 + +## 3.6.1 + +### Patch Changes + +- e4e4f93fc: Support pausable ISM in deployer and checker +- Updated dependencies [3c298d064] +- Updated dependencies [df24eec8b] +- Updated dependencies [78e50e7da] + - @hyperlane-xyz/utils@3.6.1 + +## 3.6.0 + +### Patch Changes + +- @hyperlane-xyz/utils@3.6.0 + +## 3.5.1 + +### Patch Changes + +- @hyperlane-xyz/utils@3.5.1 + +## 3.5.0 + +### Patch Changes + +- @hyperlane-xyz/utils@3.5.0 + +## 3.4.0 + +### Patch Changes + +- e06fe0b32: Supporting DefaultFallbackRoutingIsm through non-factory deployments +- Updated dependencies [fd4fc1898] + - @hyperlane-xyz/utils@3.4.0 + +## 3.3.0 + +### Patch Changes + +- 350175581: Rename StaticProtocolFee hook to ProtocolFee for clarity + - @hyperlane-xyz/utils@3.3.0 + +## 3.2.0 + +### Minor Changes + +- df34198d4: Includes storage gap in Mailbox Client for forwards compatibility + +### Patch Changes + +- @hyperlane-xyz/utils@3.2.0 + ## 3.1.10 ### Patch Changes diff --git a/solidity/contracts/client/MailboxClient.sol b/solidity/contracts/client/MailboxClient.sol index ccbcf41189..d4f1d1c78e 100644 --- a/solidity/contracts/client/MailboxClient.sol +++ b/solidity/contracts/client/MailboxClient.sol @@ -22,6 +22,8 @@ abstract contract MailboxClient is OwnableUpgradeable { IInterchainSecurityModule public interchainSecurityModule; + uint256[48] private __GAP; // gap for upgrade safety + // ============ Modifiers ============ modifier onlyContract(address _contract) { require( diff --git a/solidity/contracts/hooks/StaticProtocolFee.sol b/solidity/contracts/hooks/ProtocolFee.sol similarity index 92% rename from solidity/contracts/hooks/StaticProtocolFee.sol rename to solidity/contracts/hooks/ProtocolFee.sol index ffd44bcdd9..4e1f470482 100644 --- a/solidity/contracts/hooks/StaticProtocolFee.sol +++ b/solidity/contracts/hooks/ProtocolFee.sol @@ -24,10 +24,10 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /** - * @title StaticProtocolFee + * @title ProtocolFee * @notice Collects a static protocol fee from the sender. */ -contract StaticProtocolFee is AbstractPostDispatchHook, Ownable { +contract ProtocolFee is AbstractPostDispatchHook, Ownable { using StandardHookMetadata for bytes; using Address for address payable; using Message for bytes; @@ -97,7 +97,7 @@ contract StaticProtocolFee is AbstractPostDispatchHook, Ownable { ) internal override { require( msg.value >= protocolFee, - "StaticProtocolFee: insufficient protocol fee" + "ProtocolFee: insufficient protocol fee" ); uint256 refund = msg.value - protocolFee; @@ -123,7 +123,7 @@ contract StaticProtocolFee is AbstractPostDispatchHook, Ownable { function _setProtocolFee(uint256 _protocolFee) internal { require( _protocolFee <= MAX_PROTOCOL_FEE, - "StaticProtocolFee: exceeds max protocol fee" + "ProtocolFee: exceeds max protocol fee" ); protocolFee = _protocolFee; } @@ -133,10 +133,7 @@ contract StaticProtocolFee is AbstractPostDispatchHook, Ownable { * @param _beneficiary The new beneficiary. */ function _setBeneficiary(address _beneficiary) internal { - require( - _beneficiary != address(0), - "StaticProtocolFee: invalid beneficiary" - ); + require(_beneficiary != address(0), "ProtocolFee: invalid beneficiary"); beneficiary = _beneficiary; } } diff --git a/solidity/contracts/hooks/libs/StandardHookMetadata.sol b/solidity/contracts/hooks/libs/StandardHookMetadata.sol index be2b889a54..e12f6822ad 100644 --- a/solidity/contracts/hooks/libs/StandardHookMetadata.sol +++ b/solidity/contracts/hooks/libs/StandardHookMetadata.sol @@ -98,9 +98,9 @@ library StandardHookMetadata { } /** - * @notice Returns the specified refund address for the message. + * @notice Returns any custom metadata. * @param _metadata ABI encoded standard hook metadata. - * @return Refund address for the message as address. + * @return Custom metadata. */ function getCustomMetadata( bytes calldata _metadata diff --git a/solidity/contracts/isms/PausableIsm.sol b/solidity/contracts/isms/PausableIsm.sol index d2c4784f38..cbe696d2e4 100644 --- a/solidity/contracts/isms/PausableIsm.sol +++ b/solidity/contracts/isms/PausableIsm.sol @@ -11,6 +11,10 @@ import {IInterchainSecurityModule} from "../interfaces/IInterchainSecurityModule contract PausableIsm is IInterchainSecurityModule, Ownable, Pausable { uint8 public constant override moduleType = uint8(Types.NULL); + constructor(address owner) Ownable() Pausable() { + _transferOwnership(owner); + } + /** * @inheritdoc IInterchainSecurityModule * @dev Reverts when paused, otherwise returns `true`. diff --git a/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol b/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol index d0681723e3..fc8b203834 100644 --- a/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol +++ b/solidity/contracts/isms/routing/DomainRoutingIsmFactory.sol @@ -18,10 +18,12 @@ abstract contract AbstractDomainRoutingIsmFactory { /** * @notice Deploys and initializes a DomainRoutingIsm using a minimal proxy + * @param _owner The owner to set on the ISM * @param _domains The origin domains * @param _modules The ISMs to use to verify messages */ function deploy( + address _owner, uint32[] calldata _domains, IInterchainSecurityModule[] calldata _modules ) external returns (DomainRoutingIsm) { @@ -29,7 +31,7 @@ abstract contract AbstractDomainRoutingIsmFactory { MinimalProxy.create(implementation()) ); emit ModuleDeployed(_ism); - _ism.initialize(msg.sender, _domains, _modules); + _ism.initialize(_owner, _domains, _modules); return _ism; } @@ -51,19 +53,3 @@ contract DomainRoutingIsmFactory is AbstractDomainRoutingIsmFactory { return _implementation; } } - -/** - * @title DefaultFallbackRoutingIsmFactory - */ -contract DefaultFallbackRoutingIsmFactory is AbstractDomainRoutingIsmFactory { - // ============ Immutables ============ - address internal immutable _implementation; - - constructor(address mailbox) { - _implementation = address(new DefaultFallbackRoutingIsm(mailbox)); - } - - function implementation() public view override returns (address) { - return _implementation; - } -} diff --git a/solidity/contracts/token/FastHypERC20.sol b/solidity/contracts/token/FastHypERC20.sol deleted file mode 100644 index 9ec040809a..0000000000 --- a/solidity/contracts/token/FastHypERC20.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity >=0.8.0; - -import {HypERC20} from "./HypERC20.sol"; - -import {TokenRouter} from "./libs/TokenRouter.sol"; -import {FastTokenRouter} from "./libs/FastTokenRouter.sol"; -import {TokenMessage} from "./libs/TokenMessage.sol"; - -import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; - -/** - * @title Hyperlane ERC20 Token Router that extends ERC20 with remote transfer functionality. - * @author Abacus Works - * @dev Supply on each chain is not constant but the aggregate supply across all chains is. - */ -contract FastHypERC20 is FastTokenRouter, HypERC20 { - constructor( - uint8 __decimals, - address _mailbox - ) HypERC20(__decimals, _mailbox) {} - - /** - * @dev delegates transfer logic to `_transferTo`. - * @inheritdoc TokenRouter - */ - function _handle( - uint32 _origin, - bytes32 _sender, - bytes calldata _message - ) internal virtual override(FastTokenRouter, TokenRouter) { - FastTokenRouter._handle(_origin, _sender, _message); - } - - /** - * @dev Mints `_amount` of tokens to `_recipient`. - * @inheritdoc FastTokenRouter - */ - function _fastTransferTo( - address _recipient, - uint256 _amount - ) internal override { - _mint(_recipient, _amount); - } - - /** - * @dev Burns `_amount` of tokens from `_recipient`. - * @inheritdoc FastTokenRouter - */ - function _fastRecieveFrom( - address _sender, - uint256 _amount - ) internal override { - _burn(_sender, _amount); - } - - function balanceOf( - address _account - ) public view virtual override(TokenRouter, HypERC20) returns (uint256) { - return HypERC20.balanceOf(_account); - } -} diff --git a/solidity/contracts/token/FastHypERC20Collateral.sol b/solidity/contracts/token/FastHypERC20Collateral.sol deleted file mode 100644 index 434555c17a..0000000000 --- a/solidity/contracts/token/FastHypERC20Collateral.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity >=0.8.0; - -import {HypERC20Collateral} from "./HypERC20Collateral.sol"; -import {TokenRouter} from "./libs/TokenRouter.sol"; -import {FastTokenRouter} from "./libs/FastTokenRouter.sol"; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -/** - * @title Hyperlane ERC20 Token Collateral that wraps an existing ERC20 with remote transfer functionality. - * @author Abacus Works - */ -contract FastHypERC20Collateral is FastTokenRouter, HypERC20Collateral { - using SafeERC20 for IERC20; - - /** - * @notice Constructor - * @param erc20 Address of the token to keep as collateral - * @param _mailbox Address of the mailbox address - */ - constructor( - address erc20, - address _mailbox - ) HypERC20Collateral(erc20, _mailbox) {} - - /** - * @dev delegates transfer logic to `_transferTo`. - * @inheritdoc FastTokenRouter - */ - function _handle( - uint32 _origin, - bytes32 _sender, - bytes calldata _message - ) internal virtual override(FastTokenRouter, TokenRouter) { - FastTokenRouter._handle(_origin, _sender, _message); - } - - /** - * @dev Transfers `_amount` of `wrappedToken` to `_recipient`. - * @inheritdoc FastTokenRouter - */ - function _fastTransferTo( - address _recipient, - uint256 _amount - ) internal override { - wrappedToken.safeTransfer(_recipient, _amount); - } - - /** - * @dev Transfers in `_amount` of `wrappedToken` from `_recipient`. - * @inheritdoc FastTokenRouter - */ - function _fastRecieveFrom( - address _sender, - uint256 _amount - ) internal override { - wrappedToken.safeTransferFrom(_sender, address(this), _amount); - } -} diff --git a/solidity/contracts/token/README.md b/solidity/contracts/token/README.md index ef7e12efba..3d8c900820 100644 --- a/solidity/contracts/token/README.md +++ b/solidity/contracts/token/README.md @@ -1,8 +1,8 @@ # Hyperlane Tokens and Warp Routes -This repo contains contracts and SDK tooling for Hyperlane-connected ERC20 and ERC721 tokens. The contracts herein can be used to create [Hyperlane Warp Routes](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route) across different chains. +This repo contains contracts and SDK tooling for Hyperlane-connected ERC20 and ERC721 tokens. The contracts herein can be used to create [Hyperlane Warp Routes](https://docs.hyperlane.xyz/docs/reference/applications/warp-routes) across different chains. -For instructions on deploying Warp Routes, see [the deployment documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route/deploy-a-warp-route) and the [Hyperlane-Deploy repository](https://github.com/hyperlane-xyz/hyperlane-deploy). +For instructions on deploying Warp Routes, see [the deployment documentation](https://docs.hyperlane.xyz/docs/deploy-hyperlane#deploy-a-warp-route) and the [Hyperlane CLI](https://www.npmjs.com/package/@hyperlane-xyz/cli). ## Warp Route Architecture @@ -51,7 +51,7 @@ The Token Router contract comes in several flavors and a warp route can be compo ## Interchain Security Models -Warp routes are unique amongst token bridging solutions because they provide modular security. Because the `TokenRouter` implements the `IMessageRecipient` interface, it can be configured with a custom interchain security module. Please refer to the relevant guide to specifying interchain security modules on the [Messaging API receive docs](https://docs.hyperlane.xyz/docs/apis/messaging-api/receive#interchain-security-modules). +Warp routes are unique amongst token bridging solutions because they provide modular security. Because the `TokenRouter` implements the `IMessageRecipient` interface, it can be configured with a custom interchain security module. Please refer to the relevant guide to specifying interchain security modules on the [Messaging API receive docs](https://docs.hyperlane.xyz/docs/reference/messaging/messaging-interface). ## Remote Transfer Lifecycle Diagrams @@ -67,7 +67,7 @@ interface TokenRouter { } ``` -**NOTE:** The [Relayer](https://docs.hyperlane.xyz/docs/protocol/agents/relayer) shown below must be compensated. Please refer to the relevant guide on [paying for interchain gas](https://docs.hyperlane.xyz/docs/build-with-hyperlane/guides/paying-for-interchain-gas) on the `messageID` returned from the `transferRemote` call. +**NOTE:** The [Relayer](https://docs.hyperlane.xyz/docs/operate/relayer/run-relayer) shown below must be compensated. Please refer to the details on [paying for interchain gas](https://docs.hyperlane.xyz/docs/protocol/interchain-gas-payment). Depending on the flavor of TokenRouter on the source and destination chain, this flow looks slightly different. The following diagrams illustrate these differences. @@ -227,26 +227,6 @@ graph TB | [audit-v2-remediation]() | 2023-02-15 | Hyperlane V2 Audit remediation | | [main]() | ~ | Bleeding edge | -## Setup for local development - -```sh -# Install dependencies -yarn - -# Build source and generate types -yarn build:dev -``` - -## Unit testing - -```sh -# Run all unit tests -yarn test - -# Lint check code -yarn lint -``` - ## Learn more -For more information, see the [Hyperlane introduction documentation](https://docs.hyperlane.xyz/docs/introduction/readme) or the [details about Warp Routes](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route). +For more information, see the [Hyperlane introduction documentation](https://docs.hyperlane.xyz/docs/intro). diff --git a/solidity/coverage.sh b/solidity/coverage.sh index ba44371136..567dd7f044 100755 --- a/solidity/coverage.sh +++ b/solidity/coverage.sh @@ -1,5 +1,7 @@ +#!/bin/bash + # generates lcov.info -forge coverage --report lcov +forge coverage --report lcov --no-match-test testFork if ! command -v lcov &>/dev/null; then echo "lcov is not installed. Installing..." diff --git a/solidity/exportBuildArtifact.sh b/solidity/exportBuildArtifact.sh new file mode 100755 index 0000000000..f7f64fca3f --- /dev/null +++ b/solidity/exportBuildArtifact.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# set script location as working directory +cd "$(dirname "$0")" + +# Define the artifacts directory +artifactsDir="./artifacts/build-info" +# Define the output file +outputFile="./buildArtifact.json" + +# log that we're in the script +echo 'Finding and processing hardhat build artifact...' + +# Find most recently modified JSON build artifact +if [[ $OSTYPE == 'darwin'* ]]; then + # for local flow + jsonFiles=$(find "$artifactsDir" -type f -name "*.json" -exec stat -f "%m %N" {} \; | sort -rn | head -n 1 | cut -d' ' -f2-) +else + # for CI flow + jsonFiles=$(find "$artifactsDir" -type f -name "*.json" -exec stat -c "%Y %n" {} \; | sort -rn | head -n 1 | cut -d' ' -f2-) +fi + +if [[ ! -f "$jsonFiles" ]]; then + echo 'Failed to find build artifact' + exit 1 +fi + +# Extract required keys and write to outputFile +if jq -c '{input, solcLongVersion}' "$jsonFiles" > "$outputFile"; then + echo 'Finished processing build artifact.' +else + echo 'Failed to process build artifact with jq' + exit 1 +fi diff --git a/solidity/flatten.sh b/solidity/flatten.sh deleted file mode 100755 index 21c6a09788..0000000000 --- a/solidity/flatten.sh +++ /dev/null @@ -1,23 +0,0 @@ -LICENSE="// SPDX-License-Identifier: MIT OR Apache-2.0" - -rm -rf flattened -mkdir -p flattened - -# flatten contracts -yarn hardhat flatten > flattened/flattened.sol - -# remove duplicate licenses -grep -vE "// SPDX.*" flattened/flattened.sol > flattened/delicensed.sol - -# add license -echo "$LICENSE" | cat - flattened/delicensed.sol > flattened/licensed.sol - -# compile -solc flattened/licensed.sol - -# TODO: automate this? -if [ $? -ne 0 ]; then - echo "Remove @openzeppelin/../ICrossDomainMessenger and replace Optimism_Bridge with ICrossDomainMessenger" - echo "Then try compiling again with solc flattened/licensed.sol" - exit 1 -fi diff --git a/solidity/foundry.toml b/solidity/foundry.toml index 111db4f931..75766fc7d7 100644 --- a/solidity/foundry.toml +++ b/solidity/foundry.toml @@ -13,5 +13,5 @@ optimizer_runs = 999_999 verbosity = 4 [rpc_endpoints] -mainnet = "https://rpc.ankr.com/eth" -optimism = "https://rpc.ankr.com/optimism" +mainnet = "https://eth.merkle.io" +optimism = "https://mainnet.optimism.io " diff --git a/solidity/package.json b/solidity/package.json index ccde9512ba..4ba4201d31 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "3.1.10", + "version": "3.7.0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.1.10", + "@hyperlane-xyz/utils": "3.7.0", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3" }, @@ -31,6 +31,7 @@ "test": "test" }, "files": [ + "/buildArtifact.json", "/dist", "/contracts", "/interfaces", @@ -45,14 +46,15 @@ "main": "dist/index.js", "repository": "https://github.com/hyperlane-xyz/hyperlane-monorepo", "scripts": { - "build": "hardhat compile && tsc", + "build": "hardhat compile && ./exportBuildArtifact.sh && tsc", "lint": "solhint contracts/**/*.sol", "clean": "hardhat clean && rm -rf ./dist ./cache ./types ./coverage", "coverage": "./coverage.sh", "docs": "forge doc", - "flatten": "./flatten.sh", + "storage": "./storage.sh", "prettier": "prettier --write ./contracts ./test", "test": "hardhat test && forge test -vvv", + "test:ci": "hardhat test && forge test --no-match-test testFork -vvv", "gas": "forge snapshot", "gas-ci": "yarn gas --check --tolerance 2 || (echo 'Manually update gas snapshot' && exit 1)", "slither": "slither ." diff --git a/solidity/storage.sh b/solidity/storage.sh new file mode 100755 index 0000000000..7662e252fb --- /dev/null +++ b/solidity/storage.sh @@ -0,0 +1,21 @@ +#!/bin/bash +OUTPUT_PATH=${1:-storage} +EXCLUDE="test|mock|interfaces|libs|upgrade|README|Abstract|Static" + +IFS=$'\n' +CONTRACT_FILES=($(find ./contracts -type f)) +unset IFS + +echo "Generating layouts in $OUTPUT_PATH" +mkdir -p $OUTPUT_PATH + +for file in "${CONTRACT_FILES[@]}"; +do + if [[ $file =~ .*($EXCLUDE).* ]]; then + continue + fi + + contract=$(basename "$file" .sol) + echo "Generating storage layout of $contract" + forge inspect "$contract" storage --pretty > "$OUTPUT_PATH/$contract.md" +done diff --git a/solidity/test/hooks/StaticProtocolFee.t.sol b/solidity/test/hooks/ProtocolFee.t.sol similarity index 93% rename from solidity/test/hooks/StaticProtocolFee.t.sol rename to solidity/test/hooks/ProtocolFee.t.sol index a110c8304f..df0d3471c0 100644 --- a/solidity/test/hooks/StaticProtocolFee.t.sol +++ b/solidity/test/hooks/ProtocolFee.t.sol @@ -7,12 +7,12 @@ import {MessageUtils} from "../isms/IsmTestUtils.sol"; import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol"; import {IPostDispatchHook} from "../../contracts/interfaces/hooks/IPostDispatchHook.sol"; -import {StaticProtocolFee} from "../../contracts/hooks/StaticProtocolFee.sol"; +import {ProtocolFee} from "../../contracts/hooks/ProtocolFee.sol"; -contract StaticProtocolFeeTest is Test { +contract ProtocolFeeTest is Test { using TypeCasts for address; - StaticProtocolFee internal fees; + ProtocolFee internal fees; address internal alice = address(0x1); // alice the user address internal bob = address(0x2); // bob the beneficiary @@ -27,7 +27,7 @@ contract StaticProtocolFeeTest is Test { bytes internal testMessage; function setUp() public { - fees = new StaticProtocolFee(MAX_FEE, FEE, bob, address(this)); + fees = new ProtocolFee(MAX_FEE, FEE, bob, address(this)); testMessage = _encodeTestMessage(); } @@ -63,7 +63,7 @@ contract StaticProtocolFeeTest is Test { 10 * fees.MAX_PROTOCOL_FEE() ); - vm.expectRevert("StaticProtocolFee: exceeds max protocol fee"); + vm.expectRevert("ProtocolFee: exceeds max protocol fee"); fees.setProtocolFee(fee); assertEq(fees.protocolFee(), FEE); @@ -95,7 +95,7 @@ contract StaticProtocolFeeTest is Test { uint256 balanceBefore = alice.balance; vm.prank(alice); - vm.expectRevert("StaticProtocolFee: insufficient protocol fee"); + vm.expectRevert("ProtocolFee: insufficient protocol fee"); fees.postDispatch{value: feeSent}("", ""); assertEq(alice.balance, balanceBefore); diff --git a/solidity/test/isms/DomainRoutingIsm.t.sol b/solidity/test/isms/DomainRoutingIsm.t.sol index 58166d0647..6042791097 100644 --- a/solidity/test/isms/DomainRoutingIsm.t.sol +++ b/solidity/test/isms/DomainRoutingIsm.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import {DomainRoutingIsm} from "../../contracts/isms/routing/DomainRoutingIsm.sol"; import {DefaultFallbackRoutingIsm} from "../../contracts/isms/routing/DefaultFallbackRoutingIsm.sol"; -import {DefaultFallbackRoutingIsmFactory, DomainRoutingIsmFactory} from "../../contracts/isms/routing/DomainRoutingIsmFactory.sol"; +import {DomainRoutingIsmFactory} from "../../contracts/isms/routing/DomainRoutingIsmFactory.sol"; import {IInterchainSecurityModule} from "../../contracts/interfaces/IInterchainSecurityModule.sol"; import {MessageUtils, TestIsm} from "./IsmTestUtils.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; @@ -56,7 +56,7 @@ contract DomainRoutingIsmTest is Test { _domains[i] = domain - i; _isms[i] = deployTestIsm(bytes32(0)); } - ism = factory.deploy(_domains, _isms); + ism = factory.deploy(address(this), _domains, _isms); for (uint256 i = 0; i < count; ++i) { assertEq(address(ism.module(_domains[i])), address(_isms[i])); } diff --git a/solidity/test/isms/OPStackIsm.t.sol b/solidity/test/isms/OPStackIsm.t.sol index d07ce02ca9..3066d7bda7 100644 --- a/solidity/test/isms/OPStackIsm.t.sol +++ b/solidity/test/isms/OPStackIsm.t.sol @@ -75,8 +75,8 @@ contract OPStackIsmTest is Test { function setUp() public { // block numbers to fork from, chain data is cached to ../../forge-cache/ - mainnetFork = vm.createFork(vm.rpcUrl("mainnet"), 17_586_909); - optimismFork = vm.createFork(vm.rpcUrl("optimism"), 106_233_774); + mainnetFork = vm.createFork(vm.rpcUrl("mainnet"), 18_992_500); + optimismFork = vm.createFork(vm.rpcUrl("optimism"), 114_696_811); testRecipient = new TestRecipient(); diff --git a/solidity/test/isms/PausableIsm.t.sol b/solidity/test/isms/PausableIsm.t.sol index 74a2bfdd7a..c31511e296 100644 --- a/solidity/test/isms/PausableIsm.t.sol +++ b/solidity/test/isms/PausableIsm.t.sol @@ -8,24 +8,35 @@ import {PausableIsm} from "../../contracts/isms/PausableIsm.sol"; contract PausableIsmTest is Test { PausableIsm ism; + address owner; + function setUp() public { - ism = new PausableIsm(); + owner = msg.sender; + ism = new PausableIsm(owner); } function test_verify() public { assertTrue(ism.verify("", "")); + vm.prank(owner); ism.pause(); vm.expectRevert(bytes("Pausable: paused")); ism.verify("", ""); } function test_pause() public { + vm.expectRevert(bytes("Ownable: caller is not the owner")); + ism.pause(); + vm.prank(owner); ism.pause(); assertTrue(ism.paused()); } function test_unpause() public { + vm.prank(owner); ism.pause(); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + ism.unpause(); + vm.prank(owner); ism.unpause(); assertFalse(ism.paused()); } diff --git a/solidity/test/message.test.ts b/solidity/test/message.test.ts index f39cb6affc..bdee259fb0 100644 --- a/solidity/test/message.test.ts +++ b/solidity/test/message.test.ts @@ -8,21 +8,26 @@ import { } from '@hyperlane-xyz/utils'; import testCases from '../../vectors/message.json'; -import { TestMessage, TestMessage__factory } from '../types'; +import { Mailbox__factory, TestMessage, TestMessage__factory } from '../types'; const remoteDomain = 1000; const localDomain = 2000; -const version = 0; const nonce = 11; describe('Message', async () => { let messageLib: TestMessage; + let version: number; before(async () => { const [signer] = await ethers.getSigners(); const Message = new TestMessage__factory(signer); messageLib = await Message.deploy(); + + // For consistency with the Mailbox version + const Mailbox = new Mailbox__factory(signer); + const mailbox = await Mailbox.deploy(localDomain); + version = await mailbox.VERSION(); }); it('Returns fields from a message', async () => { diff --git a/solidity/test/test/TestSendReceiver.t.sol b/solidity/test/test/TestSendReceiver.t.sol new file mode 100644 index 0000000000..4ca8cf666b --- /dev/null +++ b/solidity/test/test/TestSendReceiver.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; +import {TestSendReceiver} from "../../contracts/test/TestSendReceiver.sol"; +import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; +import {TestIsm} from "../../contracts/test/TestIsm.sol"; +import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol"; +import {TestMerkleTreeHook} from "../../contracts/test/TestMerkleTreeHook.sol"; + +contract TestSendReceiverTest is Test { + using TypeCasts for address; + + uint32 internal constant TEST_ORIGIN_DOMAIN = 1; + uint32 internal constant TEST_DESTINATION_DOMAIN = 2; + bytes internal constant TEST_MESSAGE_CONTENT = bytes("Bonjour"); + TestSendReceiver internal testSendReceiver; + TestInterchainGasPaymaster internal igp; + TestMailbox internal mailbox; + uint256 internal gasPayment; + bytes internal testMessage; + + function setUp() public { + mailbox = new TestMailbox(TEST_ORIGIN_DOMAIN); + TestIsm ism = new TestIsm(); + igp = new TestInterchainGasPaymaster(); + TestMerkleTreeHook requiredHook = new TestMerkleTreeHook( + address(mailbox) + ); + mailbox.initialize( + address(this), + address(ism), + address(igp), + address(requiredHook) + ); + testSendReceiver = new TestSendReceiver(); + + gasPayment = mailbox.quoteDispatch( + TEST_DESTINATION_DOMAIN, + address(testSendReceiver).addressToBytes32(), + TEST_MESSAGE_CONTENT + ); + testMessage = mailbox.buildOutboundMessage( + TEST_DESTINATION_DOMAIN, + address(testSendReceiver).addressToBytes32(), + TEST_MESSAGE_CONTENT + ); + } + + event Dispatch( + address indexed sender, + uint32 indexed destination, + bytes32 indexed recipient, + bytes message + ); + + function testDispatchToSelf() public { + vm.expectEmit(true, true, true, false, address(mailbox)); + // sender address is the testSendReceiver which doesn't match sender + // in event (not in scope for test here - tested in Mailbox.t.sol) + emit Dispatch( + address(testSendReceiver), + TEST_DESTINATION_DOMAIN, + address(testSendReceiver).addressToBytes32(), + testMessage + ); + testSendReceiver.dispatchToSelf{value: gasPayment}( + mailbox, + TEST_DESTINATION_DOMAIN, + TEST_MESSAGE_CONTENT + ); + } + + function testDispatchToSelf_withHook() public { + vm.expectEmit(true, true, true, false, address(mailbox)); + // sender address is the testSendReceiver which doesn't match sender + // in event (not in scope for test here - tested in Mailbox.t.sol) + emit Dispatch( + address(testSendReceiver), + TEST_DESTINATION_DOMAIN, + address(testSendReceiver).addressToBytes32(), + testMessage + ); + testSendReceiver.dispatchToSelf{value: gasPayment}( + mailbox, + TEST_DESTINATION_DOMAIN, + TEST_MESSAGE_CONTENT, + igp + ); + } + + event Handled(bytes32 blockhash); + + function testHandle(uint256 blockNumber) public { + vm.assume(blockNumber > 0); + vm.roll(blockNumber); + + // blockhash(n) = n for forge tests + // previousBlockHash() = blockhash(n-1) = n-1 + if (blockNumber % 16 == 1) { + vm.expectRevert("block hash ends in 0"); // blockhash(n-1) ends in 0 + } else { + vm.expectEmit(true, true, true, false, address(testSendReceiver)); // Process + emit Handled(bytes32(blockNumber - 1)); + } + testSendReceiver.handle( + 0, + address(testSendReceiver).addressToBytes32(), + "0x1234" + ); + } +} diff --git a/solidity/test/testSendReceiver.test.ts b/solidity/test/testSendReceiver.test.ts deleted file mode 100644 index 527b968596..0000000000 --- a/solidity/test/testSendReceiver.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -import { addressToBytes32 } from '@hyperlane-xyz/utils'; - -import { TestSendReceiver__factory } from '../types'; - -describe('TestSendReceiver', () => { - it('randomly handles a message', async () => { - const [signer] = await ethers.getSigners(); - const signerAddress = await signer.getAddress(); - const recipientFactory = new TestSendReceiver__factory(signer); - const recipient = await recipientFactory.deploy(); - - // Didn't know how else to test the randomness - let successes = 0; - let failures = 0; - for (let i = 0; i < 100; i++) { - try { - // "Inject randomness" - await signer.sendTransaction({ - from: signerAddress, - to: signerAddress, - value: 1, - }); - await recipient.handle( - 0, - addressToBytes32(recipient.address), - '0x1234', - ); - successes += 1; - } catch (error) { - failures += 1; - } - } - - expect(successes).to.be.greaterThan(5); - expect(failures).to.be.greaterThan(1); - }); -}); diff --git a/solidity/tsconfig.json b/solidity/tsconfig.json index 3a0356c92d..d5353f6c3b 100644 --- a/solidity/tsconfig.json +++ b/solidity/tsconfig.json @@ -5,4 +5,4 @@ }, "exclude": ["./test", "hardhat.config.ts", "./dist"], "extends": "../tsconfig.json" -} \ No newline at end of file +} diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 0725063dc1..a1b271d0fd 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,126 @@ # @hyperlane-xyz/cli +## 3.7.0 + +### Minor Changes + +- 84e508039: Improve send transfer ergonomics by omitting token type flag +- 7ff826a8f: Merged agent addresses will now include igp as the zero address if not configured as the hook + +### Patch Changes + +- ab17af5f7: Updating HyperlaneIgpDeployer to configure storage gas oracles as part of deployment +- Updated dependencies [6f464eaed] +- Updated dependencies [87151c62b] +- Updated dependencies [ab17af5f7] +- Updated dependencies [7b40232af] +- Updated dependencies [54aeb6420] + - @hyperlane-xyz/sdk@3.7.0 + - @hyperlane-xyz/utils@3.7.0 + +## 3.6.2 + +### Patch Changes + +- 99fe93a5b: Removed IGP from preset hook config + - @hyperlane-xyz/sdk@3.6.2 + - @hyperlane-xyz/utils@3.6.2 + +## 3.6.1 + +### Patch Changes + +- Updated dependencies [3c298d064] +- Updated dependencies [ae4476ad0] +- Updated dependencies [f3b7ddb69] +- Updated dependencies [df24eec8b] +- Updated dependencies [78e50e7da] +- Updated dependencies [e4e4f93fc] + - @hyperlane-xyz/utils@3.6.1 + - @hyperlane-xyz/sdk@3.6.1 + +## 3.6.0 + +### Patch Changes + +- 67a6d971e: Added `shouldRecover` flag to deployContractFromFactory so that the `TestRecipientDeployer` can deploy new contracts if it's not the owner of the prior deployments (We were recovering the SDK artifacts which meant the deployer won't be able to set the ISM as they needed) +- Updated dependencies [67a6d971e] +- Updated dependencies [612d4163a] +- Updated dependencies [0488ef31d] +- Updated dependencies [8d8ba3f7a] + - @hyperlane-xyz/sdk@3.6.0 + - @hyperlane-xyz/utils@3.6.0 + +## 3.5.1 + +### Patch Changes + +- Updated dependencies [a04454d6d] + - @hyperlane-xyz/sdk@3.5.1 + - @hyperlane-xyz/utils@3.5.1 + +## 3.5.0 + +### Patch Changes + +- 05a943b4a: Skip mandatory balance check for remotes in send commands" +- Updated dependencies [655b6a0cd] +- Updated dependencies [08ba0d32b] +- Updated dependencies [f7d285e3a] + - @hyperlane-xyz/sdk@3.5.0 + - @hyperlane-xyz/utils@3.5.0 + +## 3.4.0 + +### Patch Changes + +- e06fe0b32: Supporting DefaultFallbackRoutingIsm through non-factory deployments +- dcf8b800a: Fixes for commands with --yes flag +- 9c7dbcb94: Remove domainId and protocolType setting when creating chain config +- Updated dependencies [7919417ec] +- Updated dependencies [fd4fc1898] +- Updated dependencies [e06fe0b32] +- Updated dependencies [b832e57ae] +- Updated dependencies [79c96d718] + - @hyperlane-xyz/sdk@3.4.0 + - @hyperlane-xyz/utils@3.4.0 + +## 3.3.0 + +### Minor Changes + +- 7e620c9df: Allow CLI to accept hook as a config + +### Patch Changes + +- f44589e45: Improve warp and kurtosis deploy command UX +- 2da6ccebe: Allow users to only configure validators for their chain + + - Don't restrict user to having two chains for ism config + - If the user accidentally picks two chains, we prompt them again to confirm if they don't want to use the hyperlane validators for their multisigConfig + +- 9f2c7ce7c: Removing agentStartBlocks and using mailbox.deployedBlock() instead +- 9705079f9: Improve UX of the send and status commands +- c606b6a48: Add figlet to CLI +- Updated dependencies [7e620c9df] +- Updated dependencies [350175581] +- Updated dependencies [9f2c7ce7c] + - @hyperlane-xyz/sdk@3.3.0 + - @hyperlane-xyz/utils@3.3.0 + +## 3.2.0 + +### Minor Changes + +- df693708b: Add support for all ISM types in CLI interactive config creation + +### Patch Changes + +- 433c5aadb: Fix error form version command +- Updated dependencies [df693708b] + - @hyperlane-xyz/sdk@3.2.0 + - @hyperlane-xyz/utils@3.2.0 + ## 3.1.10 ### Patch Changes diff --git a/typescript/cli/ci-test-docker.sh b/typescript/cli/ci-test-docker.sh deleted file mode 100755 index 416f3847ef..0000000000 --- a/typescript/cli/ci-test-docker.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/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 -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 --chain-id 31338 -p 8555 --state /tmp/anvil2/state > /dev/null & -sleep 1 - -set -e - -echo "{}" > /tmp/empty-artifacts.json - -export DEBUG=hyperlane:* - -echo "Deploying contracts to anvil1 and anvil2" -yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ - --targets anvil1,anvil2 \ - --chains ./examples/anvil-chains.yaml \ - --artifacts /tmp/empty-artifacts.json \ - --ism ./examples/multisig-ism.yaml \ - --hook ./examples/hook-config.yaml \ - --out /tmp \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - --yes - -CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` -echo "Core artifacts:" -echo $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_PATH \ - --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_PATH \ - --quick \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - | tee /tmp/message1 - -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` -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_PATH \ - --router $ANVIL1_ROUTER \ - --type native \ - --quick \ - --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - | tee /tmp/message2 - -MESSAGE2_ID=`cat /tmp/message2 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` -echo "Message 2 ID: $MESSAGE2_ID" - - -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" - docker run \ - --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/data/${AGENT_CONFIG_FILENAME} -e HYP_ORIGINCHAINNAME=$1 \ - -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ - -e HYP_CHAINS_${3}_CUSTOMRPCURLS=${DOCKER_CONNECTION_URL}:${2} \ - -e HYP_CHAINS_${3}_BLOCKS_REORGPERIOD=0 \ - -e HYP_VALIDATOR_TYPE=hexKey \ - -e HYP_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ - -e HYP_CHECKPOINTSYNCER_TYPE=localStorage \ - -e HYP_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ - -e HYP_TRACING_LEVEL=debug -e HYP_TRACING_FMT=compact \ - gcr.io/abacus-labs-dev/hyperlane-agent:b92ecd3-20231115-182824 ./validator > /tmp/${1}/validator-logs.txt & -done - -echo "Validator running, sleeping to let it sync" -sleep 15 -echo "Done sleeping" - -echo "Validator Announcement:" -cat /tmp/anvil1/validator/announcement.json - -echo "Running relayer" - -docker run \ - --mount type=bind,source="/tmp",target=/data --net=host \ - -e CONFIG_FILES=/data/${AGENT_CONFIG_FILENAME} \ - -e HYP_CHAINS_ANVIL1_CUSTOMRPCURLS=${DOCKER_CONNECTION_URL}:8545 \ - -e HYP_CHAINS_ANVIL2_CUSTOMRPCURLS=${DOCKER_CONNECTION_URL}:8555 \ - -e HYP_CHAINS_ANVIL1_BLOCKS_REORGPERIOD=0 \ - -e HYP_CHAINS_ANVIL2_BLOCKS_REORGPERIOD=0 \ - -e HYP_TRACING_LEVEL=debug -e HYP_TRACING_FMT=compact \ - -e HYP_RELAYCHAINS=anvil1,anvil2 \ - -e HYP_ALLOWLOCALCHECKPOINTSYNCERS=true \ - -e HYP_DB=/data/relayer \ - -e HYP_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ - -e HYP_CHAINS_ANVIL1_SIGNER_TYPE=hexKey \ - -e HYP_CHAINS_ANVIL1_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - -e HYP_CHAINS_ANVIL2_SIGNER_TYPE=hexKey \ - -e HYP_CHAINS_ANVIL2_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ - gcr.io/abacus-labs-dev/hyperlane-agent:b92ecd3-20231115-182824 ./relayer > /tmp/relayer/relayer-logs.txt & - -sleep 10 -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: $2" - yarn workspace @hyperlane-xyz/cli run hyperlane status \ - --id $2 \ - --destination anvil2 \ - --chains ./examples/anvil-chains.yaml \ - --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" - exit 1 - else - echo "Message $1 was delivered!" - fi -done - -docker ps -aq | xargs docker stop | xargs docker rm -pkill -f anvil -echo "Done" diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 2c185f8357..22528dc82c 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -1,5 +1,15 @@ #!/usr/bin/env bash +# NOTE: This script is intended to be run from the root of the repo + +# the first arg to this script is a flag to enable the hook config as part of core deployment +# motivation is to test both the bare bone deployment (included in the docs) and the deployment with the routing over igp hook (which is closer to production deployment) +HOOK_FLAG=$1 +if [ -z "$HOOK_FLAG" ]; then + echo "Usage: ci-test.sh " + exit 1 +fi + # Optional cleanup for previous runs, useful when running locally pkill -f anvil rm -rf /tmp/anvil* @@ -31,13 +41,14 @@ export DEBUG=hyperlane:* DEPLOYER=$(cast rpc eth_accounts | jq -r '.[0]') BEFORE=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) + echo "Deploying contracts to anvil1 and anvil2" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --targets anvil1,anvil2 \ --chains ./examples/anvil-chains.yaml \ --artifacts /tmp/empty-artifacts.json \ - --ism ./examples/multisig-ism.yaml \ - --hook ./examples/hook-config.yaml \ + $(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ./examples/hooks.yaml"; fi) \ + --ism ./examples/ism.yaml \ --out /tmp \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes @@ -71,6 +82,7 @@ echo "Sending test message" yarn workspace @hyperlane-xyz/cli run hyperlane send message \ --origin anvil1 \ --destination anvil2 \ + --messageBody "Howdy!" \ --chains ./examples/anvil-chains.yaml \ --core $CORE_ARTIFACTS_PATH \ --quick \ @@ -95,7 +107,6 @@ yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ --chains ./examples/anvil-chains.yaml \ --core $CORE_ARTIFACTS_PATH \ --router $ANVIL1_ROUTER \ - --type native \ --quick \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ | tee /tmp/message2 @@ -103,67 +114,79 @@ 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" -# ANVIL_CONNECTION_URL="http://127.0.0.1" -# cd ../../rust -# for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" -# do -# set -- $i -# echo "Running validator on $1" -# export CONFIG_FILES=/tmp/${AGENT_CONFIG_FILENAME} -# export HYP_ORIGINCHAINNAME=$1 -# export HYP_CHAINS_${3}_BLOCKS_REORGPERIOD=0 -# export HYP_VALIDATOR_INTERVAL=1 -# export HYP_CHAINS_${3}_CUSTOMRPCURLS=${ANVIL_CONNECTION_URL}:${2} -# export HYP_VALIDATOR_TYPE=hexKey -# export HYP_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 -# export HYP_CHECKPOINTSYNCER_TYPE=localStorage -# export HYP_CHECKPOINTSYNCER_PATH=/tmp/${1}/validator -# export HYP_TRACING_LEVEL=debug -# export HYP_TRACING_FMT=compact - -# cargo run --bin validator > /tmp/${1}/validator-logs.txt & -# done - -# echo "Validator running, sleeping to let it sync" -# sleep 15 -# echo "Done sleeping" - -# echo "Validator Announcement:" -# cat /tmp/anvil1/validator/announcement.json - -# echo "Running relayer" - -# export HYP_RELAYCHAINS=anvil1,anvil2 -# export HYP_ALLOWLOCALCHECKPOINTSYNCERS=true -# export HYP_DB=/tmp/relayer -# export HYP_GASPAYMENTENFORCEMENT='[{"type":"none"}]' -# export HYP_CHAINS_ANVIL1_SIGNER_TYPE=hexKey -# export HYP_CHAINS_ANVIL1_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 -# export HYP_CHAINS_ANVIL2_SIGNER_TYPE=hexKey -# export HYP_CHAINS_ANVIL2_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 - -# cargo run --bin relayer > /tmp/relayer/relayer-logs.txt & - -# sleep 10 -# 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: $2" -# yarn workspace @hyperlane-xyz/cli run hyperlane status \ -# --id $2 \ -# --destination anvil2 \ -# --chains ./examples/anvil-chains.yaml \ -# --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" -# exit 1 -# else -# echo "Message $1 was delivered!" -# fi -# done +cd ./rust +echo "Pre-building validator with cargo" +cargo build --bin validator + +ANVIL_CONNECTION_URL="http://127.0.0.1" +VALIDATOR_PORT=9091 + +for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" +do + VALIDATOR_PORT=$((VALIDATOR_PORT+1)) + set -- $i + echo "Running validator on $1 on port $VALIDATOR_PORT" + export CONFIG_FILES=/tmp/${AGENT_CONFIG_FILENAME} + export HYP_ORIGINCHAINNAME=$1 + export HYP_CHAINS_${3}_BLOCKS_REORGPERIOD=0 + export HYP_VALIDATOR_INTERVAL=1 + export HYP_CHAINS_${3}_CUSTOMRPCURLS=${ANVIL_CONNECTION_URL}:${2} + export HYP_VALIDATOR_TYPE=hexKey + export HYP_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 + export HYP_CHECKPOINTSYNCER_TYPE=localStorage + export HYP_CHECKPOINTSYNCER_PATH=/tmp/${1}/validator + export HYP_TRACING_LEVEL=debug + export HYP_TRACING_FMT=compact + export HYP_METRICSPORT=$VALIDATOR_PORT + + cargo run --bin validator > /tmp/${1}/validator-logs.txt & +done + +echo "Validator running, sleeping to let it sync" +# This needs to be long to allow time for the cargo build to finish +sleep 15 +echo "Done sleeping" + +echo "Validator Announcement:" +cat /tmp/anvil1/validator/announcement.json + +echo "Pre-building relayer with cargo" +cargo build --bin relayer + +echo "Running relayer" +export HYP_RELAYCHAINS=anvil1,anvil2 +export HYP_ALLOWLOCALCHECKPOINTSYNCERS=true +export HYP_DB=/tmp/relayer +export HYP_GASPAYMENTENFORCEMENT='[{"type":"none"}]' +export HYP_CHAINS_ANVIL1_SIGNER_TYPE=hexKey +export HYP_CHAINS_ANVIL1_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 +export HYP_CHAINS_ANVIL2_SIGNER_TYPE=hexKey +export HYP_CHAINS_ANVIL2_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 +export HYP_METRICSPORT=9091 + +cargo run --bin relayer > /tmp/relayer/relayer-logs.txt & + +# This needs to be long to allow time for the cargo build to finish +sleep 20 +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: $2" + yarn workspace @hyperlane-xyz/cli run hyperlane status \ + --id $2 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --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" + exit 1 + else + echo "Message $1 was delivered!" + fi +done pkill -f anvil echo "Done" diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 5265f8462c..7218cf884e 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -8,16 +8,17 @@ 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'; -import { readJson } from './src/utils/files.js'; +import { checkVersion } from './src/utils/version-check.js'; +import { VERSION } from './src/version.js'; // From yargs code: const MISSING_PARAMS_ERROR = 'Not enough non-option arguments'; console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); -try { - const version = readJson('./package.json').version; +await checkVersion(); +try { await yargs(process.argv.slice(2)) .scriptName('hyperlane') .command(chainsCommand) @@ -25,7 +26,7 @@ try { .command(deployCommand) .command(sendCommand) .command(statusCommand) - .version(version) + .version(VERSION) .demandCommand() .strict() .help() diff --git a/typescript/cli/examples/hook-config.yaml b/typescript/cli/examples/hook-config.yaml deleted file mode 100644 index 1b9b9e0936..0000000000 --- a/typescript/cli/examples/hook-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -anvil1: - required: - type: protocolFee - maxProtocolFee: '10000000000000000' - protocolFee: '10000000000' - beneficiary: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - owner: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - default: - type: merkleTreeHook -anvil2: - required: - type: protocolFee - maxProtocolFee: '10000000000000000' - protocolFee: '10000000000' - beneficiary: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - owner: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - default: - type: merkleTreeHook diff --git a/typescript/cli/examples/hooks.yaml b/typescript/cli/examples/hooks.yaml new file mode 100644 index 0000000000..9fc19433ab --- /dev/null +++ b/typescript/cli/examples/hooks.yaml @@ -0,0 +1,66 @@ +# A config to define the hooks for core contract deployments +# Ideally, use the `hyperlane config create hooks` command to generate this file +# but you we can refer to https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/hook/types.ts for the matching types + +# HooksConfig: +# required: HookConfig +# default: HookConfig + +# HookConfig: +# type: HookType +# ... hook-specific config + +# HookType: +# - merkleTreeHook +# - domainRoutingHook +# - interchainGasPaymaster +# - protocolFee +# - aggregationHook +# - opStack (not yet supported) + +anvil1: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' # in wei (string) + protocolFee: '200000000000000' # in wei (string) + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil2: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + overhead: + anvil2: 50000 # gas amount (number) + gasOracleType: + anvil2: StorageGasOracle +anvil2: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' + protocolFee: '200000000000000' + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil1: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + overhead: + anvil1: 50000 + gasOracleType: + anvil1: StorageGasOracle diff --git a/typescript/cli/examples/ism-advanced.yaml b/typescript/cli/examples/ism-advanced.yaml new file mode 100644 index 0000000000..5447811082 --- /dev/null +++ b/typescript/cli/examples/ism-advanced.yaml @@ -0,0 +1,33 @@ +anvil1: + type: defaultFallbackRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/ism.yaml similarity index 65% rename from typescript/cli/examples/multisig-ism.yaml rename to typescript/cli/examples/ism.yaml index dd7c65864e..ac292bf687 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/ism.yaml @@ -1,21 +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 # -# Valid module types: -# routing -# aggregation -# merkleRootMultisig -# messageIdMultisigIsm -# ism type don't work currently (sets to messageIdMultisigIsm for all) --- anvil1: - type: 'messageIdMultisigIsm' threshold: 1 # Number: Signatures required to approve a message validators: # Array: List of validator addresses - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' anvil2: - type: 'messageIdMultisigIsm' threshold: 1 validators: - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/logger.ts b/typescript/cli/logger.ts index 5f2c220d31..3e0a20e162 100644 --- a/typescript/cli/logger.ts +++ b/typescript/cli/logger.ts @@ -50,6 +50,8 @@ export const logPink = (...args: any) => 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 logBoldUnderlinedRed = (...args: any) => + console.log(chalk.red.bold.underline(...args)); export const logTip = (...args: any) => console.log(chalk.bgYellow(...args)); export const errorRed = (...args: any) => console.error(chalk.red(...args)); export const log = (...args: any) => console.log(...args); diff --git a/typescript/cli/package.json b/typescript/cli/package.json index ab7c284754..be454f3f3d 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,14 +1,15 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.1.10", + "version": "3.7.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/sdk": "3.1.10", - "@hyperlane-xyz/utils": "3.1.10", + "@hyperlane-xyz/sdk": "3.7.0", + "@hyperlane-xyz/utils": "3.7.0", "@inquirer/prompts": "^3.0.0", "bignumber.js": "^9.1.1", "chalk": "^5.3.0", "ethers": "^5.7.2", + "latest-version": "^8.0.0", "terminal-link": "^3.0.0", "yaml": "^2.3.1", "yargs": "^17.7.2", @@ -29,12 +30,14 @@ }, "scripts": { "hyperlane": "node ./dist/cli.js", - "build": "tsc", + "build": "yarn version:update && tsc", + "dev": "yarn version:update && tsc --watch", "clean": "rm -rf ./dist", - "dev": "tsc --watch", "lint": "eslint . --ext .ts", "prettier": "prettier --write ./src ./examples", - "test": "mocha --config .mocharc.json" + "test": "mocha --config .mocharc.json", + "test:ci": "yarn test", + "version:update": "echo \"export const VERSION = '$npm_package_version';\" > src/version.ts" }, "files": [ "./dist", diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts index 539163654a..23a0748075 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/chains.ts @@ -32,7 +32,21 @@ export const chainsCommand: CommandModule = { const listCommand: CommandModule = { command: 'list', describe: 'List all core chains included in the Hyperlane SDK', - handler: () => { + builder: (yargs) => + yargs + .option('mainnet', { + alias: 'm', + describe: 'Only list mainnet chains', + }) + .option('testnet', { + alias: 't', + describe: 'Only list testnet chains', + }) + .conflicts('mainnet', 'testnet'), + handler: (args) => { + const mainnet = args.mainnet as string | undefined; + const testnet = args.testnet as string | undefined; + const serializer = (chains: string[]) => chains.reduce((result, chain) => { result[chain] = { @@ -41,13 +55,22 @@ const listCommand: CommandModule = { }; return result; }, {}); + const logMainnet = () => { + logBlue('\nHyperlane core mainnet chains:'); + logGray('------------------------------'); + logTable(serializer(Mainnets)); + }; + const logTestnet = () => { + logBlue('\nHyperlane core testnet chains:'); + logGray('------------------------------'); + logTable(serializer(Testnets)); + }; + + if (mainnet) return logMainnet(); + else if (testnet) return logTestnet(); - logBlue('Hyperlane core mainnet chains:'); - logGray('------------------------------'); - logTable(serializer(Mainnets)); - logBlue('\nHyperlane core testnet chains:'); - logGray('------------------------------'); - logTable(serializer(Testnets)); + logMainnet(); + logTestnet(); }, }; diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 5ef6e1b508..74b3c45646 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -2,7 +2,8 @@ import { CommandModule } from 'yargs'; import { log, logGreen } from '../../logger.js'; import { createChainConfig, readChainConfigs } from '../config/chain.js'; -import { createHookConfig } from '../config/hooks.js'; +import { createHooksConfigMap } from '../config/hooks.js'; +import { createIsmConfigMap, readIsmConfig } from '../config/ism.js'; import { createMultisigConfig, readMultisigConfig, @@ -40,7 +41,7 @@ const createCommand: CommandModule = { builder: (yargs) => yargs .command(createChainConfigCommand) - .command(createMultisigConfigCommand) + .command(createIsmConfigCommand) .command(createHookConfigCommand) .command(createWarpConfigCommand) .version(false) @@ -64,27 +65,39 @@ const createChainConfigCommand: CommandModule = { }, }; -const createMultisigConfigCommand: CommandModule = { - command: 'multisig', - describe: 'Create a new Multisig ISM config', +const createIsmConfigCommand: CommandModule = { + command: 'ism', + describe: 'Create a basic or advanced ISM config for a validator set', builder: (yargs) => yargs.options({ - output: outputFileOption('./configs/multisig-ism.yaml'), + output: outputFileOption('./configs/ism.yaml'), format: fileFormatOption, chains: chainsCommandOption, + advanced: { + type: 'boolean', + describe: 'Create an advanced ISM configuration', + default: false, + }, }), handler: async (argv: any) => { const format: FileFormat = argv.format; const outPath: string = argv.output; const chainConfigPath: string = argv.chains; - await createMultisigConfig({ format, outPath, chainConfigPath }); + const isAdvanced: boolean = argv.advanced; + + if (isAdvanced) { + await createIsmConfigMap({ format, outPath, chainConfigPath }); + } else { + await createMultisigConfig({ format, outPath, chainConfigPath }); + } + process.exit(0); }, }; const createHookConfigCommand: CommandModule = { - command: 'hook', - describe: 'Create a new Hook config', + command: 'hooks', + describe: 'Create a new hooks config (required & default)', builder: (yargs) => yargs.options({ output: outputFileOption('./configs/hooks.yaml'), @@ -95,7 +108,7 @@ const createHookConfigCommand: CommandModule = { const format: FileFormat = argv.format; const outPath: string = argv.output; const chainConfigPath: string = argv.chains; - await createHookConfig({ format, outPath, chainConfigPath }); + await createHooksConfigMap({ format, outPath, chainConfigPath }); process.exit(0); }, }; @@ -127,7 +140,8 @@ const validateCommand: CommandModule = { builder: (yargs) => yargs .command(validateChainCommand) - .command(validateMultisigCommand) + .command(validateIsmCommand) + .command(validateIsmAdvancedCommand) .command(validateWarpCommand) .version(false) .demandCommand(), @@ -152,9 +166,9 @@ const validateChainCommand: CommandModule = { }, }; -const validateMultisigCommand: CommandModule = { - command: 'multisig', - describe: 'Validate a multisig ism config in a YAML or JSON file', +const validateIsmCommand: CommandModule = { + command: 'ism', + describe: 'Validate the basic ISM config in a YAML or JSON file', builder: (yargs) => yargs.options({ path: { @@ -171,6 +185,25 @@ const validateMultisigCommand: CommandModule = { }, }; +const validateIsmAdvancedCommand: CommandModule = { + command: 'ism-advanced', + describe: 'Validate the advanced 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; + readIsmConfig(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', diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 2e8b9f5148..84d0cc110a 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -4,6 +4,7 @@ import { log, logGray } from '../../logger.js'; import { runKurtosisAgentDeploy } from '../deploy/agent.js'; import { runCoreDeploy } from '../deploy/core.js'; import { runWarpDeploy } from '../deploy/warp.js'; +import { ENV } from '../utils/env.js'; import { agentConfigurationOption, @@ -19,7 +20,7 @@ import { */ export const deployCommand: CommandModule = { command: 'deploy', - describe: 'Permisionslessly deploy a Hyperlane contracts or extensions', + describe: 'Permissionlessly deploy a Hyperlane contracts or extensions', builder: (yargs) => yargs .command(coreCommand) @@ -38,26 +39,29 @@ const agentCommand: CommandModule = { describe: 'Deploy Hyperlane agents with Kurtosis', builder: (yargs) => yargs.options({ - originChain: { + origin: { type: 'string', description: 'The name of the origin chain to deploy to', }, - agentConfiguration: agentConfigurationOption, - relayChains: { + targets: { type: 'string', description: 'Comma separated list of chains to relay between', }, + chains: chainsCommandOption, + config: agentConfigurationOption, }), handler: async (argv: any) => { logGray('Hyperlane Agent Deployment with Kurtosis'); logGray('----------------------------------------'); - const originChain: string = argv.originChain; - const agentConfigurationPath: string = argv.agentConfiguration; - const relayChains: string = argv.relayChains; + const chainConfigPath: string = argv.chains; + const originChain: string = argv.origin; + const agentConfigurationPath: string = argv.config; + const relayChains: string = argv.targets; await runKurtosisAgentDeploy({ originChain, - agentConfigurationPath, relayChains, + chainConfigPath, + agentConfigurationPath, }); process.exit(0); }, @@ -81,7 +85,7 @@ const coreCommand: CommandModule = { ism: { type: 'string', description: - 'A path to a JSON or YAML file with ISM configs (e.g. Multisig)', + 'A path to a JSON or YAML file with basic or advanced ISM configs (e.g. Multisig)', }, hook: { type: 'string', @@ -95,7 +99,7 @@ const coreCommand: CommandModule = { handler: async (argv: any) => { logGray('Hyperlane permissionless core deployment'); logGray('----------------------------------------'); - const key: string = argv.key || process.env.HYP_KEY; + const key: string = argv.key || ENV.HYP_KEY; const chainConfigPath: string = argv.chains; const outPath: string = argv.out; const chains: string[] | undefined = argv.targets @@ -139,7 +143,7 @@ const warpCommand: CommandModule = { yes: skipConfirmationOption, }), handler: async (argv: any) => { - const key: string = argv.key || process.env.HYP_KEY; + const key: string = argv.key || ENV.HYP_KEY; const chainConfigPath: string = argv.chains; const warpConfigPath: string | undefined = argv.config; const coreArtifactsPath: string | undefined = argv.core; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index af038d9391..8b4d977a9d 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -25,13 +25,12 @@ export const outDirCommandOption: Options = { export const coreArtifactsOption: Options = { type: 'string', description: 'File path to core deployment output artifacts', - alias: 'ca', + alias: 'a', }; export const agentConfigurationOption: Options = { type: 'string', description: 'File path to agent configuration artifacts', - alias: 'ac', }; export const fileFormatOption: Options = { diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 90bd7e526e..9ae02d438e 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -1,10 +1,10 @@ +import { ethers } from 'ethers'; import { CommandModule, Options } from 'yargs'; -import { TokenType } from '@hyperlane-xyz/sdk'; - import { log } from '../../logger.js'; import { sendTestMessage } from '../send/message.js'; import { sendTestTransfer } from '../send/transfer.js'; +import { ENV } from '../utils/env.js'; import { chainsCommandOption, @@ -35,15 +35,13 @@ const messageOptions: { [k: string]: Options } = { origin: { type: 'string', description: 'Origin chain to send message from', - demandOption: true, }, destination: { type: 'string', description: 'Destination chain to send message to', - demandOption: true, }, - core: coreArtifactsOption, chains: chainsCommandOption, + core: coreArtifactsOption, timeout: { type: 'number', description: 'Timeout in seconds', @@ -59,21 +57,31 @@ const messageOptions: { [k: string]: Options } = { const messageCommand: CommandModule = { command: 'message', describe: 'Send a test message to a remote chain', - builder: (yargs) => yargs.options(messageOptions), + builder: (yargs) => + yargs.options({ + ...messageOptions, + messageBody: { + type: 'string', + description: 'Optional Message body', + default: 'Hello!', + }, + }), handler: async (argv: any) => { - const key: string = argv.key || process.env.HYP_KEY; + const key: string = argv.key || ENV.HYP_KEY; const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string = argv.core; - const origin: string = argv.origin; - const destination: string = argv.destination; + const coreArtifactsPath: string | undefined = argv.core; + const origin: string | undefined = argv.origin; + const destination: string | undefined = argv.destination; const timeoutSec: number = argv.timeout; const skipWaitForDelivery: boolean = argv.quick; + const messageBody: string = argv.messageBody; await sendTestMessage({ key, chainConfigPath, coreArtifactsPath, origin, destination, + messageBody: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(messageBody)), timeoutSec, skipWaitForDelivery, }); @@ -93,13 +101,6 @@ const transferCommand: CommandModule = { router: { type: 'string', 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', @@ -112,14 +113,13 @@ const transferCommand: CommandModule = { }, }), handler: async (argv: any) => { - const key: string = argv.key || process.env.HYP_KEY; + const key: string = argv.key || ENV.HYP_KEY; const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string = argv.core; - const origin: string = argv.origin; - const destination: string = argv.destination; + const coreArtifactsPath: string | undefined = argv.core; + const origin: string | undefined = argv.origin; + const destination: string | undefined = argv.destination; const timeoutSec: number = argv.timeout; - const routerAddress: string = argv.router; - const tokenType: TokenType = argv.type; + const routerAddress: string | undefined = argv.router; const wei: string = argv.wei; const recipient: string | undefined = argv.recipient; const skipWaitForDelivery: boolean = argv.quick; @@ -130,7 +130,6 @@ const transferCommand: CommandModule = { origin, destination, routerAddress, - tokenType, wei, recipient, timeoutSec, diff --git a/typescript/cli/src/commands/status.ts b/typescript/cli/src/commands/status.ts index 6cc99db458..a7a671a63b 100644 --- a/typescript/cli/src/commands/status.ts +++ b/typescript/cli/src/commands/status.ts @@ -12,21 +12,19 @@ export const statusCommand: CommandModule = { id: { type: 'string', description: 'Message ID', - demandOption: true, }, destination: { type: 'string', description: 'Destination chain name', - demandOption: true, }, chains: chainsCommandOption, core: coreArtifactsOption, }), handler: async (argv: any) => { const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string = argv.core; - const messageId: string = argv.id; - const destination: string = argv.destination; + const coreArtifactsPath: string | undefined = argv.core; + const messageId: string | undefined = argv.id; + const destination: string | undefined = argv.destination; await checkMessageStatus({ chainConfigPath, coreArtifactsPath, diff --git a/typescript/cli/src/config/artifacts.ts b/typescript/cli/src/config/artifacts.ts index 9171dd5f45..87a994deda 100644 --- a/typescript/cli/src/config/artifacts.ts +++ b/typescript/cli/src/config/artifacts.ts @@ -1,9 +1,10 @@ +import { confirm } from '@inquirer/prompts'; import { ZodTypeAny, z } from 'zod'; -import { HyperlaneContractsMap } from '@hyperlane-xyz/sdk'; +import { ChainName, HyperlaneContractsMap } from '@hyperlane-xyz/sdk'; -import { logBlue } from '../../logger.js'; -import { readYamlOrJson } from '../utils/files.js'; +import { log, logBlue } from '../../logger.js'; +import { readYamlOrJson, runFileSelectionStep } from '../utils/files.js'; const RecursiveObjectSchema: ZodTypeAny = z.lazy(() => z.object({}).catchall(z.union([z.string(), RecursiveObjectSchema])), @@ -31,3 +32,48 @@ export function readDeploymentArtifacts(filePath: string) { } return artifacts; } + +export async function runDeploymentArtifactStep({ + artifactsPath, + message, + selectedChains, + defaultArtifactsPath = './artifacts', + defaultArtifactsNamePattern = 'core-deployment', + skipConfirmation = false, +}: { + artifactsPath?: string; + message?: string; + selectedChains?: ChainName[]; + defaultArtifactsPath?: string; + defaultArtifactsNamePattern?: string; + skipConfirmation?: boolean; +}): Promise | undefined> { + if (!artifactsPath) { + if (skipConfirmation) return undefined; + + const useArtifacts = await confirm({ + message: message || 'Do you want use some existing contract addresses?', + }); + if (!useArtifacts) return undefined; + + artifactsPath = await runFileSelectionStep( + defaultArtifactsPath, + 'contract deployment artifacts', + defaultArtifactsNamePattern, + ); + } + const artifacts = readDeploymentArtifacts(artifactsPath); + + if (selectedChains) { + const artifactChains = Object.keys(artifacts).filter((c) => + selectedChains.includes(c), + ); + if (artifactChains.length === 0) { + log('No artifacts found for selected chains'); + } else { + log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); + } + } + + return artifacts; +} diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 7a1f2509e8..28e5507762 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -1,4 +1,4 @@ -import { confirm, input, select } from '@inquirer/prompts'; +import { confirm, input } from '@inquirer/prompts'; import { ChainMap, @@ -60,8 +60,8 @@ export function readChainConfigs(filePath: string) { return chainToMetadata; } -export function readChainConfigsIfExists(filePath: string) { - if (!isFile(filePath)) { +export function readChainConfigsIfExists(filePath?: string) { + if (!filePath || !isFile(filePath)) { log('No chain config file provided'); return {}; } else { @@ -81,32 +81,72 @@ export async function createChainConfig({ 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 domainId = chainId; const rpcUrl = await input({ message: 'Enter http or https rpc url' }); const metadata: ChainMetadata = { name, chainId: parseInt(chainId, 10), domainId: parseInt(domainId, 10), - protocol, + protocol: ProtocolType.Ethereum, rpcUrls: [{ http: rpcUrl }], }; + const wantAdvancedConfig = await confirm({ + message: + 'Do you want to set block or gas properties for this chain config?(optional)', + }); + if (wantAdvancedConfig) { + const wantBlockConfig = await confirm({ + message: 'Do you want to add block config for this chain?', + }); + if (wantBlockConfig) { + const blockConfirmation = await input({ + message: + 'Enter no. of blocks to wait before considering a transaction confirmed(0-500)', + validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 500, + }); + const blockReorgPeriod = await input({ + message: + 'Enter no. of blocks before a transaction has a near-zero chance of reverting(0-500)', + validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 500, + }); + const blockTimeEstimate = await input({ + message: 'Enter the rough estimate of time per block in seconds(0-20)', + validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 20, + }); + metadata.blocks = { + confirmations: parseInt(blockConfirmation, 10), + reorgPeriod: parseInt(blockReorgPeriod, 10), + estimateBlockTime: parseInt(blockTimeEstimate, 10), + }; + } + const wantGasConfig = await confirm({ + message: 'Do you want to add gas config for this chain?', + }); + if (wantGasConfig) { + const isEIP1559 = await confirm({ + message: 'Is your chain an EIP1559 enabled?', + }); + if (isEIP1559) { + const maxFeePerGas = await input({ + message: 'Enter the max fee per gas in gwei', + }); + const maxPriorityFeePerGas = await input({ + message: 'Enter the max priority fee per gas in gwei', + }); + metadata.transactionOverrides = { + maxFeePerGas: BigInt(maxFeePerGas) * BigInt(10 ** 9), + maxPriorityFeePerGas: BigInt(maxPriorityFeePerGas) * BigInt(10 ** 9), + }; + } else { + const gasPrice = await input({ + message: 'Enter the gas price in gwei', + }); + metadata.transactionOverrides = { + gasPrice: BigInt(gasPrice) * BigInt(10 ** 9), + }; + } + } + } const parseResult = ChainMetadataSchema.safeParse(metadata); if (parseResult.success) { logGreen(`Chain config is valid, writing to file ${outPath}`); diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 10f8f12b34..0c0f49111c 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -1,5 +1,5 @@ import { confirm, input, select } from '@inquirer/prompts'; -import { BigNumber } from 'bignumber.js'; +import { BigNumber as BigNumberJs } from 'bignumber.js'; import { ethers } from 'ethers'; import { z } from 'zod'; @@ -7,14 +7,9 @@ import { ChainMap, ChainName, GasOracleContractType, - HookConfig, HookType, - IgpHookConfig, - MerkleTreeHookConfig, - MultisigIsmConfig, - ProtocolFeeHookConfig, - defaultMultisigConfigs, - multisigIsmVerificationCost, + HooksConfig, + chainMetadata, } from '@hyperlane-xyz/sdk'; import { Address, @@ -41,85 +36,72 @@ const MerkleTreeSchema = z.object({ type: z.literal(HookType.MERKLE_TREE), }); -const HookSchema = z.union([ProtocolFeeSchema, MerkleTreeSchema]); +const IGPSchema = z.object({ + type: z.literal(HookType.INTERCHAIN_GAS_PAYMASTER), + owner: z.string(), + beneficiary: z.string(), + overhead: z.record(z.number()), + gasOracleType: z.record(z.literal(GasOracleContractType.StorageGasOracle)), + oracleKey: z.string(), +}); + +const RoutingConfigSchema: z.ZodSchema = z.lazy(() => + z.object({ + type: z.literal(HookType.ROUTING), + owner: z.string(), + domains: z.record(HookConfigSchema), + }), +); + +const AggregationConfigSchema: z.ZodSchema = z.lazy(() => + z.object({ + type: z.literal(HookType.AGGREGATION), + hooks: z.array(HookConfigSchema), + }), +); + +const HookConfigSchema = z.union([ + ProtocolFeeSchema, + MerkleTreeSchema, + IGPSchema, + RoutingConfigSchema, + AggregationConfigSchema, +]); +export type HookConfig = z.infer; -const ConfigSchema = z.object({ - required: HookSchema, - default: HookSchema, +const HooksConfigSchema = z.object({ + required: HookConfigSchema, + default: HookConfigSchema, }); -const HookConfigMapSchema = z.object({}).catchall(ConfigSchema); -export type HookConfigMap = z.infer; +const HooksConfigMapSchema = z.record(HooksConfigSchema); +export type HooksConfigMap = z.infer; export function isValidHookConfigMap(config: any) { - return HookConfigMapSchema.safeParse(config).success; + return HooksConfigMapSchema.safeParse(config).success; } -export function presetHookConfigs( - owner: Address, - local: ChainName, - destinationChains: ChainName[], - ismConfig?: MultisigIsmConfig, -) { - const gasOracleType = destinationChains.reduce< - ChainMap - >((acc, chain) => { - acc[chain] = GasOracleContractType.StorageGasOracle; - return acc; - }, {}); - const overhead = destinationChains.reduce>((acc, chain) => { - let validatorThreshold: number; - let validatorCount: number; - if (ismConfig) { - validatorThreshold = ismConfig.threshold; - validatorCount = ismConfig.validators.length; - } else if (local in defaultMultisigConfigs) { - validatorThreshold = defaultMultisigConfigs[local].threshold; - validatorCount = defaultMultisigConfigs[local].validators.length; - } else { - throw new Error('Cannot estimate gas overhead for IGP hook'); - } - acc[chain] = multisigIsmVerificationCost( - validatorThreshold, - validatorCount, - ); - return acc; - }, {}); - - // TODO improve types here to avoid need for `as` casts +export function presetHookConfigs(owner: Address): HooksConfig { return { required: { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), - protocolFee: ethers.utils.parseUnits('0', 'wei'), + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), + protocolFee: ethers.utils.parseUnits('0', 'wei').toString(), beneficiary: owner, owner: owner, - } as ProtocolFeeHookConfig, + }, default: { - type: HookType.AGGREGATION, - hooks: [ - { - type: HookType.MERKLE_TREE, - } as MerkleTreeHookConfig, - { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - owner: owner, - beneficiary: owner, - gasOracleType, - overhead, - oracleKey: owner, - } as IgpHookConfig, - ], + type: HookType.MERKLE_TREE, }, }; } -export function readHookConfig(filePath: string) { +export function readHooksConfigMap(filePath: string) { const config = readYamlOrJson(filePath); if (!config) { - logRed(`No multisig config found at ${filePath}`); + logRed(`No hook config found at ${filePath}`); return; } - const result = HookConfigMapSchema.safeParse(config); + const result = HooksConfigMapSchema.safeParse(config); if (!result.success) { const firstIssue = result.error.issues[0]; throw new Error( @@ -127,21 +109,15 @@ export function readHookConfig(filePath: string) { ); } const parsedConfig = result.data; - const defaultHook: ChainMap = objMap( + const hooks: ChainMap = objMap( parsedConfig, - (_, config) => - ({ - type: config.default.type, - } as HookConfig), + (_, config) => config as HooksConfig, ); - logGreen(`All multisig configs in ${filePath} are valid`); - return defaultHook; + logGreen(`All hook configs in ${filePath} are valid for ${hooks}`); + return hooks; } -// TODO: read different hook configs -// export async function readProtocolFeeHookConfig(config: {type: HookType.PROTOCOL_FEE, ...}) { - -export async function createHookConfig({ +export async function createHooksConfigMap({ format, outPath, chainConfigPath, @@ -154,79 +130,229 @@ export async function createHookConfig({ const customChains = readChainConfigsIfExists(chainConfigPath); const chains = await runMultiChainSelectionStep(customChains); - const result: HookConfigMap = {}; + const result: HooksConfigMap = {}; for (const chain of chains) { for (const hookRequirements of ['required', 'default']) { log(`Setting ${hookRequirements} hook for chain ${chain}`); - const hookType = await select({ - message: 'Select hook type', - choices: [ - { value: 'merkle_tree', name: 'MerkleTreeHook' }, - { value: 'protocol_fee', name: 'StaticProtocolFee' }, - ], - pageSize: 5, - }); - if (hookType === 'merkle_tree') { - result[chain] = { - ...result[chain], - [hookRequirements]: { type: HookType.MERKLE_TREE }, - }; - } else if (hookType === 'protocol_fee') { - const owner = await input({ - message: 'Enter owner address', - }); - const ownerAddress = normalizeAddressEvm(owner); - let beneficiary; - let sameAsOwner = false; - sameAsOwner = await confirm({ - message: 'Use this same address for the beneficiary?', - }); - if (sameAsOwner) { - beneficiary = ownerAddress; - } else { - beneficiary = await input({ - message: 'Enter beneficiary address', - }); - } - const beneficiaryAddress = normalizeAddressEvm(beneficiary); - // TODO: input in gwei, wei, etc - const maxProtocolFee = toWei( - await input({ - message: 'Enter max protocol fee in (e.g. 1.0)', - }), - ); - const protocolFee = toWei( - await input({ - message: 'Enter protocol fee (e.g. 1.0)', - }), - ); - if (BigNumber(protocolFee).gt(maxProtocolFee)) { - errorRed('Protocol fee cannot be greater than max protocol fee'); - throw new Error('Invalid protocol fee'); - } - - result[chain] = { - ...result[chain], - [hookRequirements]: { - type: HookType.PROTOCOL_FEE, - maxProtocolFee: maxProtocolFee.toString(), - protocolFee: protocolFee.toString(), - beneficiary: beneficiaryAddress, - owner: ownerAddress, - }, - }; - } else { - throw new Error(`Invalid hook type: ${hookType}}`); - } + const remotes = chains.filter((c) => c !== chain); + result[chain] = { + ...result[chain], + [hookRequirements]: await createHookConfig(chain, remotes), + }; } if (isValidHookConfigMap(result)) { logGreen(`Hook config is valid, writing to file ${outPath}`); mergeYamlOrJson(outPath, result, format); } else { errorRed( - `Hook config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/hook-config.yaml for an example`, + `Hook config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/hooks.yaml for an example`, ); throw new Error('Invalid hook config'); } } } + +export async function createHookConfig( + chain: ChainName, + remotes: ChainName[], +): Promise { + let lastConfig: HookConfig; + const hookType = await select({ + message: 'Select hook type', + choices: [ + { + value: HookType.MERKLE_TREE, + name: HookType.MERKLE_TREE, + description: + 'Add messages to the incremental merkle tree on origin chain (needed for the merkleRootMultisigIsm on the remote chain)', + }, + { + value: HookType.PROTOCOL_FEE, + name: HookType.PROTOCOL_FEE, + description: 'Charge fees for each message dispatch from this chain', + }, + { + value: HookType.INTERCHAIN_GAS_PAYMASTER, + name: HookType.INTERCHAIN_GAS_PAYMASTER, + description: + 'Allow for payments for expected gas to be paid by the relayer while delivering on remote chain', + }, + { + value: HookType.AGGREGATION, + name: HookType.AGGREGATION, + description: + 'Aggregate multiple hooks into a single hook (e.g. merkle tree + IGP) which will be called in sequence', + }, + { + value: HookType.ROUTING, + name: HookType.ROUTING, + description: + 'Each destination domain can have its own hook configured via DomainRoutingHook', + }, + ], + pageSize: 10, + }); + if (hookType === HookType.MERKLE_TREE) { + lastConfig = { type: HookType.MERKLE_TREE }; + } else if (hookType === HookType.PROTOCOL_FEE) { + lastConfig = await createProtocolFeeConfig(chain); + } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { + lastConfig = await createIGPConfig(remotes); + } else if (hookType === HookType.AGGREGATION) { + lastConfig = await createAggregationConfig(chain, remotes); + } else if (hookType === HookType.ROUTING) { + lastConfig = await createRoutingConfig(chain, remotes); + } else { + throw new Error(`Invalid hook type: ${hookType}`); + } + return lastConfig; +} + +export async function createProtocolFeeConfig( + chain: ChainName, +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = normalizeAddressEvm(owner); + let beneficiary; + let sameAsOwner = false; + sameAsOwner = await confirm({ + message: 'Use this same address for the beneficiary?', + }); + if (sameAsOwner) { + beneficiary = ownerAddress; + } else { + beneficiary = await input({ + message: 'Enter beneficiary address', + }); + } + const beneficiaryAddress = normalizeAddressEvm(beneficiary); + // TODO: input in gwei, wei, etc + const maxProtocolFee = toWei( + await input({ + message: `Enter max protocol fee ${nativeTokenAndDecimals( + chain, + )} e.g. 1.0)`, + }), + ); + const protocolFee = toWei( + await input({ + message: `Enter protocol fee in ${nativeTokenAndDecimals( + chain, + )} e.g. 0.01)`, + }), + ); + if (BigNumberJs(protocolFee).gt(maxProtocolFee)) { + errorRed('Protocol fee cannot be greater than max protocol fee'); + throw new Error('Invalid protocol fee'); + } + + return { + type: HookType.PROTOCOL_FEE, + maxProtocolFee: maxProtocolFee.toString(), + protocolFee: protocolFee.toString(), + beneficiary: beneficiaryAddress, + owner: ownerAddress, + }; +} + +export async function createIGPConfig( + remotes: ChainName[], +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = normalizeAddressEvm(owner); + let beneficiary, oracleKey; + let sameAsOwner = false; + sameAsOwner = await confirm({ + message: 'Use this same address for the beneficiary and gasOracleKey?', + }); + if (sameAsOwner) { + beneficiary = ownerAddress; + oracleKey = ownerAddress; + } else { + beneficiary = await input({ + message: 'Enter beneficiary address', + }); + oracleKey = await input({ + message: 'Enter gasOracleKey address', + }); + } + const beneficiaryAddress = normalizeAddressEvm(beneficiary); + const oracleKeyAddress = normalizeAddressEvm(oracleKey); + const overheads: ChainMap = {}; + for (const chain of remotes) { + const overhead = parseInt( + await input({ + message: `Enter overhead for ${chain} (eg 75000)`, + }), + ); + overheads[chain] = overhead; + } + return { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: beneficiaryAddress, + owner: ownerAddress, + oracleKey: oracleKeyAddress, + overhead: overheads, + gasOracleType: objMap( + overheads, + () => GasOracleContractType.StorageGasOracle, + ), + }; +} + +export async function createAggregationConfig( + chain: ChainName, + remotes: ChainName[], +): Promise { + const hooksNum = parseInt( + await input({ + message: 'Enter the number of hooks to aggregate (number)', + }), + 10, + ); + const hooks: Array = []; + for (let i = 0; i < hooksNum; i++) { + logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`); + hooks.push(await createHookConfig(chain, remotes)); + } + return { + type: HookType.AGGREGATION, + hooks, + }; +} + +export async function createRoutingConfig( + origin: ChainName, + remotes: ChainName[], +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = owner; + + const domainsMap: ChainMap = {}; + for (const chain of remotes) { + await confirm({ + message: `You are about to configure hook for remote chain ${chain}. Continue?`, + }); + const config = await createHookConfig(origin, remotes); + domainsMap[chain] = config; + } + return { + type: HookType.ROUTING, + owner: ownerAddress, + domains: domainsMap, + }; +} + +function nativeTokenAndDecimals(chain: ChainName) { + return `10^${ + chainMetadata[chain].nativeToken?.decimals ?? '18' + } which you cannot exceed (in ${ + chainMetadata[chain].nativeToken?.symbol ?? 'eth' + }`; +} diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts new file mode 100644 index 0000000000..d035bec83d --- /dev/null +++ b/typescript/cli/src/config/ism.ts @@ -0,0 +1,290 @@ +import { confirm, input, select } from '@inquirer/prompts'; +import { z } from 'zod'; + +import { ChainMap, ChainName, IsmType, ZHash } from '@hyperlane-xyz/sdk'; + +import { + errorRed, + log, + logBlue, + logBoldUnderlinedRed, + logGreen, + logRed, +} from '../../logger.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; + +import { readChainConfigsIfExists } from './chain.js'; + +const MultisigIsmConfigSchema = z.object({ + type: z.union([ + z.literal(IsmType.MERKLE_ROOT_MULTISIG), + z.literal(IsmType.MESSAGE_ID_MULTISIG), + ]), + threshold: z.number(), + validators: z.array(ZHash), +}); + +const RoutingIsmConfigSchema: z.ZodSchema = z.lazy(() => + z.object({ + type: z.union([ + z.literal(IsmType.ROUTING), + z.literal(IsmType.FALLBACK_ROUTING), + ]), + owner: ZHash, + domains: z.record(IsmConfigSchema), + }), +); + +const AggregationIsmConfigSchema: z.ZodSchema = z + .lazy(() => + z.object({ + type: z.literal(IsmType.AGGREGATION), + modules: z.array(IsmConfigSchema), + threshold: z.number(), + }), + ) + .refine( + // check ig modules.length >= threshold + (ismConfig) => { + return ismConfig.modules.length >= ismConfig.threshold; + }, + { + message: 'Threshold cannot be greater than number of modules', + }, + ); + +const TestIsmConfigSchema = z.object({ + type: z.literal(IsmType.TEST_ISM), +}); + +const IsmConfigSchema = z.union([ + MultisigIsmConfigSchema, + RoutingIsmConfigSchema, + AggregationIsmConfigSchema, + TestIsmConfigSchema, +]); +const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( + (ismConfigMap) => { + // check if any key in IsmConfigMap is found in its own RoutingIsmConfigSchema.domains + for (const [key, config] of Object.entries(ismConfigMap)) { + if (config.type === IsmType.ROUTING) { + if (config.domains && key in config.domains) { + return false; + } + } + } + return true; + }, + { + message: + 'Cannot set RoutingIsm.domain to the same chain you are configuring', + }, +); +export type ZodIsmConfig = z.infer; +export type ZodIsmConfigMap = z.infer; + +export function parseIsmConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No ISM config found at ${filePath}`); + return IsmConfigMapSchema.safeParse(config); +} + +export function readIsmConfig(filePath: string) { + const result = parseIsmConfig(filePath); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid ISM config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + const parsedConfig = result.data; + return parsedConfig; +} + +export function isValildIsmConfig(config: any) { + return IsmConfigMapSchema.safeParse(config).success; +} + +export async function createIsmConfigMap({ + format, + outPath, + chainConfigPath, +}: { + format: FileFormat; + outPath: string; + chainConfigPath: string; +}) { + logBlue('Creating a new advanced ISM config'); + logBoldUnderlinedRed('WARNING: USE AT YOUR RISK.'); + logRed( + 'Advanced ISM configs require knowledge of different ISM types and how they work together topologically. If possible, use the basic ISM configs are recommended.', + ); + const customChains = readChainConfigsIfExists(chainConfigPath); + const chains = await runMultiChainSelectionStep( + customChains, + 'Select chains to configure ISM for', + true, + ); + + const result: ZodIsmConfigMap = {}; + for (const chain of chains) { + log(`Setting values for chain ${chain}`); + result[chain] = await createIsmConfig(chain, chains); + + // TODO consider re-enabling. Disabling based on feedback from @nambrot for now. + // repeat = await confirm({ + // message: 'Use this same config for remaining chains?', + // }); + } + + if (isValildIsmConfig(result)) { + logGreen(`ISM config is valid, writing to file ${outPath}`); + mergeYamlOrJson(outPath, result, format); + } else { + errorRed( + `ISM config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, + ); + throw new Error('Invalid ISM config'); + } +} + +export async function createIsmConfig( + remote: ChainName, + origins: ChainName[], +): Promise { + let lastConfig: ZodIsmConfig; + const moduleType = await select({ + message: 'Select ISM type', + choices: [ + { + value: IsmType.MESSAGE_ID_MULTISIG, + name: IsmType.MESSAGE_ID_MULTISIG, + description: 'Validators need to sign just this messageId', + }, + { + value: IsmType.MERKLE_ROOT_MULTISIG, + name: IsmType.MERKLE_ROOT_MULTISIG, + description: + 'Validators need to sign the root of the merkle tree of all messages from origin chain', + }, + { + value: IsmType.ROUTING, + name: IsmType.ROUTING, + description: + 'Each origin chain can be verified by the specified ISM type via RoutingISM', + }, + { + value: IsmType.FALLBACK_ROUTING, + name: IsmType.FALLBACK_ROUTING, + description: + "You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM", + }, + { + value: IsmType.AGGREGATION, + name: IsmType.AGGREGATION, + description: + 'You can aggregate multiple ISMs into one ISM via AggregationISM', + }, + { + value: IsmType.TEST_ISM, + name: IsmType.TEST_ISM, + description: + 'ISM where you can deliver messages without any validation (WARNING: only for testing, do not use in production)', + }, + ], + pageSize: 10, + }); + if ( + moduleType === IsmType.MESSAGE_ID_MULTISIG || + moduleType === IsmType.MERKLE_ROOT_MULTISIG + ) { + lastConfig = await createMultisigConfig(moduleType); + } else if ( + moduleType === IsmType.ROUTING || + moduleType === IsmType.FALLBACK_ROUTING + ) { + lastConfig = await createRoutingConfig(moduleType, remote, origins); + } else if (moduleType === IsmType.AGGREGATION) { + lastConfig = await createAggregationConfig(remote, origins); + } else if (moduleType === IsmType.TEST_ISM) { + lastConfig = { type: IsmType.TEST_ISM }; + } else { + throw new Error(`Invalid ISM type: ${moduleType}}`); + } + return lastConfig; +} + +export async function createMultisigConfig( + type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG, +): Promise { + 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()); + return { + type, + threshold, + validators, + }; +} + +export async function createAggregationConfig( + remote: ChainName, + chains: ChainName[], +): Promise { + const isms = parseInt( + await input({ + message: 'Enter the number of ISMs to aggregate (number)', + }), + 10, + ); + + const threshold = parseInt( + await input({ + message: 'Enter the threshold of ISMs to for verification (number)', + }), + 10, + ); + + const modules: Array = []; + for (let i = 0; i < isms; i++) { + modules.push(await createIsmConfig(remote, chains)); + } + return { + type: IsmType.AGGREGATION, + modules, + threshold, + }; +} + +export async function createRoutingConfig( + type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING, + remote: ChainName, + chains: ChainName[], +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = owner; + const origins = chains.filter((chain) => chain !== remote); + + const domainsMap: ChainMap = {}; + for (const chain of origins) { + await confirm({ + message: `You are about to configure ISM from source chain ${chain}. Continue?`, + }); + const config = await createIsmConfig(chain, chains); + domainsMap[chain] = config; + } + return { + type, + owner: ownerAddress, + domains: domainsMap, + }; +} diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index c9483ae039..ba59111abf 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -1,10 +1,16 @@ -import { input, select } from '@inquirer/prompts'; +import { confirm, input } from '@inquirer/prompts'; import { z } from 'zod'; -import { ChainMap, IsmType, MultisigConfig } from '@hyperlane-xyz/sdk'; -import { objMap } from '@hyperlane-xyz/utils'; +import { ChainMap, MultisigConfig, ZHash } from '@hyperlane-xyz/sdk'; +import { + Address, + isValidAddress, + normalizeAddressEvm, + objMap, +} from '@hyperlane-xyz/utils'; import { errorRed, log, logBlue, logGreen } from '../../logger.js'; +import { sdkContractAddressesMap } from '../context.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; @@ -12,9 +18,8 @@ import { readChainConfigsIfExists } from './chain.js'; const MultisigConfigMapSchema = z.object({}).catchall( z.object({ - type: z.nativeEnum(IsmType), threshold: z.number(), - validators: z.array(z.string()), + validators: z.array(ZHash), }), ); export type MultisigConfigMap = z.infer; @@ -32,14 +37,24 @@ export function readMultisigConfig(filePath: string) { const parsedConfig = result.data; const formattedConfig: ChainMap = objMap( parsedConfig, - (_, config) => - ({ - type: config.type as IsmType, + (_, config) => { + if (config.threshold > config.validators.length) + throw new Error( + 'Threshold cannot be greater than number of validators', + ); + if (config.threshold < 1) + throw new Error('Threshold must be greater than 0'); + const validators: Address[] = []; + for (const v of config.validators) { + if (isValidAddress(v)) validators.push(normalizeAddressEvm(v)); + else throw new Error(`Invalid address ${v}`); + } + return { threshold: config.threshold, - validators: config.validators, - } as MultisigConfig), + validators: validators, + } as MultisigConfig; + }, ); - logGreen(`All multisig configs in ${filePath} are valid`); return formattedConfig; } @@ -58,6 +73,9 @@ export async function createMultisigConfig({ chainConfigPath: string; }) { logBlue('Creating a new multisig config'); + log( + 'Select your own chain below to run your own validators. If you want to reuse existing Hyperlane validators instead of running your own, do not select additional mainnet or testnet chains.', + ); const customChains = readChainConfigsIfExists(chainConfigPath); const chains = await runMultiChainSelectionStep(customChains); @@ -70,17 +88,12 @@ export async function createMultisigConfig({ result[chain] = lastConfig; continue; } - // TODO consider using default and not offering options here - const moduleType = await select({ - message: 'Select multisig type', - choices: [ - // { value: 'routing, name: 'routing' }, // TODO add support - // { value: 'aggregation, name: 'aggregation' }, // TODO add support - { value: IsmType.MERKLE_ROOT_MULTISIG, name: 'merkle root multisig' }, - { value: IsmType.MESSAGE_ID_MULTISIG, name: 'message id multisig' }, - ], - pageSize: 5, - }); + if (Object.keys(sdkContractAddressesMap).includes(chain)) { + const reuseCoreConfig = await confirm({ + message: 'Use existing Hyperlane validators for this chain?', + }); + if (reuseCoreConfig) continue; + } const thresholdInput = await input({ message: 'Enter threshold of signers (number)', @@ -92,7 +105,6 @@ export async function createMultisigConfig({ }); const validators = validatorsInput.split(',').map((v) => v.trim()); lastConfig = { - type: moduleType, threshold, validators, }; @@ -109,7 +121,7 @@ export async function createMultisigConfig({ 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`, + `Multisig config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, ); throw new Error('Invalid multisig config'); } diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index f793959d9a..d187e9b9e7 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -2,7 +2,7 @@ import { confirm, input } from '@inquirer/prompts'; import { ethers } from 'ethers'; import { z } from 'zod'; -import { TokenType } from '@hyperlane-xyz/sdk'; +import { TokenType, ZHash } from '@hyperlane-xyz/sdk'; import { errorRed, logBlue, logGreen } from '../../logger.js'; import { @@ -14,9 +14,9 @@ import { FileFormat, readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; import { readChainConfigsIfExists } from './chain.js'; const ConnectionConfigSchema = { - mailbox: z.string().optional(), - interchainGasPaymaster: z.string().optional(), - interchainSecurityModule: z.string().optional(), + mailbox: ZHash.optional(), + interchainGasPaymaster: ZHash.optional(), + interchainSecurityModule: ZHash.optional(), foreignDeployment: z.string().optional(), }; @@ -24,7 +24,7 @@ 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(), + address: ZHash.optional(), isNft: z.boolean().optional(), name: z.string().optional(), symbol: z.string().optional(), diff --git a/typescript/cli/src/context.test.ts b/typescript/cli/src/context.test.ts new file mode 100644 index 0000000000..41805197e4 --- /dev/null +++ b/typescript/cli/src/context.test.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { ethers } from 'ethers'; + +import { getContext } from './context.js'; + +describe('context', () => { + it('Gets minimal read-only context correctly', async () => { + const context = await getContext({ chainConfigPath: './fakePath' }); + expect(!!context.multiProvider).to.be.true; + expect(context.customChains).to.eql({}); + }); + + it('Handles conditional type correctly', async () => { + const randomWallet = ethers.Wallet.createRandom(); + const context = await getContext({ + chainConfigPath: './fakePath', + keyConfig: { key: randomWallet.privateKey }, + }); + expect(!!context.multiProvider).to.be.true; + expect(context.customChains).to.eql({}); + expect(await context.signer.getAddress()).to.eql(randomWallet.address); + }); +}); diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index da87c0db32..11fa8326d0 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -1,43 +1,119 @@ +import { input } from '@inquirer/prompts'; import { ethers } from 'ethers'; import { ChainMap, ChainMetadata, + ChainName, HyperlaneContractsMap, MultiProvider, chainMetadata, hyperlaneEnvironments, } from '@hyperlane-xyz/sdk'; -import { objMerge } from '@hyperlane-xyz/utils'; +import { objFilter, objMap, objMerge } from '@hyperlane-xyz/utils'; +import { runDeploymentArtifactStep } from './config/artifacts.js'; import { readChainConfigsIfExists } from './config/chain.js'; import { keyToSigner } from './utils/keys.js'; -export const sdkContractAddressesMap = { +export const sdkContractAddressesMap: HyperlaneContractsMap = { ...hyperlaneEnvironments.testnet, ...hyperlaneEnvironments.mainnet, }; export function getMergedContractAddresses( artifacts?: HyperlaneContractsMap, + chains?: ChainName[], ) { + // if chains include non sdkContractAddressesMap chains, don't recover interchainGasPaymaster + let sdkContractsAddressesToRecover = sdkContractAddressesMap; + if ( + chains?.some( + (chain) => !Object.keys(sdkContractAddressesMap).includes(chain), + ) + ) { + sdkContractsAddressesToRecover = objMap(sdkContractAddressesMap, (_, v) => + objFilter( + v as ChainMap, + (key, v): v is any => key !== 'interchainGasPaymaster', + ), + ); + } return objMerge( - sdkContractAddressesMap, + sdkContractsAddressesToRecover, artifacts || {}, ) as HyperlaneContractsMap; } -export function getContext(chainConfigPath: string) { - const customChains = readChainConfigsIfExists(chainConfigPath); - const multiProvider = getMultiProvider(customChains); - return { customChains, multiProvider }; +interface ContextSettings { + chainConfigPath?: string; + coreConfig?: { + coreArtifactsPath?: string; + promptMessage?: string; + }; + keyConfig?: { + key?: string; + promptMessage?: string; + }; + skipConfirmation?: boolean; +} + +interface CommandContextBase { + customChains: ChainMap; + multiProvider: MultiProvider; } -export function getContextWithSigner(key: string, chainConfigPath: string) { - const signer = keyToSigner(key); +// This makes return type dynamic based on the input settings +type CommandContext

= CommandContextBase & + (P extends { keyConfig: object } + ? { signer: ethers.Signer } + : { signer: undefined }) & + (P extends { coreConfig: object } + ? { coreArtifacts: HyperlaneContractsMap } + : { coreArtifacts: undefined }); + +export async function getContext

({ + chainConfigPath, + coreConfig, + keyConfig, + skipConfirmation, +}: P): Promise> { const customChains = readChainConfigsIfExists(chainConfigPath); + + let signer = undefined; + if (keyConfig) { + let key: string; + if (keyConfig.key) key = keyConfig.key; + else if (skipConfirmation) throw new Error('No key provided'); + else + key = await input({ + message: + keyConfig.promptMessage || + 'Please enter a private key or use the HYP_KEY environment variable', + }); + signer = keyToSigner(key); + } + + let coreArtifacts = undefined; + if (coreConfig) { + coreArtifacts = + (await runDeploymentArtifactStep({ + artifactsPath: coreConfig.coreArtifactsPath, + message: + coreConfig.promptMessage || + 'Do you want to use some core deployment address artifacts? This is required for PI chains (non-core chains).', + skipConfirmation, + })) || {}; + } + const multiProvider = getMultiProvider(customChains, signer); - return { signer, customChains, multiProvider }; + + return { + customChains, + signer, + multiProvider, + coreArtifacts, + } as CommandContext

; } export function getMultiProvider( diff --git a/typescript/cli/src/deploy/agent.ts b/typescript/cli/src/deploy/agent.ts index 96f52fc710..a3d66865d3 100644 --- a/typescript/cli/src/deploy/agent.ts +++ b/typescript/cli/src/deploy/agent.ts @@ -1,26 +1,41 @@ -import { input } from '@inquirer/prompts'; import terminalLink from 'terminal-link'; -import { logBlue, logGreen, logRed } from '../../logger.js'; +import { toBase64 } from '@hyperlane-xyz/utils'; + +import { logBlue, logGreen } from '../../logger.js'; +import { getContext } from '../context.js'; +import { + runMultiChainSelectionStep, + runSingleChainSelectionStep, +} from '../utils/chains.js'; import { readJson, runFileSelectionStep } from '../utils/files.js'; export async function runKurtosisAgentDeploy({ originChain, - agentConfigurationPath, relayChains, + chainConfigPath, + agentConfigurationPath, }: { originChain: string; - agentConfigurationPath: string; relayChains: string; + chainConfigPath: string; + agentConfigurationPath: string; }) { + const { customChains } = await getContext({ chainConfigPath }); + if (!originChain) { - originChain = await input({ message: 'Enter the origin chain' }); + originChain = await runSingleChainSelectionStep( + customChains, + 'Select the origin chain', + ); } if (!relayChains) { - relayChains = await input({ - message: 'Enter a comma separated list of chains to relay between', - }); - relayChains = trimSpaces(relayChains); + const selectedRelayChains = await runMultiChainSelectionStep( + customChains, + 'Select chains to relay between', + true, + ); + relayChains = selectedRelayChains.join(','); } if (!agentConfigurationPath) { @@ -48,12 +63,13 @@ export async function runKurtosisAgentDeploy({ args: hyperlanePackageArgs, }; - const base64EncodedPackageConfig = jsonToBase64(kurtosisPackageConfig); + const base64EncodedPackageConfig = toBase64(kurtosisPackageConfig) || ''; const kurtosisCloudUrl = getKurtosisCloudUrl(base64EncodedPackageConfig); const kurtosisCloudLink = terminalLink( 'Cmd+Click or Ctrl+Click here', kurtosisCloudUrl, + { fallback: () => kurtosisCloudUrl }, ); logGreen( @@ -67,18 +83,3 @@ export async function runKurtosisAgentDeploy({ const getKurtosisCloudUrl = (base64Params: string) => `https://cloud.kurtosis.com/enclave-manager?package-id=github.com%2Fkurtosis-tech%2Fhyperlane-package&package-args=${base64Params}`; - -const trimSpaces = (a: string) => - a - .split('') - .filter((char) => char !== ' ') - .join(''); - -function jsonToBase64(jsonData: any): string { - try { - return btoa(JSON.stringify(jsonData)); - } catch (error) { - logRed('Error occurred creating kurtosis cloud url.'); - return ''; - } -} diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index fd3aa7a6bd..2ae0b5c359 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -7,35 +7,43 @@ import { CoreConfig, DeployedIsm, GasOracleContractType, - HookType, - HyperlaneAddresses, + HooksConfig, HyperlaneAddressesMap, HyperlaneContractsMap, + HyperlaneCore, HyperlaneCoreDeployer, HyperlaneDeploymentArtifacts, HyperlaneIsmFactory, HyperlaneProxyFactoryDeployer, IgpConfig, + IsmConfig, IsmType, MultiProvider, MultisigConfig, RoutingIsmConfig, - agentStartBlocks, buildAgentConfig, - buildMultisigIsmConfigs, + buildAggregationIsmConfigs, defaultMultisigConfigs, multisigIsmVerificationCost, serializeContractsMap, } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; -import { log, logBlue, logGray, logGreen, logRed } from '../../logger.js'; -import { readDeploymentArtifacts } from '../config/artifacts.js'; -import { readHookConfig } from '../config/hooks.js'; +import { + log, + logBlue, + logBoldUnderlinedRed, + logGray, + logGreen, + logRed, +} from '../../logger.js'; +import { runDeploymentArtifactStep } from '../config/artifacts.js'; +import { presetHookConfigs, readHooksConfigMap } from '../config/hooks.js'; +import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; import { - getContextWithSigner, + getContext, getMergedContractAddresses, sdkContractAddressesMap, } from '../context.js'; @@ -47,10 +55,10 @@ import { } from '../utils/files.js'; import { - TestRecipientConfig, - TestRecipientDeployer, -} from './TestRecipientDeployer.js'; -import { runPreflightChecksForChains } from './utils.js'; + isISMConfig, + isZODISMConfig, + runPreflightChecksForChains, +} from './utils.js'; export async function runCoreDeploy({ key, @@ -71,28 +79,42 @@ export async function runCoreDeploy({ outPath: string; skipConfirmation: boolean; }) { - const { customChains, multiProvider, signer } = getContextWithSigner( - key, + const { customChains, multiProvider, signer } = await getContext({ chainConfigPath, - ); + keyConfig: { key }, + skipConfirmation, + }); if (!chains?.length) { + if (skipConfirmation) throw new Error('No chains provided'); chains = await runMultiChainSelectionStep( customChains, 'Select chains to connect', + true, ); } - const artifacts = await runArtifactStep(chains, artifactsPath); - const multisigConfig = await runIsmStep(chains, ismConfigPath); - // TODO re-enable when hook config is actually used - await runHookStep(chains, hookConfigPath); + const artifacts = await runArtifactStep( + chains, + skipConfirmation, + artifactsPath, + ); + const result = await runIsmStep(chains, skipConfirmation, ismConfigPath); + // we can either specify the full ISM config or just the multisig config + const isIsmConfig = isISMConfig(result); + const ismConfigs = isIsmConfig ? (result as ChainMap) : undefined; + const multisigConfigs = isIsmConfig + ? defaultMultisigConfigs + : (result as ChainMap); + const hooksConfig = await runHookStep(chains, hookConfigPath); const deploymentParams: DeployParams = { chains, signer, multiProvider, artifacts, - multisigConfig, + ismConfigs, + multisigConfigs, + hooksConfig, outPath, skipConfirmation, }; @@ -105,95 +127,103 @@ export async function runCoreDeploy({ await executeDeploy(deploymentParams); } -async function runArtifactStep( +function runArtifactStep( selectedChains: ChainName[], + skipConfirmation: boolean, artifactsPath?: string, ) { - if (!artifactsPath) { - logBlue( - '\n', - 'Deployments can be totally new or can use some existing contract addresses.', - ); - const isResume = await confirm({ - message: 'Do you want use some existing contract addresses?', - }); - if (!isResume) return undefined; - - artifactsPath = await runFileSelectionStep( - './artifacts', - 'contract artifacts', - 'core-deployment', - ); - } - const artifacts = readDeploymentArtifacts(artifactsPath); - const artifactChains = Object.keys(artifacts).filter((c) => - selectedChains.includes(c), + logBlue( + '\nDeployments can be totally new or can use some existing contract addresses.', ); - log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); - return artifacts; + return runDeploymentArtifactStep({ + artifactsPath, + selectedChains, + skipConfirmation, + }); } -async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) { +async function runIsmStep( + selectedChains: ChainName[], + skipConfirmation: boolean, + ismConfigPath?: string, +) { 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', + 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/ism.yaml', ); + if (skipConfirmation) throw new Error('ISM config required'); ismConfigPath = await runFileSelectionStep( './configs', 'ISM config', 'ism', ); } - // first we check for user provided chains - const multisigConfigs = { - ...defaultMultisigConfigs, - ...readMultisigConfig(ismConfigPath), - } as ChainMap; - const requiredMultisigs = objFilter( - multisigConfigs, - (chain, config): config is MultisigConfig => selectedChains.includes(chain), - ); - // selected chains - (user configs + default configs) = missing config - const missingConfigs = selectedChains.filter( - (c) => !Object.keys(requiredMultisigs).includes(c), - ); - if (missingConfigs.length > 0) { - throw new Error( - `Missing ISM config for one or more chains: ${missingConfigs.join(', ')}`, + + const isAdvancedIsm = isZODISMConfig(ismConfigPath); + // separate flow for 'ism' and 'ism-advanced' options + if (isAdvancedIsm) { + logBoldUnderlinedRed( + 'WARNING: YOU ARE DEPLOYING WITH AN ADVANCED ISM CONFIG', ); - } + logRed( + 'Advanced ISM configs require knowledge of different ISM types and how they work together topologically. If possible, use the basic ISM configs are recommended.', + ); + const ismConfig = readIsmConfig(ismConfigPath); + const requiredIsms = objFilter( + ismConfig, + (chain, config): config is IsmConfig => selectedChains.includes(chain), + ); + // selected chains - (user configs + default configs) = missing config + const missingConfigs = selectedChains.filter( + (c) => !Object.keys(ismConfig).includes(c), + ); + if (missingConfigs.length > 0) { + throw new Error( + `Missing advanced ISM config for one or more chains: ${missingConfigs.join( + ', ', + )}`, + ); + } + + log(`Found configs for chains: ${selectedChains.join(', ')}`); + return requiredIsms as ChainMap; + } else { + const multisigConfigs = { + ...defaultMultisigConfigs, + ...readMultisigConfig(ismConfigPath), + } as ChainMap; + const requiredMultisigs = objFilter( + multisigConfigs, + (chain, config): config is MultisigConfig => + selectedChains.includes(chain), + ); + // selected chains - (user configs + default configs) = missing config + const missingConfigs = selectedChains.filter( + (c) => !Object.keys(requiredMultisigs).includes(c), + ); + if (missingConfigs.length > 0) { + throw new Error( + `Missing ISM config for one or more chains: ${missingConfigs.join( + ', ', + )}`, + ); + } - log(`Found configs for chains: ${selectedChains.join(', ')}`); - return requiredMultisigs; + log(`Found configs for chains: ${selectedChains.join(', ')}`); + return requiredMultisigs as ChainMap; + } } async function runHookStep( _selectedChains: ChainName[], hookConfigPath?: string, ) { - if ('TODO: Skip this step for now as values are unused') return; - - // const presetConfigChains = Object.keys(presetHookConfigs); - - if (!hookConfigPath) { - logBlue( - '\n', - 'Hyperlane instances can take an Interchain Security Module (ISM).', - ); - hookConfigPath = await runFileSelectionStep( - './configs/', - 'Hook config', - 'hook', - ); - } - const configs = readHookConfig(hookConfigPath); - if (!configs) return; - log(`Found hook configs for chains: ${Object.keys(configs).join(', ')}`); + if (!hookConfigPath) return {}; + return readHooksConfigMap(hookConfigPath); } interface DeployParams { @@ -201,7 +231,9 @@ interface DeployParams { signer: ethers.Signer; multiProvider: MultiProvider; artifacts?: HyperlaneAddressesMap; - multisigConfig?: ChainMap; + ismConfigs?: ChainMap; + multisigConfigs?: ChainMap; + hooksConfig?: ChainMap; outPath: string; skipConfirmation: boolean; } @@ -237,7 +269,9 @@ async function executeDeploy({ multiProvider, outPath, artifacts = {}, - multisigConfig = {}, + ismConfigs = {}, + multisigConfigs = {}, + hooksConfig = {}, }: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); @@ -247,7 +281,7 @@ async function executeDeploy({ ]); const owner = await signer.getAddress(); - const mergedContractAddrs = getMergedContractAddresses(artifacts); + const mergedContractAddrs = getMergedContractAddresses(artifacts, chains); // 1. Deploy ISM factories to all deployable chains that don't have them. logBlue('Deploying ISM factory contracts'); @@ -273,21 +307,15 @@ async function executeDeploy({ mergedContractAddrs, multiProvider, ); - - // 3. Deploy ISM contracts to remote deployable chains - logBlue('Deploying ISMs'); - const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; - const defaultIsms: ChainMap

= {}; + // 3. Construct ISM configs for all deployable chains + const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = {}; + const defaultIsms: ChainMap = {}; for (const ismOrigin of chains) { - logBlue(`Deploying ISM to ${ismOrigin}`); - const ismConfig = buildIsmConfig(owner, ismOrigin, chains, multisigConfig); - ismContracts[ismOrigin] = { - multisigIsm: await ismFactory.deploy(ismOrigin, ismConfig), - }; - defaultIsms[ismOrigin] = ismContracts[ismOrigin].multisigIsm.address; + defaultIsms[ismOrigin] = + ismConfigs[ismOrigin] ?? + buildIsmConfig(owner, ismOrigin, chains, multisigConfigs); } artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); - logGreen('ISM contracts deployed'); // 4. Deploy core contracts to chains logBlue(`Deploying core contracts to ${chains.join(', ')}`); @@ -297,27 +325,22 @@ async function executeDeploy({ owner, chains, defaultIsms, - multisigConfig, + hooksConfig, ); const coreContracts = await coreDeployer.deploy(coreConfigs); + + // 4.5 recover the toplevel ISM address + const isms: HyperlaneAddressesMap = {}; + for (const chain of chains) { + isms[chain] = { + interchainSecurityModule: + coreDeployer.cachedAddresses[chain].interchainSecurityModule, + }; + } + artifacts = objMerge(artifacts, isms); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); logGreen('Core contracts deployed'); - // 5. Deploy TestRecipients to all deployable chains - log('Deploying test recipient contracts'); - const testRecipientConfig = buildTestRecipientConfigMap(chains, artifacts); - const testRecipientDeployer = new TestRecipientDeployer(multiProvider); - testRecipientDeployer.cacheAddressesMap(mergedContractAddrs); - const testRecipients = await testRecipientDeployer.deploy( - testRecipientConfig, - ); - artifacts = writeMergedAddresses( - contractsFilePath, - artifacts, - testRecipients, - ); - logGreen('Test recipient contracts deployed'); - log('Writing agent configs'); await writeAgentConfig(agentFilePath, artifacts, chains, multiProvider); logGreen('Agent configs written'); @@ -333,8 +356,7 @@ function buildIsmConfig( chains: ChainName[], multisigIsmConfigs: ChainMap, ): RoutingIsmConfig { - const multisigConfigs = buildMultisigIsmConfigs( - IsmType.MESSAGE_ID_MULTISIG, + const aggregationIsmConfigs = buildAggregationIsmConfigs( local, chains, multisigIsmConfigs, @@ -342,64 +364,29 @@ function buildIsmConfig( return { owner, type: IsmType.ROUTING, - domains: multisigConfigs, + domains: aggregationIsmConfigs, }; } function buildCoreConfigMap( owner: Address, chains: ChainName[], - defaultIsms: ChainMap
, - multisigConfig: ChainMap, + defaultIsms: ChainMap, + hooksConfig: ChainMap, ): ChainMap { return chains.reduce>((config, chain) => { - const igpConfig = buildIgpConfigMap(owner, chains, multisigConfig); + const hooks = hooksConfig[chain] ?? presetHookConfigs(owner); config[chain] = { owner, defaultIsm: defaultIsms[chain], - defaultHook: { - type: HookType.AGGREGATION, - hooks: [ - { - type: HookType.MERKLE_TREE, - }, - { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - ...igpConfig[chain], - }, - ], - }, - requiredHook: { - type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: ethers.utils.parseUnits('0', 'wei'), // 1 wei - beneficiary: owner, - owner, - }, + defaultHook: hooks.default, + requiredHook: hooks.required, }; return config; }, {}); } -function buildTestRecipientConfigMap( - chains: ChainName[], - addressesMap: HyperlaneAddressesMap, -): ChainMap { - 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( +export function buildIgpConfigMap( owner: Address, chains: ChainName[], multisigConfigs: ChainMap, @@ -410,9 +397,16 @@ function buildIgpConfigMap( const gasOracleType: ChainMap = {}; for (const remote of chains) { if (chain === remote) continue; + // TODO: accurate estimate of gas from ChainMap + const threshold = multisigConfigs[remote] + ? multisigConfigs[remote].threshold + : 2; + const validatorsLength = multisigConfigs[remote] + ? multisigConfigs[remote].validators.length + : 3; overhead[remote] = multisigIsmVerificationCost( - multisigConfigs[chain].threshold, - multisigConfigs[chain].validators.length, + threshold, + validatorsLength, ); gasOracleType[remote] = GasOracleContractType.StorageGasOracle; } @@ -444,33 +438,29 @@ async function writeAgentConfig( chains: ChainName[], multiProvider: MultiProvider, ) { - // TODO: share with rust/config/* - const startBlocks: ChainMap = { ...agentStartBlocks }; + const startBlocks: ChainMap = {}; + const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider); for (const chain of chains) { - if (startBlocks[chain]) continue; - startBlocks[chain] = await multiProvider - .getProvider(chain) - .getBlockNumber(); + const mailbox = core.getContracts(chain).mailbox; + startBlocks[chain] = (await mailbox.deployedBlock()).toNumber(); } - const mergedAddressesMap: HyperlaneAddressesMap = objMerge( + const mergedAddressesMap = objMerge( sdkContractAddressesMap, artifacts, - ); - const filteredAddressesMap = objFilter( - mergedAddressesMap, - (chain, v): v is HyperlaneAddresses => - chains.includes(chain) && - !!v.mailbox && - !!v.interchainGasPaymaster && - !!v.validatorAnnounce, ) as ChainMap; + for (const chain of chains) { + if (!mergedAddressesMap[chain].interchainGasPaymaster) { + mergedAddressesMap[chain].interchainGasPaymaster = + ethers.constants.AddressZero; + } + } const agentConfig = buildAgentConfig( - Object.keys(filteredAddressesMap), + chains, // Use only the chains that were deployed to multiProvider, - filteredAddressesMap, + mergedAddressesMap, startBlocks, ); writeJson(filePath, agentConfig); diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 871a96c5c6..477c50d30f 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -1,9 +1,16 @@ import { ethers } from 'ethers'; -import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainName, + IsmConfig, + MultiProvider, + MultisigConfig, +} from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { log, logGreen } from '../../logger.js'; +import { parseIsmConfig } from '../config/ism.js'; import { assertGasBalances } from '../utils/balances.js'; import { assertSigner } from '../utils/keys.js'; @@ -13,12 +20,14 @@ export async function runPreflightChecks({ signer, multiProvider, minGas, + chainsToGasCheck, }: { origin: ChainName; remotes: ChainName[]; signer: ethers.Signer; multiProvider: MultiProvider; minGas: string; + chainsToGasCheck?: ChainName[]; }) { log('Running pre-flight checks...'); @@ -30,6 +39,7 @@ export async function runPreflightChecks({ signer, multiProvider, minGas, + chainsToGasCheck, }); } @@ -38,11 +48,15 @@ export async function runPreflightChecksForChains({ signer, multiProvider, minGas, + chainsToGasCheck, }: { chains: ChainName[]; signer: ethers.Signer; multiProvider: MultiProvider; minGas: string; + // Chains for which to assert a native balance + // Defaults to all chains if not specified + chainsToGasCheck?: ChainName[]; }) { log('Running pre-flight checks...'); @@ -58,6 +72,23 @@ export async function runPreflightChecksForChains({ assertSigner(signer); logGreen('Signer is valid ✅'); - await assertGasBalances(multiProvider, signer, chains, minGas); + await assertGasBalances( + multiProvider, + signer, + chainsToGasCheck ?? chains, + minGas, + ); logGreen('Balances are sufficient ✅'); } + +// from parsed types +export function isISMConfig( + config: ChainMap | ChainMap, +): boolean { + return Object.values(config).some((c) => 'type' in c); +} + +// directly from filepath +export function isZODISMConfig(filepath: string): boolean { + return parseIsmConfig(filepath).success; +} diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index d44b75e82b..adc70844d6 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -20,13 +20,9 @@ import { import { Address, ProtocolType, 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_GAS } from '../consts.js'; -import { - getContextWithSigner, - getMergedContractAddresses, -} from '../context.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; import { isFile, prepNewArtifactsFiles, @@ -52,9 +48,15 @@ export async function runWarpDeploy({ outPath: string; skipConfirmation: boolean; }) { - const { multiProvider, signer } = getContextWithSigner(key, chainConfigPath); + const { multiProvider, signer, coreArtifacts } = await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + keyConfig: { key }, + skipConfirmation, + }); if (!warpConfigPath || !isFile(warpConfigPath)) { + if (skipConfirmation) throw new Error('Warp config required'); warpConfigPath = await runFileSelectionStep( './configs', 'Warp config', @@ -65,15 +67,12 @@ export async function runWarpDeploy({ } const warpRouteConfig = readWarpRouteConfig(warpConfigPath); - const artifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; - const configs = await runBuildConfigStep({ warpRouteConfig, - artifacts, + coreArtifacts, multiProvider, signer, + skipConfirmation, }); const deploymentParams = { @@ -98,12 +97,14 @@ async function runBuildConfigStep({ warpRouteConfig, multiProvider, signer, - artifacts, + coreArtifacts, + skipConfirmation, }: { warpRouteConfig: WarpRouteConfig; multiProvider: MultiProvider; signer: ethers.Signer; - artifacts?: HyperlaneContractsMap; + coreArtifacts?: HyperlaneContractsMap; + skipConfirmation: boolean; }) { log('Assembling token configs'); const { base, synthetics } = warpRouteConfig; @@ -116,7 +117,10 @@ async function runBuildConfigStep({ `Using base token metadata: Name: ${baseMetadata.name}, Symbol: ${baseMetadata.symbol}, Decimals: ${baseMetadata.decimals}`, ); - const mergedContractAddrs = getMergedContractAddresses(artifacts); + const mergedContractAddrs = getMergedContractAddresses( + coreArtifacts, + Object.keys(warpRouteConfig), + ); // Create configs that coalesce together values from the config file, // the artifacts, and the SDK as a fallback @@ -165,6 +169,8 @@ async function runBuildConfigStep({ for (const [chain, token] of Object.entries(configMap)) { for (const field of requiredRouterFields) { if (token[field]) continue; + if (skipConfirmation) + throw new Error(`Field ${field} for token on ${chain} required`); 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.', diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 09d1028522..563b3de62f 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -9,39 +9,50 @@ import { 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_GAS } from '../consts.js'; -import { - getContextWithSigner, - getMergedContractAddresses, -} from '../context.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; -const MESSAGE_BODY = '0x48656c6c6f21'; // Hello!' - -// TODO improve the UX here by making params optional and -// prompting for missing values export async function sendTestMessage({ key, chainConfigPath, coreArtifactsPath, origin, destination, + messageBody, timeoutSec, skipWaitForDelivery, }: { key: string; chainConfigPath: string; - coreArtifactsPath: string; - origin: ChainName; - destination: ChainName; + coreArtifactsPath?: string; + origin?: ChainName; + destination?: ChainName; + messageBody: string; timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); - const coreArtifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; + const { signer, multiProvider, customChains, coreArtifacts } = + await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + keyConfig: { key }, + }); + + if (!origin) { + origin = await runSingleChainSelectionStep( + customChains, + 'Select the origin chain', + ); + } + + if (!destination) { + destination = await runSingleChainSelectionStep( + customChains, + 'Select the destination chain', + ); + } await runPreflightChecks({ origin, @@ -49,12 +60,14 @@ export async function sendTestMessage({ multiProvider, signer, minGas: MINIMUM_TEST_SEND_GAS, + chainsToGasCheck: [origin], }); await timeout( executeDelivery({ origin, destination, + messageBody, multiProvider, coreArtifacts, skipWaitForDelivery, @@ -67,12 +80,14 @@ export async function sendTestMessage({ async function executeDelivery({ origin, destination, + messageBody, multiProvider, coreArtifacts, skipWaitForDelivery, }: { origin: ChainName; destination: ChainName; + messageBody: string; multiProvider: MultiProvider; coreArtifacts?: HyperlaneContractsMap; skipWaitForDelivery: boolean; @@ -84,6 +99,14 @@ async function executeDelivery({ ); const mailbox = core.getContracts(origin).mailbox; + let hook = mergedContractAddrs[origin]?.customHook; + if (hook) { + logBlue(`Using custom hook ${hook} for ${origin} -> ${destination}`); + } else { + hook = await mailbox.defaultHook(); + logBlue(`Using default hook ${hook} for ${origin} -> ${destination}`); + } + const destinationDomain = multiProvider.getDomainId(destination); let txReceipt: ethers.ContractReceipt; try { @@ -94,19 +117,29 @@ async function executeDelivery({ const formattedRecipient = addressToBytes32(recipient); log('Getting gas quote'); - const value = await mailbox['quoteDispatch(uint32,bytes32,bytes)']( + const value = await mailbox[ + 'quoteDispatch(uint32,bytes32,bytes,bytes,address)' + ]( destinationDomain, formattedRecipient, - MESSAGE_BODY, + messageBody, + ethers.utils.hexlify([]), + hook, ); log(`Paying for gas with ${value} wei`); log('Dispatching message'); - const messageTx = await mailbox['dispatch(uint32,bytes32,bytes)']( + const messageTx = await mailbox[ + 'dispatch(uint32,bytes32,bytes,bytes,address)' + ]( destinationDomain, formattedRecipient, - MESSAGE_BODY, - { value }, + messageBody, + ethers.utils.hexlify([]), + hook, + { + value, + }, ); txReceipt = await multiProvider.handleTx(origin, messageTx); const message = core.getDispatchedMessages(txReceipt)[0]; diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 5e413309ba..76889665cd 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -1,14 +1,19 @@ -import { BigNumber, ethers } from 'ethers'; +import { input } from '@inquirer/prompts'; +import { PopulatedTransaction, ethers } from 'ethers'; import { ERC20__factory, HypERC20Collateral__factory, + HypERC20__factory, } from '@hyperlane-xyz/core'; import { ChainName, EvmHypCollateralAdapter, + EvmHypNativeAdapter, + EvmHypSyntheticAdapter, HyperlaneContractsMap, HyperlaneCore, + IHypTokenAdapter, MultiProtocolProvider, MultiProvider, TokenType, @@ -16,17 +21,12 @@ import { import { Address, timeout } from '@hyperlane-xyz/utils'; import { log, logBlue, logGreen } from '../../logger.js'; -import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; -import { - getContextWithSigner, - getMergedContractAddresses, -} from '../context.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; -// TODO improve the UX here by making params optional and -// prompting for missing values export async function sendTestTransfer({ key, chainConfigPath, @@ -34,7 +34,6 @@ export async function sendTestTransfer({ origin, destination, routerAddress, - tokenType, wei, recipient, timeoutSec, @@ -42,35 +41,79 @@ export async function sendTestTransfer({ }: { key: string; chainConfigPath: string; - coreArtifactsPath: string; - origin: ChainName; - destination: ChainName; - routerAddress: Address; - tokenType: TokenType; + coreArtifactsPath?: string; + origin?: ChainName; + destination?: ChainName; + routerAddress?: Address; wei: string; recipient?: string; timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); - const artifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; + const { signer, multiProvider, customChains, coreArtifacts } = + await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + keyConfig: { key }, + }); + + if (!origin) { + origin = await runSingleChainSelectionStep( + customChains, + 'Select the origin chain', + ); + } - if (tokenType === TokenType.collateral) { + if (!destination) { + destination = await runSingleChainSelectionStep( + customChains, + 'Select the destination chain', + ); + } + + if (!routerAddress) { + routerAddress = await input({ + message: 'Please specify the router address', + }); + } + + // TODO: move to SDK token router app + // deduce TokenType + // 1. decimals() call implies synthetic + // 2. wrappedToken() call implies collateral + // 3. if neither, it's native + let tokenAddress: Address | undefined; + let tokenType: TokenType; + const provider = multiProvider.getProvider(origin); + try { + const synthRouter = HypERC20__factory.connect(routerAddress, provider); + await synthRouter.decimals(); + tokenType = TokenType.synthetic; + tokenAddress = routerAddress; + } catch (error) { + try { + const collateralRouter = HypERC20Collateral__factory.connect( + routerAddress, + provider, + ); + tokenAddress = await collateralRouter.wrappedToken(); + tokenType = TokenType.collateral; + } catch (error) { + tokenType = TokenType.native; + } + } + + if (tokenAddress) { + // checks token balances for collateral and synthetic await assertTokenBalance( multiProvider, signer, origin, - routerAddress, + tokenAddress, 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 assertNativeBalances(multiProvider, signer, [origin], wei.toString()); } await runPreflightChecks({ @@ -79,6 +122,7 @@ export async function sendTestTransfer({ multiProvider, signer, minGas: MINIMUM_TEST_SEND_GAS, + chainsToGasCheck: [origin], }); await timeout( @@ -91,7 +135,7 @@ export async function sendTestTransfer({ recipient, signer, multiProvider, - artifacts, + coreArtifacts, skipWaitForDelivery, }), timeoutSec * 1000, @@ -108,7 +152,7 @@ async function executeDelivery({ recipient, multiProvider, signer, - artifacts, + coreArtifacts, skipWaitForDelivery, }: { origin: ChainName; @@ -119,13 +163,13 @@ async function executeDelivery({ recipient?: string; multiProvider: MultiProvider; signer: ethers.Signer; - artifacts?: HyperlaneContractsMap; + coreArtifacts?: HyperlaneContractsMap; skipWaitForDelivery: boolean; }) { const signerAddress = await signer.getAddress(); recipient ||= signerAddress; - const mergedContractAddrs = getMergedContractAddresses(artifacts); + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const core = HyperlaneCore.fromAddressesMap( mergedContractAddrs, @@ -135,6 +179,9 @@ async function executeDelivery({ const provider = multiProvider.getProvider(origin); const connectedSigner = signer.connect(provider); + // TODO replace all code below with WarpCore + // https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3259 + if (tokenType === TokenType.collateral) { const wrappedToken = await getWrappedToken(routerAddress, provider); const token = ERC20__factory.connect(wrappedToken, connectedSigner); @@ -145,24 +192,33 @@ async function executeDelivery({ } } - // TODO move next section into MultiProtocolTokenApp when it exists - const adapter = new EvmHypCollateralAdapter( - origin, - MultiProtocolProvider.fromMultiProvider(multiProvider), - { token: routerAddress }, - ); + let adapter: IHypTokenAdapter; + const multiProtocolProvider = + MultiProtocolProvider.fromMultiProvider(multiProvider); + if (tokenType === TokenType.native) { + adapter = new EvmHypNativeAdapter(origin, multiProtocolProvider, { + token: routerAddress, + }); + } else if (tokenType === TokenType.collateral) { + adapter = new EvmHypCollateralAdapter(origin, multiProtocolProvider, { + token: routerAddress, + }); + } else { + adapter = new EvmHypSyntheticAdapter(origin, multiProtocolProvider, { + token: routerAddress, + }); + } + 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({ + log('Fetching interchain gas quote'); + const interchainGas = await adapter.quoteGasPayment(destinationDomain); + log('Interchain gas quote:', interchainGas); + const transferTx = (await adapter.populateTransferRemoteTx({ weiAmountOrId: wei, destination: destinationDomain, recipient, - txValue, - }); + interchainGas, + })) as ethers.PopulatedTransaction; const txResponse = await connectedSigner.sendTransaction(transferTx); const txReceipt = await multiProvider.handleTx(origin, txResponse); diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts index 933b75cea2..0001f9be0e 100644 --- a/typescript/cli/src/status/message.ts +++ b/typescript/cli/src/status/message.ts @@ -1,8 +1,10 @@ +import { input } from '@inquirer/prompts'; + 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'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; export async function checkMessageStatus({ chainConfigPath, @@ -11,14 +13,27 @@ export async function checkMessageStatus({ destination, }: { chainConfigPath: string; - coreArtifactsPath: string; - messageId: string; - destination: ChainName; + coreArtifactsPath?: string; + messageId?: string; + destination?: ChainName; }) { - const { multiProvider } = getContext(chainConfigPath); - const coreArtifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; + const { multiProvider, customChains, coreArtifacts } = await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + }); + + if (!destination) { + destination = await runSingleChainSelectionStep( + customChains, + 'Select the destination chain', + ); + } + + if (!messageId) { + messageId = await input({ + message: 'Please specify the message id', + }); + } const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const core = HyperlaneCore.fromAddressesMap( diff --git a/typescript/cli/src/tests/hooks.test.ts b/typescript/cli/src/tests/hooks.test.ts new file mode 100644 index 0000000000..2848c0c061 --- /dev/null +++ b/typescript/cli/src/tests/hooks.test.ts @@ -0,0 +1,92 @@ +import { expect } from 'chai'; + +import { + ChainMap, + GasOracleContractType, + HookType, + HooksConfig, +} from '@hyperlane-xyz/sdk'; + +import { readHooksConfigMap } from '../config/hooks.js'; + +describe('readHooksConfigMap', () => { + it('parses and validates example correctly', () => { + const hooks = readHooksConfigMap('examples/hooks.yaml'); + + const exampleHooksConfig: ChainMap = { + anvil1: { + required: { + type: HookType.PROTOCOL_FEE, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + maxProtocolFee: '1000000000000000000', + protocolFee: '200000000000000', + }, + default: { + type: HookType.ROUTING, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + domains: { + anvil2: { + type: HookType.AGGREGATION, + hooks: [ + { + type: HookType.MERKLE_TREE, + }, + { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + gasOracleType: { + anvil2: GasOracleContractType.StorageGasOracle, + }, + overhead: { anvil2: 50000 }, + }, + ], + }, + }, + }, + }, + anvil2: { + required: { + type: HookType.PROTOCOL_FEE, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + maxProtocolFee: '1000000000000000000', + protocolFee: '200000000000000', + }, + default: { + type: HookType.ROUTING, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + domains: { + anvil1: { + type: HookType.AGGREGATION, + hooks: [ + { + type: HookType.MERKLE_TREE, + }, + { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + gasOracleType: { + anvil1: GasOracleContractType.StorageGasOracle, + }, + overhead: { anvil1: 50000 }, + }, + ], + }, + }, + }, + }, + }; + expect(hooks).to.deep.equal(exampleHooksConfig); + }); + + it('parsing failure, missing internal key "overhead"', () => { + expect(() => { + readHooksConfigMap('src/tests/hooks/safe-parse-fail.yaml'); + }).to.throw('Invalid hook config: anvil2,default => Invalid input'); + }); +}); diff --git a/typescript/cli/src/tests/hooks/safe-parse-fail.yaml b/typescript/cli/src/tests/hooks/safe-parse-fail.yaml new file mode 100644 index 0000000000..4a2a5cedbf --- /dev/null +++ b/typescript/cli/src/tests/hooks/safe-parse-fail.yaml @@ -0,0 +1,44 @@ +anvil1: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' + protocolFee: '200000000000000' + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil2: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + overhead: + anvil2: 50000 + gasOracleType: + anvil2: StorageGasOracle +anvil2: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' + protocolFee: '200000000000000' + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil1: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + gasOracleType: + anvil1: StorageGasOracle diff --git a/typescript/cli/src/tests/ism.test.ts b/typescript/cli/src/tests/ism.test.ts new file mode 100644 index 0000000000..4942963cf1 --- /dev/null +++ b/typescript/cli/src/tests/ism.test.ts @@ -0,0 +1,85 @@ +import { expect } from 'chai'; + +import { ChainMap, IsmConfig, IsmType } from '@hyperlane-xyz/sdk'; + +import { readIsmConfig } from '../config/ism.js'; + +describe('readIsmConfig', () => { + it('parses and validates example correctly', () => { + const ism = readIsmConfig('examples/ism-advanced.yaml'); + + const exampleIsmConfig: ChainMap = { + anvil1: { + type: IsmType.FALLBACK_ROUTING, + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', + domains: { + anvil2: { + type: IsmType.AGGREGATION, + modules: [ + { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + { + type: IsmType.MERKLE_ROOT_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + ], + threshold: 1, + }, + }, + }, + anvil2: { + type: IsmType.ROUTING, + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', + domains: { + anvil1: { + type: IsmType.AGGREGATION, + modules: [ + { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + { + type: IsmType.MERKLE_ROOT_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + ], + threshold: 1, + }, + }, + }, + }; + expect(ism).to.deep.equal(exampleIsmConfig); + }); + + it('parsing failure, missing internal key "threshold"', () => { + expect(function () { + readIsmConfig('src/tests/ism/safe-parse-fail.yaml'); + }).to.throw(); + }); + + it('parsing failure, routingIsm.domains includes destination domain', () => { + expect(function () { + readIsmConfig('src/tests/ism/routing-same-chain-fail.yaml'); + }).to.throw( + 'Cannot set RoutingIsm.domain to the same chain you are configuring', + ); + }); + + it('parsing failure, wrong ism type', () => { + expect(function () { + readIsmConfig('src/tests/ism/wrong-ism-type-fail.yaml'); + }).to.throw('Invalid ISM config: anvil2 => Invalid input'); + }); + + it('parsing failure, threshold > modules.length', () => { + expect(function () { + readIsmConfig('src/tests/ism/threshold-gt-modules-length-fail.yaml'); + }).to.throw('Threshold cannot be greater than number of modules'); + }); +}); diff --git a/typescript/cli/src/tests/ism/routing-same-chain-fail.yaml b/typescript/cli/src/tests/ism/routing-same-chain-fail.yaml new file mode 100644 index 0000000000..f78998cc36 --- /dev/null +++ b/typescript/cli/src/tests/ism/routing-same-chain-fail.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 diff --git a/typescript/cli/src/tests/ism/safe-parse-fail.yaml b/typescript/cli/src/tests/ism/safe-parse-fail.yaml new file mode 100644 index 0000000000..195d2a08b3 --- /dev/null +++ b/typescript/cli/src/tests/ism/safe-parse-fail.yaml @@ -0,0 +1,32 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml b/typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml new file mode 100644 index 0000000000..670b560388 --- /dev/null +++ b/typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 3 diff --git a/typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml b/typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml new file mode 100644 index 0000000000..7395c718de --- /dev/null +++ b/typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: domainRoutingIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 diff --git a/typescript/cli/src/tests/multisig.test.ts b/typescript/cli/src/tests/multisig.test.ts new file mode 100644 index 0000000000..f56b3e7bdd --- /dev/null +++ b/typescript/cli/src/tests/multisig.test.ts @@ -0,0 +1,41 @@ +import { expect } from 'chai'; + +import { ChainMap, MultisigConfig } from '@hyperlane-xyz/sdk'; + +import { readMultisigConfig } from '../config/multisig.js'; + +describe('readMultisigConfig', () => { + it('parses and validates example correctly', () => { + const multisig = readMultisigConfig('examples/ism.yaml'); + + const exampleMultisigConfig: ChainMap = { + anvil1: { + threshold: 1, + validators: ['0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'], + }, + anvil2: { + threshold: 1, + validators: ['0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'], + }, + }; + expect(multisig).to.deep.equal(exampleMultisigConfig); + }); + + it('parsing failure', () => { + expect(function () { + readMultisigConfig('src/tests/multisig/safe-parse-fail.yaml'); + }).to.throw('Invalid multisig config: anvil2,validators => Required'); + }); + + it('threshold cannot be greater than the # of validators', () => { + expect(function () { + readMultisigConfig('src/tests/multisig/threshold-gt-fail.yaml'); + }).to.throw('Threshold cannot be greater than number of validators'); + }); + + it('invalid address', () => { + expect(function () { + readMultisigConfig('src/tests/multisig/invalid-address-fail.yaml'); + }).to.throw('Invalid multisig config: anvil2,validators,0 => Invalid'); + }); +}); diff --git a/typescript/cli/src/tests/multisig/invalid-address-fail.yaml b/typescript/cli/src/tests/multisig/invalid-address-fail.yaml new file mode 100644 index 0000000000..073a76189a --- /dev/null +++ b/typescript/cli/src/tests/multisig/invalid-address-fail.yaml @@ -0,0 +1,8 @@ +anvil1: + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + threshold: 1 + validators: + - '0xa0ee7a142d267c1n36714e4a8f7561f20a79720' diff --git a/typescript/cli/src/tests/multisig/safe-parse-fail.yaml b/typescript/cli/src/tests/multisig/safe-parse-fail.yaml new file mode 100644 index 0000000000..a19e8868c6 --- /dev/null +++ b/typescript/cli/src/tests/multisig/safe-parse-fail.yaml @@ -0,0 +1,6 @@ +anvil1: + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + threshold: 1 diff --git a/typescript/cli/src/tests/multisig/threshold-gt-fail.yaml b/typescript/cli/src/tests/multisig/threshold-gt-fail.yaml new file mode 100644 index 0000000000..ae3a3fb001 --- /dev/null +++ b/typescript/cli/src/tests/multisig/threshold-gt-fail.yaml @@ -0,0 +1,8 @@ +anvil1: + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + threshold: 3 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index c3a07a7eeb..e273498abd 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -32,6 +32,7 @@ export async function runSingleChainSelectionStep( export async function runMultiChainSelectionStep( customChains: ChainMap, message = 'Select chains', + requireMultiple = false, ) { const choices = getChainChoices(customChains); while (true) { @@ -42,8 +43,11 @@ export async function runMultiChainSelectionStep( pageSize: 20, })) as string[]; handleNewChain(chains); - if (chains?.length >= 2) return chains; - else logRed('Please select at least 2 chains'); + if (requireMultiple && chains?.length < 2) { + logRed('Please select at least 2 chains'); + continue; + } + return chains; } } diff --git a/typescript/cli/src/utils/env.ts b/typescript/cli/src/utils/env.ts new file mode 100644 index 0000000000..6f2fb060a6 --- /dev/null +++ b/typescript/cli/src/utils/env.ts @@ -0,0 +1,9 @@ +import z from 'zod'; + +const envScheme = z.object({ + HYP_KEY: z.string().optional(), +}); + +const parsedEnv = envScheme.safeParse(process.env); + +export const ENV = parsedEnv.success ? parsedEnv.data : {}; diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index aa0cfd65f5..afed211ffa 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -163,11 +163,16 @@ export async function runFileSelectionStep( description: string, pattern?: string, ) { + const noFilesErrorMessage = `No "${description}" found in ${folderPath}. Please confirm the path for "${description}". By default, the CLI writes to folders relative to where its run.`; + if (!fs.existsSync(folderPath)) throw new Error(noFilesErrorMessage); + let filenames = fs.readdirSync(folderPath); if (pattern) { filenames = filenames.filter((f) => f.includes(pattern)); } + if (filenames.length === 0) throw new Error(noFilesErrorMessage); + let filename = (await select({ message: `Select ${description} file`, choices: [ diff --git a/typescript/cli/src/utils/version-check.ts b/typescript/cli/src/utils/version-check.ts new file mode 100644 index 0000000000..6d59d77170 --- /dev/null +++ b/typescript/cli/src/utils/version-check.ts @@ -0,0 +1,11 @@ +import latestVersion from 'latest-version'; + +import { log } from '../../logger.js'; +import { VERSION } from '../version.js'; + +export async function checkVersion() { + const currentVersion = await latestVersion('@hyperlane-xyz/cli'); + if (VERSION < currentVersion) { + log(`Your CLI version: ${VERSION}, latest version: ${currentVersion}`); + } +} diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts new file mode 100644 index 0000000000..457dafcc43 --- /dev/null +++ b/typescript/cli/src/version.ts @@ -0,0 +1 @@ +export const VERSION = '3.7.0'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 4a824ed605..1b8a4b9fcb 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,94 @@ # @hyperlane-xyz/helloworld +## 3.7.0 + +### Patch Changes + +- Updated dependencies [6f464eaed] +- Updated dependencies [87151c62b] +- Updated dependencies [ab17af5f7] +- Updated dependencies [7b40232af] +- Updated dependencies [54aeb6420] + - @hyperlane-xyz/sdk@3.7.0 + - @hyperlane-xyz/core@3.7.0 + +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/core@3.6.2 +- @hyperlane-xyz/sdk@3.6.2 + +## 3.6.1 + +### Patch Changes + +- Updated dependencies [ae4476ad0] +- Updated dependencies [f3b7ddb69] +- Updated dependencies [e4e4f93fc] + - @hyperlane-xyz/sdk@3.6.1 + - @hyperlane-xyz/core@3.6.1 + +## 3.6.0 + +### Patch Changes + +- Updated dependencies [67a6d971e] +- Updated dependencies [612d4163a] +- Updated dependencies [0488ef31d] +- Updated dependencies [8d8ba3f7a] + - @hyperlane-xyz/sdk@3.6.0 + - @hyperlane-xyz/core@3.6.0 + +## 3.5.1 + +### Patch Changes + +- Updated dependencies [a04454d6d] + - @hyperlane-xyz/sdk@3.5.1 + - @hyperlane-xyz/core@3.5.1 + +## 3.5.0 + +### Patch Changes + +- Updated dependencies [655b6a0cd] +- Updated dependencies [08ba0d32b] +- Updated dependencies [f7d285e3a] + - @hyperlane-xyz/sdk@3.5.0 + - @hyperlane-xyz/core@3.5.0 + +## 3.4.0 + +### Patch Changes + +- Updated dependencies [7919417ec] +- Updated dependencies [fd4fc1898] +- Updated dependencies [e06fe0b32] +- Updated dependencies [b832e57ae] +- Updated dependencies [79c96d718] + - @hyperlane-xyz/sdk@3.4.0 + - @hyperlane-xyz/core@3.4.0 + +## 3.3.0 + +### Patch Changes + +- Updated dependencies [7e620c9df] +- Updated dependencies [350175581] +- Updated dependencies [9f2c7ce7c] + - @hyperlane-xyz/sdk@3.3.0 + - @hyperlane-xyz/core@3.3.0 + +## 3.2.0 + +### Patch Changes + +- Updated dependencies [df34198d4] +- Updated dependencies [df693708b] + - @hyperlane-xyz/core@3.2.0 + - @hyperlane-xyz/sdk@3.2.0 + ## 3.1.10 ### Patch Changes diff --git a/typescript/helloworld/hardhat.config.ts b/typescript/helloworld/hardhat.config.ts index 9efa6c0a93..ef7ce035c4 100644 --- a/typescript/helloworld/hardhat.config.ts +++ b/typescript/helloworld/hardhat.config.ts @@ -9,11 +9,7 @@ import 'solidity-coverage'; */ module.exports = { solidity: { - compilers: [ - { - version: '0.8.19', - }, - ], + version: '0.8.19', }, gasReporter: { currency: 'USD', diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 6df75b0b54..356e404669 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.1.10", + "version": "3.7.0", "dependencies": { - "@hyperlane-xyz/core": "3.1.10", - "@hyperlane-xyz/sdk": "3.1.10", + "@hyperlane-xyz/core": "3.7.0", + "@hyperlane-xyz/sdk": "3.7.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, @@ -57,6 +57,7 @@ "lint": "solhint contracts/**/*.sol && eslint . --ext .ts", "prettier": "prettier --write ./contracts ./src", "test": "hardhat test ./src/test/**/*.test.ts", + "test:ci": "yarn test", "sync": "ts-node scripts/sync-with-template-repo.ts" }, "types": "dist/src/index.d.ts", diff --git a/typescript/helloworld/src/deploy/deploy.ts b/typescript/helloworld/src/deploy/deploy.ts index 495b46cb57..8b9d67b890 100644 --- a/typescript/helloworld/src/deploy/deploy.ts +++ b/typescript/helloworld/src/deploy/deploy.ts @@ -2,6 +2,7 @@ import { ethers } from 'ethers'; import { ChainName, + ContractVerifier, HyperlaneContracts, HyperlaneIsmFactory, HyperlaneRouterDeployer, @@ -20,8 +21,12 @@ export class HelloWorldDeployer extends HyperlaneRouterDeployer< constructor( multiProvider: MultiProvider, readonly ismFactory?: HyperlaneIsmFactory, + readonly contractVerifier?: ContractVerifier, ) { - super(multiProvider, helloWorldFactories, { ismFactory }); + super(multiProvider, helloWorldFactories, { + ismFactory, + contractVerifier, + }); } router(contracts: HyperlaneContracts): HelloWorld { diff --git a/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts b/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts index 481c1eb34d..0d565f4657 100644 --- a/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts +++ b/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts @@ -29,6 +29,7 @@ export class EvmHelloWorldAdapter destination: ChainName, message: string, value: string, + sender: Address, ): Promise { const contract = this.getConnectedContract(); const toDomain = this.multiProvider.getDomainId(destination); @@ -44,7 +45,14 @@ export class EvmHelloWorldAdapter const estimated = await contract.estimateGas.sendHelloWorld( toDomain, message, - { ...transactionOverrides, value: BigNumber.from(value).add(quote) }, + { + ...transactionOverrides, + // Some networks, like PolygonZkEvm, require a `from` address + // with funds to be specified when estimating gas for a transaction + // that provides non-zero `value`. + from: sender, + value: BigNumber.from(value).add(quote), + }, ); const gasLimit = estimated.mul(12).div(10); diff --git a/typescript/helloworld/src/test/helloworld.test.ts b/typescript/helloworld/src/test/helloworld.test.ts index 5fdf43c6e6..5d9f1e062e 100644 --- a/typescript/helloworld/src/test/helloworld.test.ts +++ b/typescript/helloworld/src/test/helloworld.test.ts @@ -82,7 +82,7 @@ describe('HelloWorld', async () => { local.sendHelloWorld(remoteDomain, body, { value: 0, }), - ).to.be.revertedWith('StaticProtocolFee: insufficient protocol fee'); + ).to.be.revertedWith('ProtocolFee: insufficient protocol fee'); }); it('handles a message', async () => { diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 1c059b7c0b..23c8734dc8 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,122 @@ # @hyperlane-xyz/infra +## 3.7.0 + +### Minor Changes + +- 54aeb6420: Added warp route artifacts type adopting registry schema + +### Patch Changes + +- 87151c62b: Bumped injective reorg period +- ab17af5f7: Updating HyperlaneIgpDeployer to configure storage gas oracles as part of deployment +- Updated dependencies [6f464eaed] +- Updated dependencies [87151c62b] +- Updated dependencies [ab17af5f7] +- Updated dependencies [7b40232af] +- Updated dependencies [54aeb6420] + - @hyperlane-xyz/sdk@3.7.0 + - @hyperlane-xyz/helloworld@3.7.0 + - @hyperlane-xyz/utils@3.7.0 + +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/helloworld@3.6.2 +- @hyperlane-xyz/sdk@3.6.2 +- @hyperlane-xyz/utils@3.6.2 + +## 3.6.1 + +### Patch Changes + +- ae4476ad0: Bumped mantapacific reorgPeriod to 1, a reorg period in chain metadata is now required by infra. +- e4e4f93fc: Support pausable ISM in deployer and checker +- Updated dependencies [3c298d064] +- Updated dependencies [ae4476ad0] +- Updated dependencies [f3b7ddb69] +- Updated dependencies [df24eec8b] +- Updated dependencies [78e50e7da] +- Updated dependencies [e4e4f93fc] + - @hyperlane-xyz/utils@3.6.1 + - @hyperlane-xyz/sdk@3.6.1 + - @hyperlane-xyz/helloworld@3.6.1 + +## 3.6.0 + +### Patch Changes + +- 67a6d971e: Added `shouldRecover` flag to deployContractFromFactory so that the `TestRecipientDeployer` can deploy new contracts if it's not the owner of the prior deployments (We were recovering the SDK artifacts which meant the deployer won't be able to set the ISM as they needed) +- Updated dependencies [67a6d971e] +- Updated dependencies [612d4163a] +- Updated dependencies [0488ef31d] +- Updated dependencies [8d8ba3f7a] + - @hyperlane-xyz/sdk@3.6.0 + - @hyperlane-xyz/helloworld@3.6.0 + - @hyperlane-xyz/utils@3.6.0 + +## 3.5.1 + +### Patch Changes + +- Updated dependencies [a04454d6d] + - @hyperlane-xyz/sdk@3.5.1 + - @hyperlane-xyz/helloworld@3.5.1 + - @hyperlane-xyz/utils@3.5.1 + +## 3.5.0 + +### Minor Changes + +- 655b6a0cd: Redeploy Routing ISM Factories + +### Patch Changes + +- f7d285e3a: Adds Test Recipient addresses to the SDK artifacts +- Updated dependencies [655b6a0cd] +- Updated dependencies [08ba0d32b] +- Updated dependencies [f7d285e3a] + - @hyperlane-xyz/sdk@3.5.0 + - @hyperlane-xyz/helloworld@3.5.0 + - @hyperlane-xyz/utils@3.5.0 + +## 3.4.0 + +### Patch Changes + +- e06fe0b32: Supporting DefaultFallbackRoutingIsm through non-factory deployments +- Updated dependencies [7919417ec] +- Updated dependencies [fd4fc1898] +- Updated dependencies [e06fe0b32] +- Updated dependencies [b832e57ae] +- Updated dependencies [79c96d718] + - @hyperlane-xyz/sdk@3.4.0 + - @hyperlane-xyz/utils@3.4.0 + - @hyperlane-xyz/helloworld@3.4.0 + +## 3.3.0 + +### Patch Changes + +- 7e620c9df: Allow CLI to accept hook as a config +- 9f2c7ce7c: Removing agentStartBlocks and using mailbox.deployedBlock() instead +- Updated dependencies [7e620c9df] +- Updated dependencies [350175581] +- Updated dependencies [9f2c7ce7c] + - @hyperlane-xyz/sdk@3.3.0 + - @hyperlane-xyz/helloworld@3.3.0 + - @hyperlane-xyz/utils@3.3.0 + +## 3.2.0 + +### Patch Changes + +- Updated dependencies [df693708b] + - @hyperlane-xyz/sdk@3.2.0 + - @hyperlane-xyz/helloworld@3.2.0 + - @hyperlane-xyz/utils@3.2.0 + ## 3.1.10 ### Patch Changes diff --git a/typescript/infra/config/aw-multisig.json b/typescript/infra/config/aw-multisig.json new file mode 100644 index 0000000000..e873050443 --- /dev/null +++ b/typescript/infra/config/aw-multisig.json @@ -0,0 +1,75 @@ +{ + "alfajores": { + "validators": [ + "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", + "0xba279f965489d90f90490e3c49e860e0b43c2ae6", + "0x86485dcec5f7bb8478dd251676372d054dea6653" + ] + }, + "fuji": { + "validators": [ + "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", + "0x895ae30bc83ff1493b9cf7781b0b813d23659857", + "0x43e915573d9f1383cbf482049e4a012290759e7f" + ] + }, + "mumbai": { + "validators": [ + "0xebc301013b6cd2548e347c28d2dc43ec20c068f2", + "0x315db9868fc8813b221b1694f8760ece39f45447", + "0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5" + ] + }, + "bsctestnet": { + "validators": [ + "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", + "0xf620f5e3d25a3ae848fec74bccae5de3edcd8796", + "0x1f030345963c54ff8229720dd3a711c15c554aeb" + ] + }, + "goerli": { + "validators": [ + "0x05a9b5efe9f61f9142453d8e9f61565f333c6768", + "0x43a96c7dfbd8187c95013d6ee8665650cbdb2673", + "0x7940a12c050e24e1839c21ecb12f65afd84e8c5b" + ] + }, + "scrollsepolia": { + "validators": [ + "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", + "0x9a11ed23ae962974018ab45bc133caabff7b3271", + "0x7867bea3c9761fe64e6d124b171f91fd5dd79644" + ] + }, + "sepolia": { + "validators": [ + "0xb22b65f202558adf86a8bb2847b76ae1036686a5", + "0x469f0940684d147defc44f3647146cb90dd0bc8e", + "0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83" + ] + }, + "optimismgoerli": { + "validators": [ + "0x79e58546e2faca865c6732ad5f6c4951051c4d67", + "0x7bbfe1bb7146aad7df309c637987d856179ebbc1", + "0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40" + ] + }, + "arbitrumgoerli": { + "validators": [ + "0x071c8d135845ae5a2cb73f98d681d519014c0a8b", + "0x1bcf03360989f15cbeb174c188288f2c6d2760d7", + "0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d" + ] + }, + "polygonzkevmtestnet": { + "validators": [ + "0x3f06b725bc9648917eb11c414e9f8d76fd959550", + "0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636", + "0xd476548222f43206d0abaa30e46e28670aa7859c" + ] + }, + "plumetestnet": { + "validators": ["0xe765a214849f3ecdf00793b97d00422f2d408ea6"] + } +} diff --git a/typescript/infra/config/contexts.ts b/typescript/infra/config/contexts.ts index a8df3d4d65..6c9e551700 100644 --- a/typescript/infra/config/contexts.ts +++ b/typescript/infra/config/contexts.ts @@ -2,4 +2,5 @@ export enum Contexts { Hyperlane = 'hyperlane', ReleaseCandidate = 'rc', + Neutron = 'neutron', } diff --git a/typescript/infra/config/environments/agents.ts b/typescript/infra/config/environments/agents.ts new file mode 100644 index 0000000000..f9f7055aa7 --- /dev/null +++ b/typescript/infra/config/environments/agents.ts @@ -0,0 +1,9 @@ +import { agents as mainnet3Agents } from './mainnet3/agent'; +import { agents as testAgents } from './test/agent'; +import { agents as testnet4Agents } from './testnet4/agent'; + +export const agents = { + mainnet3: mainnet3Agents, + testnet4: testnet4Agents, + test: testAgents, +}; diff --git a/typescript/infra/config/environments/helloworld.ts b/typescript/infra/config/environments/helloworld.ts new file mode 100644 index 0000000000..104b3c01ee --- /dev/null +++ b/typescript/infra/config/environments/helloworld.ts @@ -0,0 +1,7 @@ +import { helloWorld as mainnet3HelloWorld } from './mainnet3/helloworld'; +import { helloWorld as testnet4HelloWorld } from './testnet4/helloworld'; + +export const helloworld = { + mainnet3: mainnet3HelloWorld, + testnet4: testnet4HelloWorld, +}; diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index f190ad7731..ea038e2549 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -2,15 +2,23 @@ import { GasPaymentEnforcementPolicyType, RpcConsensusType, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; import { RootAgentConfig, allAgentChainNames } from '../../../src/config'; -import { GasPaymentEnforcementConfig } from '../../../src/config/agent/relayer'; +import { + GasPaymentEnforcementConfig, + routerMatchingList, +} from '../../../src/config/agent/relayer'; import { ALL_KEY_ROLES, Role } from '../../../src/roles'; import { Contexts } from '../../contexts'; -import { agentChainNames, environment } from './chains'; +import { agentChainNames, environment, ethereumChainNames } from './chains'; +import { helloWorld } from './helloworld'; import { validatorChainConfig } from './validators'; +import arbitrumTIAAddresses from './warp/arbitrum-TIA-addresses.json'; +import injectiveInevmAddresses from './warp/injective-inevm-addresses.json'; +import mantaTIAAddresses from './warp/manta-TIA-addresses.json'; // const releaseCandidateHelloworldMatchingList = routerMatchingList( // helloWorld[Contexts.ReleaseCandidate].addresses, @@ -42,14 +50,26 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '54aeb64-20240206-163119', }, gasPaymentEnforcement, + metricAppContexts: [ + { + name: 'helloworld', + matchingList: routerMatchingList( + helloWorld[Contexts.Hyperlane].addresses, + ), + }, + { + name: 'injective_inevm_inj', + matchingList: routerMatchingList(injectiveInevmAddresses), + }, + ], }, validators: { docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '54aeb64-20240206-163119', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -58,7 +78,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '54aeb64-20240206-163119', }, }, }; @@ -66,12 +86,16 @@ const hyperlane: RootAgentConfig = { const releaseCandidate: RootAgentConfig = { ...contextBase, context: Contexts.ReleaseCandidate, + contextChainNames: { + ...contextBase.contextChainNames, + [Role.Validator]: ethereumChainNames, + }, rolesWithKeys: [Role.Relayer, Role.Kathy, Role.Validator], relayer: { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '35fdc74-20230913-104940', + tag: '54aeb64-20240206-163119', }, // whitelist: releaseCandidateHelloworldMatchingList, gasPaymentEnforcement, @@ -83,14 +107,67 @@ const releaseCandidate: RootAgentConfig = { validators: { docker: { repo, - tag: 'ed7569d-20230725-171222', + tag: '54aeb64-20240206-163119', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.ReleaseCandidate), }, }; +const neutron: RootAgentConfig = { + ...contextBase, + contextChainNames: { + validator: [], + relayer: [ + chainMetadata.neutron.name, + chainMetadata.mantapacific.name, + chainMetadata.arbitrum.name, + ], + scraper: [], + }, + context: Contexts.Neutron, + rolesWithKeys: [Role.Relayer], + relayer: { + rpcConsensusType: RpcConsensusType.Fallback, + docker: { + repo, + tag: '54aeb64-20240206-163119', + }, + gasPaymentEnforcement: [ + { + type: GasPaymentEnforcementPolicyType.None, + matchingList: [ + { + originDomain: getDomainId(chainMetadata.neutron), + destinationDomain: getDomainId(chainMetadata.mantapacific), + senderAddress: '*', + recipientAddress: '*', + }, + { + originDomain: getDomainId(chainMetadata.neutron), + destinationDomain: getDomainId(chainMetadata.arbitrum), + senderAddress: '*', + recipientAddress: '*', + }, + ], + }, + ...gasPaymentEnforcement, + ], + metricAppContexts: [ + { + name: 'manta_tia', + matchingList: routerMatchingList(mantaTIAAddresses), + }, + { + name: 'arbitrum_tia', + matchingList: routerMatchingList(arbitrumTIAAddresses), + }, + ], + }, +}; + export const agents = { [Contexts.Hyperlane]: hyperlane, [Contexts.ReleaseCandidate]: releaseCandidate, + [Contexts.Neutron]: neutron, }; diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 14465a19ce..60a30dbefe 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -1,16 +1,28 @@ -import { ChainMap, ChainMetadata, chainMetadata } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainMetadata, + Chains, + Mainnets, + chainMetadata, +} from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { AgentChainNames, Role } from '../../../src/roles'; +const defaultEthereumMainnetConfigs = Object.fromEntries( + Mainnets.map((chain) => chainMetadata[chain]) + .filter((metadata) => metadata.protocol === ProtocolType.Ethereum) + .map((metadata) => [metadata.name, metadata]), +); + export const ethereumMainnetConfigs: ChainMap = { + ...defaultEthereumMainnetConfigs, bsc: { ...chainMetadata.bsc, transactionOverrides: { gasPrice: 7 * 10 ** 9, // 7 gwei }, }, - avalanche: chainMetadata.avalanche, - base: chainMetadata.base, polygon: { ...chainMetadata.polygon, blocks: { @@ -18,16 +30,11 @@ export const ethereumMainnetConfigs: ChainMap = { confirmations: 3, }, transactionOverrides: { - maxFeePerGas: 500 * 10 ** 9, // 500 gwei - maxPriorityFeePerGas: 100 * 10 ** 9, // 100 gwei + maxFeePerGas: 250 * 10 ** 9, // 250 gwei + maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei // gasPrice: 50 * 10 ** 9, // 50 gwei }, }, - polygonzkevm: chainMetadata.polygonzkevm, - scroll: chainMetadata.scroll, - celo: chainMetadata.celo, - arbitrum: chainMetadata.arbitrum, - optimism: chainMetadata.optimism, ethereum: { ...chainMetadata.ethereum, blocks: { @@ -39,18 +46,18 @@ export const ethereumMainnetConfigs: ChainMap = { maxPriorityFeePerGas: 5 * 10 ** 9, // gwei }, }, - moonbeam: chainMetadata.moonbeam, - gnosis: chainMetadata.gnosis, }; // Blessed non-Ethereum chains. -// export const nonEthereumMainnetConfigs: ChainMap = { -// solana: chainMetadata.solana, -// }; +export const nonEthereumMainnetConfigs: ChainMap = { + // solana: chainMetadata.solana, + // neutron: chainMetadata.neutron, + injective: chainMetadata.injective, +}; export const mainnetConfigs: ChainMap = { ...ethereumMainnetConfigs, - // ...nonEthereumMainnetConfigs, + ...nonEthereumMainnetConfigs, }; export type MainnetChains = keyof typeof mainnetConfigs; @@ -63,16 +70,22 @@ export const ethereumChainNames = Object.keys( ethereumMainnetConfigs, ) as MainnetChains[]; -const validatorChainNames = [ - ...supportedChainNames, - // chainMetadata.solana.name, - // chainMetadata.nautilus.name, -]; +// Remove mantapacific, as it's not considered a "blessed" +// chain and we don't relay to mantapacific on the Hyperlane or RC contexts. +const relayerHyperlaneContextChains = supportedChainNames.filter( + (chainName) => chainName !== Chains.mantapacific, +); -const relayerChainNames = validatorChainNames; +// Ethereum chains only. +const scraperHyperlaneContextChains = ethereumChainNames.filter( + // Has RPC non-compliance that breaks scraping. + (chainName) => chainName !== Chains.viction, +); +// Hyperlane & RC context agent chain names. export const agentChainNames: AgentChainNames = { - [Role.Validator]: validatorChainNames, - [Role.Relayer]: relayerChainNames, - [Role.Scraper]: ethereumChainNames, + // Run validators for all chains. + [Role.Validator]: supportedChainNames, + [Role.Relayer]: relayerHyperlaneContextChains, + [Role.Scraper]: scraperHyperlaneContextChains, }; diff --git a/typescript/infra/config/environments/mainnet3/core.ts b/typescript/infra/config/environments/mainnet3/core.ts index 54405eb532..8dc23b6a38 100644 --- a/typescript/infra/config/environments/mainnet3/core.ts +++ b/typescript/infra/config/environments/mainnet3/core.ts @@ -2,23 +2,68 @@ import { BigNumber, ethers } from 'ethers'; import { AggregationHookConfig, + AggregationIsmConfig, ChainMap, CoreConfig, + FallbackRoutingHookConfig, HookType, IgpHookConfig, + IsmType, MerkleTreeHookConfig, + MultisigConfig, + MultisigIsmConfig, + PausableHookConfig, + PausableIsmConfig, ProtocolFeeHookConfig, + RoutingIsmConfig, + defaultMultisigConfigs, } from '@hyperlane-xyz/sdk'; import { objMap } from '@hyperlane-xyz/utils'; -import { Contexts } from '../../contexts'; -import { routingIsm } from '../../routingIsm'; - +import { supportedChainNames } from './chains'; import { igp } from './igp'; -import { owners, safes } from './owners'; +import { owners } from './owners'; export const core: ChainMap = objMap(owners, (local, owner) => { - const defaultIsm = routingIsm('mainnet3', local, Contexts.Hyperlane); + const originMultisigs: ChainMap = Object.fromEntries( + supportedChainNames + .filter((chain) => chain !== local) + .map((origin) => [origin, defaultMultisigConfigs[origin]]), + ); + + const merkleRoot = (multisig: MultisigConfig): MultisigIsmConfig => ({ + type: IsmType.MERKLE_ROOT_MULTISIG, + ...multisig, + }); + + const messageIdIsm = (multisig: MultisigConfig): MultisigIsmConfig => ({ + type: IsmType.MESSAGE_ID_MULTISIG, + ...multisig, + }); + + const routingIsm: RoutingIsmConfig = { + type: IsmType.ROUTING, + domains: objMap( + originMultisigs, + (_, multisig): AggregationIsmConfig => ({ + type: IsmType.AGGREGATION, + modules: [messageIdIsm(multisig), merkleRoot(multisig)], + threshold: 1, + }), + ), + ...owner, + }; + + const pausableIsm: PausableIsmConfig = { + type: IsmType.PAUSABLE, + ...owner, + }; + + const defaultIsm: AggregationIsmConfig = { + type: IsmType.AGGREGATION, + modules: [routingIsm, pausableIsm], + threshold: 2, + }; const merkleHook: MerkleTreeHookConfig = { type: HookType.MERKLE_TREE, @@ -29,29 +74,36 @@ export const core: ChainMap = objMap(owners, (local, owner) => { ...igp[local], }; - const defaultHook: AggregationHookConfig = { - type: HookType.AGGREGATION, - hooks: [merkleHook, igpHook], + const pausableHook: PausableHookConfig = { + type: HookType.PAUSABLE, + ...owner, + }; + const aggregationHooks = objMap( + originMultisigs, + (_origin, _): AggregationHookConfig => ({ + type: HookType.AGGREGATION, + hooks: [pausableHook, merkleHook, igpHook], + }), + ); + const defaultHook: FallbackRoutingHookConfig = { + type: HookType.FALLBACK_ROUTING, + ...owner, + domains: aggregationHooks, + fallback: merkleHook, }; const requiredHook: ProtocolFeeHookConfig = { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(0), // 0 wei - beneficiary: owner, - owner, + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(0).toString(), // 0 wei + beneficiary: owner.owner, + ...owner, }; return { - owner, defaultIsm, defaultHook, requiredHook, - ownerOverrides: { - proxyAdmin: - local === 'arbitrum' - ? `0xAC98b0cD1B64EA4fe133C6D2EDaf842cE5cF4b01` - : safes[local] ?? owner, - }, + ...owner, }; }); diff --git a/typescript/infra/config/environments/mainnet3/core/verification.json b/typescript/infra/config/environments/mainnet3/core/verification.json index 1f81718f07..c559233f17 100644 --- a/typescript/infra/config/environments/mainnet3/core/verification.json +++ b/typescript/infra/config/environments/mainnet3/core/verification.json @@ -119,6 +119,54 @@ "address": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false } ], "arbitrum": [ @@ -241,6 +289,54 @@ "address": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08", "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb9", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb9000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000748040afb89b8fdbb992799808215419d36a0930", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb9000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000748040afb89b8fdbb992799808215419d36a0930", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb9000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000748040afb89b8fdbb992799808215419d36a0930", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb9000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000748040afb89b8fdbb992799808215419d36a0930", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "constructorArguments": "", + "isProxy": false } ], "base": [ @@ -339,6 +435,54 @@ "address": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B", "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000019dc38aeae620380430c200a6e990d5af5480117", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000019dc38aeae620380430c200a6e990d5af5480117", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000019dc38aeae620380430c200a6e990d5af5480117", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000019dc38aeae620380430c200a6e990d5af5480117", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "constructorArguments": "", + "isProxy": false } ], "avalanche": [ @@ -461,6 +605,54 @@ "address": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f", "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd6", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd6000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000084eea61d679f42d92145fa052c89900cbacce95a", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd6000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000084eea61d679f42d92145fa052c89900cbacce95a", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd6000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000084eea61d679f42d92145fa052c89900cbacce95a", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd6000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000084eea61d679f42d92145fa052c89900cbacce95a", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "constructorArguments": "", + "isProxy": false } ], "scroll": [ @@ -619,6 +811,54 @@ "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "constructorArguments": "", + "isProxy": false } ], "polygonzkevm": [ @@ -777,6 +1017,42 @@ "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "constructorArguments": "", + "isProxy": false } ], "bsc": [ @@ -871,6 +1147,54 @@ "address": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002684c6f89e901987e1fdb7649dc5be0c57c61645", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002684c6f89e901987e1fdb7649dc5be0c57c61645", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002684c6f89e901987e1fdb7649dc5be0c57c61645", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002684c6f89e901987e1fdb7649dc5be0c57c61645", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "constructorArguments": "", + "isProxy": false } ], "celo": [ @@ -1115,6 +1439,54 @@ "address": "0xCe74905e51497b4adD3639366708b821dcBcff96", "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000048e6c30b97748d1e2e03bf3e9fbe3890ca5f8cca", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000048e6c30b97748d1e2e03bf3e9fbe3890ca5f8cca", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000048e6c30b97748d1e2e03bf3e9fbe3890ca5f8cca", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000048e6c30b97748d1e2e03bf3e9fbe3890ca5f8cca", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "constructorArguments": "", + "isProxy": false } ], "moonbeam": [ @@ -1237,6 +1609,54 @@ "address": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "constructorArguments": "", + "isProxy": false } ], "polygon": [ @@ -1288,5 +1708,437 @@ "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb", "isProxy": false } + ], + "mantapacific": [ + { + "name": "ProxyAdmin", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000a9", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f112", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x7556a0E61d577D921Cba8Fca0d7D6299d36E607E", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f112", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x7556a0E61d577D921Cba8Fca0d7D6299d36E607E", + "constructorArguments": "", + "isProxy": false + } + ], + "viction": [ + { + "name": "ProxyAdmin", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "ProxyAdmin", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "ProxyAdmin", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + } + ], + "inevm": [ + { + "name": "ProxyAdmin", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000009dd", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "000000000000000000000000481171eb1aad17ede6a56005b7f1ab00c581ef130000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "000000000000000000000000481171eb1aad17ede6a56005b7f1ab00c581ef130000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x15ab173bDB6832f9b64276bA128659b0eD77730B", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false + } ] } diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index eeb834569e..f2744eea4b 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -9,7 +9,7 @@ import { environment } from './chains'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '4a3e6ee-20231025-192258', + tag: 'c037206-20240220-152500', }, // We're currently using the same deployer key as mainnet. // To minimize nonce clobbering we offset the key funder cron @@ -21,7 +21,40 @@ export const keyFunderConfig: KeyFunderConfig = { contextFundingFrom: Contexts.Hyperlane, contextsAndRolesToFund: { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], - // [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], + [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, connectionType: RpcConsensusType.Single, + // desired balance config + desiredBalancePerChain: { + arbitrum: '3', + avalanche: '5', + base: '3', + bsc: '5', + celo: '3', + ethereum: '5', + gnosis: '5', + inevm: '3', + moonbeam: '5', + optimism: '3', + polygon: '20', + polygonzkevm: '3', + scroll: '3', + viction: '3', + }, + desiredKathyBalancePerChain: { + arbitrum: '0.1', + avalanche: '6', + base: '0.05', + bsc: '0.35', + celo: '150', + ethereum: '0.4', + gnosis: '100', + inevm: '0.05', + moonbeam: '250', + optimism: '0.1', + polygon: '85', + polygonzkevm: '0.05', + scroll: '0.05', + viction: '0.05', + }, }; diff --git a/typescript/infra/config/environments/mainnet3/gas-oracle.ts b/typescript/infra/config/environments/mainnet3/gas-oracle.ts index c3dc53ec6d..e27166fbcc 100644 --- a/typescript/infra/config/environments/mainnet3/gas-oracle.ts +++ b/typescript/infra/config/environments/mainnet3/gas-oracle.ts @@ -1,6 +1,7 @@ import { BigNumber, ethers } from 'ethers'; import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; +import { objMap } from '@hyperlane-xyz/utils'; import { AllStorageGasOracleConfigs, @@ -12,78 +13,18 @@ import { } from '../../../src/config/gas-oracle'; import { supportedChainNames } from './chains'; - -// Taken by looking at each network's gas history and overestimating -// Last updated Mar 9, 2023. -const gasPrices: ChainMap = { - // https://bscscan.com/chart/gasprice - bsc: ethers.utils.parseUnits('7', 'gwei'), - // https://snowtrace.io/chart/gasprice - avalanche: ethers.utils.parseUnits('35', 'gwei'), - // https://polygonscan.com/chart/gasprice - polygon: ethers.utils.parseUnits('200', 'gwei'), - // https://celoscan.io/chart/gasprice - // This one is interesting - the average is high (~20 gwei) - // but the median is low (< 10). This is likely because a popular wallet is - // overpaying, but all our txs tend to be < 10 gwei. - celo: ethers.utils.parseUnits('10', 'gwei'), - // https://dune.com/Henrystats/arbitrum-metrics - // A bit higher to try to account for L1 fees - arbitrum: ethers.utils.parseUnits('1', 'gwei'), - // https://dune.com/optimismfnd/optimism-l1-batch-submission-fees-security-costs - // A bit higher to try to account for L1 fees - optimism: ethers.utils.parseUnits('1', 'gwei'), - // https://dune.com/hildobby/Gas - ethereum: ethers.utils.parseUnits('35', 'gwei'), - // https://moonscan.io/chart/gasprice - // Similar to Celo - average is ~200 gwei, but people - // generally are overpaying compared to us - moonbeam: ethers.utils.parseUnits('150', 'gwei'), - // https://gnosisscan.io/chart/gasprice - // People also seem to be overpaying here - gnosis: ethers.utils.parseUnits('2', 'gwei'), - // Arbitrarily chosen as gas prices aren't really a thing - // in Solana. - solana: ethers.BigNumber.from('28'), - base: ethers.utils.parseUnits('1', 'gwei'), - scroll: ethers.utils.parseUnits('1', 'gwei'), - polygonzkevm: ethers.utils.parseUnits('2', 'gwei'), -}; - -// Accurate from coingecko as of Mar 9, 2023. -// These aren't overestimates because the exchange rates between -// tokens are what matters. These generally have high beta -const tokenUsdPrices: ChainMap = { - // https://www.coingecko.com/en/coins/bnb - bsc: ethers.utils.parseUnits('211.55', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/avalanche - avalanche: ethers.utils.parseUnits('9.25', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/polygon - polygon: ethers.utils.parseUnits('0.518', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/celo - celo: ethers.utils.parseUnits('0.42', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/ethereum - arbitrum: ethers.utils.parseUnits('1619.00', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/ethereum - optimism: ethers.utils.parseUnits('1619.00', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/ethereum - ethereum: ethers.utils.parseUnits('1619.00', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/moonbeam - moonbeam: ethers.utils.parseUnits('0.166', TOKEN_EXCHANGE_RATE_DECIMALS), - // xDAI - gnosis: ethers.utils.parseUnits('1.00', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/solana - solana: ethers.utils.parseUnits('18.85', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/ethereum - base: ethers.utils.parseUnits('1619.00', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/ethereum - scroll: ethers.utils.parseUnits('1619.00', TOKEN_EXCHANGE_RATE_DECIMALS), - // https://www.coingecko.com/en/coins/ethereum - polygonzkevm: ethers.utils.parseUnits( - '1619.00', - TOKEN_EXCHANGE_RATE_DECIMALS, - ), -}; +import rawGasPrices from './gasPrices.json'; +import rawTokenPrices from './tokenPrices.json'; + +const gasPrices: ChainMap = objMap(rawGasPrices, (_, gasPrice) => + ethers.utils.parseUnits(gasPrice, 'gwei'), +); + +const tokenUsdPrices: ChainMap = objMap( + rawTokenPrices, + (_, tokenUsdPrice) => + ethers.utils.parseUnits(tokenUsdPrice, TOKEN_EXCHANGE_RATE_DECIMALS), +); // Gets the exchange rate of the remote quoted in local tokens function getTokenExchangeRate(local: ChainName, remote: ChainName): BigNumber { diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json new file mode 100644 index 0000000000..1796b74f7a --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -0,0 +1,18 @@ +{ + "arbitrum": "0.1", + "avalanche": "43.212830197", + "bsc": "1.350070319", + "celo": "10.0", + "ethereum": "26.346912847", + "mantapacific": "0.100000057", + "moonbeam": "125.0", + "optimism": "0.003225814", + "polygon": "61.601287856", + "gnosis": "1.852997796", + "base": "0.0010003", + "scroll": "0.46", + "polygonzkevm": "3.95", + "inevm": "0.1", + "viction": "0.25", + "injective": "0.1" +} diff --git a/typescript/infra/config/environments/mainnet3/helloworld.ts b/typescript/infra/config/environments/mainnet3/helloworld.ts index 4f0fdde31a..82cf5692ac 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld.ts +++ b/typescript/infra/config/environments/mainnet3/helloworld.ts @@ -13,19 +13,19 @@ export const hyperlane: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'bbe8928-20231025-215311', + tag: '86b7f98-20231207-153806', }, chainsToSkip: [], runEnv: environment, namespace: environment, runConfig: { mode: HelloWorldKathyRunMode.Service, - fullCycleTime: 1000 * 60 * 60 * 24, // every 24 hours + fullCycleTime: 1000 * 60 * 60 * 24 * 5, // every 5 days, 13 * 12 messages = 156 messages is little less than once an hour }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min connectionType: RpcConsensusType.Fallback, - cyclesBetweenEthereumMessages: 3, // Skip 3 cycles of Ethereum, i.e. send/receive Ethereum messages every 32 hours. + cyclesBetweenEthereumMessages: 1, // Skip 1 cycle of Ethereum, i.e. send/receive Ethereum messages every 5 days (not great since we still send like 12 in that cycle) }, }; @@ -34,7 +34,7 @@ export const releaseCandidate: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'bef2251-20231025-174850', + tag: '0e3f73f-20240206-160718', }, chainsToSkip: [], runEnv: environment, @@ -44,11 +44,12 @@ export const releaseCandidate: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Single, + connectionType: RpcConsensusType.Fallback, }, }; export const helloWorld = { [Contexts.Hyperlane]: hyperlane, - // [Contexts.ReleaseCandidate]: releaseCandidate, + [Contexts.ReleaseCandidate]: releaseCandidate, + [Contexts.Neutron]: undefined, }; diff --git a/typescript/infra/config/environments/mainnet3/helloworld/hyperlane/verification.json b/typescript/infra/config/environments/mainnet3/helloworld/hyperlane/verification.json index 8df25db64d..37b6b7e5be 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld/hyperlane/verification.json +++ b/typescript/infra/config/environments/mainnet3/helloworld/hyperlane/verification.json @@ -7,13 +7,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xB97d3bF2fC296c2cAC4056bBC8A783ff39408e20", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x22Fd11F93F0303346c9b9070cc67C4Bc7aB2dABB", "constructorArguments": "0000000000000000000000002971b9aec44be4eb673df1b88cdb57b96eefe8a40000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -27,13 +27,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x2A925CD8a5d919c5c6599633090c37fe38A561b6", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xd54fF402ADf0a7CBad9626B1261bF4bEB26A437a", "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd60000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -47,13 +47,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x6c0aC8cEA75232aa7BeD8cbe9C4f820E7a77a9C3", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x42ad84C5d2a90e574678958133B8a13CA9F44AAF", "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -67,13 +67,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x4151773Db70C0b2D4c43Ea44A5FB5803ff1d3e0B", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x10Add66Db9C837000A43F3601aa9c54c6744F4c8", "constructorArguments": "00000000000000000000000050da3b3907a08a24fe4999f4dcf337e8dc7954bb0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -87,13 +87,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x96271cA0ab9eeFB3Ca481749c0Ca4c705fD4F523", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xb2BFFD0248C7a0029860A29fD4Efe762a22f93Af", "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb90000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -107,13 +107,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x3da5fDCcC661c84454f49dB0Cf519561BC7c2729", "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -127,13 +127,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x9311cEE522A7C122B843b66cC31C6a63e2F92641", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x7ff2bF58C38A41AD7C9CbC14e780e8a7EDBbd48D", "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d2390000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -147,13 +147,13 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xAe067C08703508230357025B38c35Cd12793628c", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xE25d0D46a42a106Cdf4943262644DD5E26fa7bfF", "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc30000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -167,19 +167,19 @@ "isProxy": false }, { - "name": "router", + "name": "HelloWorld", "address": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4", "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xA166219dF110BDA97b91e65D4BB4Aae4159978b9", "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -187,7 +187,7 @@ ], "base": [ { - "name": "Router", + "name": "HelloWorld", "address": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -195,7 +195,7 @@ ], "scroll": [ { - "name": "Router", + "name": "HelloWorld", "address": "0x0be2Ae2f6D02a3e0e00ECB57D3E1fCbb7f8F38F4", "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -203,7 +203,7 @@ ], "polygonzkevm": [ { - "name": "Router", + "name": "HelloWorld", "address": "0xaad207a0Fd7a4e3C927Ccc78ac8134baF586B852", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false diff --git a/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json b/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json index 0967ef424b..d87d332c9a 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json +++ b/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json @@ -1 +1,47 @@ -{} +{ + "polygon": { + "router": "0xd5D06f8Ee9cfab362e5758A0A925db7470E7D22f" + }, + "bsc": { + "router": "0xC728F24AA2442d6230c9785635b81fF73C1a16Db" + }, + "arbitrum": { + "router": "0x4D172025D810DDD770e7464A41673ca8e75539b0" + }, + "optimism": { + "router": "0x8Fc3AdBF87c74dF6142f6D65aE0f8BFe042BBDd0" + }, + "moonbeam": { + "router": "0x1dA36d5c79Ae8Bc43eC080FeD9B4Dbb91b509834" + }, + "gnosis": { + "router": "0x9B0C41777A0fC5dd040BC8B043991Eb168f3bD9C" + }, + "base": { + "router": "0x6756189BDE3a29bb56466DECb50BBA76543D8752" + }, + "scroll": { + "router": "0x5366362c41e34869BDa231061603E4356D66079D" + }, + "polygonzkevm": { + "router": "0x03cF708E42C89623bd83B281A56935cB562b9258" + }, + "celo": { + "router": "0xcfacC141f090E5441D8F274659D43ec20F748b19" + }, + "ethereum": { + "router": "0xC6B1e375550343cDA762d2efD4EbdB3B8609a7a4" + }, + "avalanche": { + "router": "0x24fb6e8E7F8298696BaeE10B15bB57295a1f1e35" + }, + "mantapacific": { + "router": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762" + }, + "viction": { + "router": "0x83c2DB237e93Ce52565AB110124f78fdf159E3f4" + }, + "inevm": { + "router": "0x0BD07E3934D1C4cc8Db0eA2a5cDAc8C8d8eb9824" + } +} diff --git a/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json b/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json index bd533c7149..a95fa4d775 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json +++ b/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json @@ -1,127 +1,121 @@ { - "bsc": [ - { - "name": "router", - "address": "0x3f4663873A9aC7Ec683a5Bddc0acbC00091c10D0", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", - "isProxy": false - }, + "polygon": [ { - "name": "router", - "address": "0xe5554478F167936dB253f79f57c41770bfa00Bae", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0xd5D06f8Ee9cfab362e5758A0A925db7470E7D22f", + "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "avalanche": [ - { - "name": "router", - "address": "0xaC5a4925d8aab7B9fb33F0a1722e3b94b6f87dB4", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", - "isProxy": false - }, + "bsc": [ { - "name": "router", - "address": "0xe1De9910fe71cC216490AC7FCF019e13a34481D7", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0xC728F24AA2442d6230c9785635b81fF73C1a16Db", + "constructorArguments": "0000000000000000000000002971b9aec44be4eb673df1b88cdb57b96eefe8a40000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "polygon": [ + "arbitrum": [ { - "name": "router", - "address": "0x3Ee06Da5110117c5B9AD41e2F827B774cBb15CC3", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x4D172025D810DDD770e7464A41673ca8e75539b0", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb90000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "optimism": [ { - "name": "router", - "address": "0xAb65C41a1BC580a52f0b166879122EFdce0cB868", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x8Fc3AdBF87c74dF6142f6D65aE0f8BFe042BBDd0", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "celo": [ + "moonbeam": [ { - "name": "router", - "address": "0x8E10405F4D23060b1a75005EFB99d99D537e0A7f", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x1dA36d5c79Ae8Bc43eC080FeD9B4Dbb91b509834", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc30000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "gnosis": [ { - "name": "router", - "address": "0xfE29f6a4468536029Fc9c97d3a9669b9fe38E114", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x9B0C41777A0fC5dd040BC8B043991Eb168f3bD9C", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "arbitrum": [ + "base": [ { - "name": "router", - "address": "0xb44a2B834FFdf762051Ee26Eb41531a4A02fA8d0", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x6756189BDE3a29bb56466DECb50BBA76543D8752", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "scroll": [ { - "name": "router", - "address": "0x414B67F62b143d6db6E9b633168Dd6fd4DA20642", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x5366362c41e34869BDa231061603E4356D66079D", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "optimism": [ + "polygonzkevm": [ { - "name": "router", - "address": "0xe5F7E241F9bb6A644e88f2ca38fC373196b5392b", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x03cF708E42C89623bd83B281A56935cB562b9258", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "celo": [ { - "name": "router", - "address": "0xB4caf2CA864B413DAA502fA18A8D48cD0740fC52", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0xcfacC141f090E5441D8F274659D43ec20F748b19", + "constructorArguments": "00000000000000000000000050da3b3907a08a24fe4999f4dcf337e8dc7954bb0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], "ethereum": [ { - "name": "router", - "address": "0x43ae568363c4FA6897EE9dF0c9ca445d3872c906", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", - "isProxy": false - }, - { - "name": "router", - "address": "0xed31c20c5517EaC05decD5F6dCd01Fe6d16fD09D", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0xC6B1e375550343cDA762d2efD4EbdB3B8609a7a4", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d2390000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "moonbeam": [ + "avalanche": [ { - "name": "router", - "address": "0x1Efd1EdC42ef6cf2612F75e6C599C081d650c513", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x24fb6e8E7F8298696BaeE10B15bB57295a1f1e35", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd60000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "mantapacific": [ { - "name": "router", - "address": "0x3eB9eE2CFC8DCB6F58B5869D33336CFcBf1dC354", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "gnosis": [ + "viction": [ { - "name": "router", - "address": "0x14aBb1f28B06272c57c37723D0e671d1c3326679", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x83c2DB237e93Ce52565AB110124f78fdf159E3f4", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "inevm": [ { - "name": "router", - "address": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "HelloWorld", + "address": "0x0BD07E3934D1C4cc8Db0eA2a5cDAc8C8d8eb9824", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ] diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index aee860b461..f883c2f183 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -1,6 +1,5 @@ import { ChainMap, - GasOracleContractType, IgpConfig, defaultMultisigConfigs, multisigIsmVerificationCost, @@ -12,34 +11,32 @@ import { ethereumChainNames, supportedChainNames, } from './chains'; +import { storageGasOracleConfig } from './gas-oracle'; import { owners } from './owners'; // TODO: make this generic const KEY_FUNDER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; const DEPLOYER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; -function getGasOracles(local: MainnetChains) { - return Object.fromEntries( - exclude(local, supportedChainNames).map((name) => [ - name, - GasOracleContractType.StorageGasOracle, - ]), - ); -} +const FOREIGN_DEFAULT_OVERHEAD = 600_000; // cosmwasm warp route somewhat arbitrarily chosen + +const remoteOverhead = (remote: MainnetChains) => + ethereumChainNames.includes(remote) + ? multisigIsmVerificationCost( + defaultMultisigConfigs[remote].threshold, + defaultMultisigConfigs[remote].validators.length, + ) + : FOREIGN_DEFAULT_OVERHEAD; // non-ethereum overhead -export const igp: ChainMap = objMap(owners, (chain, owner) => ({ - owner, +export const igp: ChainMap = objMap(owners, (local, owner) => ({ + ...owner, oracleKey: DEPLOYER_ADDRESS, beneficiary: KEY_FUNDER_ADDRESS, - gasOracleType: getGasOracles(chain), overhead: Object.fromEntries( - // Not setting overhead for non-Ethereum destination chains - exclude(chain, ethereumChainNames).map((remote) => [ + exclude(local, supportedChainNames).map((remote) => [ remote, - multisigIsmVerificationCost( - defaultMultisigConfigs[remote].threshold, - defaultMultisigConfigs[remote].validators.length, - ), + remoteOverhead(remote), ]), ), + oracleConfig: storageGasOracleConfig[local], })); diff --git a/typescript/infra/config/environments/mainnet3/igp/verification.json b/typescript/infra/config/environments/mainnet3/igp/verification.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/igp/verification.json @@ -0,0 +1 @@ +{} diff --git a/typescript/infra/config/environments/mainnet3/index.ts b/typescript/infra/config/environments/mainnet3/index.ts index 3253309025..127af12260 100644 --- a/typescript/infra/config/environments/mainnet3/index.ts +++ b/typescript/infra/config/environments/mainnet3/index.ts @@ -1,9 +1,10 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMetadata, RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; import { getKeysForRole, getMultiProviderForRole, -} from '../../../scripts/utils'; +} from '../../../scripts/agent-utils'; import { EnvironmentConfig } from '../../../src/config'; import { Role } from '../../../src/roles'; import { Contexts } from '../../contexts'; @@ -12,7 +13,6 @@ import { agents } from './agent'; import { environment as environmentName, mainnetConfigs } from './chains'; import { core } from './core'; import { keyFunderConfig } from './funding'; -import { storageGasOracleConfig } from './gas-oracle'; import { helloWorld } from './helloworld'; import { igp } from './igp'; import { infrastructure } from './infrastructure'; @@ -26,15 +26,22 @@ export const environment: EnvironmentConfig = { context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, connectionType?: RpcConsensusType, - ) => - getMultiProviderForRole( + ) => { + const config = objFilter( mainnetConfigs, + (_, chainMetadata): chainMetadata is ChainMetadata => + chainMetadata.protocol === ProtocolType.Ethereum, + ); + + return getMultiProviderForRole( + config, environmentName, context, role, undefined, connectionType, - ), + ); + }, getKeys: ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, @@ -46,7 +53,6 @@ export const environment: EnvironmentConfig = { infra: infrastructure, helloWorld, keyFunderConfig, - storageGasOracleConfig, liquidityLayerConfig: { bridgeAdapters: bridgeAdapterConfigs, relayer: relayerConfig, diff --git a/typescript/infra/config/environments/mainnet3/infrastructure.ts b/typescript/infra/config/environments/mainnet3/infrastructure.ts index c0d562f91b..f123829482 100644 --- a/typescript/infra/config/environments/mainnet3/infrastructure.ts +++ b/typescript/infra/config/environments/mainnet3/infrastructure.ts @@ -40,6 +40,7 @@ export const infrastructure: InfrastructureConfig = { 'mainnet2-', 'hyperlane-mainnet3-', 'rc-mainnet3-', + 'neutron-mainnet3-', 'mainnet3-', ], }, diff --git a/typescript/infra/config/environments/mainnet3/ism/verification.json b/typescript/infra/config/environments/mainnet3/ism/verification.json index 783d89e8ac..1b8f42b360 100644 --- a/typescript/infra/config/environments/mainnet3/ism/verification.json +++ b/typescript/infra/config/environments/mainnet3/ism/verification.json @@ -119,6 +119,72 @@ "address": "0xe6a4646EE138e282A36c0665Ad028ccdC3E525E2", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x7F6cD932412508E9a8297CA626C56Ed3D279937F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xf422a4Af27e5fbDDAf799A1d92532Edd5dE58fF3", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x43379b54E27EA8387B34B5c15534E230d490f0A8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x8d9dB8bbF91d7852F36a954229BDf6ec69A3ae00", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x2A2c22B0a8615ad24839fA6Af302E896Af32d1a3", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x866599CEaE5060D774EBAaE7e9f8A0431a8c1ED5", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x7F6cD932412508E9a8297CA626C56Ed3D279937F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xf422a4Af27e5fbDDAf799A1d92532Edd5dE58fF3", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x43379b54E27EA8387B34B5c15534E230d490f0A8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x8d9dB8bbF91d7852F36a954229BDf6ec69A3ae00", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x866599CEaE5060D774EBAaE7e9f8A0431a8c1ED5", + "constructorArguments": "", + "isProxy": true } ], "ethereum": [ @@ -241,6 +307,72 @@ "address": "0x65f02B4fB2F0ccAb58955e4b816EebD4DFCbBaFf", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xD4dAcca08737d2a910b4Ad401f805F83D0C170f3", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x5371942D3Ed75b10d77F0f4184dDc85cC35A1420", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xc2cCfc65D2D5719E78a77EA5f6C10AA4cdEC6719", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x03862793C0EE59af3e475f7Ca67406b547FfD95c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x28fA9552F19039b450498B0d8e5DEAe0d0aAc559", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0xBbaDB49B1fD1A0574C8D2B0589Cd9b8A79452e67", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xD4dAcca08737d2a910b4Ad401f805F83D0C170f3", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x5371942D3Ed75b10d77F0f4184dDc85cC35A1420", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xc2cCfc65D2D5719E78a77EA5f6C10AA4cdEC6719", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x03862793C0EE59af3e475f7Ca67406b547FfD95c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xBbaDB49B1fD1A0574C8D2B0589Cd9b8A79452e67", + "constructorArguments": "", + "isProxy": true } ], "avalanche": [ @@ -363,6 +495,72 @@ "address": "0x61ca653A1F61A69E6498c45874237C4f1D8fC645", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x06b9dC1a6629122e7188698d20A92edbE966914f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xeD8F4199e4409FDAe2AfD50dC7571f6771AadE50", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x86Bb7AC2BF6044289aEAFFC421b118E38C995c5a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3988b98C43C7A1f7C9D3edce6CeAD3b8a3F3d969", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x28F7907911C7E321c596686AE6D1F20516450037", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0a3E78c160daF8be96051D318d668F97182D60Bf", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x06b9dC1a6629122e7188698d20A92edbE966914f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xeD8F4199e4409FDAe2AfD50dC7571f6771AadE50", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x86Bb7AC2BF6044289aEAFFC421b118E38C995c5a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3988b98C43C7A1f7C9D3edce6CeAD3b8a3F3d969", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0a3E78c160daF8be96051D318d668F97182D60Bf", + "constructorArguments": "", + "isProxy": true } ], "polygon": [ @@ -485,6 +683,72 @@ "address": "0x42cCAacf2666E92114F649b578056eDCeBdA8ba7", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x14CEa5Df89Fa709409e83ebEA9518C5B6fb4B19B", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x370815EdA08438c8F385a6f7AB5A2Dfa75008abC", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xCC38436BFB9B9888be96b59d825E0fE5DC19e05c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x5B8418082D87c96B7De689D0368756cddAbB35F5", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x0d0E816eE4557689d34fAd5885C53b9393C1D9fA", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x6921808186c66558e91dF1233910862A94a57475", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x14CEa5Df89Fa709409e83ebEA9518C5B6fb4B19B", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x370815EdA08438c8F385a6f7AB5A2Dfa75008abC", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xCC38436BFB9B9888be96b59d825E0fE5DC19e05c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x5B8418082D87c96B7De689D0368756cddAbB35F5", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x6921808186c66558e91dF1233910862A94a57475", + "constructorArguments": "", + "isProxy": true } ], "bsc": [ @@ -565,9 +829,75 @@ "address": "0x4EFFb1d1B817c0D2823729b343ac079431182eE2", "constructorArguments": "", "isProxy": true - } - ], - "optimism": [ + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xDc3D51c58BDb84F4F209d2684151dfCa271e504f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x707609419b70DCb4C41Ef185d3C184814c61Af9c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x769FeC9f1a1e3DD1891015A387C92Ee9631CB0bA", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xf771dA1B909B67ca41dda9133E1C9934B5A2D8bb", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xe6Af5720d34213C805C08e2470aea979e3F72F75", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x1467aB848fCCdA65c2cCed0ebeBD0d95ad89E0D8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xDc3D51c58BDb84F4F209d2684151dfCa271e504f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x707609419b70DCb4C41Ef185d3C184814c61Af9c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x769FeC9f1a1e3DD1891015A387C92Ee9631CB0bA", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xf771dA1B909B67ca41dda9133E1C9934B5A2D8bb", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x1467aB848fCCdA65c2cCed0ebeBD0d95ad89E0D8", + "constructorArguments": "", + "isProxy": true + } + ], + "optimism": [ { "name": "MerkleRootMultisigIsmFactory", "address": "0xCA6Cb9Bc3cfF9E11003A06617cF934B684Bc78BC", @@ -657,6 +987,72 @@ "address": "0x35E536a6465632d2De568F0f22c514EfEE38aEA5", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xC91A3282FE1eBc29AE494f10680006f152DcE316", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xb61544De4d3A103698AC57Fd62402627B8AC3cC2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xD416cF29F961c090e9b9b8aF0970c86D93c2Ff61", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x315d8B4229134Fcb12B8955f0B5FC1310E56E764", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xD2e905108c5e44dADA680274740f896Ea96Cf2Fb", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x71c5167429f522FA009D110954Cb08E0317d4d69", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xC91A3282FE1eBc29AE494f10680006f152DcE316", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xb61544De4d3A103698AC57Fd62402627B8AC3cC2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xD416cF29F961c090e9b9b8aF0970c86D93c2Ff61", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x315d8B4229134Fcb12B8955f0B5FC1310E56E764", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x71c5167429f522FA009D110954Cb08E0317d4d69", + "constructorArguments": "", + "isProxy": true } ], "arbitrum": [ @@ -749,6 +1145,72 @@ "address": "0xc0Ce143F44ADc65d35fB0d24436Eaa953380dF97", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xEbA276cdC61D4BC954E80985aC8FD71453fDab8e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xfD122f59ee8073528Cc5d36D5cc1451Bf1aF6923", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xBbc6e404F8d841560261b036cA3468B55CB9f566", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x97c5dC51adAa04B3BefE63F4e62e4778219D9426", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xa2931C37957f3079d3B21b877d56E1db930e02a5", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0xF5C9D13D0a3308a06375fD09CACE3a6120711206", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xEbA276cdC61D4BC954E80985aC8FD71453fDab8e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xfD122f59ee8073528Cc5d36D5cc1451Bf1aF6923", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xBbc6e404F8d841560261b036cA3468B55CB9f566", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x97c5dC51adAa04B3BefE63F4e62e4778219D9426", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xF5C9D13D0a3308a06375fD09CACE3a6120711206", + "constructorArguments": "", + "isProxy": true } ], "scroll": [ @@ -871,6 +1333,72 @@ "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xe03dad16074BC5EEA9A9311257BF02Eb0B6AAA2b", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x9d9238fD3715281De2c8FC321135bF82EB66E932", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x9d9238fD3715281De2c8FC321135bF82EB66E932", + "constructorArguments": "", + "isProxy": true } ], "base": [ @@ -993,8 +1521,74 @@ "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true - } - ], + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0d2FdC0264059C1334Aa28187E8628E1c9c6EC70", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0d2FdC0264059C1334Aa28187E8628E1c9c6EC70", + "constructorArguments": "", + "isProxy": true + } + ], "polygonzkevm": [ { "name": "MerkleRootMultisigIsmFactory", @@ -1115,6 +1709,72 @@ "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xe4057c5B0c43Dc18E36b08C39B419F190D29Ac2d", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0741b6Fd92DA99E77E5eE78CFf74cB1689B3588e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0741b6Fd92DA99E77E5eE78CFf74cB1689B3588e", + "constructorArguments": "", + "isProxy": true } ], "gnosis": [ @@ -1333,6 +1993,72 @@ "address": "0xc4c11C88AbF6087150273b2c39B27878d137a920", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xe46CDa25130A89759F1Da00591D7a920CAe7667E", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xEeBd8F72573C5a08F18BeC0DbadCCD3365c06AEF", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x5aaF70a9944d2D7cf17153ea07632618b1e45C6F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x72AffdEd251dF55c7c89566c64B9961dbc3e7A23", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xbB5Df000113e767dE11343A16f83De733e5bCC0F", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x901bFAC323Dc06b1302D82201F62729Dff39b20a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xe46CDa25130A89759F1Da00591D7a920CAe7667E", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xEeBd8F72573C5a08F18BeC0DbadCCD3365c06AEF", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x5aaF70a9944d2D7cf17153ea07632618b1e45C6F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x72AffdEd251dF55c7c89566c64B9961dbc3e7A23", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x901bFAC323Dc06b1302D82201F62729Dff39b20a", + "constructorArguments": "", + "isProxy": true } ], "moonbeam": [ @@ -1425,6 +2151,160 @@ "address": "0xE8d610DC4Baf01070FD2f223d45f84d8801D90B1", "constructorArguments": "", "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x28336d2b8783f2373bCFc173058EA932bf3b901C", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x76FD8c164F380107631160d8Fd1f4Edc2719004D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xd5FF00DD9E737c9d1a197246738876fAF43e4aC0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x568De5f1639Fe7c9eba67f1191DE19eeCc77985B", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xE8d610DC4Baf01070FD2f223d45f84d8801D90B1", + "constructorArguments": "", + "isProxy": true + } + ], + "viction": [ + { + "name": "MerkleRootMultisigIsmFactory", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "MessageIdMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationHookFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + } + ], + "inevm": [ + { + "name": "MerkleRootMultisigIsmFactory", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "MessageIdMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationHookFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true } ] } diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index 32dae82723..1a1a87a7a2 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -1,7 +1,14 @@ -import { ChainMap } from '@hyperlane-xyz/sdk'; -import { Address, objMap } from '@hyperlane-xyz/utils'; +import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; -export const safes: ChainMap
= { +import { ethereumChainNames } from './chains'; + +export const timelocks: ChainMap
= { + arbitrum: '0xAC98b0cD1B64EA4fe133C6D2EDaf842cE5cF4b01', +}; + +export const safes: ChainMap
= { + mantapacific: '0x03ed2D65f2742193CeD99D48EbF1F1D6F12345B6', // does not have a UI celo: '0x1DE69322B55AC7E0999F8e7738a1428C8b130E4d', ethereum: '0x12C5AB61Fe17dF9c65739DBa73dF294708f78d23', avalanche: '0xDF9B28B76877f1b1B4B8a11526Eb7D8D7C49f4f3', @@ -12,14 +19,18 @@ export const safes: ChainMap
= { moonbeam: '0xF0cb1f968Df01fc789762fddBfA704AE0F952197', gnosis: '0x36b0AA0e7d04e7b825D7E409FEa3c9A3d57E4C22', // solana: 'EzppBFV2taxWw8kEjxNYvby6q7W1biJEqwP3iC7YgRe3', - // TODO: create gnosis safes here - base: '', - scroll: '', - polygonzkevm: '', }; -// export const owners = safes; - -// temporarily keep ownership on deployer key const deployer = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; -export const owners = objMap(safes, (_, __) => deployer); + +export const owners: ChainMap = Object.fromEntries( + ethereumChainNames.map((local) => [ + local, + { + owner: deployer, // TODO: change this to the safe + ownerOverrides: { + proxyAdmin: timelocks[local] ?? safes[local] ?? deployer, + }, + }, + ]), +); diff --git a/typescript/infra/config/environments/mainnet3/testrecipient/addresses.json b/typescript/infra/config/environments/mainnet3/testrecipient/addresses.json deleted file mode 100644 index 2e18c24187..0000000000 --- a/typescript/infra/config/environments/mainnet3/testrecipient/addresses.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "bsc": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "avalanche": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "polygon": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "celo": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "arbitrum": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "optimism": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "ethereum": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "moonbeam": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - }, - "gnosis": { - "TestRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "TestTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb" - } -} diff --git a/typescript/infra/config/environments/mainnet3/testrecipient/verification.json b/typescript/infra/config/environments/mainnet3/testrecipient/verification.json deleted file mode 100644 index 53b20bae30..0000000000 --- a/typescript/infra/config/environments/mainnet3/testrecipient/verification.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "bsc": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "avalanche": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "polygon": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "celo": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "arbitrum": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "optimism": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "ethereum": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "moonbeam": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ], - "gnosis": [ - { - "name": "TestRecipient", - "address": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "isProxy": false, - "constructorArguments": "0x" - }, - { - "name": "TestTokenRecipient", - "address": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "isProxy": false, - "constructorArguments": "0x" - } - ] -} diff --git a/typescript/infra/config/environments/mainnet3/tokenPrices.json b/typescript/infra/config/environments/mainnet3/tokenPrices.json new file mode 100644 index 0000000000..deab6cff09 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/tokenPrices.json @@ -0,0 +1,18 @@ +{ + "arbitrum": "2914.61", + "avalanche": "35.71", + "bsc": "373.19", + "celo": "0.772619", + "ethereum": "2914.61", + "mantapacific": "2914.61", + "moonbeam": "0.419658", + "optimism": "2914.61", + "polygon": "0.982687", + "gnosis": "1.012", + "base": "2914.61", + "scroll": "2914.61", + "polygonzkevm": "2914.61", + "inevm": "32.77", + "viction": "0.750231", + "injective": "32.77" +} diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index 28d5e54fd6..c6c44d6a6c 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -1,4 +1,4 @@ -import { chainMetadata } from '@hyperlane-xyz/sdk'; +import { chainMetadata, getReorgPeriod } from '@hyperlane-xyz/sdk'; import { ValidatorBaseChainConfigMap } from '../../../src/config/agent'; import { Contexts } from '../../contexts'; @@ -13,7 +13,7 @@ export const validatorChainConfig = ( return { celo: { interval: 5, - reorgPeriod: 0, + reorgPeriod: getReorgPeriod(chainMetadata.celo), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -21,14 +21,19 @@ export const validatorChainConfig = ( '0x2f4e808744df049d8acc050628f7bdd8265807f9', '0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xb51768c1388e976486a43dbbbbf9ce04cf45e990', + '0x6325de37b33e20089c091950518a471e29c52883', + '0xd796c1d4fcfb3c63acfa6e4113aa6ae1399b337c', + ], + [Contexts.Neutron]: [], }, 'celo', ), }, ethereum: { interval: 5, - reorgPeriod: 20, + reorgPeriod: getReorgPeriod(chainMetadata.ethereum), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -36,14 +41,19 @@ export const validatorChainConfig = ( '0x4346776b10f5e0d9995d884b7a1dbaee4e24c016', '0x749d6e7ad949e522c92181dc77f7bbc1c5d71506', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x0580884289890805802012b9872afa5ae41a5fa6', + '0xa5465cb5095a2e6093587e644d6121d6ed55c632', + '0x87cf8a85465118aff9ec728ca157798201b1e368', + ], + [Contexts.Neutron]: [], }, 'ethereum', ), }, avalanche: { interval: 5, - reorgPeriod: 3, + reorgPeriod: getReorgPeriod(chainMetadata.avalanche), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -52,15 +62,18 @@ export const validatorChainConfig = ( '0x6c754f1e9cd8287088b46a7c807303d55d728b49', ], [Contexts.ReleaseCandidate]: [ - '0x706976391e23dea28152e0207936bd942aba01ce', + '0x2c7cf6d1796e37676ba95f056ff21bf536c6c2d3', + '0xcd250d48d16e2ce4b939d44b5215f9e978975152', + '0x26691cd3e9c1b8a82588606b31d9d69b14cb2729', ], + [Contexts.Neutron]: [], }, 'avalanche', ), }, polygon: { interval: 5, - reorgPeriod: 256, + reorgPeriod: getReorgPeriod(chainMetadata.polygon), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -68,14 +81,19 @@ export const validatorChainConfig = ( '0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916', '0xdbf3666de031bea43ec35822e8c33b9a9c610322', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xf0a990959f833ccde624c8bcd4c7669286a57a0f', + '0x456b636bdde99d69176261d7a4fba42c16f57f56', + '0xe78d3681d4f59e0768be8b1171f920ed4d52409f', + ], + [Contexts.Neutron]: [], }, 'polygon', ), }, bsc: { interval: 5, - reorgPeriod: 15, + reorgPeriod: getReorgPeriod(chainMetadata.bsc), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -83,14 +101,19 @@ export const validatorChainConfig = ( '0x7bf928d5d262365d31d64eaa24755d48c3cae313', '0x03047213365800f065356b4a2fe97c3c3a52296a', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x911dfcc19dd5b723e84be452f6af52adef020bc8', + '0xee2d4fd5fe2170e51c6279552297117feaeb19e1', + '0x50ff94984161976a13e9ec3b2a7647da5319448f', + ], + [Contexts.Neutron]: [], }, 'bsc', ), }, arbitrum: { interval: 5, - reorgPeriod: 0, + reorgPeriod: getReorgPeriod(chainMetadata.arbitrum), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -98,14 +121,19 @@ export const validatorChainConfig = ( '0x6333e110b8a261cab28acb43030bcde59f26978a', '0x3369e12edd52570806f126eb50be269ba5e65843', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xb4c18167c163391facb345bb069d12d0430a6a89', + '0x2f6dc057ae079997f76205903b85c8302164a78c', + '0x229d4dc6a740212da746b0e35314419a24bc2a5b', + ], + [Contexts.Neutron]: [], }, 'arbitrum', ), }, optimism: { interval: 5, - reorgPeriod: 0, + reorgPeriod: getReorgPeriod(chainMetadata.optimism), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -114,15 +142,18 @@ export const validatorChainConfig = ( '0x779a17e035018396724a6dec8a59bda1b5adf738', ], [Contexts.ReleaseCandidate]: [ - '0x60e938bf280bbc21bacfd8bf435459d9003a8f98', + '0x7e4391786e0b5b0cbaada12d32c931e46e44f104', + '0x138ca73e805afa14e85d80f6e35c46e6f235429e', + '0x2d58cdb2bed9aac57b488b1bad06839ddc280a78', ], + [Contexts.Neutron]: [], }, 'optimism', ), }, moonbeam: { interval: 5, - reorgPeriod: 2, + reorgPeriod: getReorgPeriod(chainMetadata.moonbeam), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -130,14 +161,19 @@ export const validatorChainConfig = ( '0x4fe067bb455358e295bfcfb92519a6f9de94b98e', '0xcc4a78aa162482bea43313cd836ba7b560b44fc4', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x75e3cd4e909089ae6c9f3a42b1468b33eec84161', + '0xc28418d0858a82a46a11e07db75f8bf4eed43881', + '0xcaa9c6e6efa35e4a8b47565f3ce98845fa638bf3', + ], + [Contexts.Neutron]: [], }, 'moonbeam', ), }, gnosis: { interval: 5, - reorgPeriod: 14, + reorgPeriod: getReorgPeriod(chainMetadata.gnosis), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -145,14 +181,19 @@ export const validatorChainConfig = ( '0x06a833508579f8b59d756b3a1e72451fc70840c3', '0xb93a72cee19402553c9dd7fed2461aebd04e2454', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xd5122daa0c3dfc94a825ae928f3ea138cdb6a2e1', + '0x2d1f367e942585f8a1c25c742397dc8be9a61dee', + '0x2111141b7f985d305f392c502ad52dd74ef9c569', + ], + [Contexts.Neutron]: [], }, 'gnosis', ), }, base: { interval: 5, - reorgPeriod: 0, + reorgPeriod: getReorgPeriod(chainMetadata.base), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -160,14 +201,55 @@ export const validatorChainConfig = ( '0x4512985a574cb127b2af2d4bb676876ce804e3f8', '0xb144bb2f599a5af095bc30367856f27ea8a8adc7', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xa8363570749080c7faa1de714e0782ff444af4cc', + '0x3b55d9febe02a9038ef8c867fa8bbfdd8d70f9b8', + '0xed7703e06572768bb09e03d88e6b788d8800b9fb', + ], + [Contexts.Neutron]: [], }, 'base', ), }, + injective: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.injective), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0xbfb8911b72cfb138c7ce517c57d9c691535dc517', + '0x6faa139c33a7e6f53cb101f6b2ae392298283ed2', + '0x0115e3a66820fb99da30d30e2ce52a453ba99d92', + ], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'injective', + ), + }, + inevm: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.inevm), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0xf9e35ee88e4448a3673b4676a4e153e3584a08eb', + '0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2', + '0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3', + ], + [Contexts.ReleaseCandidate]: [ + '0x52a0376903294c796c091c785a66c62943d99aa8', + '0xc2ea1799664f753bedb9872d617e3ebc60b2e0ab', + '0xe83d36fd00d9ef86243d9f7147b29e98d11df0ee', + ], + [Contexts.Neutron]: [], + }, + 'inevm', + ), + }, scroll: { interval: 5, - reorgPeriod: 0, + reorgPeriod: getReorgPeriod(chainMetadata.scroll), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -175,14 +257,19 @@ export const validatorChainConfig = ( '0xb37fe43a9f47b7024c2d5ae22526cc66b5261533', '0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x11387d89856219cf685f22781bf4e85e00468d54', + '0x64b98b96ccae6e660ecf373b5dd61bcc34fd19ee', + '0x07c2f32a402543badc3141f6b98969d75ef2ac28', + ], + [Contexts.Neutron]: [], }, 'scroll', ), }, polygonzkevm: { interval: 5, - reorgPeriod: 0, + reorgPeriod: getReorgPeriod(chainMetadata.polygonzkevm), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -190,10 +277,71 @@ export const validatorChainConfig = ( '0xc84076030bdabaabb9e61161d833dd84b700afda', '0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x75cffb90391d7ecf58a84e9e70c67e7b306211c0', + '0x82c10acb56f3d7ed6738b61668111a6b5250283e', + '0x1cd73544c000fd519784f56e59bc380a5fef53d6', + ], + [Contexts.Neutron]: [], }, 'polygonzkevm', ), }, + neutron: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.neutron), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', + '0x7885fae56dbcf5176657f54adbbd881dc6714132', + ], + [Contexts.ReleaseCandidate]: [ + '0x307a8fe091b8273c7ce3d277b161b4a2167279b1', + '0xb825c1bd020cb068f477b320f591b32e26814b5b', + '0x0a5b31090d4c3c207b9ea6708f938e328f895fce', + ], + [Contexts.Neutron]: [], + }, + 'neutron', + ), + }, + mantapacific: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.mantapacific), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0x8e668c97ad76d0e28375275c41ece4972ab8a5bc', + '0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3', + '0x5dda0c4cf18de3b3ab637f8df82b24921082b54c', + ], + [Contexts.ReleaseCandidate]: [ + '0x84fcb05e6e5961df2dfd9f36e8f2b3e87ede7d76', + '0x45f3e2655a08feda821ee7b495cf2595401e1569', + '0x4cfccfd66dbb702b643b56f6986a928ed1b50c7e', + ], + [Contexts.Neutron]: [], + }, + 'mantapacific', + ), + }, + viction: { + interval: 5, + reorgPeriod: 0, + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x1f87c368f8e05a85ef9126d984a980a20930cb9c'], + [Contexts.ReleaseCandidate]: [ + '0xe858971cd865b11d3e8fb6b6af72db0d85881baf', + '0xad94659e2383214e4a1c4e8d3c17caffb75bc31b', + '0x0f9e5775ac4d3b73dd28e5a3f8394443186cb70c', + ], + [Contexts.Neutron]: [], + }, + 'viction', + ), + }, }; }; diff --git a/typescript/infra/config/environments/mainnet3/warp/addresses.json b/typescript/infra/config/environments/mainnet3/warp/addresses.json new file mode 100644 index 0000000000..2e38c664a0 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/addresses.json @@ -0,0 +1,8 @@ +{ + "inevm": { + "HypERC20": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147" + }, + "ethereum": { + "HypERC20Collateral": "0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/arbitrum-TIA-addresses.json b/typescript/infra/config/environments/mainnet3/warp/arbitrum-TIA-addresses.json new file mode 100644 index 0000000000..b578857016 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/arbitrum-TIA-addresses.json @@ -0,0 +1,8 @@ +{ + "neutron": { + "router": "neutron1jyyjd3x0jhgswgm6nnctxvzla8ypx50tew3ayxxwkrjfxhvje6kqzvzudq" + }, + "arbitrum": { + "router": "0xd56734d7f9979dd94fae3d67c7e928234e71cd4c" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml new file mode 100644 index 0000000000..0d8ff77557 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between injective and inevm +description: Hyperlane Warp Route artifacts +timestamp: '2024-02-06T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + ethereum: + protocolType: ethereum + type: collateral + hypAddress: '0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f' + tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC + name: USDC + symbol: USDC + decimals: 6 + inevm: + protocolType: ethereum + type: synthetic + hypAddress: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147' + name: USDC + symbol: USDC + decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml new file mode 100644 index 0000000000..25ec599f73 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between injective and inevm +description: Hyperlane Warp Route artifacts +timestamp: '2024-02-06T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + ethereum: + protocolType: ethereum + type: collateral + hypAddress: '0xab852e67bf03E74C89aF67C4BA97dd1088D3dA19' + tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7' # USDT + name: Tether USD + symbol: USDT + decimals: 6 + inevm: + protocolType: ethereum + type: synthetic + hypAddress: '0x97423A68BAe94b5De52d767a17aBCc54c157c0E5' + name: Tether USD + symbol: USDT + decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/inevm-USDC-addresses.json b/typescript/infra/config/environments/mainnet3/warp/inevm-USDC-addresses.json new file mode 100644 index 0000000000..2e38c664a0 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/inevm-USDC-addresses.json @@ -0,0 +1,8 @@ +{ + "inevm": { + "HypERC20": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147" + }, + "ethereum": { + "HypERC20Collateral": "0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/inevm-USDT-addresses.json b/typescript/infra/config/environments/mainnet3/warp/inevm-USDT-addresses.json new file mode 100644 index 0000000000..b9e1b65223 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/inevm-USDT-addresses.json @@ -0,0 +1,8 @@ +{ + "inevm": { + "HypERC20": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5" + }, + "ethereum": { + "HypERC20Collateral": "0xab852e67bf03E74C89aF67C4BA97dd1088D3dA19" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/injective-inevm-addresses.json b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-addresses.json new file mode 100644 index 0000000000..d92b53abf8 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-addresses.json @@ -0,0 +1,8 @@ +{ + "injective": { + "router": "inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k" + }, + "inevm": { + "router": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml new file mode 100644 index 0000000000..efabaf59da --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between injective and inevm +description: Hyperlane Warp Route artifacts +timestamp: '2024-01-31T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + injective: + protocolType: cosmos + type: native + hypAddress: inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k + name: Injective Coin + symbol: INJ + decimals: 18 + ibcDenom: inj + inevm: + protocolType: ethereum + type: native + hypAddress: '0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4' + name: Injective coin + symbol: INJ + decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/manta-TIA-addresses.json b/typescript/infra/config/environments/mainnet3/warp/manta-TIA-addresses.json new file mode 100644 index 0000000000..cfe967bb17 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/manta-TIA-addresses.json @@ -0,0 +1,8 @@ +{ + "neutron": { + "router": "neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa" + }, + "mantapacific": { + "router": "0x6fae4d9935e2fcb11fc79a64e917fb2bf14dafaa" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/nautilus-solana-bsc-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/nautilus-solana-bsc-deployments.yaml new file mode 100644 index 0000000000..e3e8770078 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/nautilus-solana-bsc-deployments.yaml @@ -0,0 +1,31 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between nautilus and bsc, solana +description: Hyperlane Warp Route artifacts +timestamp: '2023-09-23T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + bsc: + protocolType: ethereum + type: collateral + hypAddress: '0xC27980812E2E66491FD457D488509b7E04144b98' + tokenAddress: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303' + name: Zebec + symbol: ZBC + decimals: 9 + nautilus: + protocolType: ethereum + type: native + hypAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7' + name: Zebec + symbol: ZBC + decimals: 18 + solana: + protocolType: sealevel + type: collateral + tokenAddress: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59' + hypAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa' + name: Zebec + symbol: ZBC + decimals: 9 + isSpl2022: true diff --git a/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml new file mode 100644 index 0000000000..ae13acf3d7 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between neutron and mantapacific +description: Hyperlane Warp Route artifacts +timestamp: '2023-09-23T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + neutron: + protocolType: cosmos + type: collateral + hypAddress: neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa + tokenAddress: ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7 + name: Celestia + symbol: TIA + decimals: 6 + mantapacific: + protocolType: ethereum + type: synthetic + hypAddress: '0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa' + name: Celestia + symbol: TIA + decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/verification.json b/typescript/infra/config/environments/mainnet3/warp/verification.json new file mode 100644 index 0000000000..a48214e7e4 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/verification.json @@ -0,0 +1,76 @@ +{ + "arbitrum": [ + { + "name": "HypERC20", + "address": "0x93ca0d85837FF83158Cd14D65B169CdB223b1921", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb9", + "isProxy": false + } + ], + "viction": [ + { + "name": "HypERC20", + "address": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000120000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "HypERC20", + "address": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000120000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "HypERC20", + "address": "0x61DDB465eEA5bc3708Cf8B53156aC91a77A2f029", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000120000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + } + ], + "ethereum": [ + { + "name": "HypNative", + "address": "0x15b5D6B614242B118AA404528A7f3E2Ad241e4A4", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", + "isProxy": false + }, + { + "name": "HypERC20Collateral", + "address": "0x31Dca7762930f56D81292f85E65c9D67575804fE", + "constructorArguments": "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", + "isProxy": false + }, + { + "name": "HypERC20Collateral", + "address": "0x4221a16A01F61c2b38A03C52d828a7041f6AAA49", + "constructorArguments": "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", + "isProxy": false + }, + { + "name": "HypERC20Collateral", + "address": "0xab852e67bf03E74C89aF67C4BA97dd1088D3dA19", + "constructorArguments": "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", + "isProxy": false + }, + { + "name": "HypERC20Collateral", + "address": "0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f", + "constructorArguments": "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", + "isProxy": false + } + ], + "inevm": [ + { + "name": "HypERC20", + "address": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000060000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "HypERC20", + "address": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000060000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + } + ] +} diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ETH-addresses.json b/typescript/infra/config/environments/mainnet3/warp/viction-ETH-addresses.json new file mode 100644 index 0000000000..c5842c1768 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ETH-addresses.json @@ -0,0 +1,8 @@ +{ + "viction": { + "router": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B" + }, + "ethereum": { + "router": "0x15b5D6B614242B118AA404528A7f3E2Ad241e4A4" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-USDC-addresses.json b/typescript/infra/config/environments/mainnet3/warp/viction-USDC-addresses.json new file mode 100644 index 0000000000..c567193098 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/viction-USDC-addresses.json @@ -0,0 +1,10 @@ +{ + "ethereum": { + "HypERC20Collateral": "0x31Dca7762930f56D81292f85E65c9D67575804fE", + "router": "0x31Dca7762930f56D81292f85E65c9D67575804fE" + }, + "viction": { + "HypERC20": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "router": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-USDT-addresses.json b/typescript/infra/config/environments/mainnet3/warp/viction-USDT-addresses.json new file mode 100644 index 0000000000..49c09c3356 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/viction-USDT-addresses.json @@ -0,0 +1,10 @@ +{ + "ethereum": { + "router": "0x4221a16A01F61c2b38A03C52d828a7041f6AAA49", + "HypERC20Collateral": "0x4221a16A01F61c2b38A03C52d828a7041f6AAA49" + }, + "viction": { + "router": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", + "HypERC20": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml new file mode 100644 index 0000000000..8923facc00 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml @@ -0,0 +1,21 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between viction and ethereum, ETH +description: Hyperlane Warp Route artifacts +timestamp: '2023-02-14T20:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + ethereum: + protocolType: ethereum + type: native + hypAddress: '0x15b5D6B614242B118AA404528A7f3E2Ad241e4A4' + name: Ether + symbol: ETH + decimals: 18 + viction: + protocolType: ethereum + type: synthetic + hypAddress: '0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B' + name: ETH + symbol: ETH + decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml new file mode 100644 index 0000000000..a278a93e8c --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between viction and ethereum, USDC +description: Hyperlane Warp Route artifacts +timestamp: '2023-02-14T20:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + ethereum: + protocolType: ethereum + type: collateral + hypAddress: '0x31Dca7762930f56D81292f85E65c9D67575804fE' + tokenAddress: '0x31Dca7762930f56D81292f85E65c9D67575804fE' # USDC + name: USD Coin + symbol: USDC + decimals: 6 + viction: + protocolType: ethereum + type: synthetic + hypAddress: '0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0' + name: USDC + symbol: USDC + decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml new file mode 100644 index 0000000000..20dd075f8d --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between viction and ethereum, USDT +description: Hyperlane Warp Route artifacts +timestamp: '2023-02-14T20:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + ethereum: + protocolType: ethereum + type: collateral + hypAddress: '0x4221a16A01F61c2b38A03C52d828a7041f6AAA49' + tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' # USDT + name: Tether USD + symbol: USDT + decimals: 6 + viction: + protocolType: ethereum + type: synthetic + hypAddress: '0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4' + name: USDT + symbol: USDT + decimals: 6 diff --git a/typescript/infra/config/environments/test/core.ts b/typescript/infra/config/environments/test/core.ts index 0c2f476e42..5bfefd1456 100644 --- a/typescript/infra/config/environments/test/core.ts +++ b/typescript/infra/config/environments/test/core.ts @@ -22,7 +22,7 @@ import { owners } from './owners'; export const core: ChainMap = objMap(owners, (local, owner) => { const defaultIsm: RoutingIsmConfig = { type: IsmType.ROUTING, - owner, + ...owner, domains: Object.fromEntries( Object.entries(chainToValidator) .filter(([chain, _]) => chain !== local) @@ -46,7 +46,7 @@ export const core: ChainMap = objMap(owners, (local, owner) => { const defaultHook: FallbackRoutingHookConfig = { type: HookType.FALLBACK_ROUTING, - owner, + ...owner, fallback: merkleHook, domains: Object.fromEntries( Object.entries(chainToValidator) @@ -57,16 +57,16 @@ export const core: ChainMap = objMap(owners, (local, owner) => { const requiredHook: ProtocolFeeHookConfig = { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(1), // 1 wei - beneficiary: owner, - owner, + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(1).toString(), // 1 wei + beneficiary: owner.owner, + ...owner, }; return { - owner, defaultIsm, defaultHook, requiredHook, + ...owner, }; }); diff --git a/typescript/infra/config/environments/test/igp.ts b/typescript/infra/config/environments/test/igp.ts index 6590c933ab..cb7ffa4bf6 100644 --- a/typescript/infra/config/environments/test/igp.ts +++ b/typescript/infra/config/environments/test/igp.ts @@ -19,7 +19,7 @@ function getGasOracles(local: TestChains) { ); } -export const igp: ChainMap = objMap(owners, (chain, owner) => { +export const igp: ChainMap = objMap(owners, (chain, ownerConfig) => { const overhead = Object.fromEntries( exclude(chain, chainNames).map((remote) => [ remote, @@ -30,10 +30,10 @@ export const igp: ChainMap = objMap(owners, (chain, owner) => { ]), ); return { - owner, - oracleKey: owner, - beneficiary: owner, + oracleKey: ownerConfig.owner, + beneficiary: ownerConfig.owner, gasOracleType: getGasOracles(chain), overhead, + ...ownerConfig, }; }); diff --git a/typescript/infra/config/environments/test/index.ts b/typescript/infra/config/environments/test/index.ts index a458f47a08..c11ab549f0 100644 --- a/typescript/infra/config/environments/test/index.ts +++ b/typescript/infra/config/environments/test/index.ts @@ -7,7 +7,6 @@ import { EnvironmentConfig } from '../../../src/config'; import { agents } from './agent'; import { testConfigs } from './chains'; import { core } from './core'; -import { storageGasOracleConfig } from './gas-oracle'; import { igp } from './igp'; import { infra } from './infra'; import { owners } from './owners'; @@ -31,5 +30,4 @@ export const environment: EnvironmentConfig = { getKeys: async () => { throw new Error('Not implemented'); }, - storageGasOracleConfig, }; diff --git a/typescript/infra/config/environments/test/middleware/accounts/addresses.json b/typescript/infra/config/environments/test/middleware/accounts/addresses.json index 0efdde8771..e58797a5ba 100644 --- a/typescript/infra/config/environments/test/middleware/accounts/addresses.json +++ b/typescript/infra/config/environments/test/middleware/accounts/addresses.json @@ -14,9 +14,6 @@ "goerli": { "router": "0xc011170d9795a7a2d065E384EAd1CA3394A7d35E" }, - "moonbasealpha": { - "router": "0xc011170d9795a7a2d065E384EAd1CA3394A7d35E" - }, "optimismgoerli": { "router": "0xc011170d9795a7a2d065E384EAd1CA3394A7d35E" }, diff --git a/typescript/infra/config/environments/test/middleware/queries/addresses.json b/typescript/infra/config/environments/test/middleware/queries/addresses.json index e09eed8422..e14048a62a 100644 --- a/typescript/infra/config/environments/test/middleware/queries/addresses.json +++ b/typescript/infra/config/environments/test/middleware/queries/addresses.json @@ -14,9 +14,6 @@ "goerli": { "router": "0x6141e7E7fA2c1beB8be030B0a7DB4b8A10c7c3cd" }, - "moonbasealpha": { - "router": "0x6141e7E7fA2c1beB8be030B0a7DB4b8A10c7c3cd" - }, "optimismgoerli": { "router": "0x6141e7E7fA2c1beB8be030B0a7DB4b8A10c7c3cd" }, diff --git a/typescript/infra/config/environments/test/owners.ts b/typescript/infra/config/environments/test/owners.ts index 3134145ed0..4156bd0ecb 100644 --- a/typescript/infra/config/environments/test/owners.ts +++ b/typescript/infra/config/environments/test/owners.ts @@ -1,10 +1,9 @@ -import { ChainMap } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; import { chainNames } from './chains'; // Owner is hardhat account 0 const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'; -export const owners: ChainMap
= Object.fromEntries( - chainNames.map((chain) => [chain, OWNER_ADDRESS]), +export const owners: ChainMap = Object.fromEntries( + chainNames.map((chain) => [chain, { owner: OWNER_ADDRESS }]), ); diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 4d1019d648..f586992d8b 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -2,6 +2,7 @@ import { GasPaymentEnforcementPolicyType, RpcConsensusType, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; import { @@ -49,7 +50,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '54aeb64-20240206-163119', }, blacklist: [ ...releaseCandidateHelloworldMatchingList, @@ -59,14 +60,40 @@ const hyperlane: RootAgentConfig = { // gas, we blacklist the old TestRecipient address. recipientAddress: '0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE', }, + // OptimismGoerli griefers: + { + destinationDomain: getDomainId(chainMetadata.optimismgoerli), + recipientAddress: [ + '0xed4de02c6f4cb1161bdfefdb2fcdeef4546fa36c', + '0x723192fc414fe536b414117a4b2c5a7b71f912e3', + '0x5a48723d80a7ee3be6855ca293059b5287ee6689', + ], + }, + // Goerli griefers: + { + destinationDomain: getDomainId(chainMetadata.goerli), + recipientAddress: [ + '0x0461c69ff7f29cfb5efd36b9d377fdfc95418c2b', + '0xe747c82ed8560ba137b24a3a97ff7504b50c3e91', + '0x6ad92511ee4a3835bde9b1bfd7063023b56a8c56', + ], + }, ], gasPaymentEnforcement, + metricAppContexts: [ + { + name: 'helloworld', + matchingList: routerMatchingList( + helloWorld[Contexts.Hyperlane].addresses, + ), + }, + ], }, validators: { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '54aeb64-20240206-163119', }, chains: validatorChainConfig(Contexts.Hyperlane), }, @@ -74,7 +101,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '54aeb64-20240206-163119', }, }, }; @@ -87,7 +114,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'fd890d2-20231024-132022', + tag: '54aeb64-20240206-163119', }, whitelist: [...releaseCandidateHelloworldMatchingList], gasPaymentEnforcement, @@ -100,7 +127,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'fd890d2-20231024-132022', + tag: '54aeb64-20240206-163119', }, chains: validatorChainConfig(Contexts.ReleaseCandidate), }, diff --git a/typescript/infra/config/environments/testnet4/chains.ts b/typescript/infra/config/environments/testnet4/chains.ts index 64ad786e04..e21c0c5af0 100644 --- a/typescript/infra/config/environments/testnet4/chains.ts +++ b/typescript/infra/config/environments/testnet4/chains.ts @@ -1,12 +1,30 @@ -import { ChainMap, ChainMetadata, chainMetadata } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainMetadata, + Chains, + chainMetadata, +} from '@hyperlane-xyz/sdk'; +import { plumetestnet } from '@hyperlane-xyz/sdk/dist/consts/chainMetadata'; import { AgentChainNames, Role } from '../../../src/roles'; -// Blessed -export const ethereumTestnetConfigs: ChainMap = { - alfajores: chainMetadata.alfajores, - basegoerli: chainMetadata.basegoerli, - fuji: chainMetadata.fuji, +const selectedChains = [ + Chains.alfajores, + Chains.arbitrumgoerli, + Chains.bsctestnet, + Chains.fuji, + Chains.goerli, + Chains.optimismgoerli, + Chains.polygonzkevmtestnet, + Chains.scrollsepolia, + Chains.sepolia, + Chains.plumetestnet, +]; + +export const testnetConfigs: ChainMap = { + ...Object.fromEntries( + selectedChains.map((chain) => [chain, chainMetadata[chain]]), + ), mumbai: { ...chainMetadata.mumbai, transactionOverrides: { @@ -14,44 +32,27 @@ export const ethereumTestnetConfigs: ChainMap = { maxPriorityFeePerGas: 40 * 10 ** 9, // 40 gwei }, }, - bsctestnet: chainMetadata.bsctestnet, - goerli: chainMetadata.goerli, - scrollsepolia: chainMetadata.scrollsepolia, - sepolia: chainMetadata.sepolia, - moonbasealpha: chainMetadata.moonbasealpha, - optimismgoerli: chainMetadata.optimismgoerli, - arbitrumgoerli: chainMetadata.arbitrumgoerli, - polygonzkevmtestnet: chainMetadata.polygonzkevmtestnet, -}; - -// Blessed non-Ethereum chains. -// export const nonEthereumTestnetConfigs: ChainMap = { -// solanadevnet: chainMetadata.solanadevnet, -// }; - -export const testnetConfigs: ChainMap = { - ...ethereumTestnetConfigs, - // ...nonEthereumTestnetConfigs, + bsctestnet: { + ...chainMetadata.bsctestnet, + transactionOverrides: { + gasPrice: 80 * 10 ** 9, // 8 gwei + }, + }, + plumetestnet: { + ...plumetestnet, + transactionOverrides: { + gasPrice: 0.001 * 10 ** 9, // 0.001 gwei + }, + }, }; -export type TestnetChains = keyof typeof testnetConfigs; -export const supportedChainNames = Object.keys( - testnetConfigs, -) as TestnetChains[]; +export const supportedChainNames = Object.keys(testnetConfigs); export const environment = 'testnet4'; -export const ethereumChainNames = Object.keys( - ethereumTestnetConfigs, -) as TestnetChains[]; -const validatorChainNames = [ - ...supportedChainNames, - // chainMetadata.solanadevnet.name, - // chainMetadata.proteustestnet.name, -]; -const relayerChainNames = validatorChainNames; - +// Hyperlane & RC context agent chain names. export const agentChainNames: AgentChainNames = { - [Role.Validator]: validatorChainNames, - [Role.Relayer]: relayerChainNames, - [Role.Scraper]: ethereumChainNames, + [Role.Validator]: supportedChainNames, + // Only run relayers for Ethereum chains at the moment. + [Role.Relayer]: supportedChainNames, + [Role.Scraper]: supportedChainNames, }; diff --git a/typescript/infra/config/environments/testnet4/core.ts b/typescript/infra/config/environments/testnet4/core.ts index 6198c55799..3c05099bce 100644 --- a/typescript/infra/config/environments/testnet4/core.ts +++ b/typescript/infra/config/environments/testnet4/core.ts @@ -12,6 +12,8 @@ import { MerkleTreeHookConfig, MultisigConfig, MultisigIsmConfig, + PausableHookConfig, + PausableIsmConfig, ProtocolFeeHookConfig, RoutingIsmConfig, defaultMultisigConfigs, @@ -22,72 +24,91 @@ import { supportedChainNames } from './chains'; import { igp } from './igp'; import { owners } from './owners'; -export const core: ChainMap = objMap(owners, (local, owner) => { - const originMultisigs: ChainMap = Object.fromEntries( - supportedChainNames - .filter((chain) => chain !== local) - .map((origin) => [origin, defaultMultisigConfigs[origin]]), - ); +export const core: ChainMap = objMap( + owners, + (local, ownerConfig) => { + const originMultisigs: ChainMap = Object.fromEntries( + supportedChainNames + .filter((chain) => chain !== local) + .map((origin) => [origin, defaultMultisigConfigs[origin]]), + ); - const merkleRoot = (multisig: MultisigConfig): MultisigIsmConfig => ({ - type: IsmType.MERKLE_ROOT_MULTISIG, - ...multisig, - }); + const merkleRoot = (multisig: MultisigConfig): MultisigIsmConfig => ({ + type: IsmType.MERKLE_ROOT_MULTISIG, + ...multisig, + }); - const messageIdIsm = (multisig: MultisigConfig): MultisigIsmConfig => ({ - type: IsmType.MESSAGE_ID_MULTISIG, - ...multisig, - }); + const messageIdIsm = (multisig: MultisigConfig): MultisigIsmConfig => ({ + type: IsmType.MESSAGE_ID_MULTISIG, + ...multisig, + }); - const defaultIsm: RoutingIsmConfig = { - type: IsmType.ROUTING, - domains: objMap( - originMultisigs, - (_, multisig): AggregationIsmConfig => ({ - type: IsmType.AGGREGATION, - modules: [messageIdIsm(multisig), merkleRoot(multisig)], - threshold: 1, - }), - ), - owner, - }; + const routingIsm: RoutingIsmConfig = { + type: IsmType.ROUTING, + domains: objMap( + originMultisigs, + (_, multisig): AggregationIsmConfig => ({ + type: IsmType.AGGREGATION, + modules: [messageIdIsm(multisig), merkleRoot(multisig)], + threshold: 1, + }), + ), + ...ownerConfig, + }; + + const pausableIsm: PausableIsmConfig = { + type: IsmType.PAUSABLE, + ...ownerConfig, + }; + + const defaultIsm: AggregationIsmConfig = { + type: IsmType.AGGREGATION, + modules: [routingIsm, pausableIsm], + threshold: 2, + }; - const merkleHook: MerkleTreeHookConfig = { - type: HookType.MERKLE_TREE, - }; + const merkleHook: MerkleTreeHookConfig = { + type: HookType.MERKLE_TREE, + }; - const igpHook: IgpHookConfig = { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - ...igp[local], - }; + const igpHook: IgpHookConfig = { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + ...igp[local], + }; - const aggregationHooks = objMap( - originMultisigs, - (_origin, _): AggregationHookConfig => ({ - type: HookType.AGGREGATION, - hooks: [igpHook, merkleHook], - }), - ); + const pausableHook: PausableHookConfig = { + type: HookType.PAUSABLE, + ...ownerConfig, + }; + + const aggregationHooks = objMap( + originMultisigs, + (_origin, _): AggregationHookConfig => ({ + type: HookType.AGGREGATION, + hooks: [pausableHook, merkleHook, igpHook], + }), + ); - const defaultHook: FallbackRoutingHookConfig = { - type: HookType.FALLBACK_ROUTING, - owner, - fallback: merkleHook, - domains: aggregationHooks, - }; + const defaultHook: FallbackRoutingHookConfig = { + type: HookType.FALLBACK_ROUTING, + ...ownerConfig, + domains: aggregationHooks, + fallback: merkleHook, + }; - const requiredHook: ProtocolFeeHookConfig = { - type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(1), // 1 wei - beneficiary: owner, - owner, - }; + const requiredHook: ProtocolFeeHookConfig = { + type: HookType.PROTOCOL_FEE, + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(1).toString(), // 1 wei of native token + beneficiary: ownerConfig.owner, + ...ownerConfig, + }; - return { - owner, - defaultIsm, - defaultHook, - requiredHook, - }; -}); + return { + defaultIsm, + defaultHook, + requiredHook, + ...ownerConfig, + }; + }, +); diff --git a/typescript/infra/config/environments/testnet4/core/verification.json b/typescript/infra/config/environments/testnet4/core/verification.json index 31eb4ee72a..cf8e5b5d70 100644 --- a/typescript/infra/config/environments/testnet4/core/verification.json +++ b/typescript/infra/config/environments/testnet4/core/verification.json @@ -1,4130 +1,3278 @@ { "alfajores": [ { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "FallbackRoutingHook", "address": "0xE1386148385275A27D29fC39Bd58a969CD5dCAF0", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000221fa9cbafcd6c1c3d206571cf4427703e023ffa", - "isProxy": false + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "FallbackRoutingHook", "address": "0xE1386148385275A27D29fC39Bd58a969CD5dCAF0", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000221fa9cbafcd6c1c3d206571cf4427703e023ffa", - "isProxy": false + "isProxy": false, + "name": "FallbackRoutingHook" } ], - "basegoerli": [ + "arbitrumgoerli": [ { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x13dABc0351407d5aAa0A50003a166A73b4febfDc", + "constructorArguments": "0x000000000000000000000000385c7f179168f5da92c72e17ae8ef50f3874077f00000000000000000000000000dfb81bfc45fa03060b605273147f274ea807e500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", + "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000066eed", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", + "constructorArguments": "0x00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x76189acFA212298d7022624a4633411eE0d2f26F", + "constructorArguments": "0x000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000dfb81bfc45fa03060b605273147f274ea807e500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", + "constructorArguments": "0x00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x76189acFA212298d7022624a4633411eE0d2f26F", + "constructorArguments": "0x000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000dfb81bfc45fa03060b605273147f274ea807e500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0x0358ba0D90ED2d90fB8cBb610F27C274D8077a0B", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x4a01EEBa1CC20F47A2e60aE4ec932051601FcB9e", + "constructorArguments": "0x00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f", + "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000f0a38e1eea49dac7968f470c3aa0bde2565a5d80", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f", + "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000f0a38e1eea49dac7968f470c3aa0bde2565a5d80", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "bsctestnet": [ + { + "address": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", + "constructorArguments": "0x000000000000000000000000bfb3eb3bcc00a17d04237c98f2d1061548f8ac38000000000000000000000000b12282d2e838aa5f2a4f9ee5f624a77b7199a07800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0xbfB3EB3Bcc00A17d04237C98F2D1061548f8AC38", + "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000000061", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", + "constructorArguments": "0x000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", + "constructorArguments": "0x000000000000000000000000795b9b7aa901c8b999b62b8c80299e79a5c96057000000000000000000000000b12282d2e838aa5f2a4f9ee5f624a77b7199a07800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x795B9b7AA901C8B999b62B8c80299e79a5c96057", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", + "constructorArguments": "0x000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", + "constructorArguments": "0x000000000000000000000000795b9b7aa901c8b999b62b8c80299e79a5c96057000000000000000000000000b12282d2e838aa5f2a4f9ee5f624a77b7199a07800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x795B9b7AA901C8B999b62B8c80299e79a5c96057", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0x3eF0a63B8976b838704Bcc93C78C56b6653E5a39", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf09701B0a93210113D175461b6135a96773B5465", + "constructorArguments": "0x000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xc278DDe83018F0e8c624b208e6D9E6251d263B1d", + "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000c6cbf39a747f5e28d1bdc8d9dfdab2960abd5a8f", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, + "address": "0xc278DDe83018F0e8c624b208e6D9E6251d263B1d", + "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000c6cbf39a747f5e28d1bdc8d9dfdab2960abd5a8f", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "fuji": [ { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "constructorArguments": "0x", + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "constructorArguments": "0x", + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "constructorArguments": "0x", + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", + "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", + "constructorArguments": "0x0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", + "constructorArguments": "0x00000000000000000000000004be088a7aab0e1b6ec7d73af9e152344fbc8a0c000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x04BE088A7aAb0E1B6Ec7d73af9E152344Fbc8A0C", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", + "constructorArguments": "0x0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", + "constructorArguments": "0x00000000000000000000000004be088a7aab0e1b6ec7d73af9e152344fbc8a0c000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x04BE088A7aAb0E1B6Ec7d73af9E152344Fbc8A0C", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x4f7179A691F8a684f56cF7Fed65171877d30739a", + "constructorArguments": "0x0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x50897eDCb3f1bB2A90f20DA5a8dF0e5c57A146e3", + "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009ff6ac3daf63103620bbf76136ea1aff43c2f612", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x50897eDCb3f1bB2A90f20DA5a8dF0e5c57A146e3", + "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009ff6ac3daf63103620bbf76136ea1aff43c2f612", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "goerli": [ + { + "address": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x49cfd6Ef774AcAb14814D699e3F7eE36Fdfba932", + "constructorArguments": "0x0000000000000000000000004a67be3a7db40a8ae110f85ab66b0203f9401a770000000000000000000000000edb3604d230963ece9d83963164cfe2fdef576b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x4a67bE3a7DB40a8Ae110F85ab66b0203f9401a77", + "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000000005", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", + "constructorArguments": "0x00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0xeC34c715ee6d050b2172E8aF650Db779561266C1", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", + "constructorArguments": "0x000000000000000000000000ee99ea7cc4035e942917cd444e0a653a4b9d3e1a0000000000000000000000000edb3604d230963ece9d83963164cfe2fdef576b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0xee99eA7cC4035E942917cd444e0A653A4B9d3e1A", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", + "constructorArguments": "0x00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0xeC34c715ee6d050b2172E8aF650Db779561266C1", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", + "constructorArguments": "0x000000000000000000000000ee99ea7cc4035e942917cd444e0a653a4b9d3e1a0000000000000000000000000edb3604d230963ece9d83963164cfe2fdef576b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0xee99eA7cC4035E942917cd444e0A653A4B9d3e1A", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0x9293B8dAcA7933765de499C992B0Fa86Bb104b0f", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x3c182AD9cA8A71bc107Ef440C2667E8360e1158E", + "constructorArguments": "0x00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x66F356393a9d66C7757dE475d02969783796d54c", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x7523AE9fAebf49749a0E7148403c8d26C23a53da", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000001681cc382e08a72d4b64a123080896e30f96b7400000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", + "isProxy": false, + "name": "OpStackHook" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0x66F356393a9d66C7757dE475d02969783796d54c", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x7523AE9fAebf49749a0E7148403c8d26C23a53da", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000001681cc382e08a72d4b64a123080896e30f96b7400000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", + "isProxy": false, + "name": "OpStackHook" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x108FD05a2c0Ba834506167ef8f9FD715B3319d8F", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000003212977fbe6464c2bb60fdb85ab0a5e06e25cdfb0000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", + "isProxy": false, + "name": "OpStackHook" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x108FD05a2c0Ba834506167ef8f9FD715B3319d8F", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000003212977fbe6464c2bb60fdb85ab0a5e06e25cdfb0000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", + "isProxy": false, + "name": "OpStackHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x8BAB125B823ee5E55797394c03B15874bF176A53", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0x8BAB125B823ee5E55797394c03B15874bF176A53", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xDd66CB60D4Ffb7f0d8FB91CB1D20aBcaBC82900a", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0xce59701919507F2d379270657A4e410F570aBe0D", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a33000000000000000000000000e0c5bdafee7f7065402337040e426a42b5c336500000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", + "isProxy": false, + "name": "OpStackHook" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0xDd66CB60D4Ffb7f0d8FB91CB1D20aBcaBC82900a", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false - }, + "address": "0xce59701919507F2d379270657A4e410F570aBe0D", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a33000000000000000000000000e0c5bdafee7f7065402337040e426a42b5c336500000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", + "isProxy": false, + "name": "OpStackHook" + } + ], + "moonbasealpha": [ { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x76189acFA212298d7022624a4633411eE0d2f26F", + "constructorArguments": "0x00000000000000000000000096ee22fb5cad052bef57f8ee9d5050b45204cc92000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x96ee22Fb5Cad052bEf57f8EE9D5050B45204cc92", + "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000000507", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x155B1CD2f7Cbc58d403B9BE341FaB6CD77425175", + "constructorArguments": "0x00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x62fA20dE68Dbe425f0bc474b12235a4F8449E608", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x92F05669A354a032A84FcfABfD13beE1aBc5bFd0", + "constructorArguments": "0x000000000000000000000000700eb93ae3a4cfccfefd2f17d92b79cd3ff202e4000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x700EB93Ae3A4CFcCfEFd2F17d92b79CD3FF202e4", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x155B1CD2f7Cbc58d403B9BE341FaB6CD77425175", + "constructorArguments": "0x00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x62fA20dE68Dbe425f0bc474b12235a4F8449E608", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x92F05669A354a032A84FcfABfD13beE1aBc5bFd0", + "constructorArguments": "0x000000000000000000000000700eb93ae3a4cfccfefd2f17d92b79cd3ff202e4000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x700EB93Ae3A4CFcCfEFd2F17d92b79CD3FF202e4", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0xe2A73F106902983452713F24Bd019F6eb8712986", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", + "constructorArguments": "0x00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x6c9EB73793F9Cd535DB1bF86dC307f6d899b2fE3", + "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x6c9EB73793F9Cd535DB1bF86dC307f6d899b2fE3", + "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "mumbai": [ + { + "address": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x2d1889fe5B092CD988972261434F7E5f26041115", + "constructorArguments": "0x000000000000000000000000de1973894d1418ae463013cb6415cb24fce15575000000000000000000000000a99ad6b1c10e92db8d3510f1865a6d2ab43ead5800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0xde1973894d1418ae463013CB6415Cb24fcE15575", + "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000013881", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", + "constructorArguments": "0x0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x8aB67CAF605c6ee83cbFeFb0D8d67FDd3BF7B591", + "constructorArguments": "0x00000000000000000000000098f44ea5b9ca6aa02a5b75f31e0621083d9096a2000000000000000000000000a99ad6b1c10e92db8d3510f1865a6d2ab43ead5800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", + "constructorArguments": "0x0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x8aB67CAF605c6ee83cbFeFb0D8d67FDd3BF7B591", + "constructorArguments": "0x00000000000000000000000098f44ea5b9ca6aa02a5b75f31e0621083d9096a2000000000000000000000000a99ad6b1c10e92db8d3510f1865a6d2ab43ead5800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0x244d1F7e30Be144A87602905baBF86630e8f39DC", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x99303EFF09332cDd93E8BC8b2F07b2416e4501e5", + "constructorArguments": "0x0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xFA005A892EbDACFcc3f1EF0111A7406c779c3647", + "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009af85731edd41e2e50f81ef8a0a69d2fb836edf9", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0xFA005A892EbDACFcc3f1EF0111A7406c779c3647", + "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009af85731edd41e2e50f81ef8a0a69d2fb836edf9", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "optimismgoerli": [ + { + "address": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xB5f021728Ea6223E3948Db2da61d612307945eA2", + "constructorArguments": "0x000000000000000000000000a04b18c7e45f41cb28590d37784017ea1bbce052000000000000000000000000800b4be4dc91e56de934d9f16888d113eff89ebb00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0xA04b18c7E45F41CB28590D37784017Ea1bbCe052", + "constructorArguments": "0x00000000000000000000000000000000000000000000000000000000000001a4", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", + "constructorArguments": "0x000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x4927C33299091033D935C15DE6b6073164e99BE0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", + "constructorArguments": "0x00000000000000000000000003c39954c2dc91d32f68461300391e8605e83176000000000000000000000000800b4be4dc91e56de934d9f16888d113eff89ebb00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x03C39954C2DC91d32f68461300391E8605e83176", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", + "constructorArguments": "0x000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "address": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "address": "0x4927C33299091033D935C15DE6b6073164e99BE0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", + "constructorArguments": "0x00000000000000000000000003c39954c2dc91d32f68461300391e8605e83176000000000000000000000000800b4be4dc91e56de934d9f16888d113eff89ebb00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "address": "0x03C39954C2DC91d32f68461300391E8605e83176", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", + "address": "0x962e30F6A3ECDA85c7fa1FcF38cD04efA991Ee20", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x24D31e12E4d3bc2C46C994FcE0c828b218A1aeAb", + "constructorArguments": "0x000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xba962f31B8DE02238fDdf8CE6a21260Af8C5Dd2F", + "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fee074b31b5b259eb3109737be13d39b853b47b9", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false - }, + "address": "0xba962f31B8DE02238fDdf8CE6a21260Af8C5Dd2F", + "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fee074b31b5b259eb3109737be13d39b853b47b9", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "plumetestnet": [ { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000099c0a0f", + "isProxy": false, + "name": "Mailbox" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000ddf4c3e791cacafd26d7fb275549739b38ae6e75", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "MerkleTreeHook", "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" }, { - "name": "StorageGasOracle", "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "InterchainGasPaymaster", "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "StorageGasOracle", "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "InterchainGasPaymaster", "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "TransparentUpgradeableProxy", "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "InterchainGasPaymaster", "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "StorageGasOracle", "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "TransparentUpgradeableProxy", "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "StorageGasOracle", "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "0x000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "Mailbox", - "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000014a33", - "isProxy": false + "address": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000ddf4c3e791cacafd26d7fb275549739b38ae6e75", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "MerkleTreeHook", "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "StorageGasOracle", - "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "TransparentUpgradeableProxy", "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "MerkleTreeHook", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "StorageGasOracle", "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "0x000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "InterchainGasPaymaster", "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "0x", - "isProxy": false + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ValidatorAnnounce", - "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "constructorArguments": "0x00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a", - "isProxy": false + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "FallbackRoutingHook", - "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000005821f3b6ee05f3dc62b43b74ab1c8f8e6904b1c8", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "FallbackRoutingHook", - "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000005821f3b6ee05f3dc62b43b74ab1c8f8e6904b1c8", - "isProxy": false - } - ], - "fuji": [ - { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x1b33611fCc073aB0737011d5512EF673Bff74962", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false - }, + "address": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065", + "isProxy": false, + "name": "ValidatorAnnounce" + } + ], + "polygonzkevmtestnet": [ { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", + "constructorArguments": "0x000000000000000000000000ef48bd850e5827b96b55c4d28fb32bbaa73616f2000000000000000000000000666a24f62f7a97ba33c151776eb3d9441a059eb800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xef48bd850E5827B96B55C4D28FB32Bbaa73616F2", + "constructorArguments": "0x00000000000000000000000000000000000000000000000000000000000005a2", + "isProxy": false, + "name": "Mailbox" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0x68311418D79fE8d96599384ED767d225635d88a8", + "constructorArguments": "0x000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", + "constructorArguments": "0x", + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", + "constructorArguments": "0x000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0000000000000000000000000666a24f62f7a97ba33c151776eb3d9441a059eb800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x68311418D79fE8d96599384ED767d225635d88a8", + "constructorArguments": "0x000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", + "constructorArguments": "0x", + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", + "constructorArguments": "0x000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0000000000000000000000000666a24f62f7a97ba33c151776eb3d9441a059eb800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", + "constructorArguments": "0x", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "constructorArguments": "0x", - "isProxy": false + "address": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x7914A3349107A7295Bbf2374db5A973d73D1b324", + "constructorArguments": "0x000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", + "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000068311418d79fe8d96599384ed767d225635d88a8", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", + "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000068311418d79fe8d96599384ed767d225635d88a8", + "isProxy": false, + "name": "FallbackRoutingHook" + } + ], + "scrollsepolia": [ + { + "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "constructorArguments": "0x000000000000000000000000f28969a6a930c68a2758d55c291de595670961fe000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", + "constructorArguments": "0x0000000000000000000000007914a3349107a7295bbf2374db5a973d73d1b324000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xf28969A6A930c68a2758d55c291DE595670961FE", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false + "address": "0x7914A3349107A7295Bbf2374db5A973d73D1b324", + "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000008274f", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", - "constructorArguments": "0x0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0", - "isProxy": false + "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", + "constructorArguments": "0x0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", + "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "constructorArguments": "0x00000000000000000000000004be088a7aab0e1b6ec7d73af9e152344fbc8a0c000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "constructorArguments": "0x000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0x04BE088A7aAb0E1B6Ec7d73af9E152344Fbc8A0C", + "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", - "constructorArguments": "0x0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0", - "isProxy": false + "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", + "constructorArguments": "0x0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", + "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "constructorArguments": "0x00000000000000000000000004be088a7aab0e1b6ec7d73af9e152344fbc8a0c000000000000000000000000378da02f7dc3c23a8b5ece32b8056cdf01e8d47700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "constructorArguments": "0x000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0x04BE088A7aAb0E1B6Ec7d73af9E152344Fbc8A0C", + "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", + "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ValidatorAnnounce", - "address": "0x4f7179A691F8a684f56cF7Fed65171877d30739a", - "constructorArguments": "0x0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0", - "isProxy": false + "address": "0x527768930D889662Fe7ACF64294871e86e4C2381", + "constructorArguments": "0x0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "FallbackRoutingHook", - "address": "0x50897eDCb3f1bB2A90f20DA5a8dF0e5c57A146e3", - "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009ff6ac3daf63103620bbf76136ea1aff43c2f612", - "isProxy": false + "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", + "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "FallbackRoutingHook", - "address": "0x50897eDCb3f1bB2A90f20DA5a8dF0e5c57A146e3", - "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b0000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009ff6ac3daf63103620bbf76136ea1aff43c2f612", - "isProxy": false + "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", + "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", + "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", + "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", + "isProxy": false, + "name": "FallbackRoutingHook" } ], - "mumbai": [ + "sepolia": [ { - "name": "ProxyAdmin", - "address": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", + "address": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x2d1889fe5B092CD988972261434F7E5f26041115", - "constructorArguments": "0x000000000000000000000000de1973894d1418ae463013cb6415cb24fce15575000000000000000000000000a99ad6b1c10e92db8d3510f1865a6d2ab43ead5800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", + "constructorArguments": "0x00000000000000000000000062b2203d0757d1845ab2422eb61ab30d4ad3c51500000000000000000000000097bbc6bbafa5ce3b2fa966c121af63bd09e940f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "Mailbox", - "address": "0xde1973894d1418ae463013CB6415Cb24fcE15575", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000013881", - "isProxy": false + "address": "0x62b2203d0757d1845Ab2422Eb61ab30D4Ad3c515", + "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000aa36a7", + "isProxy": false, + "name": "Mailbox" }, { - "name": "MerkleTreeHook", - "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", - "constructorArguments": "0x0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115", - "isProxy": false + "address": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", + "constructorArguments": "0x000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", + "address": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", + "address": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x8aB67CAF605c6ee83cbFeFb0D8d67FDd3BF7B591", - "constructorArguments": "0x00000000000000000000000098f44ea5b9ca6aa02a5b75f31e0621083d9096a2000000000000000000000000a99ad6b1c10e92db8d3510f1865a6d2ab43ead5800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", + "constructorArguments": "0x0000000000000000000000003f09709b9865993f0320c3e836f3a3f4ff83ea9b00000000000000000000000097bbc6bbafa5ce3b2fa966c121af63bd09e940f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", + "address": "0x3f09709b9865993f0320C3e836f3a3F4ff83eA9b", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "MerkleTreeHook", - "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", - "constructorArguments": "0x0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x8aB67CAF605c6ee83cbFeFb0D8d67FDd3BF7B591", - "constructorArguments": "0x00000000000000000000000098f44ea5b9ca6aa02a5b75f31e0621083d9096a2000000000000000000000000a99ad6b1c10e92db8d3510f1865a6d2ab43ead5800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0x244d1F7e30Be144A87602905baBF86630e8f39DC", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x99303EFF09332cDd93E8BC8b2F07b2416e4501e5", - "constructorArguments": "0x0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xFA005A892EbDACFcc3f1EF0111A7406c779c3647", - "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009af85731edd41e2e50f81ef8a0a69d2fb836edf9", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xFA005A892EbDACFcc3f1EF0111A7406c779c3647", - "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f26041115000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000009af85731edd41e2e50f81ef8a0a69d2fb836edf9", - "isProxy": false - } - ], - "bsctestnet": [ - { - "name": "ProxyAdmin", - "address": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", - "constructorArguments": "0x000000000000000000000000bfb3eb3bcc00a17d04237c98f2d1061548f8ac38000000000000000000000000b12282d2e838aa5f2a4f9ee5f624a77b7199a07800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0xbfB3EB3Bcc00A17d04237C98F2D1061548f8AC38", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000000061", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", - "constructorArguments": "0x000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", - "constructorArguments": "0x000000000000000000000000795b9b7aa901c8b999b62b8c80299e79a5c96057000000000000000000000000b12282d2e838aa5f2a4f9ee5f624a77b7199a07800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x795B9b7AA901C8B999b62B8c80299e79a5c96057", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", - "constructorArguments": "0x000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", - "constructorArguments": "0x000000000000000000000000795b9b7aa901c8b999b62b8c80299e79a5c96057000000000000000000000000b12282d2e838aa5f2a4f9ee5f624a77b7199a07800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x795B9b7AA901C8B999b62B8c80299e79a5c96057", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0x3eF0a63B8976b838704Bcc93C78C56b6653E5a39", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xf09701B0a93210113D175461b6135a96773B5465", - "constructorArguments": "0x000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xc278DDe83018F0e8c624b208e6D9E6251d263B1d", - "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000c6cbf39a747f5e28d1bdc8d9dfdab2960abd5a8f", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xc278DDe83018F0e8c624b208e6D9E6251d263B1d", - "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000c6cbf39a747f5e28d1bdc8d9dfdab2960abd5a8f", - "isProxy": false - } - ], - "goerli": [ - { - "name": "ProxyAdmin", - "address": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x49cfd6Ef774AcAb14814D699e3F7eE36Fdfba932", - "constructorArguments": "0x0000000000000000000000004a67be3a7db40a8ae110f85ab66b0203f9401a770000000000000000000000000edb3604d230963ece9d83963164cfe2fdef576b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0x4a67bE3a7DB40a8Ae110F85ab66b0203f9401a77", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000000005", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", - "constructorArguments": "0x00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xeC34c715ee6d050b2172E8aF650Db779561266C1", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", - "constructorArguments": "0x000000000000000000000000ee99ea7cc4035e942917cd444e0a653a4b9d3e1a0000000000000000000000000edb3604d230963ece9d83963164cfe2fdef576b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0xee99eA7cC4035E942917cd444e0A653A4B9d3e1A", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", - "constructorArguments": "0x00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xeC34c715ee6d050b2172E8aF650Db779561266C1", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", - "constructorArguments": "0x000000000000000000000000ee99ea7cc4035e942917cd444e0a653a4b9d3e1a0000000000000000000000000edb3604d230963ece9d83963164cfe2fdef576b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0xee99eA7cC4035E942917cd444e0A653A4B9d3e1A", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0x9293B8dAcA7933765de499C992B0Fa86Bb104b0f", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x3c182AD9cA8A71bc107Ef440C2667E8360e1158E", - "constructorArguments": "0x00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x66F356393a9d66C7757dE475d02969783796d54c", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", - "isProxy": false - }, - { - "name": "OpStackHook", - "address": "0x7523AE9fAebf49749a0E7148403c8d26C23a53da", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000001681cc382e08a72d4b64a123080896e30f96b7400000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x66F356393a9d66C7757dE475d02969783796d54c", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", - "isProxy": false - }, - { - "name": "OpStackHook", - "address": "0x7523AE9fAebf49749a0E7148403c8d26C23a53da", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000001681cc382e08a72d4b64a123080896e30f96b7400000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", - "isProxy": false - }, - { - "name": "OpStackHook", - "address": "0x108FD05a2c0Ba834506167ef8f9FD715B3319d8F", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000003212977fbe6464c2bb60fdb85ab0a5e06e25cdfb0000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", - "isProxy": false - }, - { - "name": "OpStackHook", - "address": "0x108FD05a2c0Ba834506167ef8f9FD715B3319d8F", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a330000000000000000000000003212977fbe6464c2bb60fdb85ab0a5e06e25cdfb0000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x8BAB125B823ee5E55797394c03B15874bF176A53", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x8BAB125B823ee5E55797394c03B15874bF176A53", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xDd66CB60D4Ffb7f0d8FB91CB1D20aBcaBC82900a", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", - "isProxy": false - }, - { - "name": "OpStackHook", - "address": "0xce59701919507F2d379270657A4e410F570aBe0D", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a33000000000000000000000000e0c5bdafee7f7065402337040e426a42b5c336500000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xDd66CB60D4Ffb7f0d8FB91CB1D20aBcaBC82900a", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000028c294c61d3de053462d2cfa5d5f8c8d70605a59", - "isProxy": false - }, - { - "name": "OpStackHook", - "address": "0xce59701919507F2d379270657A4e410F570aBe0D", - "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000014a33000000000000000000000000e0c5bdafee7f7065402337040e426a42b5c336500000000000000000000000008e5693140ea606bceb98761d9beb1bc87383706d", - "isProxy": false - } - ], - "scrollsepolia": [ - { - "name": "ProxyAdmin", - "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", - "constructorArguments": "0x0000000000000000000000007914a3349107a7295bbf2374db5a973d73d1b324000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0x7914A3349107A7295Bbf2374db5A973d73D1b324", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000008274f", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "constructorArguments": "0x0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "constructorArguments": "0x000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "constructorArguments": "0x0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "constructorArguments": "0x000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x527768930D889662Fe7ACF64294871e86e4C2381", - "constructorArguments": "0x0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", - "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", - "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a68000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0", - "isProxy": false - } - ], - "sepolia": [ - { - "name": "ProxyAdmin", - "address": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", - "constructorArguments": "0x00000000000000000000000062b2203d0757d1845ab2422eb61ab30d4ad3c51500000000000000000000000097bbc6bbafa5ce3b2fa966c121af63bd09e940f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0x62b2203d0757d1845Ab2422Eb61ab30D4Ad3c515", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000aa36a7", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", - "constructorArguments": "0x000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", - "constructorArguments": "0x0000000000000000000000003f09709b9865993f0320c3e836f3a3f4ff83ea9b00000000000000000000000097bbc6bbafa5ce3b2fa966c121af63bd09e940f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x3f09709b9865993f0320C3e836f3a3F4ff83eA9b", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", - "constructorArguments": "0x000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", - "constructorArguments": "0x0000000000000000000000003f09709b9865993f0320c3e836f3a3f4ff83ea9b00000000000000000000000097bbc6bbafa5ce3b2fa966c121af63bd09e940f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x3f09709b9865993f0320C3e836f3a3F4ff83eA9b", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9", - "constructorArguments": "0x000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x7CE74319699165430fC651F6b99406eEbee95493", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x7CE74319699165430fC651F6b99406eEbee95493", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", - "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", - "isProxy": false - } - ], - "moonbasealpha": [ - { - "name": "ProxyAdmin", - "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x76189acFA212298d7022624a4633411eE0d2f26F", - "constructorArguments": "0x00000000000000000000000096ee22fb5cad052bef57f8ee9d5050b45204cc92000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0x96ee22Fb5Cad052bEf57f8EE9D5050B45204cc92", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000000507", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x155B1CD2f7Cbc58d403B9BE341FaB6CD77425175", - "constructorArguments": "0x00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x62fA20dE68Dbe425f0bc474b12235a4F8449E608", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x92F05669A354a032A84FcfABfD13beE1aBc5bFd0", - "constructorArguments": "0x000000000000000000000000700eb93ae3a4cfccfefd2f17d92b79cd3ff202e4000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x700EB93Ae3A4CFcCfEFd2F17d92b79CD3FF202e4", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x155B1CD2f7Cbc58d403B9BE341FaB6CD77425175", - "constructorArguments": "0x00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x62fA20dE68Dbe425f0bc474b12235a4F8449E608", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x92F05669A354a032A84FcfABfD13beE1aBc5bFd0", - "constructorArguments": "0x000000000000000000000000700eb93ae3a4cfccfefd2f17d92b79cd3ff202e4000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x700EB93Ae3A4CFcCfEFd2F17d92b79CD3FF202e4", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0xe2A73F106902983452713F24Bd019F6eb8712986", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", - "constructorArguments": "0x00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x6c9EB73793F9Cd535DB1bF86dC307f6d899b2fE3", - "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x6c9EB73793F9Cd535DB1bF86dC307f6d899b2fE3", - "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175", - "isProxy": false - } - ], - "optimismgoerli": [ - { - "name": "ProxyAdmin", - "address": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xB5f021728Ea6223E3948Db2da61d612307945eA2", - "constructorArguments": "0x000000000000000000000000a04b18c7e45f41cb28590d37784017ea1bbce052000000000000000000000000800b4be4dc91e56de934d9f16888d113eff89ebb00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0xA04b18c7E45F41CB28590D37784017Ea1bbCe052", - "constructorArguments": "0x00000000000000000000000000000000000000000000000000000000000001a4", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", - "constructorArguments": "0x000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x4927C33299091033D935C15DE6b6073164e99BE0", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", - "constructorArguments": "0x00000000000000000000000003c39954c2dc91d32f68461300391e8605e83176000000000000000000000000800b4be4dc91e56de934d9f16888d113eff89ebb00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x03C39954C2DC91d32f68461300391E8605e83176", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", - "constructorArguments": "0x000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x4927C33299091033D935C15DE6b6073164e99BE0", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", - "constructorArguments": "0x00000000000000000000000003c39954c2dc91d32f68461300391e8605e83176000000000000000000000000800b4be4dc91e56de934d9f16888d113eff89ebb00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0x03C39954C2DC91d32f68461300391E8605e83176", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "ProtocolFee", - "address": "0x962e30F6A3ECDA85c7fa1FcF38cD04efA991Ee20", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x24D31e12E4d3bc2C46C994FcE0c828b218A1aeAb", - "constructorArguments": "0x000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xba962f31B8DE02238fDdf8CE6a21260Af8C5Dd2F", - "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fee074b31b5b259eb3109737be13d39b853b47b9", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xba962f31B8DE02238fDdf8CE6a21260Af8C5Dd2F", - "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea2000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fee074b31b5b259eb3109737be13d39b853b47b9", - "isProxy": false - } - ], - "arbitrumgoerli": [ - { - "name": "ProxyAdmin", - "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x13dABc0351407d5aAa0A50003a166A73b4febfDc", - "constructorArguments": "0x000000000000000000000000385c7f179168f5da92c72e17ae8ef50f3874077f00000000000000000000000000dfb81bfc45fa03060b605273147f274ea807e500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", - "constructorArguments": "0x0000000000000000000000000000000000000000000000000000000000066eed", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", - "constructorArguments": "0x00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc", - "isProxy": false - }, - { - "name": "ProxyAdmin", - "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x76189acFA212298d7022624a4633411eE0d2f26F", - "constructorArguments": "0x000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000dfb81bfc45fa03060b605273147f274ea807e500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainGasPaymaster", - "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", - "constructorArguments": "0x00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc", - "isProxy": false + "address": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", + "constructorArguments": "0x000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", + "isProxy": false, + "name": "MerkleTreeHook" }, { - "name": "ProxyAdmin", - "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", + "address": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "ProxyAdmin" }, { - "name": "StorageGasOracle", - "address": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", + "address": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "StorageGasOracle" }, { - "name": "TransparentUpgradeableProxy", - "address": "0x76189acFA212298d7022624a4633411eE0d2f26F", - "constructorArguments": "0x000000000000000000000000b241991527f1c21ade14f55589e5940ac4852fa000000000000000000000000000dfb81bfc45fa03060b605273147f274ea807e500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", + "constructorArguments": "0x0000000000000000000000003f09709b9865993f0320c3e836f3a3f4ff83ea9b00000000000000000000000097bbc6bbafa5ce3b2fa966c121af63bd09e940f800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "name": "InterchainGasPaymaster", - "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", + "address": "0x3f09709b9865993f0320C3e836f3a3F4ff83eA9b", "constructorArguments": "0x", - "isProxy": false + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "name": "ProtocolFee", - "address": "0x0358ba0D90ED2d90fB8cBb610F27C274D8077a0B", + "address": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5", "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x4a01EEBa1CC20F47A2e60aE4ec932051601FcB9e", - "constructorArguments": "0x00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f", - "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000f0a38e1eea49dac7968f470c3aa0bde2565a5d80", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f", - "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000f0a38e1eea49dac7968f470c3aa0bde2565a5d80", - "isProxy": false - } - ], - "polygonzkevmtestnet": [ - { - "name": "ProxyAdmin", - "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "constructorArguments": "0x000000000000000000000000ef48bd850e5827b96b55c4d28fb32bbaa73616f2000000000000000000000000666a24f62f7a97ba33c151776eb3d9441a059eb800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "Mailbox", - "address": "0xef48bd850E5827B96B55C4D28FB32Bbaa73616F2", - "constructorArguments": "0x00000000000000000000000000000000000000000000000000000000000005a2", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x68311418D79fE8d96599384ED767d225635d88a8", - "constructorArguments": "0x000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8", - "isProxy": false + "isProxy": false, + "name": "ProtocolFee" }, { - "name": "ProxyAdmin", - "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "0x000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0000000000000000000000000666a24f62f7a97ba33c151776eb3d9441a059eb800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9", + "constructorArguments": "0x000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", + "isProxy": false, + "name": "ValidatorAnnounce" }, { - "name": "InterchainGasPaymaster", - "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "constructorArguments": "0x", - "isProxy": false + "address": "0x7CE74319699165430fC651F6b99406eEbee95493", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "MerkleTreeHook", - "address": "0x68311418D79fE8d96599384ED767d225635d88a8", - "constructorArguments": "0x000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8", - "isProxy": false + "address": "0x7CE74319699165430fC651F6b99406eEbee95493", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProxyAdmin", - "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", - "constructorArguments": "0x", - "isProxy": false + "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "StorageGasOracle", - "address": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", - "constructorArguments": "0x", - "isProxy": false + "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "TransparentUpgradeableProxy", - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "0x000000000000000000000000863e8c26621c52aca1849c53500606e73ba272f0000000000000000000000000666a24f62f7a97ba33c151776eb3d9441a059eb800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true + "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "InterchainGasPaymaster", - "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "constructorArguments": "0x", - "isProxy": false + "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ProtocolFee", - "address": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "ValidatorAnnounce", - "address": "0x7914A3349107A7295Bbf2374db5A973d73D1b324", - "constructorArguments": "0x000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8", - "isProxy": false + "address": "0x977837C7bf2863403d08a57Ee952d63fA1ae279E", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004917a9746a7b6e0a57159ccb7f5a6744247f2d0d", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "name": "FallbackRoutingHook", - "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", - "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000068311418d79fe8d96599384ed767d225635d88a8", - "isProxy": false + "address": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" }, { - "name": "FallbackRoutingHook", - "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", - "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000068311418d79fe8d96599384ed767d225635d88a8", - "isProxy": false + "address": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" } ] } diff --git a/typescript/infra/config/environments/testnet4/create2/addresses.json b/typescript/infra/config/environments/testnet4/create2/addresses.json index 8325277590..65fb9e104e 100644 --- a/typescript/infra/config/environments/testnet4/create2/addresses.json +++ b/typescript/infra/config/environments/testnet4/create2/addresses.json @@ -14,9 +14,6 @@ "goerli": { "Create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a" }, - "moonbasealpha": { - "Create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a" - }, "optimismgoerli": { "Create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a" }, diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 24baf21608..43228eb1e2 100644 --- a/typescript/infra/config/environments/testnet4/funding.ts +++ b/typescript/infra/config/environments/testnet4/funding.ts @@ -9,7 +9,7 @@ import { environment } from './chains'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'cfaf553-20231009-174629', + tag: 'c037206-20240220-152500', }, // We're currently using the same deployer key as testnet2. // To minimize nonce clobbering we offset the key funder cron @@ -24,4 +24,21 @@ export const keyFunderConfig: KeyFunderConfig = { [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, connectionType: RpcConsensusType.Quorum, + // desired balance config + desiredBalancePerChain: { + alfajores: '5', + arbitrumgoerli: '0.5', + bsctestnet: '5', + fuji: '5', + goerli: '0.5', + mumbai: '5', + optimismgoerli: '0.5', + plumetestnet: '0.2', + polygonzkevmtestnet: '1', + scrollsepolia: '1', + sepolia: '5', + }, + desiredKathyBalancePerChain: { + plumetestnet: '0.05', + }, }; diff --git a/typescript/infra/config/environments/testnet4/gas-oracle.ts b/typescript/infra/config/environments/testnet4/gas-oracle.ts index b173f4d3a5..805a6799a1 100644 --- a/typescript/infra/config/environments/testnet4/gas-oracle.ts +++ b/typescript/infra/config/environments/testnet4/gas-oracle.ts @@ -11,7 +11,7 @@ import { getTokenExchangeRateFromValues, } from '../../../src/config/gas-oracle'; -import { TestnetChains, supportedChainNames } from './chains'; +import { supportedChainNames } from './chains'; // Taken by looking at each testnet and overestimating gas prices const gasPrices: ChainMap = { @@ -21,15 +21,15 @@ const gasPrices: ChainMap = { bsctestnet: ethers.utils.parseUnits('15', 'gwei'), goerli: ethers.utils.parseUnits('5', 'gwei'), sepolia: ethers.utils.parseUnits('5', 'gwei'), - moonbasealpha: ethers.utils.parseUnits('5', 'gwei'), optimismgoerli: ethers.utils.parseUnits('0.5', 'gwei'), arbitrumgoerli: ethers.utils.parseUnits('0.5', 'gwei'), - basegoerli: ethers.utils.parseUnits('0.2', 'gwei'), scrollsepolia: ethers.utils.parseUnits('0.5', 'gwei'), lineagoerli: ethers.utils.parseUnits('1', 'gwei'), polygonzkevmtestnet: ethers.utils.parseUnits('1', 'gwei'), chiado: ethers.utils.parseUnits('2', 'gwei'), - // solanadevnet: ethers.BigNumber.from('28'), + solanatestnet: ethers.BigNumber.from('28'), + eclipsetestnet: ethers.BigNumber.from('28'), + plumetestnet: ethers.utils.parseUnits('0.01', 'gwei'), }; // Used to categorize rarity of testnet tokens & approximate exchange rates. @@ -55,19 +55,19 @@ const chainTokenRarity: ChainMap = { bsctestnet: Rarity.Rare, goerli: Rarity.Mythic, sepolia: Rarity.Mythic, - moonbasealpha: Rarity.Common, optimismgoerli: Rarity.Mythic, arbitrumgoerli: Rarity.Mythic, - basegoerli: Rarity.Mythic, scrollsepolia: Rarity.Rare, lineagoerli: Rarity.Rare, polygonzkevmtestnet: Rarity.Common, chiado: Rarity.Common, - // solanadevnet: Rarity.Common, + solanatestnet: Rarity.Common, + eclipsetestnet: Rarity.Common, + plumetestnet: Rarity.Common, }; // Gets the "value" of a testnet chain -function getApproximateValue(chain: TestnetChains): BigNumber { +function getApproximateValue(chain: ChainName): BigNumber { const rarity = chainTokenRarity[chain]; return RARITY_APPROXIMATE_VALUE[rarity]; } diff --git a/typescript/infra/config/environments/testnet4/helloworld.ts b/typescript/infra/config/environments/testnet4/helloworld.ts index c7db766866..079d96d4dd 100644 --- a/typescript/infra/config/environments/testnet4/helloworld.ts +++ b/typescript/infra/config/environments/testnet4/helloworld.ts @@ -1,4 +1,4 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { Chains, RpcConsensusType } from '@hyperlane-xyz/sdk'; import { HelloWorldConfig } from '../../../src/config'; import { HelloWorldKathyRunMode } from '../../../src/config/helloworld/types'; @@ -13,14 +13,14 @@ export const hyperlaneHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '52a8416-20231024-025635', + tag: '620925c-20240213-171901', }, chainsToSkip: [], runEnv: environment, namespace: environment, runConfig: { mode: HelloWorldKathyRunMode.Service, - fullCycleTime: 1000 * 60 * 60 * 48, // every 48 hours + fullCycleTime: 1000 * 60 * 60 * 24 * 6, // every 6 days. At 12 chains it 12 * 11 messages = 132 messages its a bit less than once an hour }, messageSendTimeout: 1000 * 60 * 10, // 10 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min @@ -33,7 +33,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '52a8416-20231024-025635', + tag: '620925c-20240213-171901', }, chainsToSkip: [], runEnv: environment, @@ -43,7 +43,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Single, + connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json index 00719dfd49..e8a9ab59a0 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json +++ b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json @@ -1,7 +1,4 @@ { - "basegoerli": { - "router": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE" - }, "arbitrumgoerli": { "router": "0xFd1e3710347659703962934d2381970ae0022227" }, @@ -20,9 +17,6 @@ "fuji": { "router": "0x0B1C1B54f45e02552331D3106e71f5e0b573D5D4" }, - "moonbasealpha": { - "router": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f" - }, "mumbai": { "router": "0x04980C17e2CE26578C82f81207e706e4505FaE3B" }, diff --git a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json index 06f1f6badf..23f22a4833 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json +++ b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json @@ -7,37 +7,37 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x477D860f8F41bC69dDD32821F2Bf2C2Af0243F16", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x76bDE8069b3467A459262192509Ad5c00AcbdaF0", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000001246529eddca523afe5c6b9414299633d2e16697", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x39e08602570237433673B1340Da17105cA098EE7", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000001246529eddca523afe5c6b9414299633d2e16697", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x0231C1A2CfDbC2d2FA8363c3eC60c85a458088aE", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xb58F3D0CA2B26803eA6a64696989102cE301Fd23", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xD0Ef694E96Bb695DC829f71956227eD141e3089F", "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -51,25 +51,25 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x5da3b8d6F73dF6003A490072106730218c475AAd", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xE04b1D70d487eFCa454EEBf3821E42Dd41e66609", "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b00000000000000000000000006895d3916b94b386faa6ec9276756e16dae7480e", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x6F7A1B7868f14a2f4c36cc561d910aE7958bc8D8", "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b00000000000000000000000006895d3916b94b386faa6ec9276756e16dae7480e", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x0B1C1B54f45e02552331D3106e71f5e0b573D5D4", "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b00000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -83,25 +83,25 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x1A4d8a5eD6C93Af828655e15C44eeE2c2851F0D6", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x79B6151129780f5A80DFEF7c1E967b21A9674A7D", "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f260411150000000000000000000000008ab67caf605c6ee83cbfefb0d8d67fdd3bf7b591", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xBEadC848cAB77875d066466b9F3509f069a0a4F5", "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f260411150000000000000000000000008ab67caf605c6ee83cbfefb0d8d67fdd3bf7b591", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x04980C17e2CE26578C82f81207e706e4505FaE3B", "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f260411150000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -115,31 +115,31 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xE09BF59dCA6e622efC33f6fbd8EF85dE45233388", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x5b297d6BBE080B66E6acece8d378F70F02c0Bc41", "constructorArguments": "00000000000000000000000089280d0b68a246b276a910a518531e2a861ceb650000000000000000000000000dd20e410bdb95404f71c5a4e7fa67b892a5f949", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x90230E8cf53f4F9aFC2A9777eB0093C0B1C81b59", "constructorArguments": "00000000000000000000000089280d0b68a246b276a910a518531e2a861ceb650000000000000000000000000dd20e410bdb95404f71c5a4e7fa67b892a5f949", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xd0e07a58BC91a686235E542350E63211C7A34c17", "constructorArguments": "00000000000000000000000089280d0b68a246b276a910a518531e2a861ceb650000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xEF6DE81100B9314B45A1bdfA5C9148aFC4DdbDeE", "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -153,31 +153,31 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x405BFdEcB33230b4Ad93C29ba4499b776CfBa189", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xBF5b89eBdF86560F872603e2063f17b78dFbae02", "constructorArguments": "000000000000000000000000c4796aabce2a9d774ce4603f5db5fb1605791dbd0000000000000000000000000cd26594ea6c6526927c0f5225ac09f6288e7140", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xA5F234545270cea8f31A8760b8b7ABcaA3654951", "constructorArguments": "000000000000000000000000c4796aabce2a9d774ce4603f5db5fb1605791dbd0000000000000000000000000cd26594ea6c6526927c0f5225ac09f6288e7140", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x33AA9cFBC7E222B3eC231EcAa58dd846cC393C28", "constructorArguments": "000000000000000000000000c4796aabce2a9d774ce4603f5db5fb1605791dbd0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x31b6a75ef30C8612738CC55eA3CB2d5DD3694DC7", "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -191,25 +191,25 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x89e02C3C7b97bCBa63279E10E2a44e6cEF69E6B2", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x36a53DDa7626e5eb62e59a0192Af07b67AF434D8", "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f00000000000000000000000092f05669a354a032a84fcfabfd13bee1abc5bfd0", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x893b57DE7E3dd885F827c74E74F3099C1c250c52", "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f00000000000000000000000092f05669a354a032a84fcfabfd13bee1abc5bfd0", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x3Ce607F6FcE5Dfb9821f33504d86E04A4CD0C75f", "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -223,37 +223,37 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x3582d1238cBC812165981E4fFaB0E8D9a4518910", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x050500E43Fa7F245f3012527DE29d22176c594c7", "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea200000000000000000000000002a7661273528eff3d78cbe7cbd1a717b28b89fc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x7Dc5cE596995af8e5D568563038109B9bCA7F7c6", "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea200000000000000000000000002a7661273528eff3d78cbe7cbd1a717b28b89fc", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xe7B2e23A9A77556736DBB040A8a5159a80Db73D5", "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea20000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x138e726C327E9dd0779104B8d42331757CAeeB9F", "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea20000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x372Cf98E673C235C1abD78DEAA61480e6fe55e46", "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea20000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -267,37 +267,37 @@ "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x339B46496D60b1b6B42e9715DeD8B3D2154dA0Bb", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xB8D70C9352AA59f5EB138e045117841910c107a3", "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xA3bAaC7d60d10abA54cC2Ffb4b63469810C5aDc0", "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x9C63cE44d595cfd97215fcb97d58CFD07a9D6BD7", "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x22C7d91A4533536FE6006EFA46074E2Df8f82e82", "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xFd1e3710347659703962934d2381970ae0022227", "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -305,107 +305,69 @@ ], "sepolia": [ { - "name": "Router", + "name": "HelloWorld", "address": "0x5d56B8a669F50193b54319442c6EEE5edD662381", "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f987d7edcb5890cb321437d8145e3d51131298b6", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x8FF62Cd16C689C6541A284c0e2389D4371255Bc2", "constructorArguments": "00000000000000000000000033abaf6708be03bdf0595da0745a7111b01db8c70000000000000000000000006f2756380fd49228ae25aa7f2817993cb74ecc56", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x73A7bDa325Ad8E5F591179C4ccA61b0CeF70d05C", "constructorArguments": "00000000000000000000000033abaf6708be03bdf0595da0745a7111b01db8c70000000000000000000000006f2756380fd49228ae25aa7f2817993cb74ecc56", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xE14FE2a74Ba1E1bD0EE14B780e58fCcc3c95C013", "constructorArguments": "00000000000000000000000033abaf6708be03bdf0595da0745a7111b01db8c70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x37590302D4E60fbCAdF708E8Fc1DCd903a5880F8", "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec28847660000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "basegoerli": [ - { - "name": "Router", - "address": "0xeAEfB1458b032e75de3e9A3a480d005c426FB1c5", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a00000000000000000000000028b02b97a850872c4d33c3e024fab6499ad96564", - "isProxy": false - }, - { - "name": "Router", - "address": "0xae7a78916Ba4c507aCB2F0e474ace545Ff4bF841", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a00000000000000000000000028b02b97a850872c4d33c3e024fab6499ad96564", - "isProxy": false - }, - { - "name": "Router", - "address": "0x04438ef7622f5412f82915F59caD4f704C61eA48", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a00000000000000000000000028b02b97a850872c4d33c3e024fab6499ad96564", - "isProxy": false - }, - { - "name": "Router", - "address": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a0000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - }, - { - "name": "Router", - "address": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a0000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - }, - { - "name": "Router", - "address": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", - "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a0000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - } - ], "scrollsepolia": [ { - "name": "Router", + "name": "HelloWorld", "address": "0x433f7d6d0cB9eb8FF2902Ad01C1BEd6C09934a33", "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a6800000000000000000000000086fb9f1c124fb20ff130c41a79a432f770f67afd", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a6800000000000000000000000086fb9f1c124fb20ff130c41a79a432f770f67afd", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a6800000000000000000000000086fb9f1c124fb20ff130c41a79a432f770f67afd", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a680000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xeAEfB1458b032e75de3e9A3a480d005c426FB1c5", "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a680000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c", "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a680000000000000000000000000000000000000000000000000000000000000000", "isProxy": false @@ -413,37 +375,37 @@ ], "polygonzkevmtestnet": [ { - "name": "Router", + "name": "HelloWorld", "address": "0xD0680F80F4f947968206806C2598Cbc5b6FE5b03", "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x433f7d6d0cB9eb8FF2902Ad01C1BEd6C09934a33", "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f8000000000000000000000000ad34a66bf6db18e858f6b686557075568c6e031c", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f80000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f80000000000000000000000000000000000000000000000000000000000000000", "isProxy": false }, { - "name": "Router", + "name": "HelloWorld", "address": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f80000000000000000000000000000000000000000000000000000000000000000", "isProxy": false diff --git a/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json b/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json index 5a90998c34..a358fa8514 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json +++ b/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json @@ -1,32 +1,32 @@ { - "alfajores": { - "router": "0x40Adcb03F3C58170b4751c4140636FC6085Ff475" + "arbitrumgoerli": { + "router": "0xEAb4d0A8ba9F1d3a3A665b3F1DE05890A72135D3" }, - "fuji": { - "router": "0xAc003FcDD0EE223664F2A000B5A59D082745700b" + "optimismgoerli": { + "router": "0x7A4A358bF134a55920B4E2cdEbB62961C7a48D19" }, - "mumbai": { - "router": "0xaB0892029C3E7dD4c0235590dc296E618A7b4d03" + "scrollsepolia": { + "router": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77" }, - "bsctestnet": { - "router": "0xd259b0e793535325786675542aB296c451535c27" + "alfajores": { + "router": "0x4D1d8394cBb445A75aE63fDd24421A353B73FF25" }, - "goerli": { - "router": "0x03e9531ae74e8F0f96DE26788a22d35bdaD24185" + "polygonzkevmtestnet": { + "router": "0xb3D796584fDeBE2321894eeF31e0C3ec52169C61" }, - "moonbasealpha": { - "router": "0xE9D6317a10860340f035f3d09052D9d376855bE8" + "sepolia": { + "router": "0xEB25e6e42B743a815E5C0409007993a828a0565f" }, - "optimismgoerli": { - "router": "0x057d38d184d74192B96840D8FbB37e584dDb569A" + "fuji": { + "router": "0x29d70a6753D3F3E756502dE6dCd393fE85a97b73" }, - "arbitrumgoerli": { - "router": "0xaAF1BF6f2BfaE290ea8615066fd167e396a2f578" + "bsctestnet": { + "router": "0x643C7A37FB191A8a63BAB40264B251714F527AED" }, - "sepolia": { - "router": "0x6AD4DEBA8A147d000C09de6465267a9047d1c217" + "goerli": { + "router": "0x916e550aF85E0Ee7A28FAf54b3E1d87f8f4c0Cdd" }, - "solanadevnet": { - "router": "DdTMkk9nuqH5LnD56HLkPiKMV3yB3BNEYSQfgmJHa5i7" + "mumbai": { + "router": "0x4d8323Bb5cD72148e826fCAb9B4A9dd09f77C905" } } diff --git a/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json b/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json index cd0fd9f388..b6aa669e8e 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json +++ b/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json @@ -1,73 +1,89 @@ { - "alfajores": [ + "arbitrumgoerli": [ { - "name": "router", - "address": "0x40Adcb03F3C58170b4751c4140636FC6085Ff475", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0xEAb4d0A8ba9F1d3a3A665b3F1DE05890A72135D3", + "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "fuji": [ + "optimismgoerli": [ { - "name": "router", - "address": "0xAc003FcDD0EE223664F2A000B5A59D082745700b", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0x7A4A358bF134a55920B4E2cdEbB62961C7a48D19", + "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea20000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "mumbai": [ + "scrollsepolia": [ { - "name": "router", - "address": "0xaB0892029C3E7dD4c0235590dc296E618A7b4d03", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77", + "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a680000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "bsctestnet": [ + "alfajores": [ { - "name": "router", - "address": "0xd259b0e793535325786675542aB296c451535c27", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0x4D1d8394cBb445A75aE63fDd24421A353B73FF25", + "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "goerli": [ + "polygonzkevmtestnet": [ { - "name": "router", - "address": "0x03e9531ae74e8F0f96DE26788a22d35bdaD24185", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0xb3D796584fDeBE2321894eeF31e0C3ec52169C61", + "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f80000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "moonbasealpha": [ + "sepolia": [ { - "name": "router", - "address": "0xE9D6317a10860340f035f3d09052D9d376855bE8", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0xEB25e6e42B743a815E5C0409007993a828a0565f", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec28847660000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "optimismgoerli": [ + "fuji": [ { - "name": "router", - "address": "0x057d38d184d74192B96840D8FbB37e584dDb569A", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0x29d70a6753D3F3E756502dE6dCd393fE85a97b73", + "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b00000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "arbitrumgoerli": [ + "bsctestnet": [ { - "name": "router", - "address": "0xaAF1BF6f2BfaE290ea8615066fd167e396a2f578", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "HelloWorld", + "address": "0x643C7A37FB191A8a63BAB40264B251714F527AED", + "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "sepolia": [ + "goerli": [ + { + "name": "HelloWorld", + "address": "0x916e550aF85E0Ee7A28FAf54b3E1d87f8f4c0Cdd", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000000000", + "isProxy": false + } + ], + "moonbasealpha": [ + { + "name": "HelloWorld", + "address": "0xabB6e0A30acEB8327EcC6D25bABf409081fDF2DA", + "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f0000000000000000000000000000000000000000000000000000000000000000", + "isProxy": false + } + ], + "mumbai": [ { - "name": "Router", - "address": "0x6AD4DEBA8A147d000C09de6465267a9047d1c217", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f987d7edcb5890cb321437d8145e3d51131298b6", + "name": "HelloWorld", + "address": "0x4d8323Bb5cD72148e826fCAb9B4A9dd09f77C905", + "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f260411150000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ] diff --git a/typescript/infra/config/environments/testnet4/hooks.ts b/typescript/infra/config/environments/testnet4/hooks.ts index b88082c4b9..81f8b96550 100644 --- a/typescript/infra/config/environments/testnet4/hooks.ts +++ b/typescript/infra/config/environments/testnet4/hooks.ts @@ -5,9 +5,3 @@ export const opHookConfig: OpStackHookConfig = { nativeBridge: '0xDa2332D0a7608919Cd331B1304Cd179129a90495', destinationChain: Chains.optimismgoerli, }; - -export const baseHookConfig: OpStackHookConfig = { - type: HookType.OP_STACK, - nativeBridge: '0x8e5693140eA606bcEB98761d9beB1BC87383706D', - destinationChain: Chains.basegoerli, -}; diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index c098d0af61..2294bff2da 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -1,30 +1,21 @@ import { ChainMap, - GasOracleContractType, IgpConfig, defaultMultisigConfigs, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; import { exclude, objMap } from '@hyperlane-xyz/utils'; -import { TestnetChains, supportedChainNames } from './chains'; +import { supportedChainNames } from './chains'; +import { storageGasOracleConfig } from './gas-oracle'; import { owners } from './owners'; -function getGasOracles(local: TestnetChains) { - return Object.fromEntries( - exclude(local, supportedChainNames).map((name) => [ - name, - GasOracleContractType.StorageGasOracle, - ]), - ); -} - -export const igp: ChainMap = objMap(owners, (chain, owner) => { +export const igp: ChainMap = objMap(owners, (chain, ownerConfig) => { return { - owner, - oracleKey: owner, - beneficiary: owner, - gasOracleType: getGasOracles(chain), + ...ownerConfig, + oracleKey: ownerConfig.owner, + beneficiary: ownerConfig.owner, + oracleConfig: storageGasOracleConfig[chain], overhead: Object.fromEntries( exclude(chain, supportedChainNames).map((remote) => [ remote, diff --git a/typescript/infra/config/environments/testnet4/index.ts b/typescript/infra/config/environments/testnet4/index.ts index 69ca175aed..b522e7eef3 100644 --- a/typescript/infra/config/environments/testnet4/index.ts +++ b/typescript/infra/config/environments/testnet4/index.ts @@ -3,7 +3,7 @@ import { RpcConsensusType } from '@hyperlane-xyz/sdk'; import { getKeysForRole, getMultiProviderForRole, -} from '../../../scripts/utils'; +} from '../../../scripts/agent-utils'; import { EnvironmentConfig } from '../../../src/config'; import { Role } from '../../../src/roles'; import { Contexts } from '../../contexts'; @@ -12,7 +12,6 @@ import { agents } from './agent'; import { environment as environmentName, testnetConfigs } from './chains'; import { core } from './core'; import { keyFunderConfig } from './funding'; -import { storageGasOracleConfig } from './gas-oracle'; import { helloWorld } from './helloworld'; import { igp } from './igp'; import { infrastructure } from './infrastructure'; @@ -51,5 +50,4 @@ export const environment: EnvironmentConfig = { bridgeAdapters: bridgeAdapterConfigs, relayer: liquidityLayerRelayerConfig, }, - storageGasOracleConfig, }; diff --git a/typescript/infra/config/environments/testnet4/ism/verification.json b/typescript/infra/config/environments/testnet4/ism/verification.json index 128dbc6204..11b9163cff 100644 --- a/typescript/infra/config/environments/testnet4/ism/verification.json +++ b/typescript/infra/config/environments/testnet4/ism/verification.json @@ -185,6 +185,126 @@ { "name": "DomaingRoutingIsm", "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", + "constructorArguments": "", + "isProxy": true } ], "fuji": [ @@ -377,6 +497,132 @@ { "name": "DomaingRoutingIsm", "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true } ], "mumbai": [ @@ -459,6 +705,132 @@ { "name": "DomaingRoutingIsm", "address": "0x4A6B11110c45125D73c366C4F9CD320Be2aA60B4" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xEd57F6Eb1750610b3879F7F5093267AF10b3a67D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x4265E4082290e2beF66670A0fb117F3aA0aa126D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xE0978760a9E66E6B9101eAA08A73155C5F61d29D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xe18F31FCd00D8DA65A11610a55458d84b2700a3d", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x832Ea28749C93C05E5AaF8207E4e61Bd56aE3877", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0d48031f320feEa2b2a71DCf678bC42bE8f73B45", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xEd57F6Eb1750610b3879F7F5093267AF10b3a67D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x4265E4082290e2beF66670A0fb117F3aA0aa126D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xE0978760a9E66E6B9101eAA08A73155C5F61d29D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xe18F31FCd00D8DA65A11610a55458d84b2700a3d", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0d48031f320feEa2b2a71DCf678bC42bE8f73B45", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xEd57F6Eb1750610b3879F7F5093267AF10b3a67D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x4265E4082290e2beF66670A0fb117F3aA0aa126D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xE0978760a9E66E6B9101eAA08A73155C5F61d29D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xe18F31FCd00D8DA65A11610a55458d84b2700a3d", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0d48031f320feEa2b2a71DCf678bC42bE8f73B45", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xEd57F6Eb1750610b3879F7F5093267AF10b3a67D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x4265E4082290e2beF66670A0fb117F3aA0aa126D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xE0978760a9E66E6B9101eAA08A73155C5F61d29D", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xe18F31FCd00D8DA65A11610a55458d84b2700a3d", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomaingRoutingIsm", + "address": "0x0d48031f320feEa2b2a71DCf678bC42bE8f73B45", + "constructorArguments": "", + "isProxy": true } ], "bsctestnet": [ @@ -621,6 +993,78 @@ { "name": "DomaingRoutingIsm", "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "constructorArguments": "", + "isProxy": true } ], "goerli": [ @@ -783,6 +1227,42 @@ { "name": "DomaingRoutingIsm", "address": "0x84e9B533555E76a8E9B95daCCbdeE8d3cFA2F5D4" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xcc493EB29FC244004436a69354771F2C38aFC4c9", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x6A995F21a74332BCc280597807DF6Af3e4833f83", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x8D090384691b7a5ED7f418Ddf8135438913EFDcc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xDd4b9980b52A5e786fA903b2190aaFDdA461dED5", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xeB998dC788E2c1e772d198d32e50890544776e75", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0xDF3dbca700227925C39cAF902919f94571A50755", + "constructorArguments": "", + "isProxy": true } ], "sepolia": [ @@ -945,6 +1425,42 @@ { "name": "DomaingRoutingIsm", "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", + "constructorArguments": "", + "isProxy": true } ], "moonbasealpha": [ @@ -1101,12 +1617,48 @@ "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "name": "StaticAggregationHook", + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + }, + { + "name": "DomaingRoutingIsm", + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", + "constructorArguments": "", + "isProxy": false }, { "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", + "constructorArguments": "", + "isProxy": true } ], "optimismgoerli": [ @@ -1279,6 +1831,42 @@ { "name": "DomaingRoutingIsm", "address": "0xE2B83e0E3fd5Cd37b969aB3cA4Fcb92b9bc59612" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x76E4295E6d4bB5Be6314da5759fD0a9003BA522E", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x12D2e2B9bB44a34eb6a284861be742EE852b4Fc1", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x4287827a2F143ab05B708D54f1c47585D4F2aFaB", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xAA3A04799E5Fb6daf989d952802FC8d0dADA44fe", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xce8E9D701A1DFfe672c1d8dB20De2B3fa6F4437D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0xEDff0A731Fb99a85181d84F46a473d5754dA236C", + "constructorArguments": "", + "isProxy": true } ], "arbitrumgoerli": [ @@ -1451,180 +2039,36 @@ { "name": "DomaingRoutingIsm", "address": "0xC204a9864A10d52995E71FE5cc1fDaAA9f7D6FCB" - } - ], - "basegoerli": [ - { - "name": "MerkleRootMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "MerkleRootMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" - }, - { - "name": "MerkleRootMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "MerkleRootMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953" - }, - { - "name": "MessageIdMultisigIsmFactory", - "address": "0x54148470292C24345fb828B003461a9444414517", + "address": "0x4186a6C7aa0D0be93Eda994279974CD2a1E59aE2", "constructorArguments": "", - "isProxy": false + "isProxy": true }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2" - }, - { - "name": "AggregationIsmFactory", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "address": "0x2f0354820d0Da451be63D20D63AbE028DF3cb9A0", "constructorArguments": "", - "isProxy": false + "isProxy": true }, { "name": "StaticAggregationIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7" - }, - { - "name": "AggregationHookFactory", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "address": "0xc5a060a9aC418FafD4eF03334A9973762157C867", "constructorArguments": "", - "isProxy": false + "isProxy": true }, { "name": "StaticAggregationHook", - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61" - }, - { - "name": "RoutingIsmFactory", - "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "address": "0x4e2F4E576b622E3246df8430CeeCb63eA28749Ef", "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomaingRoutingIsm", - "address": "0x7d5dCb6cbE5391fc041Fdf1ce2CdFC7E28cc39Bb" - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953" - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2" - }, - { - "name": "StaticAggregationIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7" - }, - { - "name": "StaticAggregationHook", - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61" - }, - { - "name": "DomaingRoutingIsm", - "address": "0x7d5dCb6cbE5391fc041Fdf1ce2CdFC7E28cc39Bb" - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953" - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2" - }, - { - "name": "StaticAggregationIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7" - }, - { - "name": "StaticAggregationHook", - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61" - }, - { - "name": "DomaingRoutingIsm", - "address": "0x7d5dCb6cbE5391fc041Fdf1ce2CdFC7E28cc39Bb" - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953" - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2" - }, - { - "name": "StaticAggregationIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7" - }, - { - "name": "StaticAggregationHook", - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61" - }, - { - "name": "DomaingRoutingIsm", - "address": "0x7d5dCb6cbE5391fc041Fdf1ce2CdFC7E28cc39Bb" - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953" - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2" - }, - { - "name": "StaticAggregationIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7" - }, - { - "name": "StaticAggregationHook", - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61" - }, - { - "name": "DomaingRoutingIsm", - "address": "0x7d5dCb6cbE5391fc041Fdf1ce2CdFC7E28cc39Bb" - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953" - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2" - }, - { - "name": "StaticAggregationIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7" - }, - { - "name": "StaticAggregationHook", - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61" + "isProxy": true }, { "name": "DomaingRoutingIsm", - "address": "0x7d5dCb6cbE5391fc041Fdf1ce2CdFC7E28cc39Bb" + "address": "0x6bc6101acBFD309d7B9540c1d6B50c9985742399", + "constructorArguments": "", + "isProxy": true } ], "polygonzkevmtestnet": [ @@ -1773,6 +2217,42 @@ { "name": "DomaingRoutingIsm", "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0xc08675806BA844467E559E45E4bB59e66778bDcd", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x772E6fa7a868C94e6031683AD13E821e6649d60C", + "constructorArguments": "", + "isProxy": true } ], "scrollsepolia": [ @@ -1915,6 +2395,104 @@ { "name": "DomaingRoutingIsm", "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0xea80345322520d37770dbDeD3FE9c53ba93E70D8", + "constructorArguments": "", + "isProxy": true + } + ], + "plumetestnet": [ + { + "name": "MerkleRootMultisigIsmFactory", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "MessageIdMultisigIsmFactory", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationIsmFactory", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationHookFactory", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x54148470292C24345fb828B003461a9444414517", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", + "constructorArguments": "", + "isProxy": true } ] } diff --git a/typescript/infra/config/environments/testnet4/owners.ts b/typescript/infra/config/environments/testnet4/owners.ts index 2522ea3892..f63732d4c8 100644 --- a/typescript/infra/config/environments/testnet4/owners.ts +++ b/typescript/infra/config/environments/testnet4/owners.ts @@ -1,14 +1,16 @@ -import { ChainMap } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; -import { ethereumChainNames } from './chains'; +import { supportedChainNames } from '../testnet4/chains'; const ETHEREUM_DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'; // const SEALEVEL_DEPLOYER_ADDRESS = '6DjHX6Ezjpq3zZMZ8KsqyoFYo1zPSDoiZmLLkxD4xKXS'; -export const owners: ChainMap
= { +export const owners: ChainMap = { ...Object.fromEntries( - ethereumChainNames.map((chain) => [chain, ETHEREUM_DEPLOYER_ADDRESS]), + supportedChainNames.map((chain) => [ + chain, + { owner: ETHEREUM_DEPLOYER_ADDRESS }, + ]), ), // [chainMetadata.solanadevnet.name]: SEALEVEL_DEPLOYER_ADDRESS, }; diff --git a/typescript/infra/config/environments/testnet4/plume-sepolia-ETH-deployments.yaml b/typescript/infra/config/environments/testnet4/plume-sepolia-ETH-deployments.yaml new file mode 100644 index 0000000000..092d070ead --- /dev/null +++ b/typescript/infra/config/environments/testnet4/plume-sepolia-ETH-deployments.yaml @@ -0,0 +1,21 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between Plume Testnet and ethereum, ETH +description: Hyperlane Warp Route artifacts +timestamp: '2023-02-14T20:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + sepolia: + protocolType: ethereum + type: native + hypAddress: '0xd99eA1D8b9542D35252504DDd59EDe8C43FB15fd' + name: Ether + symbol: ETH + decimals: 18 + plumetestnet: + protocolType: ethereum + type: synthetic + hypAddress: '0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f' + name: Wrapped Ether + symbol: WETH + decimals: 18 diff --git a/typescript/infra/config/environments/testnet4/testquerysender/addresses.json b/typescript/infra/config/environments/testnet4/testquerysender/addresses.json index 124b301448..fd5455812e 100644 --- a/typescript/infra/config/environments/testnet4/testquerysender/addresses.json +++ b/typescript/infra/config/environments/testnet4/testquerysender/addresses.json @@ -14,9 +14,6 @@ "goerli": { "TestQuerySender": "0x96D7D6Eba6C635e3EaC12b593Ef8B2eE1F6E6683" }, - "moonbasealpha": { - "TestQuerySender": "0x96D7D6Eba6C635e3EaC12b593Ef8B2eE1F6E6683" - }, "optimismgoerli": { "TestQuerySender": "0x96D7D6Eba6C635e3EaC12b593Ef8B2eE1F6E6683" }, diff --git a/typescript/infra/config/environments/testnet4/testrecipient/addresses.json b/typescript/infra/config/environments/testnet4/testrecipient/addresses.json deleted file mode 100644 index 1b954beb9c..0000000000 --- a/typescript/infra/config/environments/testnet4/testrecipient/addresses.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "alfajores": { - "TestRecipient": "0x6489d13AcAd3B8dce4c5B31f375DE4f9451E7b38", - "TestTokenRecipient": "0x92dC0a76452a9D9358D2d2dEd8CddA209DF67c45" - }, - "basegoerli": { - "TestRecipient": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", - "TestTokenRecipient": "0x5e65279Fb7293a058776e37587398fcc3E9184b1" - }, - "fuji": { - "TestRecipient": "0x44a7e1d76fD8AfA244AdE7278336E3D5C658D398", - "TestTokenRecipient": "0x9CC10c844B3Bbae2444E39991aB027C4A05D1F2e" - }, - "mumbai": { - "TestRecipient": "0xF45A4D54223DA32bf7b5D43a9a460Ef3C94C713B", - "TestTokenRecipient": "0x57d098e6952B6C1c85Ce0B68C9Deada3dCf7D05A" - }, - "bsctestnet": { - "TestRecipient": "0xfbcD1c00a3d809f36cC1A15918694B17B32c0b6c", - "TestTokenRecipient": "0x260f6024119549a40595d0937471e607411E8ea5" - }, - "goerli": { - "TestRecipient": "0x4fC0Ac163eFFEb7890937cB89275B2C231880F22", - "TestTokenRecipient": "0xd8958706B33E20C88679a22203F0AFa6158c834d" - }, - "scrollsepolia": { - "TestRecipient": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "TestTokenRecipient": "0xc76E477437065093D353b7d56c81ff54D167B0Ab" - }, - "sepolia": { - "TestRecipient": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07", - "TestTokenRecipient": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A" - }, - "moonbasealpha": { - "TestRecipient": "0x68729446296E413f0b7d6E85F2FD128465F0e5D0", - "TestTokenRecipient": "0xEdA6f85f4761A1f9e42FD40CA5a4E8Ce1C764015" - }, - "optimismgoerli": { - "TestRecipient": "0x518eA1802407b4b5AAF3aA92c1A803FfbA9FB7fe", - "TestTokenRecipient": "0xB9E45eA920DE14e95A16Ed5e1275F893552f2e32" - }, - "arbitrumgoerli": { - "TestRecipient": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", - "TestTokenRecipient": "0x207db41AB053213451f1a71d936353C9056A0205" - }, - "polygonzkevmtestnet": { - "TestRecipient": "0x11918DC33E067C5DA83EEF58E50F856398b8Df4C", - "TestTokenRecipient": "0x04438ef7622f5412f82915F59caD4f704C61eA48" - } -} diff --git a/typescript/infra/config/environments/testnet4/testrecipient/verification.json b/typescript/infra/config/environments/testnet4/testrecipient/verification.json deleted file mode 100644 index f60cc73b01..0000000000 --- a/typescript/infra/config/environments/testnet4/testrecipient/verification.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "alfajores": [ - { - "name": "TestRecipient", - "address": "0x6489d13AcAd3B8dce4c5B31f375DE4f9451E7b38", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x92dC0a76452a9D9358D2d2dEd8CddA209DF67c45", - "constructorArguments": "", - "isProxy": false - } - ], - "fuji": [ - { - "name": "TestRecipient", - "address": "0x44a7e1d76fD8AfA244AdE7278336E3D5C658D398", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x9CC10c844B3Bbae2444E39991aB027C4A05D1F2e", - "constructorArguments": "", - "isProxy": false - } - ], - "mumbai": [ - { - "name": "TestRecipient", - "address": "0xF45A4D54223DA32bf7b5D43a9a460Ef3C94C713B", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x57d098e6952B6C1c85Ce0B68C9Deada3dCf7D05A", - "constructorArguments": "", - "isProxy": false - } - ], - "bsctestnet": [ - { - "name": "TestRecipient", - "address": "0xfbcD1c00a3d809f36cC1A15918694B17B32c0b6c", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x260f6024119549a40595d0937471e607411E8ea5", - "constructorArguments": "", - "isProxy": false - } - ], - "goerli": [ - { - "name": "TestRecipient", - "address": "0x4fC0Ac163eFFEb7890937cB89275B2C231880F22", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0xd8958706B33E20C88679a22203F0AFa6158c834d", - "constructorArguments": "", - "isProxy": false - } - ], - "sepolia": [ - { - "name": "TestRecipient", - "address": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A", - "constructorArguments": "", - "isProxy": false - } - ], - "moonbasealpha": [ - { - "name": "TestRecipient", - "address": "0x68729446296E413f0b7d6E85F2FD128465F0e5D0", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0xEdA6f85f4761A1f9e42FD40CA5a4E8Ce1C764015", - "constructorArguments": "", - "isProxy": false - } - ], - "optimismgoerli": [ - { - "name": "TestRecipient", - "address": "0x518eA1802407b4b5AAF3aA92c1A803FfbA9FB7fe", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0xB9E45eA920DE14e95A16Ed5e1275F893552f2e32", - "constructorArguments": "", - "isProxy": false - } - ], - "arbitrumgoerli": [ - { - "name": "TestRecipient", - "address": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x207db41AB053213451f1a71d936353C9056A0205", - "constructorArguments": "", - "isProxy": false - } - ], - "basegoerli": [ - { - "name": "TestRecipient", - "address": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x5e65279Fb7293a058776e37587398fcc3E9184b1", - "constructorArguments": "", - "isProxy": false - } - ], - "scrollsepolia": [ - { - "name": "TestRecipient", - "address": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "constructorArguments": "", - "isProxy": false - } - ], - "polygonzkevmtestnet": [ - { - "name": "TestRecipient", - "address": "0x11918DC33E067C5DA83EEF58E50F856398b8Df4C", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TestTokenRecipient", - "address": "0x04438ef7622f5412f82915F59caD4f704C61eA48", - "constructorArguments": "", - "isProxy": false - } - ] -} diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 89271fbb54..065412229e 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -1,4 +1,4 @@ -import { chainMetadata } from '@hyperlane-xyz/sdk'; +import { chainMetadata, getReorgPeriod } from '@hyperlane-xyz/sdk'; import { ValidatorBaseChainConfigMap } from '../../../src/config/agent'; import { Contexts } from '../../contexts'; @@ -13,7 +13,7 @@ export const validatorChainConfig = ( return { alfajores: { interval: 5, - reorgPeriod: chainMetadata.alfajores.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.alfajores), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -26,32 +26,14 @@ export const validatorChainConfig = ( '0x6c8bfdfb8c40aba10cc9fb2cf0e3e856e0e5dbb3', '0x54c65eb7677e6086cdde3d5ccef89feb2103a11d', ], + [Contexts.Neutron]: [], }, 'alfajores', ), }, - basegoerli: { - interval: 5, - reorgPeriod: chainMetadata.basegoerli.blocks!.reorgPeriod!, - validators: validatorsConfig( - { - [Contexts.Hyperlane]: [ - '0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03', - '0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071', - '0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25', - ], - [Contexts.ReleaseCandidate]: [ - '0x81983e03363351b63848867bd76687cc80b9ff37', - '0x36de434527b8f83851d83f1b1d72ec11a5903533', - '0x4b65f7527c267e420bf62a0c5a139cb8c3906277', - ], - }, - 'basegoerli', - ), - }, fuji: { interval: 5, - reorgPeriod: chainMetadata.fuji.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.alfajores), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -64,13 +46,14 @@ export const validatorChainConfig = ( '0x0a636e76df4124b092cabb4321d6aaef9defb514', '0xbf86037899efe97bca4cea865607e10b849b5878', ], + [Contexts.Neutron]: [], }, 'fuji', ), }, // chiado: { // interval: 5, - // reorgPeriod: chainMetadata.chiado.blocks!.reorgPeriod!, + // reorgPeriod: getReorgPeriod(chainMetadata.chiado), // validators: validatorsConfig( // { // [Contexts.Hyperlane]: [ @@ -89,7 +72,7 @@ export const validatorChainConfig = ( // }, // lineagoerli: { // interval: 5, - // reorgPeriod: chainMetadata.lineagoerli.blocks!.reorgPeriod!, + // reorgPeriod: getReorgPeriod(chainMetadata.lineagoerli), // validators: validatorsConfig( // { // [Contexts.Hyperlane]: [ @@ -108,7 +91,7 @@ export const validatorChainConfig = ( // }, mumbai: { interval: 5, - reorgPeriod: chainMetadata.mumbai.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.mumbai), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -121,13 +104,14 @@ export const validatorChainConfig = ( '0x954168cf13faeaa248d412e145a17dc697556636', '0x98a9f2610e44246ac0c749c20a07a6eb192ce9eb', ], + [Contexts.Neutron]: [], }, 'mumbai', ), }, bsctestnet: { interval: 5, - reorgPeriod: chainMetadata.bsctestnet.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.bsctestnet), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -140,13 +124,14 @@ export const validatorChainConfig = ( '0xcb5be62b19c52b78cd3993c71c3fa74d821475ae', '0xc50ddb8f03133611853b7f03ffe0a8098e08ae15', ], + [Contexts.Neutron]: [], }, 'bsctestnet', ), }, goerli: { interval: 5, - reorgPeriod: chainMetadata.goerli.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.goerli), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -159,13 +144,14 @@ export const validatorChainConfig = ( '0x4711d476a5929840196def397a156c5253b44b96', '0xb0add42f2a4b824ba5fab2628f930dc1dcfc40f8', ], + [Contexts.Neutron]: [], }, 'goerli', ), }, scrollsepolia: { interval: 5, - reorgPeriod: chainMetadata.scrollsepolia.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.scrollsepolia), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -178,13 +164,14 @@ export const validatorChainConfig = ( '0x10fa7a657a06a47bcca1bacc436d61619e5d104c', '0xa0f1cf3b23bd0f8a5e2ad438657097b8287816b4', ], + [Contexts.Neutron]: [], }, 'scrollsepolia', ), }, sepolia: { interval: 5, - reorgPeriod: chainMetadata.sepolia.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.sepolia), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -197,32 +184,14 @@ export const validatorChainConfig = ( '0x13b51805e9af68e154778d973165f32e10b7446b', '0x7f699c3fc3de4928f1c0abfba1eac3fbb5a00d1b', ], + [Contexts.Neutron]: [], }, 'sepolia', ), }, - moonbasealpha: { - interval: 5, - reorgPeriod: chainMetadata.moonbasealpha.blocks!.reorgPeriod!, - validators: validatorsConfig( - { - [Contexts.Hyperlane]: [ - '0x521877064bd7ac7500d300f162c8c47c256a2f9c', - '0xbc1c70f58ae0459d4b8a013245420a893837d568', - '0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c', - ], - [Contexts.ReleaseCandidate]: [ - '0x376260b40b2ba2100890f27de1eb18a2774f54d1', - '0x776623e8be8d7218940b7c77d02162af4ff97985', - '0xb4c81facd992a6c7c4a187bcce35a6fc968399a0', - ], - }, - 'moonbasealpha', - ), - }, optimismgoerli: { interval: 5, - reorgPeriod: chainMetadata.optimismgoerli.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.optimismgoerli), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -235,13 +204,14 @@ export const validatorChainConfig = ( '0xec6b5ddfd20ee64ff0dcbc7472ad757dce151685', '0x4acd2983a51f1c33c2ab41669184c7679e0316f1', ], + [Contexts.Neutron]: [], }, 'optimismgoerli', ), }, arbitrumgoerli: { interval: 5, - reorgPeriod: chainMetadata.arbitrumgoerli.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.arbitrumgoerli), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -254,13 +224,14 @@ export const validatorChainConfig = ( '0x9be82c7a063b47b2d04c890daabcb666b670a9a4', '0x92c62f4b9cd60a7fe4216d1f12134d34cf827c41', ], + [Contexts.Neutron]: [], }, 'arbitrumgoerli', ), }, polygonzkevmtestnet: { interval: 5, - reorgPeriod: chainMetadata.polygonzkevmtestnet.blocks!.reorgPeriod!, + reorgPeriod: getReorgPeriod(chainMetadata.polygonzkevmtestnet), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -273,13 +244,26 @@ export const validatorChainConfig = ( '0x989bbbfa753431169556f69be1b0a496b252e8a6', '0x292d5788587bb5efd5c2c911115527e57f50cd05', ], + [Contexts.Neutron]: [], }, 'polygonzkevmtestnet', ), }, + injective: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.injective), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x10686BEe585491A0DA5bfCd5ABfbB95Ab4d6c86d'], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'injective', + ), + }, // proteustestnet: { // interval: 5, - // reorgPeriod: chainMetadata.proteustestnet.blocks!.reorgPeriod!, + // reorgPeriod: getReorgPeriod(chainMetadata.proteustestnet), // validators: validatorsConfig( // { // [Contexts.Hyperlane]: [ @@ -294,22 +278,29 @@ export const validatorChainConfig = ( // 'proteustestnet', // ), // }, - // solanadevnet: { - // interval: 10, - // reorgPeriod: chainMetadata.solanadevnet.blocks!.reorgPeriod!, - // validators: validatorsConfig( - // { - // [Contexts.Hyperlane]: [ - // '0xec0f73dbc5b1962a20f7dcbe07c98414025b0c43', - // '0x9c20a149dfa09ea9f77f5a7ca09ed44f9c025133', - // '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', - // ], - // [Contexts.ReleaseCandidate]: [ - // '0x21b9eff4d1a6d3122596c7fb80315bf094b6e5c2', - // ], - // }, - // 'solanadevnet', - // ), - // }, + solanatestnet: { + interval: 1, + reorgPeriod: getReorgPeriod(chainMetadata.solanatestnet), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'solanatestnet', + ), + }, + eclipsetestnet: { + interval: 1, + reorgPeriod: getReorgPeriod(chainMetadata.eclipsetestnet), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xf344f34abca9a444545b5295066348a0ae22dda3'], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'eclipsetestnet', + ), + }, }; }; diff --git a/typescript/infra/config/environments/testnet4/warp/addresses.json b/typescript/infra/config/environments/testnet4/warp/addresses.json new file mode 100644 index 0000000000..1b2a0f788b --- /dev/null +++ b/typescript/infra/config/environments/testnet4/warp/addresses.json @@ -0,0 +1,8 @@ +{ + "plumetestnet": { + "router": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f" + }, + "sepolia": { + "router": "0xd99eA1D8b9542D35252504DDd59EDe8C43FB15fd" + } +} diff --git a/typescript/infra/config/environments/testnet4/warp/verification.json b/typescript/infra/config/environments/testnet4/warp/verification.json new file mode 100644 index 0000000000..079337ceee --- /dev/null +++ b/typescript/infra/config/environments/testnet4/warp/verification.json @@ -0,0 +1,32 @@ +{ + "goerli": [ + { + "name": "HypERC20Collateral", + "address": "0xEF2a1f411d919878159303D9E3fe00D001F7D1D0", + "constructorArguments": "00000000000000000000000007865c6e87b9f70255377e024ace6630c1eaa37f00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", + "isProxy": false + }, + { + "name": "HypNative", + "address": "0x0918293aadfa13F8c9F2cdE415811b6AD9ff4a2d", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba932", + "isProxy": false + } + ], + "plumetestnet": [ + { + "name": "HypERC20", + "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000001200000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065", + "isProxy": false + } + ], + "sepolia": [ + { + "name": "HypNative", + "address": "0xd99eA1D8b9542D35252504DDd59EDe8C43FB15fd", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec2884766", + "isProxy": false + } + ] +} diff --git a/typescript/infra/config/kathy.json b/typescript/infra/config/kathy.json new file mode 100644 index 0000000000..e31ba0cfc4 --- /dev/null +++ b/typescript/infra/config/kathy.json @@ -0,0 +1,17 @@ +{ + "mainnet3": { + "hyperlane": "0x5fb02f40f56d15f0442a39d11a23f73747095b20", + "neutron": "", + "rc": "0x7691f88dccc1554788ba8f226a4a31e5f3ead7c3" + }, + "testnet4": { + "hyperlane": "0x1e8834ff0669b13cf5d37685c5327b82dbae1144", + "neutron": "", + "rc": "0xa623055727c59697961Ee5e35391D2483b26465e" + }, + "test": { + "hyperlane": "", + "neutron": "", + "rc": "" + } +} diff --git a/typescript/infra/config/rcMultisigIsmConfigs.ts b/typescript/infra/config/rcMultisigIsmConfigs.ts index e5b89e1f71..d26ba06442 100644 --- a/typescript/infra/config/rcMultisigIsmConfigs.ts +++ b/typescript/infra/config/rcMultisigIsmConfigs.ts @@ -61,10 +61,6 @@ export const rcMultisigIsmConfigs: ChainMap = { threshold: 1, validators: ['0xace978aaa61d9ee44fe3ab147fd227e0e66b8909'], }, - basegoerli: { - threshold: 1, - validators: ['0x81983e03363351b63848867bd76687cc80b9ff37'], - }, fuji: { threshold: 1, validators: ['0xfc419f9ba3c56c55e28844ade491d428f5a77d55'], @@ -97,10 +93,6 @@ export const rcMultisigIsmConfigs: ChainMap = { threshold: 1, validators: ['0x49f253c0dab33be1573d6c2769b3d9e584d91f82'], }, - moonbasealpha: { - threshold: 1, - validators: ['0x376260b40b2ba2100890f27de1eb18a2774f54d1'], - }, optimismgoerli: { threshold: 1, validators: ['0xed4cf9bf144457c927d7a39613c812c53f296283'], diff --git a/typescript/infra/config/relayer.json b/typescript/infra/config/relayer.json new file mode 100644 index 0000000000..8613cb1441 --- /dev/null +++ b/typescript/infra/config/relayer.json @@ -0,0 +1,17 @@ +{ + "mainnet3": { + "hyperlane": "0x74cae0ecc47b02ed9b9d32e000fd70b9417970c5", + "neutron": "", + "rc": "0x09b96417602ed6ac76651f7a8c4860e60e3aa6d0" + }, + "testnet4": { + "hyperlane": "0x16626cd24fd1f228a031e48b77602ae25f8930db", + "neutron": "", + "rc": "0x7fe8c60ead4ab10be736f4de2b3090db5a851f16" + }, + "test": { + "hyperlane": "", + "neutron": "", + "rc": "" + } +} diff --git a/typescript/infra/config/routingIsm.ts b/typescript/infra/config/routingIsm.ts index 71590813f9..7763ef1f84 100644 --- a/typescript/infra/config/routingIsm.ts +++ b/typescript/infra/config/routingIsm.ts @@ -12,19 +12,11 @@ import { import { DeployEnvironment } from '../src/config'; import { Contexts } from './contexts'; -import { supportedChainNames as mainnet3Chains } from './environments/mainnet3/chains'; -import { owners as mainnet3Owners } from './environments/mainnet3/owners'; -import { owners as testOwners } from './environments/test/owners'; +import { environments } from './environments'; +import { ethereumChainNames as mainnet3Chains } from './environments/mainnet3/chains'; import { supportedChainNames as testnet4Chains } from './environments/testnet4/chains'; -import { owners as testnet4Owners } from './environments/testnet4/owners'; import { multisigIsm } from './multisigIsm'; -const owners = { - test: testOwners, - testnet4: testnet4Owners, - mainnet3: mainnet3Owners, -}; - const chains = { test: TestChains, testnet4: testnet4Chains, @@ -43,7 +35,7 @@ export const routingIsm = ( context: Contexts, ): RoutingIsmConfig | string => { const aggregationIsms: ChainMap = chains[environment] - .filter((_) => _ !== local) + .filter((chain) => chain !== local) .reduce( (acc, chain) => ({ ...acc, @@ -55,7 +47,7 @@ export const routingIsm = ( return { type: IsmType.ROUTING, domains: aggregationIsms, - owner: owners[environment][local], + owner: environments[environment].core[local].owner, }; }; diff --git a/typescript/infra/fork-all.sh b/typescript/infra/fork-all.sh new file mode 100755 index 0000000000..6f39861b45 --- /dev/null +++ b/typescript/infra/fork-all.sh @@ -0,0 +1,18 @@ +ENVIRONMENT=$1 +MODULE=$2 + +if [ -z "$ENVIRONMENT" ] || [ -z "$MODULE" ]; then + echo "Usage: fork-all.sh " + exit 1 +fi + +CHAINS=`yarn ts-node ./scripts/print-chain-metadatas.ts -e $ENVIRONMENT | \ + jq -r 'to_entries | map(select(.value.protocol=="ethereum")) | map(.key) ' | \ + tr -d '\"[],'` + +# echo all subsequent commands +set -x + +for CHAIN in $CHAINS; do + ./fork.sh $ENVIRONMENT $MODULE $CHAIN +done diff --git a/typescript/infra/fork.sh b/typescript/infra/fork.sh index cb909d043b..13ed5a9a9e 100755 --- a/typescript/infra/fork.sh +++ b/typescript/infra/fork.sh @@ -1,19 +1,9 @@ ENVIRONMENT=$1 MODULE=$2 +CHAIN=$3 -if [ -z "$ENVIRONMENT" ]; then - echo "Usage: fork.sh " - exit 1 -fi - -if [ "$ENVIRONMENT" == "testnet4" ]; then - FORK_CHAIN="goerli" - RPC_URL="https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" -elif [ "$ENVIRONMENT" == "mainnet3" ]; then - FORK_CHAIN="arbitrum" - RPC_URL="https://arb1.arbitrum.io/rpc" -else - echo "Unknown environment $ENVIRONMENT" +if [ -z "$ENVIRONMENT" ] || [ -z "$MODULE" ] || [ -z "$CHAIN" ]; then + echo "Usage: fork.sh " exit 1 fi @@ -23,24 +13,36 @@ trap 'jobs -p | xargs -r kill' EXIT # exit 1 on any subsequent failures set -e -anvil --fork-url $RPC_URL --silent > /dev/null & +RPC_URL=`yarn ts-node ./scripts/print-chain-metadatas.ts -e $ENVIRONMENT | jq -r ".$CHAIN.rpcUrls[0].http"` + +anvil --fork-url $RPC_URL --fork-retry-backoff 3 --compute-units-per-second 200 --gas-price 1 --silent & ANVIL_PID=$! while ! cast bn &> /dev/null; do sleep 1 done -echo "=== Run $MODULE checker against forked $ENVIRONMENT ===" -yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN -m $MODULE +# echo all subsequent commands +set -x -echo "=== Run $MODULE deployer against forked $ENVIRONMENT ===" -yarn ts-node ./scripts/deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN -m $MODULE +yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $CHAIN -m $MODULE + +# get balance +DEPLOYER="0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba" +BEFORE=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) + +yarn ts-node ./scripts/deploy.ts -e $ENVIRONMENT -f $CHAIN -m $MODULE + +AFTER=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) +DEPLOY_DELTA="$((BEFORE-AFTER))" # build SDK to get the latest addresses yarn --cwd ../sdk build -echo "=== Run $MODULE govern against forked $ENVIRONMENT ===" -yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN --govern -m $MODULE +BEFORE=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) +yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $CHAIN --govern -m $MODULE + +AFTER=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) +GOVERN_DELTA="$((BEFORE-AFTER))" -echo "=== Run $MODULE checker against forked $ENVIRONMENT after governance ===" -yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN -m $MODULE +yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $CHAIN -m $MODULE diff --git a/typescript/infra/hardhat.config.ts b/typescript/infra/hardhat.config.ts index 404d6e0a00..e6cb48cefa 100644 --- a/typescript/infra/hardhat.config.ts +++ b/typescript/infra/hardhat.config.ts @@ -12,7 +12,7 @@ import { } from '@hyperlane-xyz/sdk'; import { addressToBytes32 } from '@hyperlane-xyz/utils'; -import { Modules, getAddresses } from './scripts/utils'; +import { Modules, getAddresses } from './scripts/agent-utils'; import { sleep } from './src/utils/utils'; enum MailboxHookType { diff --git a/typescript/infra/helm/key-funder/templates/cron-job.yaml b/typescript/infra/helm/key-funder/templates/cron-job.yaml index e0fcb09c83..d51f079184 100644 --- a/typescript/infra/helm/key-funder/templates/cron-job.yaml +++ b/typescript/infra/helm/key-funder/templates/cron-job.yaml @@ -10,6 +10,7 @@ spec: jobTemplate: spec: backoffLimit: 0 + activeDeadlineSeconds: 14400 # 60 * 60 * 4 seconds = 4 hours template: spec: restartPolicy: Never @@ -27,12 +28,18 @@ spec: {{- range $context, $roles := .Values.hyperlane.contextsAndRolesToFund }} - --contexts-and-roles - {{ $context }}={{ join "," $roles }} - - -f - - /addresses-secret/{{ $context }}-addresses.json {{- end }} {{- if .Values.hyperlane.connectionType }} - --connection-type - {{ .Values.hyperlane.connectionType }} +{{- end }} +{{- range $chain, $balance := .Values.hyperlane.desiredBalancePerChain }} + - --desired-balance-per-chain + - {{ $chain }}={{ $balance }} +{{- end }} +{{- range $chain, $balance := .Values.hyperlane.desiredKathyBalancePerChain }} + - --desired-kathy-balance-per-chain + - {{ $chain }}={{ $balance }} {{- end }} env: - name: PROMETHEUS_PUSH_GATEWAY diff --git a/typescript/infra/helm/warp-routes/templates/_helpers.tpl b/typescript/infra/helm/warp-routes/templates/_helpers.tpl index 338d8ad502..285a5842b8 100644 --- a/typescript/infra/helm/warp-routes/templates/_helpers.tpl +++ b/typescript/infra/helm/warp-routes/templates/_helpers.tpl @@ -61,8 +61,8 @@ The warp-routes container command: - ./node_modules/.bin/ts-node - ./typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts - - -l + - -v - "10000" - - -c - - {{ .Values.config }} + - -f + - {{ .Values.configFilePath }} {{- end }} diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 7673048ca2..f3a15d2a6c 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,19 +1,20 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.1.10", + "version": "3.7.0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", "@aws-sdk/client-kms": "3.48.0", "@aws-sdk/client-s3": "^3.74.0", - "@eth-optimism/sdk": "^1.7.0", + "@cosmjs/amino": "^0.31.3", + "@eth-optimism/sdk": "^3.1.6", "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.1.10", - "@hyperlane-xyz/sdk": "3.1.10", - "@hyperlane-xyz/utils": "3.1.10", + "@hyperlane-xyz/helloworld": "3.7.0", + "@hyperlane-xyz/sdk": "3.7.0", + "@hyperlane-xyz/utils": "3.7.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", @@ -21,6 +22,7 @@ "asn1.js": "5.4.1", "aws-kms-ethers-signer": "^0.1.3", "dotenv": "^10.0.0", + "json-stable-stringify": "^1.1.1", "prom-client": "^14.0.1", "prompts": "^2.4.2", "yargs": "^17.7.2" @@ -29,6 +31,7 @@ "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.6", "@types/chai": "^4.2.21", + "@types/json-stable-stringify": "^1.0.36", "@types/mocha": "^10.0.1", "@types/node": "^16.9.1", "@types/prompts": "^2.0.14", @@ -67,7 +70,8 @@ "announce": "hardhat announce --network localhost", "node": "hardhat node", "prettier": "prettier --write *.ts ./src ./config ./scripts ./test", - "test": "mocha --config ../sdk/.mocharc.json test/agents.test.ts" + "test": "mocha --config ../sdk/.mocharc.json test/agents.test.ts", + "test:ci": "yarn test" }, "peerDependencies": { "@ethersproject/abi": "*" diff --git a/typescript/infra/scripts/utils.ts b/typescript/infra/scripts/agent-utils.ts similarity index 61% rename from typescript/infra/scripts/utils.ts rename to typescript/infra/scripts/agent-utils.ts index 825193ece0..e41b19bf25 100644 --- a/typescript/infra/scripts/utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import path from 'path'; import yargs from 'yargs'; @@ -10,12 +11,14 @@ import { CoreConfig, MultiProvider, RpcConsensusType, + chainMetadata, collectValidators, } from '@hyperlane-xyz/sdk'; import { ProtocolType, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { Contexts } from '../config/contexts'; -import { environments } from '../config/environments'; +import { agents } from '../config/environments/agents'; +import { validatorBaseConfigsFn } from '../config/environments/utils'; import { getCurrentKubernetesContext } from '../src/agents'; import { getCloudAgentKey } from '../src/agents/key-utils'; import { CloudAgentKey } from '../src/agents/keys'; @@ -29,6 +32,8 @@ import { EnvironmentNames, deployEnvToSdkEnv } from '../src/config/environment'; import { Role } from '../src/roles'; import { assertContext, assertRole, readJSON } from '../src/utils/utils'; +const debugLog = debug('infra:scripts:utils'); + export enum Modules { // TODO: change PROXY_FACTORY = 'ism', @@ -50,6 +55,7 @@ export const SDK_MODULES = [ Modules.INTERCHAIN_GAS_PAYMASTER, Modules.INTERCHAIN_ACCOUNTS, Modules.INTERCHAIN_QUERY_SYSTEM, + Modules.TEST_RECIPIENT, ]; export function getArgs() { @@ -63,18 +69,26 @@ export function getArgs() { export function withModuleAndFork(args: yargs.Argv) { return args .choices('module', Object.values(Modules)) - .demandOption('module') + .demandOption('module', 'hyperlane module to deploy') .alias('m', 'module') .describe('fork', 'network to fork') .choices('fork', Object.values(Chains)) .alias('f', 'fork'); } +export function withNetwork(args: yargs.Argv) { + return args + .describe('network', 'network to target') + .choices('network', Object.values(Chains)) + .alias('n', 'network'); +} + export function withContext(args: yargs.Argv) { return args .describe('context', 'deploy context') .default('context', Contexts.Hyperlane) .coerce('context', assertContext) + .alias('x', 'context') .demandOption('context'); } @@ -112,6 +126,21 @@ export function withKeyRoleAndChain(args: yargs.Argv) { .alias('i', 'index'); } +// missing chains are chains needed which are not as part of defaultMultisigConfigs in sdk/src/consts/ but are in chainMetadata +export function withMissingChains(args: yargs.Argv) { + return args + .describe('newChains', 'new chains to add') + .string('newChains') + .alias('n', 'newChains'); +} + +export function withBuildArtifactPath(args: yargs.Argv) { + return args + .describe('buildArtifactPath', 'path to hardhat build artifact') + .string('buildArtifactPath') + .alias('b', 'buildArtifactPath'); +} + export function assertEnvironment(env: string): DeployEnvironment { if (EnvironmentNames.includes(env)) { return env as DeployEnvironment; @@ -121,39 +150,98 @@ export function assertEnvironment(env: string): DeployEnvironment { ); } -export function getEnvironmentConfig(environment: DeployEnvironment) { - return environments[environment]; -} - -export async function getConfigsBasedOnArgs(argv?: { +// not requiring to build coreConfig to get agentConfig +export async function getAgentConfigsBasedOnArgs(argv?: { environment: DeployEnvironment; context: Contexts; + newChains: string; }) { - const { environment, context = Contexts.Hyperlane } = argv - ? argv - : await withContext(getArgs()).argv; - const envConfig = getEnvironmentConfig(environment); - const agentConfig = getAgentConfig(context, envConfig); - return { envConfig, agentConfig, context, environment }; + const { + environment, + context = Contexts.Hyperlane, + newChains, + } = argv ? argv : await withMissingChains(withContext(getArgs())).argv; + + const newValidatorCounts: ChainMap = {}; + if (newChains) { + const chains = newChains.split(','); + for (const chain of chains) { + const [chainName, newValidatorCount] = chain.split('='); + newValidatorCounts[chainName] = parseInt(newValidatorCount, 10); + } + } + + const agentConfig = getAgentConfig(context, environment); + // check if new chains are needed + const missingChains = checkIfValidatorsArePersisted(agentConfig); + + // if you include a chain in chainMetadata but not in the aw-multisig.json, you need to specify the new chain in new-chains + for (const chain of missingChains) { + if (!Object.keys(newValidatorCounts).includes(chain)) { + throw new Error(`Missing chain ${chain} not specified in new-chains`); + } + const baseConfig = { + [Contexts.Hyperlane]: [], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }; + // supplementing with dummy addresses for validator as part of missingChains + const validatorsConfig = validatorBaseConfigsFn(environment, context); + + const validators = validatorsConfig( + { + ...baseConfig, + [context]: Array(newValidatorCounts[chain]).fill('0x0'), + }, + chain as Chains, + ); + // the hardcoded fields are not strictly necessary to be accurate for create-keys.ts + // ideally would still get them from the chainMetadata + if (!agentConfig.validators) { + throw new Error('AgentConfig does not have validators'); + } + + agentConfig.validators.chains[chain] = { + interval: chainMetadata[chain].blocks?.estimateBlockTime ?? 1, // dummy value + reorgPeriod: chainMetadata[chain].blocks?.reorgPeriod ?? 0, // dummy value + validators, + }; + } + + return { + agentConfig, + context, + environment, + }; } // Gets the agent config of a specific context. +// without fetching environment config export function getAgentConfig( context: Contexts, - environment: EnvironmentConfig | DeployEnvironment, + environment: DeployEnvironment, ): RootAgentConfig { - const coreConfig = - typeof environment == 'string' - ? getEnvironmentConfig(environment) - : environment; - const agentConfig = coreConfig.agents[context]; - if (!agentConfig) - throw Error( - `Invalid context ${context} for environment, must be one of ${Object.keys( - coreConfig.agents, - )}.`, + const agentsForEnvironment = agents[environment] as Record< + Contexts, + RootAgentConfig + >; + if (!Object.keys(agents[environment]).includes(context)) { + throw new Error( + `Context ${context} does not exist in agents for environment ${environment}`, ); - return agentConfig; + } + return agentsForEnvironment[context]; +} + +// check if validators are persisted in agentConfig +export function checkIfValidatorsArePersisted( + agentConfig: RootAgentConfig, +): Set { + const supportedChainNames = agentConfig.contextChainNames.validator; + const persistedChainNames = Object.keys(agentConfig.validators?.chains || {}); + return new Set( + supportedChainNames.filter((x) => !persistedChainNames.includes(x)), + ); } export function getKeyForRole( @@ -163,8 +251,8 @@ export function getKeyForRole( role: Role, index?: number, ): CloudAgentKey { - const environmentConfig = environments[environment]; - const agentConfig = getAgentConfig(context, environmentConfig); + debugLog(`Getting key for ${role} role`); + const agentConfig = getAgentConfig(context, environment); return getCloudAgentKey(agentConfig, role, chain, index); } @@ -177,17 +265,25 @@ export async function getMultiProviderForRole( // TODO: rename to consensusType? connectionType?: RpcConsensusType, ): Promise { + debugLog(`Getting multiprovider for ${role} role`); if (process.env.CI === 'true') { + debugLog('Returning multiprovider with default RPCs in CI'); return new MultiProvider(); // use default RPCs } const multiProvider = new MultiProvider(txConfigs); await promiseObjAll( objMap(txConfigs, async (chain, _) => { - const provider = await fetchProvider(environment, chain, connectionType); - const key = getKeyForRole(environment, context, chain, role, index); - const signer = await key.getSigner(provider); - multiProvider.setProvider(chain, provider); - multiProvider.setSigner(chain, signer); + if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { + const provider = await fetchProvider( + environment, + chain, + connectionType, + ); + const key = getKeyForRole(environment, context, chain, role, index); + const signer = await key.getSigner(provider); + multiProvider.setProvider(chain, provider); + multiProvider.setSigner(chain, signer); + } }), ); @@ -204,6 +300,7 @@ export async function getKeysForRole( index?: number, ): Promise> { if (process.env.CI === 'true') { + debugLog('No keys to return in CI'); return {}; } diff --git a/typescript/infra/scripts/agents/deploy-agents.ts b/typescript/infra/scripts/agents/deploy-agents.ts index 3802e240b6..f2080b7afd 100644 --- a/typescript/infra/scripts/agents/deploy-agents.ts +++ b/typescript/infra/scripts/agents/deploy-agents.ts @@ -1,6 +1,6 @@ import { createAgentKeysIfNotExists } from '../../src/agents/key-utils'; import { HelmCommand } from '../../src/utils/helm'; -import { getConfigsBasedOnArgs } from '../utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; import { AgentCli } from './utils'; diff --git a/typescript/infra/scripts/agents/utils.ts b/typescript/infra/scripts/agents/utils.ts index 607569a660..c351346807 100644 --- a/typescript/infra/scripts/agents/utils.ts +++ b/typescript/infra/scripts/agents/utils.ts @@ -10,12 +10,10 @@ import { HelmCommand } from '../../src/utils/helm'; import { assertCorrectKubeContext, getArgs, - getConfigsBasedOnArgs, withAgentRole, withContext, -} from '../utils'; - -type GetConfigsArgv = NonNullable[0]>; +} from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; export class AgentCli { roles!: Role[]; @@ -49,11 +47,10 @@ export class AgentCli { } if (this.dryRun) { - for (const m of Object.values(managers)) { - void m.helmValues().then((v) => { - console.log(JSON.stringify(v, null, 2)); - }); - } + const values = await Promise.all( + Object.values(managers).map(async (m) => m.helmValues()), + ); + console.log('Dry run values:\n', JSON.stringify(values, null, 2)); } for (const m of Object.values(managers)) { @@ -61,21 +58,18 @@ export class AgentCli { } } - protected async init( - argv?: GetConfigsArgv & { role: Role[]; 'dry-run'?: boolean }, - ) { + protected async init() { if (this.initialized) return; - if (!argv) - argv = await withAgentRole(withContext(getArgs())) - .describe('dry-run', 'Run through the steps without making any changes') - .boolean('dry-run').argv; + const argv = await withAgentRole(withContext(getArgs())) + .describe('dry-run', 'Run through the steps without making any changes') + .boolean('dry-run').argv; const { envConfig, agentConfig } = await getConfigsBasedOnArgs(argv); await assertCorrectKubeContext(envConfig); this.roles = argv.role; this.envConfig = envConfig; this.agentConfig = agentConfig; - this.dryRun = argv['dry-run'] || false; + this.dryRun = argv.dryRun || false; this.initialized = true; } } diff --git a/typescript/infra/scripts/announce-validators.ts b/typescript/infra/scripts/announce-validators.ts index 7395f74dcd..05cb607f93 100644 --- a/typescript/infra/scripts/announce-validators.ts +++ b/typescript/infra/scripts/announce-validators.ts @@ -8,13 +8,14 @@ import { AllChains, ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { S3Validator } from '../src/agents/aws/validator'; import { CheckpointSyncerType } from '../src/config'; import { deployEnvToSdkEnv } from '../src/config/environment'; +import { isEthereumProtocolChain } from '../src/utils/utils'; import { getAgentConfig, - getEnvironmentConfig, getArgs as getRootArgs, withContext, -} from './utils'; +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; function getArgs() { return withContext(getRootArgs()) @@ -74,14 +75,15 @@ async function main() { throw new Error(`Unknown location type %{location}`); } } else { - const agentConfig = getAgentConfig(context, config); + const agentConfig = getAgentConfig(context, environment); if (agentConfig.validators == undefined) { console.warn('No validators provided for context'); return; } await Promise.all( - Object.entries(agentConfig.validators.chains).map( - async ([chain, validatorChainConfig]) => { + Object.entries(agentConfig.validators.chains) + .filter(([chain, _]) => isEthereumProtocolChain(chain)) + .map(async ([chain, validatorChainConfig]) => { for (const validatorBaseConfig of validatorChainConfig.validators) { if ( validatorBaseConfig.checkpointSyncer.type == @@ -95,6 +97,7 @@ async function main() { contracts.mailbox.address, validatorBaseConfig.checkpointSyncer.bucket, validatorBaseConfig.checkpointSyncer.region, + undefined, ); announcements.push({ storageLocation: validator.storageLocation(), @@ -103,8 +106,7 @@ async function main() { chains.push(chain); } } - }, - ), + }), ); } @@ -124,7 +126,9 @@ async function main() { const announced = announcedLocations[0].includes(location); if (!announced) { const signature = ethers.utils.joinSignature(announcement.signature); - console.log(`Announcing ${address} checkpoints at ${location}`); + console.log( + `[${chain}] Announcing ${address} checkpoints at ${location}`, + ); await validatorAnnounce.announce( address, location, @@ -132,7 +136,9 @@ async function main() { multiProvider.getTransactionOverrides(chain), ); } else { - console.log(`Already announced ${address} checkpoints at ${location}`); + console.log( + `[${chain}] Already announced ${address} checkpoints at ${location}`, + ); } } } diff --git a/typescript/infra/scripts/check-deploy.ts b/typescript/infra/scripts/check-deploy.ts index c09889cf25..52b73c34b3 100644 --- a/typescript/infra/scripts/check-deploy.ts +++ b/typescript/infra/scripts/check-deploy.ts @@ -20,14 +20,14 @@ import { ProxiedRouterGovernor } from '../src/govern/ProxiedRouterGovernor'; import { Role } from '../src/roles'; import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; -import { getHelloWorldApp } from './helloworld/utils'; import { Modules, - getEnvironmentConfig, getArgs as getRootArgs, withContext, withModuleAndFork, -} from './utils'; +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; +import { getHelloWorldApp } from './helloworld/utils'; function getArgs() { return withModuleAndFork(withContext(getRootArgs())) @@ -69,11 +69,11 @@ async function check() { config.core, ismFactory, ); - governor = new HyperlaneCoreGovernor(checker, config.owners); + governor = new HyperlaneCoreGovernor(checker); } else if (module === Modules.INTERCHAIN_GAS_PAYMASTER) { const igp = HyperlaneIgp.fromEnvironment(env, multiProvider); const checker = new HyperlaneIgpChecker(multiProvider, igp, config.igp); - governor = new HyperlaneIgpGovernor(checker, config.owners); + governor = new HyperlaneIgpGovernor(checker); } else if (module === Modules.INTERCHAIN_ACCOUNTS) { const ica = InterchainAccount.fromEnvironment(env, multiProvider); const checker = new InterchainAccountChecker( @@ -81,7 +81,7 @@ async function check() { ica, routerConfig, ); - governor = new ProxiedRouterGovernor(checker, config.owners); + governor = new ProxiedRouterGovernor(checker); } else if (module === Modules.INTERCHAIN_QUERY_SYSTEM) { const iqs = InterchainQuery.fromEnvironment(env, multiProvider); const checker = new InterchainQueryChecker( @@ -89,7 +89,7 @@ async function check() { iqs, routerConfig, ); - governor = new ProxiedRouterGovernor(checker, config.owners); + governor = new ProxiedRouterGovernor(checker); } else if (module === Modules.HELLO_WORLD) { const app = await getHelloWorldApp( config, @@ -104,7 +104,7 @@ async function check() { routerConfig, ismFactory, ); - governor = new ProxiedRouterGovernor(checker, config.owners); + governor = new ProxiedRouterGovernor(checker); } else { console.log(`Skipping ${module}, checker or governor unimplemented`); return; diff --git a/typescript/infra/scripts/check-rpc-urls.ts b/typescript/infra/scripts/check-rpc-urls.ts index 6ac91ef389..daf7f69657 100644 --- a/typescript/infra/scripts/check-rpc-urls.ts +++ b/typescript/infra/scripts/check-rpc-urls.ts @@ -4,8 +4,11 @@ import { debug, error } from '@hyperlane-xyz/utils'; import { getSecretRpcEndpoint } from '../src/agents'; -import { getArgs, getEnvironmentConfig } from './utils'; +import { getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; +// TODO remove this script as part of migration to CLI +// It's redundant with metadata-check.ts in the SDK async function main() { const { environment } = await getArgs().argv; const config = await getEnvironmentConfig(environment); diff --git a/typescript/infra/scripts/core-utils.ts b/typescript/infra/scripts/core-utils.ts new file mode 100644 index 0000000000..4c41ccd83b --- /dev/null +++ b/typescript/infra/scripts/core-utils.ts @@ -0,0 +1,23 @@ +import { Contexts } from '../config/contexts'; +import { environments } from '../config/environments'; +import { DeployEnvironment } from '../src/config'; + +import { getAgentConfig, getArgs, withContext } from './agent-utils'; + +// utils which use both environment configs + +export function getEnvironmentConfig(environment: DeployEnvironment) { + return environments[environment]; +} + +export async function getConfigsBasedOnArgs(argv?: { + environment: DeployEnvironment; + context: Contexts; +}) { + const { environment, context = Contexts.Hyperlane } = argv + ? argv + : await withContext(getArgs()).argv; + const envConfig = getEnvironmentConfig(environment); + const agentConfig = getAgentConfig(context, environment); + return { envConfig, agentConfig, context, environment }; +} diff --git a/typescript/infra/scripts/create-keys.ts b/typescript/infra/scripts/create-keys.ts index 38e1df9dcc..f0778501a8 100644 --- a/typescript/infra/scripts/create-keys.ts +++ b/typescript/infra/scripts/create-keys.ts @@ -1,10 +1,11 @@ import { createAgentKeysIfNotExists } from '../src/agents/key-utils'; -import { getConfigsBasedOnArgs } from './utils'; +import { getAgentConfigsBasedOnArgs } from './agent-utils'; async function main() { - const { agentConfig } = await getConfigsBasedOnArgs(); - return createAgentKeysIfNotExists(agentConfig); + const { agentConfig } = await getAgentConfigsBasedOnArgs(); + await createAgentKeysIfNotExists(agentConfig); + return 'Keys created successfully!'; } main().then(console.log).catch(console.error); diff --git a/typescript/infra/scripts/debug-message.ts b/typescript/infra/scripts/debug-message.ts index d49b13cf60..d39a5f1bb9 100644 --- a/typescript/infra/scripts/debug-message.ts +++ b/typescript/infra/scripts/debug-message.ts @@ -10,7 +10,7 @@ import { bytes32ToAddress, ensure0x, messageId } from '@hyperlane-xyz/utils'; import { deployEnvToSdkEnv } from '../src/config/environment'; import { assertChain } from '../src/utils/utils'; -import { getArgs } from './utils'; +import { getArgs } from './agent-utils'; async function main() { const argv = await getArgs() diff --git a/typescript/infra/scripts/delete-keys.ts b/typescript/infra/scripts/delete-keys.ts index 29dfde2e64..e76aed23dd 100644 --- a/typescript/infra/scripts/delete-keys.ts +++ b/typescript/infra/scripts/delete-keys.ts @@ -1,9 +1,9 @@ import { deleteAgentKeys } from '../src/agents/key-utils'; -import { getConfigsBasedOnArgs } from './utils'; +import { getAgentConfigsBasedOnArgs } from './agent-utils'; async function main() { - const { agentConfig } = await getConfigsBasedOnArgs(); + const { agentConfig } = await getAgentConfigsBasedOnArgs(); return deleteAgentKeys(agentConfig); } diff --git a/typescript/infra/scripts/deploy-infra-external-secrets.ts b/typescript/infra/scripts/deploy-infra-external-secrets.ts index d3ac34dd40..cd20245751 100644 --- a/typescript/infra/scripts/deploy-infra-external-secrets.ts +++ b/typescript/infra/scripts/deploy-infra-external-secrets.ts @@ -1,11 +1,8 @@ import { runExternalSecretsHelmCommand } from '../src/infrastructure/external-secrets/external-secrets'; import { HelmCommand } from '../src/utils/helm'; -import { - assertCorrectKubeContext, - getArgs, - getEnvironmentConfig, -} from './utils'; +import { assertCorrectKubeContext, getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/deploy-infra-monitoring.ts b/typescript/infra/scripts/deploy-infra-monitoring.ts index 06862883a7..81e638cab0 100644 --- a/typescript/infra/scripts/deploy-infra-monitoring.ts +++ b/typescript/infra/scripts/deploy-infra-monitoring.ts @@ -1,11 +1,8 @@ import { runPrometheusHelmCommand } from '../src/infrastructure/monitoring/prometheus'; import { HelmCommand } from '../src/utils/helm'; -import { - assertCorrectKubeContext, - getArgs, - getEnvironmentConfig, -} from './utils'; +import { assertCorrectKubeContext, getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 8e109110ff..b83d1019b8 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -1,9 +1,13 @@ +import { ethers } from 'ethers'; import path from 'path'; import { prompt } from 'prompts'; import { HelloWorldDeployer } from '@hyperlane-xyz/helloworld'; import { ChainMap, + ContractVerifier, + ExplorerLicenseType, + HypERC20Deployer, HyperlaneCore, HyperlaneCoreDeployer, HyperlaneDeployer, @@ -13,14 +17,20 @@ import { InterchainAccountDeployer, InterchainQueryDeployer, LiquidityLayerDeployer, + TestRecipientDeployer, + TokenType, } from '@hyperlane-xyz/sdk'; import { objMap } from '@hyperlane-xyz/utils'; import { Contexts } from '../config/contexts'; +import { aggregationIsm } from '../config/routingIsm'; import { deployEnvToSdkEnv } from '../src/config/environment'; import { deployWithArtifacts } from '../src/deployment/deploy'; import { TestQuerySenderDeployer } from '../src/deployment/testcontracts/testquerysender'; -import { TestRecipientDeployer } from '../src/deployment/testcontracts/testrecipient'; +import { + extractBuildArtifact, + fetchExplorerApiKeys, +} from '../src/deployment/verify'; import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; import { @@ -29,11 +39,13 @@ import { getAddresses, getArgs, getContractAddressesSdkFilepath, - getEnvironmentConfig, getModuleDirectory, + withBuildArtifactPath, withContext, withModuleAndFork, -} from './utils'; + withNetwork, +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { @@ -41,51 +53,103 @@ async function main() { module, fork, environment, - } = await withContext(withModuleAndFork(getArgs())).argv; + network, + buildArtifactPath, + } = await withContext( + withNetwork(withModuleAndFork(withBuildArtifactPath(getArgs()))), + ).argv; const envConfig = getEnvironmentConfig(environment); const env = deployEnvToSdkEnv[environment]; let multiProvider = await envConfig.getMultiProvider(); - // TODO: make this more generic - const deployerAddress = - environment === 'testnet4' - ? '0xfaD1C94469700833717Fa8a3017278BC1cA8031C' - : '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; - if (fork) { multiProvider = multiProvider.extendChainMetadata({ [fork]: { blocks: { confirmations: 0 } }, }); await useLocalProvider(multiProvider, fork); - const signer = await impersonateAccount(deployerAddress); + const signer = await impersonateAccount(envConfig.owners[fork].owner); multiProvider.setSharedSigner(signer); } + let contractVerifier; + if (buildArtifactPath) { + // fetch explorer API keys from GCP + const apiKeys = await fetchExplorerApiKeys(); + // extract build artifact contents + const buildArtifact = extractBuildArtifact(buildArtifactPath); + // instantiate verifier + contractVerifier = new ContractVerifier( + multiProvider, + apiKeys, + buildArtifact, + ExplorerLicenseType.MIT, + ); + } + let config: ChainMap = {}; let deployer: HyperlaneDeployer; if (module === Modules.PROXY_FACTORY) { config = objMap(envConfig.core, (_chain) => true); - deployer = new HyperlaneProxyFactoryDeployer(multiProvider); + deployer = new HyperlaneProxyFactoryDeployer( + multiProvider, + contractVerifier, + ); } else if (module === Modules.CORE) { config = envConfig.core; const ismFactory = HyperlaneIsmFactory.fromAddressesMap( getAddresses(environment, Modules.PROXY_FACTORY), multiProvider, ); - deployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); + deployer = new HyperlaneCoreDeployer( + multiProvider, + ismFactory, + contractVerifier, + ); + } else if (module === Modules.WARP) { + const core = HyperlaneCore.fromEnvironment(env, multiProvider); + const ismFactory = HyperlaneIsmFactory.fromAddressesMap( + getAddresses(environment, Modules.PROXY_FACTORY), + multiProvider, + ); + const routerConfig = core.getRouterConfig(envConfig.owners); + const plumetestnet = { + ...routerConfig.plumetestnet, + type: TokenType.synthetic, + name: 'Wrapped Ether', + symbol: 'WETH', + decimals: 18, + totalSupply: '0', + }; + const sepolia = { + ...routerConfig.sepolia, + type: TokenType.native, + interchainSecurityModule: aggregationIsm( + 'plumetestnet', + Contexts.Hyperlane, + ), + }; + config = { + plumetestnet, + sepolia, + }; + deployer = new HypERC20Deployer( + multiProvider, + ismFactory, + contractVerifier, + ); } else if (module === Modules.INTERCHAIN_GAS_PAYMASTER) { config = envConfig.igp; - deployer = new HyperlaneIgpDeployer(multiProvider); + deployer = new HyperlaneIgpDeployer(multiProvider, contractVerifier); } else if (module === Modules.INTERCHAIN_ACCOUNTS) { const core = HyperlaneCore.fromEnvironment(env, multiProvider); config = core.getRouterConfig(envConfig.owners); - deployer = new InterchainAccountDeployer(multiProvider); + deployer = new InterchainAccountDeployer(multiProvider, contractVerifier); } else if (module === Modules.INTERCHAIN_QUERY_SYSTEM) { const core = HyperlaneCore.fromEnvironment(env, multiProvider); config = core.getRouterConfig(envConfig.owners); - deployer = new InterchainQueryDeployer(multiProvider); + deployer = new InterchainQueryDeployer(multiProvider, contractVerifier); } else if (module === Modules.LIQUIDITY_LAYER) { const core = HyperlaneCore.fromEnvironment(env, multiProvider); const routerConfig = core.getRouterConfig(envConfig.owners); @@ -99,10 +163,18 @@ async function main() { ...routerConfig[chain], }), ); - deployer = new LiquidityLayerDeployer(multiProvider); + deployer = new LiquidityLayerDeployer(multiProvider, contractVerifier); } else if (module === Modules.TEST_RECIPIENT) { - config = objMap(envConfig.core, (_chain) => true); - deployer = new TestRecipientDeployer(multiProvider); + const addresses = getAddresses(environment, Modules.CORE); + + for (const chain of Object.keys(addresses)) { + config[chain] = { + interchainSecurityModule: + addresses[chain].interchainSecurityModule ?? + ethers.constants.AddressZero, // ISM is required for the TestRecipientDeployer but onchain if the ISM is zero address, then it uses the mailbox's defaultISM + }; + } + deployer = new TestRecipientDeployer(multiProvider, contractVerifier); } else if (module === Modules.TEST_QUERY_SENDER) { // Get query router addresses const queryAddresses = getAddresses( @@ -112,11 +184,15 @@ async function main() { config = objMap(queryAddresses, (_c, conf) => ({ queryRouterAddress: conf.router, })); - deployer = new TestQuerySenderDeployer(multiProvider); + deployer = new TestQuerySenderDeployer(multiProvider, contractVerifier); } else if (module === Modules.HELLO_WORLD) { const core = HyperlaneCore.fromEnvironment(env, multiProvider); - config = core.getRouterConfig(deployerAddress); - deployer = new HelloWorldDeployer(multiProvider); + config = core.getRouterConfig(envConfig.owners); + deployer = new HelloWorldDeployer( + multiProvider, + undefined, + contractVerifier, + ); } else { console.log(`Skipping ${module}, deployer unimplemented`); return; @@ -141,7 +217,7 @@ async function main() { addresses, verification, read: environment !== 'test', - write: true, + write: !fork, }; // Don't write agent config in fork tests const agentConfig = @@ -153,9 +229,10 @@ async function main() { } : undefined; - // prompt for confirmation - if ((environment === 'mainnet3' || environment === 'testnet4') && !fork) { - console.log(JSON.stringify(config, null, 2)); + // prompt for confirmation in production environments + if (environment !== 'test' && !fork) { + const confirmConfig = network ? config[network] : config; + console.log(JSON.stringify(confirmConfig, null, 2)); const { value: confirmed } = await prompt({ type: 'confirm', name: 'value', @@ -167,7 +244,13 @@ async function main() { } } - await deployWithArtifacts(config, deployer, cache, fork, agentConfig); + await deployWithArtifacts( + config, + deployer, + cache, + network ?? fork, + agentConfig, + ); } main() diff --git a/typescript/infra/scripts/funding/deploy-key-funder.ts b/typescript/infra/scripts/funding/deploy-key-funder.ts index d4cc5d80d6..a73104c924 100644 --- a/typescript/infra/scripts/funding/deploy-key-funder.ts +++ b/typescript/infra/scripts/funding/deploy-key-funder.ts @@ -4,7 +4,8 @@ import { runKeyFunderHelmCommand, } from '../../src/funding/key-funder'; import { HelmCommand } from '../../src/utils/helm'; -import { assertCorrectKubeContext, getConfigsBasedOnArgs } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; async function main() { const { agentConfig, envConfig } = await getConfigsBasedOnArgs(); diff --git a/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts b/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts index fbbdff95c3..7aad6f55f3 100644 --- a/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts @@ -10,7 +10,8 @@ import { } from '../../src/funding/deterministic-keys'; import { Role } from '../../src/roles'; import { assertChain } from '../../src/utils/utils'; -import { getArgs, getEnvironmentConfig } from '../utils'; +import { getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; async function main() { const argv = await getArgs() diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index a63e33a7de..ee4c2da3d5 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -4,7 +4,6 @@ import { Gauge, Registry } from 'prom-client'; import { format } from 'util'; import { - AllChains, ChainMap, ChainName, Chains, @@ -12,26 +11,44 @@ import { MultiProvider, RpcConsensusType, } from '@hyperlane-xyz/sdk'; -import { Address, error, log, warn } from '@hyperlane-xyz/utils'; +import { + Address, + error, + log, + objFilter, + objMap, + warn, +} from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts'; -import { parseKeyIdentifier } from '../../src/agents/agent'; -import { getAllCloudAgentKeys } from '../../src/agents/key-utils'; import { - BaseCloudAgentKey, + KeyAsAddress, + fetchLocalKeyAddresses, + getRoleKeysPerChain, +} from '../../src/agents/key-utils'; +import { + BaseAgentKey, + LocalAgentKey, ReadOnlyCloudAgentKey, } from '../../src/agents/keys'; import { DeployEnvironment } from '../../src/config'; import { deployEnvToSdkEnv } from '../../src/config/environment'; -import { ContextAndRoles, ContextAndRolesMap } from '../../src/config/funding'; -import { ALL_AGENT_ROLES, AgentRole, Role } from '../../src/roles'; +import { + ContextAndRoles, + ContextAndRolesMap, + KeyFunderConfig, +} from '../../src/config/funding'; +import { FundableRole, Role } from '../../src/roles'; import { submitMetrics } from '../../src/utils/metrics'; import { assertContext, + assertFundableRole, assertRole, + isEthereumProtocolChain, readJSONAtPath, } from '../../src/utils/utils'; -import { getAgentConfig, getArgs, getEnvironmentConfig } from '../utils'; +import { getAgentConfig, getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; import * as L1ETHGateway from './utils/L1ETHGateway.json'; import * as L1MessageQueue from './utils/L1MessageQueue.json'; @@ -53,7 +70,6 @@ type L2Chain = | Chains.optimismgoerli | Chains.arbitrum | Chains.arbitrumgoerli - | Chains.basegoerli | Chains.base; const L2Chains: ChainName[] = [ @@ -61,7 +77,6 @@ const L2Chains: ChainName[] = [ Chains.optimismgoerli, Chains.arbitrum, Chains.arbitrumgoerli, - Chains.basegoerli, Chains.base, Chains.polygonzkevmtestnet, ]; @@ -71,7 +86,6 @@ const L2ToL1: ChainMap = { arbitrumgoerli: 'goerli', optimism: 'ethereum', arbitrum: 'ethereum', - basegoerli: 'goerli', base: 'ethereum', polygonzkevmtestnet: 'goerli', }; @@ -112,55 +126,6 @@ const MIN_DELTA_DENOMINATOR = ethers.BigNumber.from(10); const RC_FUNDING_DISCOUNT_NUMERATOR = ethers.BigNumber.from(2); const RC_FUNDING_DISCOUNT_DENOMINATOR = ethers.BigNumber.from(10); -const desiredBalancePerChain: ChainMap = { - celo: '0.3', - alfajores: '1', - avalanche: '0.3', - fuji: '1', - ethereum: '0.5', - polygon: '2', - mumbai: '0.8', - optimism: '0.5', - arbitrum: '0.5', - bsc: '0.05', - bsctestnet: '1', - goerli: '0.5', - sepolia: '0.5', - moonbasealpha: '1', - moonbeam: '0.5', - optimismgoerli: '0.5', - arbitrumgoerli: '0.5', - gnosis: '0.1', - basegoerli: '0.05', - scrollsepolia: '0.05', - polygonzkevm: '0.3', - scroll: '0.3', - base: '0.3', - polygonzkevmtestnet: '0.3', - - // unused - test1: '0', - test2: '0', - test3: '0', -}; - -// Used to fund kathy with more tokens such that it's able to pay interchain gas -// on mainnet. The amount is roughly > $100 -const desiredKathyBalancePerChain: ChainMap = { - celo: '150', - avalanche: '6', - polygon: '85', - ethereum: '0.4', - optimism: '0.1', - arbitrum: '0.1', - bsc: '0.35', - moonbeam: '250', - gnosis: '100', - scroll: '0.05', - base: '0.05', - polygonzkevm: '0.05', -}; - // The balance threshold of the IGP contract that must be met for the key funder // to call `claim()` const igpClaimThresholdPerChain: ChainMap = { @@ -177,17 +142,16 @@ const igpClaimThresholdPerChain: ChainMap = { bsctestnet: '1', goerli: '1', sepolia: '1', - moonbasealpha: '2', moonbeam: '5', optimismgoerli: '1', arbitrumgoerli: '1', gnosis: '5', - basegoerli: '0.1', scrollsepolia: '0.1', polygonzkevmtestnet: '0.1', base: '0.1', scroll: '0.1', polygonzkevm: '0.1', + plumetestnet: '0.1', // unused test1: '0', test2: '0', @@ -228,6 +192,23 @@ async function main() { .coerce('contexts-and-roles', parseContextAndRolesMap) .demandOption('contexts-and-roles') + .string('desired-balance-per-chain') + .array('desired-balance-per-chain') + .describe( + 'desired-balance-per-chain', + 'Array indicating target balance to fund for each chain. Each element is expected as =', + ) + .coerce('desired-balance-per-chain', parseBalancePerChain) + .demandOption('desired-balance-per-chain') + + .string('desired-kathy-balance-per-chain') + .array('desired-kathy-balance-per-chain') + .describe( + 'desired-kathy-balance-per-chain', + 'Array indicating target balance to fund Kathy for each chain. Each element is expected as =', + ) + .coerce('desired-kathy-balance-per-chain', parseBalancePerChain) + .string('connection-type') .describe('connection-type', 'The provider connection type to use for RPCs') .default('connection-type', RpcConsensusType.Single) @@ -256,21 +237,25 @@ async function main() { ContextFunder.fromSerializedAddressFile( environment, multiProvider, - path, argv.contextsAndRoles, argv.skipIgpClaim, + argv.desiredBalancePerChain, + argv.desiredKathyBalancePerChain ?? {}, + path, ), ); } else { const contexts = Object.keys(argv.contextsAndRoles) as Contexts[]; contextFunders = await Promise.all( contexts.map((context) => - ContextFunder.fromContext( + ContextFunder.fromLocal( environment, multiProvider, context, argv.contextsAndRoles[context]!, argv.skipIgpClaim, + argv.desiredBalancePerChain, + argv.desiredKathyBalancePerChain ?? {}, ), ), ); @@ -293,112 +278,189 @@ async function main() { class ContextFunder { igp: HyperlaneIgp; + keysToFundPerChain: ChainMap; + constructor( public readonly environment: DeployEnvironment, public readonly multiProvider: MultiProvider, - public readonly keys: BaseCloudAgentKey[], + roleKeysPerChain: ChainMap>, public readonly context: Contexts, - public readonly rolesToFund: Role[], + public readonly rolesToFund: FundableRole[], public readonly skipIgpClaim: boolean, + public readonly desiredBalancePerChain: KeyFunderConfig['desiredBalancePerChain'], + public readonly desiredKathyBalancePerChain: KeyFunderConfig['desiredKathyBalancePerChain'], ) { + // At the moment, only blessed EVM chains are supported + roleKeysPerChain = objFilter( + roleKeysPerChain, + (chain, _roleKeys): _roleKeys is Record => { + const valid = + isEthereumProtocolChain(chain) && + multiProvider.tryGetChainName(chain) !== null; + if (!valid) { + warn('Skipping funding for non-blessed or non-Ethereum chain', { + chain, + }); + } + return valid; + }, + ); + this.igp = HyperlaneIgp.fromEnvironment( deployEnvToSdkEnv[this.environment], multiProvider, ); + this.keysToFundPerChain = objMap(roleKeysPerChain, (_chain, roleKeys) => { + return Object.keys(roleKeys).reduce((agg, roleStr) => { + const role = roleStr as FundableRole; + if (this.rolesToFund.includes(role)) { + return [...agg, ...roleKeys[role]]; + } + return agg; + }, [] as BaseAgentKey[]); + }); } static fromSerializedAddressFile( environment: DeployEnvironment, multiProvider: MultiProvider, - path: string, contextsAndRolesToFund: ContextAndRolesMap, skipIgpClaim: boolean, + desiredBalancePerChain: KeyFunderConfig['desiredBalancePerChain'], + desiredKathyBalancePerChain: KeyFunderConfig['desiredKathyBalancePerChain'], + filePath: string, ) { log('Reading identifiers and addresses from file', { - path, + filePath, }); - const idsAndAddresses = readJSONAtPath(path); - const keys: BaseCloudAgentKey[] = idsAndAddresses - .filter((idAndAddress: any) => { - const parsed = parseKeyIdentifier(idAndAddress.identifier); - // Filter out any invalid chain names. This can happen if we're running an old - // version of this script but the list of identifiers (expected to be stored in GCP secrets) - // references newer chains. - return ( - parsed.chainName === undefined || - (AllChains as string[]).includes(parsed.chainName) - ); - }) - .map((idAndAddress: any) => - ReadOnlyCloudAgentKey.fromSerializedAddress( - idAndAddress.identifier, - idAndAddress.address, - ), - ); - - const context = keys[0].context; - // Ensure all keys have the same context, just to be safe - for (const key of keys) { - if (key.context !== context) { - throw Error( - `Expected all keys at path ${path} to have context ${context}, found ${key.context}`, - ); - } + // A big array of KeyAsAddress, including keys that we may not care about. + const allIdsAndAddresses: KeyAsAddress[] = readJSONAtPath(filePath); + if (!allIdsAndAddresses.length) { + throw Error(`Expected at least one key in file ${filePath}`); } - const rolesToFund = contextsAndRolesToFund[context]; - if (!rolesToFund) { - throw Error( - `Expected context ${context} to be defined in contextsAndRolesToFund`, - ); - } + // Arbitrarily pick the first key to get the context + const firstKey = allIdsAndAddresses[0]; + const context = ReadOnlyCloudAgentKey.fromSerializedAddress( + firstKey.identifier, + firstKey.address, + ).context; + + // Indexed by the identifier for quicker lookup + const idsAndAddresses: Record = + allIdsAndAddresses.reduce((agg, idAndAddress) => { + agg[idAndAddress.identifier] = idAndAddress; + return agg; + }, {} as Record); - log('Read keys for context from file', { - path, - keyCount: keys.length, + const agentConfig = getAgentConfig(context, environment); + // Unfetched keys per chain and role, so we know which keys + // we need. We'll use this to create a corresponding object + // of ReadOnlyCloudAgentKeys using addresses found in the + // serialized address file. + const roleKeysPerChain = getRoleKeysPerChain(agentConfig); + + const readOnlyKeysPerChain = objMap( + roleKeysPerChain, + (_chain, roleKeys) => { + return objMap(roleKeys, (_role, keys) => { + return keys.map((key) => { + const idAndAddress = idsAndAddresses[key.identifier]; + if (!idAndAddress) { + throw Error( + `Expected key identifier ${key.identifier} to be in file ${filePath}`, + ); + } + return ReadOnlyCloudAgentKey.fromSerializedAddress( + idAndAddress.identifier, + idAndAddress.address, + ); + }); + }); + }, + ); + + log('Successfully read keys for context from file', { + filePath, + readOnlyKeysPerChain, context, }); return new ContextFunder( environment, multiProvider, - keys, + readOnlyKeysPerChain, context, - rolesToFund, + contextsAndRolesToFund[context]!, skipIgpClaim, + desiredBalancePerChain, + desiredKathyBalancePerChain, ); } - // The keys here are not ReadOnlyCloudAgentKeys, instead they are AgentGCPKey or AgentAWSKeys, - // which require credentials to fetch. If you want to avoid requiring credentials, use - // fromSerializedAddressFile instead. - static async fromContext( + // the keys are retrieved from the local artifacts in the infra/config/relayer.json or infra/config/kathy.json + static async fromLocal( environment: DeployEnvironment, multiProvider: MultiProvider, context: Contexts, - rolesToFund: Role[], + rolesToFund: FundableRole[], skipIgpClaim: boolean, + desiredBalancePerChain: KeyFunderConfig['desiredBalancePerChain'], + desiredKathyBalancePerChain: KeyFunderConfig['desiredKathyBalancePerChain'], ) { - const agentConfig = getAgentConfig(context, environment); - const keys = getAllCloudAgentKeys(agentConfig); - await Promise.all(keys.map((key) => key.fetch())); + // only roles that are fundable keys ie. relayer and kathy + const fundableRoleKeys: Record = { + [Role.Relayer]: '', + [Role.Kathy]: '', + }; + const roleKeysPerChain: ChainMap> = {}; + const chains = getEnvironmentConfig(environment).chainMetadataConfigs; + for (const role of rolesToFund) { + assertFundableRole(role); // only the relayer and kathy are fundable keys + const roleAddress = fetchLocalKeyAddresses(role)[environment][context]; + if (!roleAddress) { + throw Error( + `Could not find address for ${role} in ${environment} ${context}`, + ); + } + fundableRoleKeys[role] = roleAddress; + + for (const chain of Object.keys(chains)) { + if (!roleKeysPerChain[chain as ChainName]) { + roleKeysPerChain[chain as ChainName] = { + [Role.Relayer]: [], + [Role.Kathy]: [], + }; + } + roleKeysPerChain[chain][role] = [ + new LocalAgentKey( + environment, + context, + role, + fundableRoleKeys[role as FundableRole], + chain, + ), + ]; + } + } return new ContextFunder( environment, multiProvider, - keys, + roleKeysPerChain, context, rolesToFund, skipIgpClaim, + desiredBalancePerChain, + desiredKathyBalancePerChain, ); } // Funds all the roles in this.rolesToFund // Returns whether a failure occurred. async fund(): Promise { - let failureOccurred = false; - - const chainKeys = this.getChainKeys(); - const promises = Object.entries(chainKeys).map(async ([chain, keys]) => { + const chainKeyEntries = Object.entries(this.keysToFundPerChain); + const promises = chainKeyEntries.map(async ([chain, keys]) => { + let failureOccurred = false; if (keys.length > 0) { if (!this.skipIgpClaim) { failureOccurred ||= await gracefullyHandleError( @@ -418,45 +480,31 @@ class ContextFunder { const failure = await this.attemptToFundKey(key, chain); failureOccurred ||= failure; } + return failureOccurred; }); - try { - await Promise.all(promises); - } catch (e) { - error('Unhandled error when funding key', { error: format(e) }); - failureOccurred = true; - } + // A failure occurred if any of the promises rejected or + // if any of them resolved with true, indicating a failure + // somewhere along the way + const failureOccurred = (await Promise.allSettled(promises)).reduce( + (failureAgg, result, i) => { + if (result.status === 'rejected') { + error('Funding promise for chain rejected', { + chain: chainKeyEntries[i][0], + error: format(result.reason), + }); + return true; + } + return result.value || failureAgg; + }, + false, + ); return failureOccurred; } - private getChainKeys() { - const chainKeys: ChainMap = Object.fromEntries( - // init with empty arrays - AllChains.map((c) => [c, []]), - ); - for (const role of this.rolesToFund) { - const keys = this.getKeysWithRole(role); - for (const key of keys) { - const chains = getAgentConfig( - key.context, - key.environment, - ).contextChainNames; - // If the role is not a relayer, we need to look up the chains for Kathy, so we'll fallback to the relayer - const roleToLookup = ALL_AGENT_ROLES.includes(role as AgentRole) - ? role - : Role.Relayer; - const chainsPicked = chains[roleToLookup as AgentRole]; - for (const chain of chainsPicked) { - chainKeys[chain].push(key); - } - } - } - return chainKeys; - } - private async attemptToFundKey( - key: BaseCloudAgentKey, + key: BaseAgentKey, chain: ChainName, ): Promise { const provider = this.multiProvider.tryGetProvider(chain); @@ -494,7 +542,7 @@ class ContextFunder { if (L2Chains.includes(chain)) { const funderAddress = await this.multiProvider.getSignerAddress(chain)!; const desiredBalanceEther = ethers.utils.parseUnits( - desiredBalancePerChain[chain], + this.desiredBalancePerChain[chain], 'ether', ); // Optionally bridge ETH to L2 before funding the desired key. @@ -561,10 +609,10 @@ class ContextFunder { private getDesiredBalanceForRole(chain: ChainName, role: Role): BigNumber { const desiredBalanceEther = - role === Role.Kathy && desiredKathyBalancePerChain[chain] - ? desiredKathyBalancePerChain[chain] - : desiredBalancePerChain[chain]; - let desiredBalance = ethers.utils.parseEther(desiredBalanceEther); + role === Role.Kathy && this.desiredKathyBalancePerChain[chain] + ? this.desiredKathyBalancePerChain[chain] + : this.desiredBalancePerChain[chain]; + let desiredBalance = ethers.utils.parseEther(desiredBalanceEther ?? '0'); if (this.context === Contexts.ReleaseCandidate) { desiredBalance = desiredBalance .mul(RC_FUNDING_DISCOUNT_NUMERATOR) @@ -577,7 +625,7 @@ class ContextFunder { // is lower than the desired balance by the min delta private async fundKeyIfRequired( chain: ChainName, - key: BaseCloudAgentKey, + key: BaseAgentKey, desiredBalance: BigNumber, ) { const fundingAmount = await this.getFundingAmount( @@ -777,10 +825,6 @@ class ContextFunder { ), ); } - - private getKeysWithRole(role: Role) { - return this.keys.filter((k) => k.role === role); - } } async function getAddressInfo( @@ -796,13 +840,13 @@ async function getAddressInfo( } async function getKeyInfo( - key: BaseCloudAgentKey, + key: BaseAgentKey, chain: ChainName, provider: ethers.providers.Provider, ) { return { ...(await getAddressInfo(key.address, chain, provider)), - context: key.context, + context: (key as LocalAgentKey).context, originChain: key.chainName, role: key.role, }; @@ -837,7 +881,9 @@ function parseContextAndRoles(str: string): ContextAndRoles { for (const role of roles) { if (!validRoles.has(role)) { throw Error( - `Invalid role ${role}, must be one of ${Array.from(validRoles)}`, + `Invalid fundable role ${role}, must be one of ${Array.from( + validRoles, + )}`, ); } } @@ -848,6 +894,18 @@ function parseContextAndRoles(str: string): ContextAndRoles { }; } +function parseBalancePerChain(strs: string[]): ChainMap { + const balanceMap: ChainMap = {}; + strs.forEach((str) => { + const [chain, balance] = str.split('='); + if (!chain || !balance) { + throw new Error(`Invalid format for balance entry: ${str}`); + } + balanceMap[chain] = balance; + }); + return balanceMap; +} + // Returns whether an error occurred async function gracefullyHandleError( fn: () => Promise, diff --git a/typescript/infra/scripts/funding/reclaim-from-igp.ts b/typescript/infra/scripts/funding/reclaim-from-igp.ts index f780b579a3..87c836054d 100644 --- a/typescript/infra/scripts/funding/reclaim-from-igp.ts +++ b/typescript/infra/scripts/funding/reclaim-from-igp.ts @@ -4,14 +4,15 @@ import { HyperlaneIgp } from '@hyperlane-xyz/sdk'; import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { deployEnvToSdkEnv } from '../../src/config/environment'; -import { getArgs, getEnvironmentConfig } from '../utils'; +import { getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; // Some arbitrary threshold for now const RECLAIM_BALANCE_THRESHOLD = BigNumber.from(10).pow(17); async function main() { const { environment } = await getArgs().argv; - const environmentConfig = await getEnvironmentConfig(environment); + const environmentConfig = getEnvironmentConfig(environment); const multiProvider = await environmentConfig.getMultiProvider(); const igp = HyperlaneIgp.fromEnvironment( deployEnvToSdkEnv[environment], diff --git a/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts b/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts deleted file mode 100644 index 23e31ba97e..0000000000 --- a/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainName, - CoinGeckoTokenPriceGetter, - HyperlaneCore, -} from '@hyperlane-xyz/sdk'; - -import { StorageGasOracleConfig } from '../../src/config'; -import { deployEnvToSdkEnv } from '../../src/config/environment'; -import { - TOKEN_EXCHANGE_RATE_DECIMALS, - TOKEN_EXCHANGE_RATE_SCALE, -} from '../../src/config/gas-oracle'; -import { getArgs, getEnvironmentConfig } from '../utils'; - -import { prettyTokenExchangeRate } from './utils'; - -// Compares the token exchange rate between chains according to the config -// to the exchange rates using current Coingecko prices. The config exchange -// rates apply the 30% spread / fee, so we expect config prices to be ~30% higher. -async function main() { - const tokenPriceGetter = CoinGeckoTokenPriceGetter.withDefaultCoinGecko(); - - const { environment } = await getArgs().argv; - const coreEnvConfig = getEnvironmentConfig(environment); - const multiProvider = await coreEnvConfig.getMultiProvider(); - - const storageGasOracleConfig = coreEnvConfig.storageGasOracleConfig; - if (!storageGasOracleConfig) { - throw Error(`No storage gas oracle config for environment ${environment}`); - } - - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[environment], - multiProvider, - ); - - for (const chain of core.chains()) { - await compare(tokenPriceGetter, storageGasOracleConfig[chain], chain); - console.log('\n==========='); - } -} - -async function compare( - tokenPriceGetter: CoinGeckoTokenPriceGetter, - localStorageGasOracleConfig: StorageGasOracleConfig, - local: ChainName, -) { - for (const remoteStr in localStorageGasOracleConfig) { - const remote = remoteStr as ChainName; - const configGasData = localStorageGasOracleConfig[remote]!; - const currentTokenExchangeRateNum = - await tokenPriceGetter.getTokenExchangeRate(remote, local); - const currentTokenExchangeRate = ethers.utils.parseUnits( - currentTokenExchangeRateNum.toFixed(TOKEN_EXCHANGE_RATE_DECIMALS), - TOKEN_EXCHANGE_RATE_DECIMALS, - ); - - const diff = configGasData.tokenExchangeRate.sub(currentTokenExchangeRate); - const percentDiff = diff - .mul(TOKEN_EXCHANGE_RATE_SCALE) - .div(currentTokenExchangeRate) - .mul(100); - - console.log(`${local} -> ${remote}`); - console.log( - `\tConfig token exchange rate:\n\t\t${prettyTokenExchangeRate( - configGasData.tokenExchangeRate, - )}`, - ); - console.log( - `\tCurrent token exchange rate:\n\t\t${prettyTokenExchangeRate( - currentTokenExchangeRate, - )}`, - ); - console.log( - `Config tokenExchangeRate is ${ethers.utils.formatUnits( - percentDiff, - TOKEN_EXCHANGE_RATE_DECIMALS, - )}% different from the current value`, - ); - console.log('------'); - } -} - -main().catch((err: any) => console.error('Error:', err)); diff --git a/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts b/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts deleted file mode 100644 index a3b36a5815..0000000000 --- a/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { ChainName, HyperlaneIgp, MultiProvider } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; - -import { RemoteGasData, StorageGasOracleConfig } from '../../src/config'; -import { deployEnvToSdkEnv } from '../../src/config/environment'; -import { RemoteGasDataConfig } from '../../src/config/gas-oracle'; -import { getArgs, getEnvironmentConfig } from '../utils'; - -import { - eqRemoteGasData, - prettyRemoteGasData, - prettyRemoteGasDataConfig, -} from './utils'; - -/** - * Idempotent. Use `--dry-run` to not send any transactions. - * Updates the currently stored gas data on the StorageGasOracle contract - * if the configured data differs from the on-chain data. - * Expects the deployer key to be the owner of the StorageGasOracle contract. - */ -async function main() { - const args = await getArgs() - .boolean('dry-run') - .describe('dry-run', 'If true, will not submit any transactions') - .default('dry-run', false).argv; - - const { environment } = await getArgs().argv; - const coreEnvConfig = getEnvironmentConfig(environment); - const multiProvider = await coreEnvConfig.getMultiProvider(); - - const storageGasOracleConfig = coreEnvConfig.storageGasOracleConfig; - if (!storageGasOracleConfig) { - throw Error(`No storage gas oracle config for environment ${environment}`); - } - - const igp = HyperlaneIgp.fromEnvironment( - deployEnvToSdkEnv[environment], - multiProvider, - ); - - for (const chain of igp.chains()) { - if ( - multiProvider.getChainMetadata(chain).protocol !== ProtocolType.Ethereum - ) { - console.log(`Skipping ${chain} because it is not an Ethereum chain`); - continue; - } - - await setStorageGasOracleValues( - igp, - multiProvider, - storageGasOracleConfig[chain], - chain, - args.dryRun, - ); - console.log('\n==========='); - } -} - -async function setStorageGasOracleValues( - igp: HyperlaneIgp, - // This multiProvider is used instead of the one on the IGP because the IGP's - // multiprovider will have filtered out non-Ethereum chains. - multiProvider: MultiProvider, - localStorageGasOracleConfig: StorageGasOracleConfig, - local: ChainName, - dryRun: boolean, -) { - console.log(`Setting remote gas data on local chain ${local}...`); - const storageGasOracle = igp.getContracts(local).storageGasOracle; - - const configsToSet: RemoteGasDataConfig[] = []; - - for (const remote in localStorageGasOracleConfig) { - const desiredGasData = localStorageGasOracleConfig[remote]!; - const remoteId = multiProvider.getDomainId(remote); - - const existingGasData: RemoteGasData = await storageGasOracle.remoteGasData( - remoteId, - ); - - console.log( - `${local} -> ${remote} existing gas data:\n`, - prettyRemoteGasData(existingGasData), - ); - console.log( - `${local} -> ${remote} desired gas data:\n`, - prettyRemoteGasData(desiredGasData), - ); - - if (eqRemoteGasData(existingGasData, desiredGasData)) { - console.log('Existing and desired gas data are the same, doing nothing'); - } else { - console.log('Existing and desired gas data differ, will update'); - configsToSet.push({ - remoteDomain: remoteId, - ...desiredGasData, - }); - } - console.log('---'); - } - - if (configsToSet.length > 0) { - console.log(`Updating ${configsToSet.length} configs on local ${local}:`); - console.log( - configsToSet - .map((config) => prettyRemoteGasDataConfig(multiProvider, config)) - .join('\n\t--\n'), - ); - - if (dryRun) { - console.log('Running in dry run mode, not sending tx'); - } else { - await igp.multiProvider.sendTransaction( - local, - await storageGasOracle.populateTransaction.setRemoteGasDataConfigs( - configsToSet, - ), - ); - } - } -} - -main().catch((err) => console.error('Error', err)); diff --git a/typescript/infra/scripts/gas-oracle/utils.ts b/typescript/infra/scripts/gas-oracle/utils.ts deleted file mode 100644 index 5711db26ba..0000000000 --- a/typescript/infra/scripts/gas-oracle/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { BigNumber, ethers } from 'ethers'; - -import { MultiProvider } from '@hyperlane-xyz/sdk'; - -import { RemoteGasData } from '../../src/config'; -import { RemoteGasDataConfig } from '../../src/config/gas-oracle'; - -export function prettyRemoteGasDataConfig( - multiProvider: MultiProvider, - config: RemoteGasDataConfig, -) { - return `\tRemote: ${config.remoteDomain} (${multiProvider.getChainName( - config.remoteDomain, - )})\n${prettyRemoteGasData(config)}`; -} - -export function prettyRemoteGasData(data: RemoteGasData) { - return `\tToken exchange rate: ${prettyTokenExchangeRate( - data.tokenExchangeRate, - )}\n\tGas price: ${data.gasPrice.toString()} (${ethers.utils.formatUnits( - data.gasPrice, - 'gwei', - )} gwei)`; -} - -export function prettyTokenExchangeRate(tokenExchangeRate: BigNumber) { - return `${tokenExchangeRate.toString()} (${ethers.utils.formatUnits( - tokenExchangeRate, - 10, - )})`; -} - -export function eqRemoteGasData(a: RemoteGasData, b: RemoteGasData): boolean { - return ( - a.tokenExchangeRate.eq(b.tokenExchangeRate) && a.gasPrice.eq(b.gasPrice) - ); -} diff --git a/typescript/infra/scripts/get-key-addresses.ts b/typescript/infra/scripts/get-key-addresses.ts index 6864177d6a..ccbc221060 100644 --- a/typescript/infra/scripts/get-key-addresses.ts +++ b/typescript/infra/scripts/get-key-addresses.ts @@ -1,11 +1,7 @@ import { getAllCloudAgentKeys } from '../src/agents/key-utils'; -import { - getArgs, - getConfigsBasedOnArgs, - withContext, - withProtocol, -} from './utils'; +import { getArgs, withContext, withProtocol } from './agent-utils'; +import { getConfigsBasedOnArgs } from './core-utils'; async function main() { const argv = await withProtocol(withContext(getArgs())).argv; diff --git a/typescript/infra/scripts/helloworld/deploy-kathy.ts b/typescript/infra/scripts/helloworld/deploy-kathy.ts index 45720c1365..2cc7bf4435 100644 --- a/typescript/infra/scripts/helloworld/deploy-kathy.ts +++ b/typescript/infra/scripts/helloworld/deploy-kathy.ts @@ -1,6 +1,7 @@ import { runHelloworldKathyHelmCommand } from '../../src/helloworld/kathy'; import { HelmCommand } from '../../src/utils/helm'; -import { assertCorrectKubeContext, getConfigsBasedOnArgs } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; import { getHelloWorldConfig } from './utils'; diff --git a/typescript/infra/scripts/helloworld/kathy.ts b/typescript/infra/scripts/helloworld/kathy.ts index 2599b895f6..cae5e858a2 100644 --- a/typescript/infra/scripts/helloworld/kathy.ts +++ b/typescript/infra/scripts/helloworld/kathy.ts @@ -40,7 +40,8 @@ import { DeployEnvironment } from '../../src/config/environment'; import { Role } from '../../src/roles'; import { startMetricsServer } from '../../src/utils/metrics'; import { assertChain, diagonalize, sleep } from '../../src/utils/utils'; -import { getArgs, getEnvironmentConfig, withContext } from '../utils'; +import { getArgs, withContext } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; import { getHelloWorldMultiProtocolApp } from './utils'; @@ -246,7 +247,7 @@ async function main(): Promise { } chains.map((chain) => - updateWalletBalanceMetricFor(app, chain, coreConfig.owners[chain]), + updateWalletBalanceMetricFor(app, chain, coreConfig.owners[chain].owner), ); // Incremented each time an entire cycle has occurred @@ -365,14 +366,16 @@ async function main(): Promise { messagesSendCount.labels({ ...labels, status: 'failure' }).inc(); errorOccurred = true; } - updateWalletBalanceMetricFor(app, origin, coreConfig.owners[origin]).catch( - (e) => { - warn('Failed to update wallet balance for chain', { - chain: origin, - err: format(e), - }); - }, - ); + updateWalletBalanceMetricFor( + app, + origin, + coreConfig.owners[origin].owner, + ).catch((e) => { + warn('Failed to update wallet balance for chain', { + chain: origin, + err: format(e), + }); + }); // Break if we should stop sending messages if (await nextMessage()) { diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index 7c4ec54756..834d259d60 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -128,7 +128,7 @@ export async function getHelloWorldMultiProtocolApp( }), ); const app = new HelloMultiProtocolApp( - multiProtocolProvider, + multiProtocolProvider.intersect(Object.keys(routersAndMailboxes)).result, routersAndMailboxes, ); @@ -162,3 +162,18 @@ export function getHelloWorldConfig( } return config; } + +// for create-key, you don't want to fetch the multisig[chain].validators.threshold for yet to be created multisigs +export function getJustHelloWorldConfig( + helloWorldConfigs: Partial> | undefined, + context: Contexts, +): HelloWorldConfig { + if (!helloWorldConfigs) { + throw new Error(`Environment does not have a HelloWorld config`); + } + const config = helloWorldConfigs[context]; + if (!config) { + throw new Error(`Context ${context} does not have a HelloWorld config`); + } + return config; +} diff --git a/typescript/infra/scripts/list-validator-checkpoint-indices.ts b/typescript/infra/scripts/list-validator-checkpoint-indices.ts index e918cb6027..d423861b34 100644 --- a/typescript/infra/scripts/list-validator-checkpoint-indices.ts +++ b/typescript/infra/scripts/list-validator-checkpoint-indices.ts @@ -4,7 +4,8 @@ import { S3Validator } from '../src/agents/aws/validator'; import { deployEnvToSdkEnv } from '../src/config/environment'; import { concurrentMap } from '../src/utils/utils'; -import { getArgs, getEnvironmentConfig, getValidatorsByChain } from './utils'; +import { getArgs, getValidatorsByChain } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/middleware/circle-relayer.ts b/typescript/infra/scripts/middleware/circle-relayer.ts index 0d874357cb..de9288ee0a 100644 --- a/typescript/infra/scripts/middleware/circle-relayer.ts +++ b/typescript/infra/scripts/middleware/circle-relayer.ts @@ -9,11 +9,8 @@ import { import { objFilter } from '@hyperlane-xyz/utils'; import { readJSON, sleep } from '../../src/utils/utils'; -import { - getArgs, - getEnvironmentConfig, - getEnvironmentDirectory, -} from '../utils'; +import { getArgs, getEnvironmentDirectory } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; async function check() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/middleware/deploy-relayers.ts b/typescript/infra/scripts/middleware/deploy-relayers.ts index e9643e5068..58a95da84e 100644 --- a/typescript/infra/scripts/middleware/deploy-relayers.ts +++ b/typescript/infra/scripts/middleware/deploy-relayers.ts @@ -4,7 +4,8 @@ import { runLiquidityLayerRelayerHelmCommand, } from '../../src/middleware/liquidity-layer-relayer'; import { HelmCommand } from '../../src/utils/helm'; -import { assertCorrectKubeContext, getConfigsBasedOnArgs } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; async function main() { const { agentConfig, envConfig, context } = await getConfigsBasedOnArgs(); diff --git a/typescript/infra/scripts/middleware/portal-relayer.ts b/typescript/infra/scripts/middleware/portal-relayer.ts index bb2d7f9749..09164bb791 100644 --- a/typescript/infra/scripts/middleware/portal-relayer.ts +++ b/typescript/infra/scripts/middleware/portal-relayer.ts @@ -9,11 +9,8 @@ import { error, log } from '@hyperlane-xyz/utils'; import { bridgeAdapterConfigs } from '../../config/environments/testnet4/token-bridge'; import { readJSON, sleep } from '../../src/utils/utils'; -import { - getArgs, - getEnvironmentConfig, - getEnvironmentDirectory, -} from '../utils'; +import { getArgs, getEnvironmentDirectory } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; async function relayPortalTransfers() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/module-can-verify.ts b/typescript/infra/scripts/module-can-verify.ts index 7dfb3c70a1..4cffb36bf3 100644 --- a/typescript/infra/scripts/module-can-verify.ts +++ b/typescript/infra/scripts/module-can-verify.ts @@ -3,7 +3,8 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { deployEnvToSdkEnv } from '../src/config/environment'; -import { getArgs, getEnvironmentConfig } from './utils'; +import { getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const args = await getArgs().argv; diff --git a/typescript/infra/scripts/print-chain-metadatas.ts b/typescript/infra/scripts/print-chain-metadatas.ts index 7b96c35807..954d676445 100644 --- a/typescript/infra/scripts/print-chain-metadatas.ts +++ b/typescript/infra/scripts/print-chain-metadatas.ts @@ -1,4 +1,5 @@ -import { getArgs, getEnvironmentConfig } from './utils'; +import { getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; // This script exists to print the chain metadata configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( diff --git a/typescript/infra/scripts/print-gas-prices.ts b/typescript/infra/scripts/print-gas-prices.ts new file mode 100644 index 0000000000..3cf0d757fc --- /dev/null +++ b/typescript/infra/scripts/print-gas-prices.ts @@ -0,0 +1,39 @@ +import { ethers } from 'ethers'; + +import { MultiProtocolProvider, ProviderType } from '@hyperlane-xyz/sdk'; +import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; + +import { mainnetConfigs } from '../config/environments/mainnet3/chains'; + +async function main() { + const metadata = mainnetConfigs; + + const mpp = new MultiProtocolProvider(metadata); + + const prices = await promiseObjAll( + objMap(metadata, async (chain, _) => { + const provider = mpp.getProvider(chain); + switch (provider.type) { + case ProviderType.EthersV5: + const gasPrice = await provider.provider.getGasPrice(); + return ethers.utils.formatUnits(gasPrice, 'gwei'); + case ProviderType.CosmJsWasm: + // TODO: get default gas price + return '0.1'; + case ProviderType.SolanaWeb3: + return '0.001'; + default: + throw new Error(`Unsupported provider type: ${provider.type}`); + } + }), + ); + + console.log(JSON.stringify(prices, null, 2)); +} + +main() + .then() + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/typescript/infra/scripts/print-multisig-ism-config.ts b/typescript/infra/scripts/print-multisig-ism-config.ts index e05aeaf996..3b27ef8b91 100644 --- a/typescript/infra/scripts/print-multisig-ism-config.ts +++ b/typescript/infra/scripts/print-multisig-ism-config.ts @@ -2,7 +2,7 @@ import { AllChains, IsmType } from '@hyperlane-xyz/sdk'; import { multisigIsms } from '../config/multisigIsm'; -import { getArgs, withContext } from './utils'; +import { getArgs, withContext } from './agent-utils'; // This script exists to print the default multisig ISM validator sets for a given environment // so they can easily be copied into the Sealevel tooling. :'( diff --git a/typescript/infra/scripts/print-token-prices.ts b/typescript/infra/scripts/print-token-prices.ts new file mode 100644 index 0000000000..9b6ba56124 --- /dev/null +++ b/typescript/infra/scripts/print-token-prices.ts @@ -0,0 +1,33 @@ +import { objMap } from '@hyperlane-xyz/utils'; + +import { mainnetConfigs } from '../config/environments/mainnet3/chains'; + +const CURRENCY = 'usd'; + +async function main() { + const metadata = mainnetConfigs; + + const ids = objMap( + metadata, + (_, metadata) => metadata.gasCurrencyCoinGeckoId ?? metadata.name, + ); + + const resp = await fetch( + `https://api.coingecko.com/api/v3/simple/price?ids=${Object.entries( + ids, + ).join(',')}&vs_currencies=${CURRENCY}`, + ); + + const idPrices = await resp.json(); + + const prices = objMap(ids, (_, id) => idPrices[id][CURRENCY].toString()); + + console.log(JSON.stringify(prices, null, 2)); +} + +main() + .then() + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/typescript/infra/scripts/rotate-key.ts b/typescript/infra/scripts/rotate-key.ts index 34f942475e..506288dad4 100644 --- a/typescript/infra/scripts/rotate-key.ts +++ b/typescript/infra/scripts/rotate-key.ts @@ -3,7 +3,7 @@ import { getArgs, withContext, withKeyRoleAndChain, -} from './utils'; +} from './agent-utils'; async function rotateKey() { const argv = await withContext(withKeyRoleAndChain(getArgs())).argv; diff --git a/typescript/infra/scripts/safe-delegate.ts b/typescript/infra/scripts/safe-delegate.ts index 22a4d3e149..cc1f22b883 100644 --- a/typescript/infra/scripts/safe-delegate.ts +++ b/typescript/infra/scripts/safe-delegate.ts @@ -8,7 +8,8 @@ import { AllChains } from '@hyperlane-xyz/sdk'; import { getSafeDelegates, getSafeService } from '../src/utils/safe'; -import { getEnvironmentConfig, getArgs as getRootArgs } from './utils'; +import { getArgs as getRootArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; function getArgs() { return getRootArgs() diff --git a/typescript/infra/scripts/update-key.ts b/typescript/infra/scripts/update-key.ts index e05013ce88..dab4b279c9 100644 --- a/typescript/infra/scripts/update-key.ts +++ b/typescript/infra/scripts/update-key.ts @@ -3,7 +3,7 @@ import { getArgs, withContext, withKeyRoleAndChain, -} from './utils'; +} from './agent-utils'; async function rotateKey() { const argv = await withKeyRoleAndChain(withContext(getArgs())).argv; diff --git a/typescript/infra/scripts/verify-validators.ts b/typescript/infra/scripts/verify-validators.ts index 2433a6b4f3..a9d288cc23 100644 --- a/typescript/infra/scripts/verify-validators.ts +++ b/typescript/infra/scripts/verify-validators.ts @@ -4,7 +4,8 @@ import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { S3Validator } from '../src/agents/aws/validator'; import { deployEnvToSdkEnv } from '../src/config/environment'; -import { getArgs, getEnvironmentConfig, getValidatorsByChain } from './utils'; +import { getArgs, getValidatorsByChain } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/verify.ts b/typescript/infra/scripts/verify.ts index a2367f087d..4b75e66500 100644 --- a/typescript/infra/scripts/verify.ts +++ b/typescript/infra/scripts/verify.ts @@ -1,72 +1,76 @@ import { ChainMap, - CompilerOptions, - ContractVerifier, + ExplorerLicenseType, + PostDeploymentContractVerifier, + VerificationInput, } from '@hyperlane-xyz/sdk'; -import { fetchGCPSecret } from '../src/utils/gcloud'; -import { execCmd, readFileAtPath, readJSONAtPath } from '../src/utils/utils'; +import { + extractBuildArtifact, + fetchExplorerApiKeys, +} from '../src/deployment/verify'; +import { readJSONAtPath } from '../src/utils/utils'; -import { assertEnvironment, getArgs, getEnvironmentConfig } from './utils'; +import { + assertEnvironment, + getArgs, + withBuildArtifactPath, + withNetwork, +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; -// Requires https://github.com/crytic/solc-select to be installed and -// present in your $PATH. The current solc compiler version should -// already be installed via `solc-select install $VERSION` async function main() { - const argv = await getArgs() - // This file can be generated by running `$ yarn workspace @hyperlane-xyz/core flatten`, - .string('source') - .describe('source', 'flattened solidity source file') - .demandOption('source') - .string('artifacts') - .describe('artifacts', 'verification artifacts JSON file') - .demandOption('artifacts') - .string('network') - .describe('network', 'optional target network').argv; + const { environment, buildArtifactPath, verificationArtifactPath, network } = + await withNetwork(withBuildArtifactPath(getArgs())) + .string('verificationArtifactPath') + .describe( + 'verificationArtifactPath', + 'path to hyperlane verification artifact', + ) + .alias('v', 'verificationArtifactPath') + .demandOption('verificationArtifactPath') + .demandOption('buildArtifactPath').argv; - const environment = assertEnvironment(argv.e!); + // set up multiprovider + assertEnvironment(environment); const config = getEnvironmentConfig(environment); const multiProvider = await config.getMultiProvider(); - const verification = readJSONAtPath(argv.artifacts!); - - const sourcePath = argv.source!; - const flattenedSource = readFileAtPath(sourcePath); - - // from solidity/core/hardhat.config.ts - const compilerOptions: CompilerOptions = { - codeformat: 'solidity-single-file', - compilerversion: 'v0.8.19+commit.7dd6d404', - optimizationUsed: '1', - runs: '999999', - }; - - const versionRegex = /v(\d.\d.\d+)\+commit.\w+/; - const matches = versionRegex.exec(compilerOptions.compilerversion); - if (!matches) { - throw new Error( - `Invalid compiler version ${compilerOptions.compilerversion}`, - ); - } + // grab verification artifacts + const verification: ChainMap = readJSONAtPath( + verificationArtifactPath, + ); - // ensures flattened source is compilable - await execCmd(`solc-select use ${matches[1]}`); - await execCmd(`solc ${sourcePath}`); + // fetch explorer API keys from GCP + const apiKeys = await fetchExplorerApiKeys(); - const apiKeys: ChainMap = (await fetchGCPSecret( - 'explorer-api-keys', - true, - )) as any; + // extract build artifact contents + const buildArtifact = extractBuildArtifact(buildArtifactPath); - const verifier = new ContractVerifier( + // instantiate verifier + const verifier = new PostDeploymentContractVerifier( verification, multiProvider, apiKeys, - flattenedSource, - compilerOptions, + buildArtifact, + ExplorerLicenseType.MIT, ); - return verifier.verify(argv.network ? [argv.network] : undefined); + // verify all the things + const failedResults = ( + await verifier.verify(network ? [network] : undefined) + ).filter((result) => result.status === 'rejected'); + + // only log the failed verifications to console + if (failedResults.length > 0) { + console.error( + 'Verification failed for the following contracts:', + failedResults.map((result) => result), + ); + process.exit(1); + } + + process.exit(0); } main().then(console.log).catch(console.error); diff --git a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts index 534267004f..8c765ba63b 100644 --- a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts +++ b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts @@ -1,12 +1,24 @@ +import yargs from 'yargs'; + import { HelmCommand } from '../../src/utils/helm'; import { runWarpRouteHelmCommand } from './helm'; async function main() { + const { filePath } = await yargs(process.argv.slice(2)) + .alias('f', 'filePath') + .describe( + 'filePath', + 'indicate the filepath to the warp route yaml file relative to the monorepo root', + ) + .demandOption('filePath') + .string('filePath') + .parse(); + await runWarpRouteHelmCommand( HelmCommand.InstallOrUpgrade, 'mainnet3', - 'neutron', + filePath, ); } diff --git a/typescript/infra/scripts/warp-routes/helm.ts b/typescript/infra/scripts/warp-routes/helm.ts index 170800e003..bcf1a20c8d 100644 --- a/typescript/infra/scripts/warp-routes/helm.ts +++ b/typescript/infra/scripts/warp-routes/helm.ts @@ -1,37 +1,38 @@ import { DeployEnvironment } from '../../src/config'; import { HelmCommand, helmifyValues } from '../../src/utils/helm'; import { execCmd } from '../../src/utils/utils'; -import { assertCorrectKubeContext, getEnvironmentConfig } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; export async function runWarpRouteHelmCommand( helmCommand: HelmCommand, runEnv: DeployEnvironment, - config: string, + configFilePath: string, ) { const envConfig = getEnvironmentConfig(runEnv); await assertCorrectKubeContext(envConfig); - const values = getWarpRoutesHelmValues(config); - + const values = getWarpRoutesHelmValues(configFilePath); + const releaseName = getHelmReleaseName(configFilePath); return execCmd( - `helm ${helmCommand} ${getHelmReleaseName( - config, - )} ./helm/warp-routes --namespace ${runEnv} ${values.join( + `helm ${helmCommand} ${releaseName} ./helm/warp-routes --namespace ${runEnv} ${values.join( ' ', - )} --set fullnameOverride="${getHelmReleaseName(config)}"`, + )} --set fullnameOverride="${releaseName}"`, ); } function getHelmReleaseName(route: string): string { - return `hyperlane-warp-route-${route}`; + const match = route.match(/\/([^/]+)-deployments\.yaml$/); + const name = match ? match[1] : route; + return `hyperlane-warp-route-${name.toLowerCase()}`; // helm requires lower case release names } -function getWarpRoutesHelmValues(config: string) { +function getWarpRoutesHelmValues(configFilePath: string) { const values = { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'ae8ce44-20231101-012032', + tag: '9b69b34-20240221-172841', }, - config: config, // nautilus or neutron + configFilePath: configFilePath, }; return helmifyValues(values); } diff --git a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts index b2631a87cd..64af067dee 100644 --- a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts @@ -7,10 +7,13 @@ import { ERC20__factory } from '@hyperlane-xyz/core'; import { ChainMap, ChainName, + CosmNativeTokenAdapter, CwNativeTokenAdapter, MultiProtocolProvider, SealevelHypCollateralAdapter, TokenType, + WarpRouteConfig, + WarpRouteConfigSchema, } from '@hyperlane-xyz/sdk'; import { ProtocolType, @@ -19,12 +22,8 @@ import { promiseObjAll, } from '@hyperlane-xyz/utils'; -import { - WarpTokenConfig, - nautilusList, - neutronList, -} from '../../src/config/grafana_token_config'; import { startMetricsServer } from '../../src/utils/metrics'; +import { readYaml } from '../../src/utils/utils'; const metricsRegister = new Registry(); const warpRouteTokenBalance = new Gauge({ @@ -40,20 +39,36 @@ const warpRouteTokenBalance = new Gauge({ ], }); +export function readWarpRouteConfig(filePath: string) { + const config = readYaml(filePath); + if (!config) throw new Error(`No warp config found at ${filePath}`); + const result = WarpRouteConfigSchema.safeParse(config); + if (!result.success) { + const errorMessages = result.error.issues.map( + (issue: any) => `${issue.path} => ${issue.message}`, + ); + throw new Error(`Invalid warp config:\n ${errorMessages.join('\n')}`); + } + return result.data; +} + async function main(): Promise { - const { checkFrequency, config } = await yargs(process.argv.slice(2)) + const { checkFrequency, filePath } = await yargs(process.argv.slice(2)) .describe('checkFrequency', 'frequency to check balances in ms') .demandOption('checkFrequency') - .alias('l', 'checkFrequency') + .alias('v', 'checkFrequency') // v as in Greek letter nu .number('checkFrequency') - .alias('c', 'config') - .describe('config', 'choose warp token config') - .demandOption('config') - .choices('config', ['neutron', 'nautilus']) + .alias('f', 'filePath') + .describe( + 'filePath', + 'indicate the filepatch to the warp route yaml file relative to typescript/infra', + ) + .demandOption('filePath') + .string('filePath') .parse(); - const tokenList: WarpTokenConfig = - config === 'neutron' ? neutronList : nautilusList; + const tokenConfig: WarpRouteConfig = + readWarpRouteConfig(filePath).data.config; startMetricsServer(metricsRegister); @@ -63,8 +78,8 @@ async function main(): Promise { setInterval(async () => { try { debug('Checking balances'); - const balances = await checkBalance(tokenList, multiProtocolProvider); - updateTokenBalanceMetrics(tokenList, balances); + const balances = await checkBalance(tokenConfig, multiProtocolProvider); + updateTokenBalanceMetrics(tokenConfig, balances); } catch (e) { console.error('Error checking balances', e); } @@ -74,20 +89,18 @@ async function main(): Promise { // TODO: see issue https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2708 async function checkBalance( - tokenConfig: WarpTokenConfig, + tokenConfig: WarpRouteConfig, multiProtocolProvider: MultiProtocolProvider, ): Promise> { - const output: ChainMap> = objMap( + const output = objMap( tokenConfig, - async (chain: ChainName, token: WarpTokenConfig[ChainName]) => { + async (chain: ChainName, token: WarpRouteConfig[ChainName]) => { switch (token.type) { case TokenType.native: { switch (token.protocolType) { case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); - const nativeBalance = await provider.getBalance( - token.hypNativeAddress, - ); + const nativeBalance = await provider.getBalance(token.hypAddress); return parseFloat( ethers.utils.formatUnits(nativeBalance, token.decimals), ); @@ -95,9 +108,20 @@ async function checkBalance( case ProtocolType.Sealevel: // TODO - solana native return 0; - case ProtocolType.Cosmos: - // TODO - cosmos native - return 0; + case ProtocolType.Cosmos: { + if (!token.ibcDenom) + throw new Error('IBC denom missing for native token'); + const adapter = new CosmNativeTokenAdapter( + chain, + multiProtocolProvider, + {}, + { ibcDenom: token.ibcDenom }, + ); + const tokenBalance = await adapter.getBalance(token.hypAddress); + return parseFloat( + ethers.utils.formatUnits(tokenBalance, token.decimals), + ); + } } break; } @@ -105,12 +129,14 @@ async function checkBalance( switch (token.protocolType) { case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); + if (!token.tokenAddress) + throw new Error('Token address missing for collateral token'); const tokenContract = ERC20__factory.connect( - token.address, + token.tokenAddress, provider, ); const collateralBalance = await tokenContract.balanceOf( - token.hypCollateralAddress, + token.hypAddress, ); return parseFloat( @@ -118,35 +144,39 @@ async function checkBalance( ); } case ProtocolType.Sealevel: { + if (!token.tokenAddress) + throw new Error('Token address missing for synthetic token'); const adapter = new SealevelHypCollateralAdapter( chain, multiProtocolProvider, { - token: token.address, - warpRouter: token.hypCollateralAddress, + token: token.tokenAddress, + warpRouter: token.hypAddress, // Mailbox only required for transfers, using system as placeholder mailbox: SystemProgram.programId.toBase58(), }, - token.isSpl2022, + token?.isSpl2022 ?? false, ); const collateralBalance = ethers.BigNumber.from( - await adapter.getBalance(token.hypCollateralAddress), + await adapter.getBalance(token.hypAddress), ); return parseFloat( ethers.utils.formatUnits(collateralBalance, token.decimals), ); } case ProtocolType.Cosmos: { + if (!token.tokenAddress) + throw new Error('Token address missing for cosmos token'); const adapter = new CwNativeTokenAdapter( chain, multiProtocolProvider, { - token: token.address, + token: token.hypAddress, }, - token.address, + token.tokenAddress, ); const collateralBalance = ethers.BigNumber.from( - await adapter.getBalance(token.hypCollateralAddress), + await adapter.getBalance(token.hypAddress), ); return parseFloat( ethers.utils.formatUnits(collateralBalance, token.decimals), @@ -160,7 +190,7 @@ async function checkBalance( case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); const tokenContract = ERC20__factory.connect( - token.hypSyntheticAddress, + token.hypAddress, provider, ); const syntheticBalance = await tokenContract.totalSupply(); @@ -178,36 +208,24 @@ async function checkBalance( break; } } + return 0; }, ); return await promiseObjAll(output); } -function updateTokenBalanceMetrics( - tokenConfig: WarpTokenConfig, +export function updateTokenBalanceMetrics( + tokenConfig: WarpRouteConfig, balances: ChainMap, ) { - objMap(tokenConfig, (chain: ChainName, token: WarpTokenConfig[ChainName]) => { - const tokenAddress = - token.type === TokenType.native - ? ethers.constants.AddressZero - : token.type === TokenType.collateral - ? token.address - : token.hypSyntheticAddress; - const walletAddress = - token.type === TokenType.native - ? token.hypNativeAddress - : token.type === TokenType.collateral - ? token.hypCollateralAddress - : token.hypSyntheticAddress; - + objMap(tokenConfig, (chain: ChainName, token: WarpRouteConfig[ChainName]) => { warpRouteTokenBalance .labels({ chain_name: chain, - token_address: tokenAddress, + token_address: token.tokenAddress ?? ethers.constants.AddressZero, token_name: token.name, - wallet_address: walletAddress, + wallet_address: token.hypAddress, token_type: token.type, }) .set(balances[chain]); diff --git a/typescript/infra/src/agents/aws/key.ts b/typescript/infra/src/agents/aws/key.ts index fb42d10aab..a6c47369e5 100644 --- a/typescript/infra/src/agents/aws/key.ts +++ b/typescript/infra/src/agents/aws/key.ts @@ -16,6 +16,7 @@ import { UpdateAliasCommand, } from '@aws-sdk/client-kms'; import { KmsEthersSigner } from 'aws-kms-ethers-signer'; +import { Debugger, debug } from 'debug'; import { ethers } from 'ethers'; import { AgentSignerKeyType, ChainName } from '@hyperlane-xyz/sdk'; @@ -41,6 +42,7 @@ export class AgentAwsKey extends CloudAgentKey { private client: KMSClient | undefined; private region: string; public remoteKey: RemoteKey = { fetched: false }; + protected logger: Debugger; constructor( agentConfig: AgentContextConfig, @@ -53,16 +55,22 @@ export class AgentAwsKey extends CloudAgentKey { throw new Error('Not configured as AWS'); } this.region = agentConfig.aws.region; + this.logger = debug(`infra:agents:key:aws:${this.identifier}`); } get privateKey(): string { + this.logger( + 'Attempting to access private key, which is unavailable for AWS keys', + ); throw new Error('Private key unavailable for AWS keys'); } async getClient(): Promise { if (this.client) { + this.logger('Returning existing KMSClient instance'); return this.client; } + this.logger('Creating new KMSClient instance'); this.client = new KMSClient({ region: this.region, }); @@ -94,6 +102,7 @@ export class AgentAwsKey extends CloudAgentKey { } async fetch() { + this.logger('Fetching key'); const address = await this.fetchAddressFromAws(); this.remoteKey = { fetched: true, @@ -102,24 +111,28 @@ export class AgentAwsKey extends CloudAgentKey { } async createIfNotExists() { + this.logger('Checking if key exists and creating if not'); const keyId = await this.getId(); // If it doesn't exist, create it if (!keyId) { - // TODO should this be awaited? create is async - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.create(); + this.logger('Key does not exist, creating new key'); + await this.create(); // It can take a moment for the change to propagate await sleep(1000); + } else { + this.logger('Key already exists'); } await this.fetch(); } async delete() { + this.logger('Delete operation called, but not implemented'); throw Error('Not implemented yet'); } // Allows the `userArn` to use the key async putKeyPolicy(userArn: string) { + this.logger(`Putting key policy for user ARN: ${userArn}`); const client = await this.getClient(); const policy = { Version: '2012-10-17', @@ -151,22 +164,29 @@ export class AgentAwsKey extends CloudAgentKey { PolicyName: 'default', // This is the only accepted name }); await client.send(cmd); + this.logger('Key policy put successfully'); } // Gets the Key's ID if it exists, undefined otherwise async getId() { try { + this.logger('Attempting to describe key to get ID'); const keyDescription = await this.describeKey(); - return keyDescription.KeyMetadata?.KeyId; + const keyId = keyDescription.KeyMetadata?.KeyId; + this.logger(`Key ID retrieved: ${keyId}`); + return keyId; } catch (err: any) { if (err.name === 'NotFoundException') { + this.logger('Key not found'); return undefined; } + this.logger(`Error retrieving key ID: ${err}`); throw err; } } create() { + this.logger('Creating new key'); return this._create(false); } @@ -175,6 +195,7 @@ export class AgentAwsKey extends CloudAgentKey { * @returns The address of the new key */ update() { + this.logger('Updating key (creating new key for rotation)'); return this._create(true); } @@ -182,6 +203,7 @@ export class AgentAwsKey extends CloudAgentKey { * Requires update to have been called on this key prior */ async rotate() { + this.logger('Rotating keys'); const canonicalAlias = this.identifier; const newAlias = canonicalAlias + '-new'; const oldAlias = canonicalAlias + '-old'; @@ -226,15 +248,19 @@ export class AgentAwsKey extends CloudAgentKey { // Address should have changed now await this.fetch(); + this.logger('Keys rotated successfully'); } async getSigner( provider?: ethers.providers.Provider, ): Promise { + this.logger('Getting signer'); const keyId = await this.getId(); if (!keyId) { + this.logger('Key ID not defined, cannot get signer'); throw Error('Key ID not defined'); } + this.logger(`Creating KmsEthersSigner with key ID: ${keyId}`); // @ts-ignore We're using a newer version of Provider than // KmsEthersSigner. The return type for getFeeData for this newer // type is a superset of the return type for getFeeData for the older type, @@ -252,12 +278,15 @@ export class AgentAwsKey extends CloudAgentKey { private requireFetched() { if (!this.remoteKey.fetched) { + this.logger('Key has not been fetched yet'); throw new Error('Key not fetched'); } + this.logger('Key has been fetched'); } // Creates a new key and returns its address private async _create(rotate: boolean) { + this.logger(`Creating key with rotation: ${rotate}`); const client = await this.getClient(); const alias = this.identifier; if (!rotate) { @@ -269,6 +298,7 @@ export class AgentAwsKey extends CloudAgentKey { (_) => _.AliasName === alias, ); if (match) { + this.logger(`Alias ${alias} already exists`); throw new Error( `Attempted to create new key but alias ${alias} already exists`, ); @@ -288,6 +318,7 @@ export class AgentAwsKey extends CloudAgentKey { const createResponse = await client.send(command); if (!createResponse.KeyMetadata) { + this.logger('KeyMetadata was not returned when creating the key'); throw new Error('KeyMetadata was not returned when creating the key'); } const keyId = createResponse.KeyMetadata?.KeyId; @@ -298,10 +329,12 @@ export class AgentAwsKey extends CloudAgentKey { ); const address = this.fetchAddressFromAws(keyId); + this.logger(`New key created with ID: ${keyId}`); return address; } private async fetchAddressFromAws(keyId?: string) { + this.logger(`Fetching address from AWS for key ID: ${keyId}`); const client = await this.getClient(); if (!keyId) { @@ -312,10 +345,15 @@ export class AgentAwsKey extends CloudAgentKey { new GetPublicKeyCommand({ KeyId: keyId }), ); - return getEthereumAddress(Buffer.from(publicKeyResponse.PublicKey!)); + const address = getEthereumAddress( + Buffer.from(publicKeyResponse.PublicKey!), + ); + this.logger(`Address fetched: ${address}`); + return address; } private async describeKey(): Promise { + this.logger('Describing key'); const client = await this.getClient(); return client.send( new DescribeKeyCommand({ @@ -325,6 +363,7 @@ export class AgentAwsKey extends CloudAgentKey { } private async getAliases(): Promise { + this.logger('Getting aliases'); const client = await this.getClient(); let aliases: AliasListEntry[] = []; let marker: string | undefined = undefined; @@ -350,6 +389,7 @@ export class AgentAwsKey extends CloudAgentKey { break; } } + this.logger(`Aliases retrieved: ${aliases.length}`); return aliases; } } diff --git a/typescript/infra/src/agents/aws/s3.ts b/typescript/infra/src/agents/aws/s3.ts index 233b4c24de..2c12cd00c3 100644 --- a/typescript/infra/src/agents/aws/s3.ts +++ b/typescript/infra/src/agents/aws/s3.ts @@ -15,23 +15,26 @@ export class S3Wrapper { private readonly client: S3Client; readonly bucket: string; readonly region: string; + readonly folder: string | undefined; static fromBucketUrl(bucketUrl: string): S3Wrapper { const match = bucketUrl.match(S3_BUCKET_REGEX); if (!match) throw new Error('Could not parse bucket url'); - return new S3Wrapper(match[1], match[2]); + return new S3Wrapper(match[1], match[2], undefined); } - constructor(bucket: string, region: string) { + constructor(bucket: string, region: string, folder: string | undefined) { this.bucket = bucket; this.region = region; + this.folder = folder; this.client = new S3Client({ region }); } async getS3Obj(key: string): Promise | undefined> { + const Key = this.folder ? `${this.folder}/${key}` : key; const command = new GetObjectCommand({ Bucket: this.bucket, - Key: key, + Key, }); try { const response = await this.client.send(command); diff --git a/typescript/infra/src/agents/aws/validator.ts b/typescript/infra/src/agents/aws/validator.ts index fd8ffa4b6f..2cea6b6a8d 100644 --- a/typescript/infra/src/agents/aws/validator.ts +++ b/typescript/infra/src/agents/aws/validator.ts @@ -50,9 +50,10 @@ export class S3Validator extends BaseValidator { mailbox: string, s3Bucket: string, s3Region: string, + s3Folder: string | undefined, ) { super(address, localDomain, mailbox); - this.s3Bucket = new S3Wrapper(s3Bucket, s3Region); + this.s3Bucket = new S3Wrapper(s3Bucket, s3Region, s3Folder); } static async fromStorageLocation( @@ -61,8 +62,8 @@ export class S3Validator extends BaseValidator { if (storageLocation.startsWith(LOCATION_PREFIX)) { const suffix = storageLocation.slice(LOCATION_PREFIX.length); const pieces = suffix.split('/'); - if (pieces.length == 2) { - const s3Bucket = new S3Wrapper(pieces[0], pieces[1]); + if (pieces.length >= 2) { + const s3Bucket = new S3Wrapper(pieces[0], pieces[1], pieces[2]); const announcement = await s3Bucket.getS3Obj(ANNOUNCEMENT_KEY); const address = announcement?.data.value.validator; const mailbox = announcement?.data.value.mailbox_address; @@ -74,6 +75,7 @@ export class S3Validator extends BaseValidator { mailbox, pieces[0], pieces[1], + pieces[2], ); } } diff --git a/typescript/infra/src/agents/gcp.ts b/typescript/infra/src/agents/gcp.ts index a2f6748880..e0fc75874a 100644 --- a/typescript/infra/src/agents/gcp.ts +++ b/typescript/infra/src/agents/gcp.ts @@ -1,4 +1,6 @@ +import { encodeSecp256k1Pubkey, pubkeyToAddress } from '@cosmjs/amino'; import { Keypair } from '@solana/web3.js'; +import { Debugger, debug } from 'debug'; import { Wallet, ethers } from 'ethers'; import { ChainName } from '@hyperlane-xyz/sdk'; @@ -37,6 +39,8 @@ interface FetchedKey { type RemoteKey = UnfetchedKey | FetchedKey; export class AgentGCPKey extends CloudAgentKey { + protected logger: Debugger; + constructor( environment: DeployEnvironment, context: Contexts, @@ -46,18 +50,23 @@ export class AgentGCPKey extends CloudAgentKey { private remoteKey: RemoteKey = { fetched: false }, ) { super(environment, context, role, chainName, index); + this.logger = debug(`infra:agents:key:gcp:${this.identifier}`); } async createIfNotExists() { + this.logger('Checking if key exists and creating if not'); try { await this.fetch(); + this.logger('Key already exists'); } catch (err) { + this.logger('Key does not exist, creating new key'); await this.create(); } } serializeAsAddress() { this.requireFetched(); + this.logger('Serializing key as address'); return { identifier: this.identifier, // @ts-ignore @@ -93,6 +102,7 @@ export class AgentGCPKey extends CloudAgentKey { addressForProtocol(protocol: ProtocolType): string | undefined { this.requireFetched(); + this.logger(`Getting address for protocol: ${protocol}`); switch (protocol) { case ProtocolType.Ethereum: @@ -101,12 +111,26 @@ export class AgentGCPKey extends CloudAgentKey { return Keypair.fromSeed( Buffer.from(strip0x(this.privateKey), 'hex'), ).publicKey.toBase58(); + case ProtocolType.Cosmos: { + const compressedPubkey = ethers.utils.computePublicKey( + this.privateKey, + true, + ); + const encodedPubkey = encodeSecp256k1Pubkey( + new Uint8Array(Buffer.from(strip0x(compressedPubkey), 'hex')), + ); + // TODO support other prefixes? + // https://cosmosdrops.io/en/tools/bech32-converter is useful for converting to other prefixes. + return pubkeyToAddress(encodedPubkey, 'neutron'); + } default: + this.logger(`Unsupported protocol: ${protocol}`); return undefined; } } async fetch() { + this.logger('Fetching key'); const secret: SecretManagerPersistedKeys = (await fetchGCPSecret( this.identifier, )) as any; @@ -115,25 +139,34 @@ export class AgentGCPKey extends CloudAgentKey { privateKey: secret.privateKey, address: secret.address, }; + this.logger(`Key fetched successfully: ${secret.address}`); } async create() { + this.logger('Creating new key'); this.remoteKey = await this._create(false); + this.logger('Key created successfully'); } async update() { + this.logger('Updating key'); this.remoteKey = await this._create(true); + this.logger('Key updated successfully'); return this.address; } async delete() { + this.logger('Deleting key'); await execCmd(`gcloud secrets delete ${this.identifier} --quiet`); + this.logger('Key deleted successfully'); } async getSigner( provider?: ethers.providers.Provider, ): Promise { + this.logger('Getting signer'); if (!this.remoteKey.fetched) { + this.logger('Key not fetched, fetching now'); await this.fetch(); } return new Wallet(this.privateKey, provider); @@ -141,12 +174,14 @@ export class AgentGCPKey extends CloudAgentKey { private requireFetched() { if (!this.remoteKey.fetched) { + this.logger('Key not fetched, throwing error'); throw new Error("Can't persist without address"); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars private async _create(rotate: boolean) { + this.logger(`Creating key with rotation: ${rotate}`); const wallet = Wallet.createRandom(); const address = await wallet.getAddress(); const identifier = this.identifier; @@ -171,6 +206,7 @@ export class AgentGCPKey extends CloudAgentKey { }), }, ); + this.logger('Key creation data persisted to GCP'); return { fetched: true, diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 1d306963cc..8802ccdaa7 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -1,7 +1,6 @@ import fs from 'fs'; import { ChainName, RpcConsensusType, chainMetadata } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts'; import { @@ -22,7 +21,7 @@ import { buildHelmChartDependencies, helmifyValues, } from '../utils/helm'; -import { execCmd } from '../utils/utils'; +import { execCmd, isEthereumProtocolChain } from '../utils/utils'; import { AgentGCPKey } from './gcp'; @@ -113,17 +112,27 @@ export abstract class AgentHelmManager { runEnv: this.environment, context: this.context, aws: !!this.config.aws, - chains: this.config.environmentChainNames.map((name) => ({ - name, - disabled: !this.config.contextChainNames[this.role].includes(name), - rpcConsensusType: this.rpcConsensusType(name), - })), + chains: this.config.environmentChainNames.map((chain) => { + const metadata = chainMetadata[chain]; + const reorgPeriod = metadata.blocks?.reorgPeriod; + if (reorgPeriod === undefined) { + throw new Error(`No reorg period found for chain ${chain}`); + } + return { + name: chain, + disabled: !this.config.contextChainNames[this.role].includes(chain), + rpcConsensusType: this.rpcConsensusType(chain), + protocol: metadata.protocol, + blocks: { reorgPeriod }, + }; + }), }, }; } rpcConsensusType(chain: ChainName): RpcConsensusType { - if (chainMetadata[chain].protocol == ProtocolType.Sealevel) { + // Non-Ethereum chains only support Single + if (!isEthereumProtocolChain(chain)) { return RpcConsensusType.Single; } @@ -195,12 +204,10 @@ export class RelayerHelmManager extends OmniscientAgentHelmManager { }; const signers = await this.config.signers(); - values.hyperlane.relayerChains = this.config.environmentChainNames.map( - (name) => ({ - name, - signer: signers[name], - }), - ); + values.hyperlane.relayerChains = this.config.relayChains.map((name) => ({ + name, + signer: signers[name], + })); return values; } @@ -250,11 +257,6 @@ export class ValidatorHelmManager extends MultichainAgentHelmManager { const helmValues = await super.helmValues(); const cfg = await this.config.buildConfig(); - helmValues.hyperlane.chains.push({ - name: cfg.originChainName, - blocks: { reorgPeriod: cfg.reorgPeriod }, - }); - helmValues.hyperlane.validator = { enabled: true, configs: cfg.validators.map((c) => ({ diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index afe3288e44..65245f6b72 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -1,144 +1,343 @@ -import { ChainName, chainMetadata } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import debug from 'debug'; +import fs from 'fs'; +import path from 'path'; +import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; +import { Address, objMap } from '@hyperlane-xyz/utils'; + +import localAWMultisigAddresses from '../../config/aw-multisig.json'; +// AW - Abacus Works import { Contexts } from '../../config/contexts'; +import { helloworld } from '../../config/environments/helloworld'; +import localKathyAddresses from '../../config/kathy.json'; +import localRelayerAddresses from '../../config/relayer.json'; +import { getJustHelloWorldConfig } from '../../scripts/helloworld/utils'; import { AgentContextConfig, DeployEnvironment, RootAgentConfig, } from '../config'; import { Role } from '../roles'; -import { fetchGCPSecret, setGCPSecret } from '../utils/gcloud'; -import { execCmd } from '../utils/utils'; +import { + execCmd, + isEthereumProtocolChain, + readJSON, + writeJSON, +} from '../utils/utils'; import { AgentAwsKey } from './aws/key'; import { AgentGCPKey } from './gcp'; import { CloudAgentKey } from './keys'; -interface KeyAsAddress { +export type LocalRoleAddresses = Record< + DeployEnvironment, + Record +>; +export const relayerAddresses: LocalRoleAddresses = + localRelayerAddresses as LocalRoleAddresses; +export const kathyAddresses: LocalRoleAddresses = + localKathyAddresses as LocalRoleAddresses; +export const awMultisigAddresses: ChainMap<{ validators: Address[] }> = + localAWMultisigAddresses as ChainMap<{ validators: Address[] }>; + +const debugLog = debug('infra:agents:key:utils'); + +export interface KeyAsAddress { identifier: string; address: string; } -export function getRelayerCloudAgentKeys( - agentConfig: AgentContextConfig, -): Array { - if (!agentConfig.aws) { - return [ - new AgentGCPKey(agentConfig.runEnv, agentConfig.context, Role.Relayer), - ]; - } +const CONFIG_DIRECTORY_PATH = path.join(__dirname, '../../config'); + +// ================== +// Functions for getting keys +// ================== + +// Returns a nested object of the shape: +// { +// [chain]: { +// [role]: keys[], +// } +// } +// +// Note that some types of keys are used on multiple different chains +// and may be duplicated in the returned object. E.g. the deployer key +// or the relayer key, etc +export function getRoleKeysPerChain( + agentConfig: RootAgentConfig, +): ChainMap> { + return objMap(getRoleKeyMapPerChain(agentConfig), (_chain, roleKeys) => { + return objMap(roleKeys, (_role, keys) => { + return Object.values(keys); + }); + }); +} + +// Returns a nested object of the shape: +// { +// [chain]: { +// [role]: { +// // To guarantee no key duplicates, the key identifier is used as the key +// [key identifier]: key +// } +// } +// } +function getRoleKeyMapPerChain( + agentConfig: RootAgentConfig, +): ChainMap>> { + const keysPerChain: ChainMap>> = + {}; - const keys = []; - keys.push(new AgentAwsKey(agentConfig, Role.Relayer)); - const hasNonEthereumChains = !!agentConfig.contextChainNames[ - Role.Relayer - ].find(isNotEthereumProtocolChain); - // If there are any non-ethereum chains, we also want hex keys. - if (hasNonEthereumChains) { - keys.push( - new AgentGCPKey(agentConfig.runEnv, agentConfig.context, Role.Relayer), + const setValidatorKeys = () => { + const validators = agentConfig.validators; + for (const chainName of agentConfig.contextChainNames.validator) { + let chainValidatorKeys = {}; + const validatorCount = + validators?.chains[chainName]?.validators.length ?? 1; + for (let index = 0; index < validatorCount; index++) { + const { validator, chainSigner } = getValidatorKeysForChain( + agentConfig, + chainName, + index, + ); + chainValidatorKeys = { + ...chainValidatorKeys, + [validator.identifier]: validator, + [chainSigner.identifier]: chainSigner, + }; + } + keysPerChain[chainName] = { + ...keysPerChain[chainName], + [Role.Validator]: chainValidatorKeys, + }; + } + }; + + const setRelayerKeys = () => { + for (const chainName of agentConfig.contextChainNames.relayer) { + const relayerKey = getRelayerKeyForChain(agentConfig, chainName); + keysPerChain[chainName] = { + ...keysPerChain[chainName], + [Role.Relayer]: { + [relayerKey.identifier]: relayerKey, + }, + }; + } + }; + + const setKathyKeys = () => { + const helloWorldConfig = getJustHelloWorldConfig( + helloworld[agentConfig.runEnv as 'mainnet3' | 'testnet4'], // test doesn't have hello world configs + agentConfig.context, ); + // Kathy is only needed on chains where the hello world contracts are deployed. + for (const chainName of Object.keys(helloWorldConfig.addresses)) { + const kathyKey = getKathyKeyForChain(agentConfig, chainName); + keysPerChain[chainName] = { + ...keysPerChain[chainName], + [Role.Kathy]: { + [kathyKey.identifier]: kathyKey, + }, + }; + } + }; + + const setDeployerKeys = () => { + const deployerKey = getDeployerKey(agentConfig); + // Default to using the relayer keys for the deployer keys + for (const chainName of agentConfig.contextChainNames.relayer) { + keysPerChain[chainName] = { + ...keysPerChain[chainName], + [Role.Deployer]: { + [deployerKey.identifier]: deployerKey, + }, + }; + } + }; + + for (const role of agentConfig.rolesWithKeys) { + switch (role) { + case Role.Validator: + setValidatorKeys(); + break; + case Role.Relayer: + setRelayerKeys(); + break; + case Role.Kathy: + setKathyKeys(); + break; + case Role.Deployer: + setDeployerKeys(); + break; + default: + throw Error(`Unsupported role with keys ${role}`); + } } - return keys; + + return keysPerChain; } -export function getKathyCloudAgentKeys( - agentConfig: AgentContextConfig, +// Gets a big array of all keys. +export function getAllCloudAgentKeys( + agentConfig: RootAgentConfig, ): Array { - const gcpKey = new AgentGCPKey( - agentConfig.runEnv, - agentConfig.context, - Role.Kathy, + debugLog('Retrieving all cloud agent keys'); + const keysPerChain = getRoleKeyMapPerChain(agentConfig); + + const keysByIdentifier = Object.keys(keysPerChain).reduce( + (acc, chainName) => { + const chainKeyRoles = keysPerChain[chainName]; + // All keys regardless of role + const chainKeys = Object.keys(chainKeyRoles).reduce((acc, role) => { + const roleKeys = chainKeyRoles[role as Role]; + return { + ...acc, + ...roleKeys, + }; + }, {}); + + return { + ...acc, + ...chainKeys, + }; + }, + {}, ); - if (!agentConfig.aws) { - return [gcpKey]; - } - // Return both GCP and AWS keys for Kathy even if the agentConfig is configured - // to use AWS. Non-Ethereum chains require GCP keys. - return [gcpKey, new AgentAwsKey(agentConfig, Role.Kathy)]; + return Object.values(keysByIdentifier); } -// If getting all keys for relayers or validators, it's recommended to use -// `getRelayerCloudAgentKeys` or `getValidatorCloudAgentKeys` instead. +// Gets a specific key. The chain name or index is required depending on the role. +// For this reason, using this function is only encouraged if the caller +// knows they want a specific key relating to a specific role. export function getCloudAgentKey( agentConfig: AgentContextConfig, role: Role, chainName?: ChainName, index?: number, ): CloudAgentKey { - // Non-evm Kathy is always GCP-based but does not index by chain - if ( - role === Role.Kathy && - chainName && - isNotEthereumProtocolChain(chainName) - ) { - return new AgentGCPKey(agentConfig.runEnv, agentConfig.context, role); - } - // Otherwise use an AWS key except for the deployer - else if (!!agentConfig.aws && role !== Role.Deployer && role !== Role.Kathy) { - return new AgentAwsKey(agentConfig, role, chainName, index); - } else { - // Fallback to GCP - return new AgentGCPKey( - agentConfig.runEnv, - agentConfig.context, - role, - chainName, - index, - ); + debugLog(`Retrieving cloud agent key for ${role} on ${chainName}`); + switch (role) { + case Role.Validator: + if (chainName === undefined || index === undefined) { + throw Error('Must provide chainName and index for validator key'); + } + // For now just get the validator key, and not the chain signer. + return getValidatorKeysForChain(agentConfig, chainName, index).validator; + case Role.Relayer: + if (chainName === undefined) { + throw Error('Must provide chainName for relayer key'); + } + return getRelayerKeyForChain(agentConfig, chainName); + case Role.Kathy: + if (chainName === undefined) { + throw Error('Must provide chainName for kathy key'); + } + return getKathyKeyForChain(agentConfig, chainName); + case Role.Deployer: + return getDeployerKey(agentConfig); + default: + throw Error(`Unsupported role ${role}`); } } -export function getValidatorCloudAgentKeys( - agentConfig: RootAgentConfig, -): Array { - // For each chainName, create validatorCount keys - if (!agentConfig.validators) return []; - const validators = agentConfig.validators; - return agentConfig.contextChainNames[Role.Validator] - .filter((chainName) => !!validators.chains[chainName]) - .flatMap((chainName) => - validators.chains[chainName].validators.map((_, index) => - getCloudAgentKey(agentConfig, Role.Validator, chainName, index), - ), - ); -} +// ================== +// Keys for specific roles +// ================== -export function getAllCloudAgentKeys( - agentConfig: RootAgentConfig, -): Array { - const keys = []; - if ((agentConfig.rolesWithKeys ?? []).includes(Role.Relayer)) - keys.push(...getRelayerCloudAgentKeys(agentConfig)); - if ((agentConfig.rolesWithKeys ?? []).includes(Role.Validator)) - keys.push(...getValidatorCloudAgentKeys(agentConfig)); - if ((agentConfig.rolesWithKeys ?? []).includes(Role.Kathy)) - keys.push(...getKathyCloudAgentKeys(agentConfig)); +// Gets the relayer key used for signing txs to the provided chain. +export function getRelayerKeyForChain( + agentConfig: AgentContextConfig, + chainName: ChainName, +): CloudAgentKey { + debugLog(`Retrieving relayer key for ${chainName}`); + // If AWS is enabled and the chain is an Ethereum-based chain, we want to use + // an AWS key. + if (agentConfig.aws && isEthereumProtocolChain(chainName)) { + return new AgentAwsKey(agentConfig, Role.Relayer); + } - for (const role of agentConfig.rolesWithKeys) { - if (role == Role.Relayer || role == Role.Validator || role == Role.Kathy) - continue; - keys.push(getCloudAgentKey(agentConfig, role)); + return new AgentGCPKey(agentConfig.runEnv, agentConfig.context, Role.Relayer); +} + +// Gets the kathy key used for signing txs to the provided chain. +// Note this is basically a dupe of getRelayerKeyForChain, but to encourage +// consumers to be aware of what role they're using, and to keep the door open +// for future per-role deviations, we have separate functions. +export function getKathyKeyForChain( + agentConfig: AgentContextConfig, + chainName: ChainName, +): CloudAgentKey { + debugLog(`Retrieving kathy key for ${chainName}`); + // If AWS is enabled and the chain is an Ethereum-based chain, we want to use + // an AWS key. + if (agentConfig.aws && isEthereumProtocolChain(chainName)) { + return new AgentAwsKey(agentConfig, Role.Kathy); } - return keys; + + return new AgentGCPKey(agentConfig.runEnv, agentConfig.context, Role.Kathy); } -export async function deleteAgentKeys(agentConfig: AgentContextConfig) { - const keys = getAllCloudAgentKeys(agentConfig); - await Promise.all(keys.map((key) => key.delete())); - await execCmd( - `gcloud secrets delete ${addressesIdentifier( +// Returns the deployer key. This is always a GCP key, not chain specific, +// and in the Hyperlane context. +export function getDeployerKey(agentConfig: AgentContextConfig): CloudAgentKey { + debugLog('Retrieving deployer key'); + return new AgentGCPKey(agentConfig.runEnv, Contexts.Hyperlane, Role.Deployer); +} + +// Returns the validator signer key and the chain signer key for the given validator for +// the given chain and index. +// The validator signer key is used to sign checkpoints and can be AWS regardless of the +// chain protocol type. The chain signer is dependent on the chain protocol type. +export function getValidatorKeysForChain( + agentConfig: AgentContextConfig, + chainName: ChainName, + index: number, +): { + validator: CloudAgentKey; + chainSigner: CloudAgentKey; +} { + debugLog(`Retrieving validator keys for ${chainName}`); + const validator = agentConfig.aws + ? new AgentAwsKey(agentConfig, Role.Validator, chainName, index) + : new AgentGCPKey( + agentConfig.runEnv, + agentConfig.context, + Role.Validator, + chainName, + index, + ); + + // If the chain is Ethereum-based, we can just use the validator key (even if it's AWS-based) + // as the chain signer. Otherwise, we need to use a GCP key. + let chainSigner; + if (isEthereumProtocolChain(chainName)) { + chainSigner = validator; + } else { + debugLog(`Retrieving GCP key for ${chainName}, as it is not EVM`); + chainSigner = new AgentGCPKey( agentConfig.runEnv, agentConfig.context, - )} --quiet`, - ); + Role.Validator, + chainName, + index, + ); + } + + return { + validator, + chainSigner, + }; } +// ================== +// Functions for managing keys +// ================== + export async function createAgentKeysIfNotExists( agentConfig: AgentContextConfig, ) { + debugLog('Creating agent keys if none exist'); const keys = getAllCloudAgentKeys(agentConfig); await Promise.all( @@ -147,10 +346,19 @@ export async function createAgentKeysIfNotExists( }), ); - await persistAddresses( - agentConfig.runEnv, - agentConfig.context, - keys.map((key) => key.serializeAsAddress()), + await persistAddressesLocally(agentConfig, keys); + return; +} + +export async function deleteAgentKeys(agentConfig: AgentContextConfig) { + debugLog('Deleting agent keys'); + const keys = getAllCloudAgentKeys(agentConfig); + await Promise.all(keys.map((key) => key.delete())); + await execCmd( + `gcloud secrets delete ${addressesIdentifier( + agentConfig.runEnv, + agentConfig.context, + )} --quiet`, ); } @@ -159,72 +367,100 @@ export async function rotateKey( role: Role, chainName: ChainName, ) { + debugLog(`Rotating key for ${role} on ${chainName}`); const key = getCloudAgentKey(agentConfig, role, chainName); await key.update(); - const keyIdentifier = key.identifier; - const addresses = await fetchGCPKeyAddresses( + await persistAddressesLocally(agentConfig, [key]); +} + +async function persistAddressesLocally( + agentConfig: AgentContextConfig, + keys: CloudAgentKey[], +) { + debugLog( + `Persisting addresses to GCP for ${agentConfig.context} context in ${agentConfig.runEnv} environment`, + ); + // recent keys fetched from aws saved to local artifacts + const multisigValidatorKeys: ChainMap<{ validators: Address[] }> = {}; + let relayer, kathy; + for (const key of keys) { + if (key.role === Role.Relayer) { + if (relayer) + throw new Error('More than one Relayer found in gcpCloudAgentKeys'); + relayer = key.address; + } + if (key.role === Role.Kathy) { + if (kathy) + throw new Error('More than one Kathy found in gcpCloudAgentKeys'); + kathy = key.address; + } + if (!key.chainName) continue; + multisigValidatorKeys[key.chainName] ||= { + validators: [], + }; + if (key.chainName) + multisigValidatorKeys[key.chainName].validators.push(key.address); + } + if (!relayer) throw new Error('No Relayer found in awsCloudAgentKeys'); + if (!kathy) throw new Error('No Kathy found in awsCloudAgentKeys'); + await persistRoleAddressesToLocalArtifacts( + Role.Relayer, agentConfig.runEnv, agentConfig.context, + relayer, + relayerAddresses, ); - const filteredAddresses = addresses.filter((_) => { - return _.identifier !== keyIdentifier; - }); - - filteredAddresses.push(key.serializeAsAddress()); - await persistAddresses( + await persistRoleAddressesToLocalArtifacts( + Role.Kathy, agentConfig.runEnv, agentConfig.context, - filteredAddresses, + kathy, + kathyAddresses, ); + await persistValidatorAddressesToLocalArtifacts(multisigValidatorKeys); } -async function persistAddresses( +// non-validator roles +export async function persistRoleAddressesToLocalArtifacts( + role: Role, environment: DeployEnvironment, context: Contexts, - keys: KeyAsAddress[], + updated: Address, + addresses: Record>, ) { - await setGCPSecret( - addressesIdentifier(environment, context), - JSON.stringify(keys), - { - environment, - context, - }, - ); -} + addresses[environment][context] = updated; -// This function returns all keys for a given mailbox chain in a dictionary where the key is the identifier -export async function fetchKeysForChain( - agentConfig: RootAgentConfig, - chainNames: ChainName | ChainName[], -): Promise> { - if (!Array.isArray(chainNames)) chainNames = [chainNames]; - - // Get all keys for the chainNames. Include keys where chainNames is undefined, - // which are keys that are not chain-specific but should still be included - const keys = await Promise.all( - getAllCloudAgentKeys(agentConfig) - .filter( - (key) => - key.chainName === undefined || chainNames.includes(key.chainName), - ) - .map(async (key) => { - await key.fetch(); - return [key.identifier, key]; - }), - ); + // Resolve the relative path + const filePath = path.resolve(__dirname, `../../config/${role}.json`); - return Object.fromEntries(keys); + fs.writeFileSync(filePath, JSON.stringify(addresses, null, 2)); } -async function fetchGCPKeyAddresses( - environment: DeployEnvironment, - context: Contexts, +// maintaining the multisigIsm schema sans threshold +export async function persistValidatorAddressesToLocalArtifacts( + fetchedValidatorAddresses: ChainMap<{ validators: Address[] }>, ) { - const addresses = await fetchGCPSecret( - addressesIdentifier(environment, context), - ); - return addresses as KeyAsAddress[]; + for (const chain of Object.keys(fetchedValidatorAddresses)) { + awMultisigAddresses[chain] = { + validators: fetchedValidatorAddresses[chain].validators, // fresh from aws + }; + } + // Write the updated object back to the file + writeJSON(CONFIG_DIRECTORY_PATH, 'aw-multisig.json', awMultisigAddresses); +} + +export function fetchLocalKeyAddresses(role: Role): LocalRoleAddresses { + try { + const addresses: LocalRoleAddresses = readJSON( + CONFIG_DIRECTORY_PATH, + `${role}.json`, + ); + + debugLog(`Fetching addresses from GCP for ${role} role ...`); + return addresses; + } catch (e) { + throw new Error(`Error fetching addresses locally for ${role} role: ${e}`); + } } function addressesIdentifier( @@ -233,8 +469,3 @@ function addressesIdentifier( ) { return `${context}-${environment}-key-addresses`; } - -function isNotEthereumProtocolChain(chainName: ChainName) { - if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`); - return chainMetadata[chainName].protocol !== ProtocolType.Ethereum; -} diff --git a/typescript/infra/src/agents/keys.ts b/typescript/infra/src/agents/keys.ts index 98e4df12e9..a6d817d891 100644 --- a/typescript/infra/src/agents/keys.ts +++ b/typescript/infra/src/agents/keys.ts @@ -73,6 +73,18 @@ export abstract class CloudAgentKey extends BaseCloudAgentKey { } } +export class LocalAgentKey extends BaseAgentKey { + constructor( + public readonly environment: DeployEnvironment, + public readonly context: Contexts, + public readonly role: Role, + public readonly address: string, + public readonly chainName?: ChainName, + ) { + super(environment, role, chainName); + } +} + // A read-only representation of a key managed internally. export class ReadOnlyCloudAgentKey extends BaseCloudAgentKey { constructor( diff --git a/typescript/infra/src/config/agent/agent.ts b/typescript/infra/src/config/agent/agent.ts index af11ea850c..d53e68e056 100644 --- a/typescript/infra/src/config/agent/agent.ts +++ b/typescript/infra/src/config/agent/agent.ts @@ -4,7 +4,9 @@ import { AgentSignerKeyType, ChainName, RpcConsensusType, + chainMetadata, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { Contexts } from '../../../config/contexts'; import { AgentChainNames, Role } from '../../roles'; @@ -94,7 +96,11 @@ interface AgentRoleConfig { export type AwsKeyConfig = Required; // only require specifying that it's the "hex" type for helm since the hex key will be pulled from secrets. export type HexKeyConfig = { type: AgentSignerKeyType.Hex }; -export type KeyConfig = AwsKeyConfig | HexKeyConfig; +export type CosmosKeyConfig = { + type: AgentSignerKeyType.Cosmos; + prefix: string; +}; +export type KeyConfig = AwsKeyConfig | HexKeyConfig | CosmosKeyConfig; interface IndexingConfig { from: number; @@ -182,3 +188,22 @@ export abstract class AgentConfigHelper export const allAgentChainNames = (agentChainNames: AgentChainNames) => [ ...new Set(Object.values(agentChainNames).reduce((a, b) => a.concat(b), [])), ]; + +// Returns the default KeyConfig for the `chainName`'s chain signer. +// For Ethereum or Sealevel, this is a hexKey, for Cosmos, this is a cosmosKey. +export function defaultChainSignerKeyConfig(chainName: ChainName): KeyConfig { + const metadata = chainMetadata[chainName]; + + switch (metadata?.protocol) { + case ProtocolType.Cosmos: + if (metadata.bech32Prefix === undefined) { + throw new Error(`Bech32 prefix for cosmos chain ${name} is undefined`); + } + return { type: AgentSignerKeyType.Cosmos, prefix: metadata.bech32Prefix }; + // For Ethereum and Sealevel, use a hex key + case ProtocolType.Ethereum: + case ProtocolType.Sealevel: + default: + return { type: AgentSignerKeyType.Hex }; + } +} diff --git a/typescript/infra/src/config/agent/relayer.ts b/typescript/infra/src/config/agent/relayer.ts index a4e4f2a1ee..b02ae1b8a9 100644 --- a/typescript/infra/src/config/agent/relayer.ts +++ b/typescript/infra/src/config/agent/relayer.ts @@ -2,7 +2,6 @@ import { BigNumberish } from 'ethers'; import { AgentConfig, - AgentSignerKeyType, ChainMap, GasPaymentEnforcement, MatchingList, @@ -10,16 +9,26 @@ import { chainMetadata, getDomainId, } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { ProtocolType, addressToBytes32 } from '@hyperlane-xyz/utils'; import { AgentAwsUser } from '../../agents/aws'; import { Role } from '../../roles'; import { HelmStatefulSetValues } from '../infrastructure'; -import { AgentConfigHelper, KeyConfig, RootAgentConfig } from './agent'; +import { + AgentConfigHelper, + KeyConfig, + RootAgentConfig, + defaultChainSignerKeyConfig, +} from './agent'; export { GasPaymentEnforcement as GasPaymentEnforcementConfig } from '@hyperlane-xyz/sdk'; +export interface MetricAppContext { + name: string; + matchingList: MatchingList; +} + // Incomplete basic relayer agent config export interface BaseRelayerConfig { gasPaymentEnforcement: GasPaymentEnforcement[]; @@ -27,6 +36,7 @@ export interface BaseRelayerConfig { blacklist?: MatchingList; transactionGasLimit?: BigNumberish; skipTransactionGasLimitFor?: string[]; + metricAppContexts?: MetricAppContext[]; } // Full relayer-specific agent config for a single chain @@ -60,7 +70,7 @@ export class RelayerConfigHelper extends AgentConfigHelper { const baseConfig = this.#relayerConfig!; const relayerConfig: RelayerConfig = { - relayChains: this.contextChainNames[Role.Relayer].join(','), + relayChains: this.relayChains.join(','), gasPaymentEnforcement: JSON.stringify(baseConfig.gasPaymentEnforcement), }; @@ -78,12 +88,19 @@ export class RelayerConfigHelper extends AgentConfigHelper { relayerConfig.skipTransactionGasLimitFor = baseConfig.skipTransactionGasLimitFor.join(','); } + if (baseConfig.metricAppContexts) { + relayerConfig.metricAppContexts = JSON.stringify( + baseConfig.metricAppContexts, + ); + } return relayerConfig; } // Get the signer configuration for each chain by the chain name. async signers(): Promise> { + const chainSigners: ChainMap = {}; + if (this.aws) { const awsUser = new AgentAwsUser( this.runEnv, @@ -93,25 +110,24 @@ export class RelayerConfigHelper extends AgentConfigHelper { ); await awsUser.createIfNotExists(); const awsKey = (await awsUser.createKeyIfNotExists(this)).keyConfig; - return Object.fromEntries( - this.contextChainNames[Role.Relayer].map((name) => { - const chain = chainMetadata[name]; - // Sealevel chains always use hex keys - if (chain?.protocol == ProtocolType.Sealevel) { - return [name, { type: AgentSignerKeyType.Hex }]; - } else { - return [name, awsKey]; - } - }), - ); - } else { - return Object.fromEntries( - this.contextChainNames[Role.Relayer].map((name) => [ - name, - { type: AgentSignerKeyType.Hex }, - ]), - ); + + // AWS keys only work for Ethereum chains + for (const chainName of this.relayChains) { + if (chainMetadata[chainName].protocol === ProtocolType.Ethereum) { + chainSigners[chainName] = awsKey; + } + } } + + // For any chains that were not configured with AWS keys, fill in the defaults + for (const chainName of this.relayChains) { + if (chainSigners[chainName] !== undefined) { + continue; + } + chainSigners[chainName] = defaultChainSignerKeyConfig(chainName); + } + + return chainSigners; } // Returns whether the relayer requires AWS credentials @@ -130,6 +146,10 @@ export class RelayerConfigHelper extends AgentConfigHelper { get role(): Role { return Role.Relayer; } + + get relayChains(): Array { + return this.contextChainNames[Role.Relayer]; + } } // Create a matching list for the given router addresses @@ -149,9 +169,9 @@ export function routerMatchingList( matchingList.push({ originDomain: getDomainId(chainMetadata[source]), - senderAddress: routers[source].router, + senderAddress: addressToBytes32(routers[source].router), destinationDomain: getDomainId(chainMetadata[destination]), - recipientAddress: routers[destination].router, + recipientAddress: addressToBytes32(routers[destination].router), }); } } diff --git a/typescript/infra/src/config/agent/validator.ts b/typescript/infra/src/config/agent/validator.ts index 380ebef111..8aeb1e5fe7 100644 --- a/typescript/infra/src/config/agent/validator.ts +++ b/typescript/infra/src/config/agent/validator.ts @@ -4,13 +4,20 @@ import { ValidatorConfig as AgentValidatorConfig, ChainMap, ChainName, + chainMetadata, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { ValidatorAgentAwsUser } from '../../agents/aws'; import { Role } from '../../roles'; import { HelmStatefulSetValues } from '../infrastructure'; -import { AgentConfigHelper, KeyConfig, RootAgentConfig } from './agent'; +import { + AgentConfigHelper, + KeyConfig, + RootAgentConfig, + defaultChainSignerKeyConfig, +} from './agent'; // Validator agents for each chain. export type ValidatorBaseChainConfigMap = ChainMap; @@ -33,11 +40,13 @@ export interface ValidatorBaseConfig { export interface ValidatorConfig { interval: number; - reorgPeriod: number; originChainName: ChainName; validators: Array<{ checkpointSyncer: CheckpointSyncerConfig; + // The key that signs checkpoints validator: KeyConfig; + // The key that signs txs (e.g. self-announcements) + chainSigner: KeyConfig | undefined; }>; } @@ -88,7 +97,6 @@ export class ValidatorConfigHelper extends AgentConfigHelper { async buildConfig(): Promise { return { interval: this.#chainConfig.interval, - reorgPeriod: this.#chainConfig.reorgPeriod, originChainName: this.chainName!, validators: await Promise.all( this.#chainConfig.validators.map((val, i) => @@ -110,7 +118,12 @@ export class ValidatorConfigHelper extends AgentConfigHelper { cfg: ValidatorBaseConfig, idx: number, ): Promise { + const metadata = chainMetadata[this.chainName]; + const protocol = metadata.protocol; + let validator: KeyConfig = { type: AgentSignerKeyType.Hex }; + let chainSigner: KeyConfig | undefined = undefined; + if (cfg.checkpointSyncer.type == CheckpointSyncerType.S3) { const awsUser = new ValidatorAgentAwsUser( this.runEnv, @@ -123,17 +136,29 @@ export class ValidatorConfigHelper extends AgentConfigHelper { await awsUser.createIfNotExists(); await awsUser.createBucketIfNotExists(); - if (this.aws) + if (this.aws) { validator = (await awsUser.createKeyIfNotExists(this)).keyConfig; + + // AWS-based chain signer keys are only used for Ethereum + if (protocol === ProtocolType.Ethereum) { + chainSigner = validator; + } + } } else { console.warn( `Validator ${cfg.address}'s checkpoint syncer is not S3-based. Be sure this is a non-k8s-based environment!`, ); } + // If the chainSigner isn't set to the AWS-based key above, then set the default. + if (chainSigner === undefined) { + chainSigner = defaultChainSignerKeyConfig(this.chainName); + } + return { checkpointSyncer: cfg.checkpointSyncer, validator, + chainSigner, }; } diff --git a/typescript/infra/src/config/chain.ts b/typescript/infra/src/config/chain.ts index 9d8197fc02..af99549c8f 100644 --- a/typescript/infra/src/config/chain.ts +++ b/typescript/infra/src/config/chain.ts @@ -1,65 +1,54 @@ import { providers } from 'ethers'; import { + ChainMetadataManager, ChainName, - RetryJsonRpcProvider, - RetryProviderOptions, + HyperlaneSmartProvider, + ProviderRetryOptions, RpcConsensusType, + chainMetadata, } from '@hyperlane-xyz/sdk'; import { getSecretRpcEndpoint } from '../agents'; import { DeployEnvironment } from './environment'; -export const defaultRetry = { - maxRequests: 6, - baseRetryMs: 50, +export const defaultRetry: ProviderRetryOptions = { + maxRetries: 6, + baseRetryDelayMs: 50, }; -function buildProvider(config?: { - url?: string; - network?: providers.Networkish; - retry?: RetryProviderOptions; -}): providers.JsonRpcProvider { - return config?.retry - ? new RetryJsonRpcProvider(config.retry, config?.url, config?.network) - : new providers.StaticJsonRpcProvider(config?.url, config?.network); -} - export async function fetchProvider( environment: DeployEnvironment, chainName: ChainName, connectionType: RpcConsensusType = RpcConsensusType.Single, ): Promise { + const cmm = new ChainMetadataManager(chainMetadata); + const chainData = cmm.tryGetChainMetadata(chainName); + if (!chainData) { + throw Error(`Unsupported chain: ${chainName}`); + } + const chainId = chainData.chainId; const single = connectionType === RpcConsensusType.Single; - const rpcData = await getSecretRpcEndpoint(environment, chainName, !single); - switch (connectionType) { - case RpcConsensusType.Single: { - return buildProvider({ url: rpcData[0], retry: defaultRetry }); - } - case RpcConsensusType.Quorum: { - return new providers.FallbackProvider( - (rpcData as string[]).map((url) => buildProvider({ url })), // disable retry for quorum - ); - } - case RpcConsensusType.Fallback: { - return new providers.FallbackProvider( - (rpcData as string[]).map((url, index) => { - const fallbackProviderConfig: providers.FallbackProviderConfig = { - provider: buildProvider({ url, retry: defaultRetry }), - // Priority is used by the FallbackProvider to determine - // how to order providers using ascending ordering. - // When not specified, all providers have the same priority - // and are ordered randomly for each RPC. - priority: index, - }; - return fallbackProviderConfig; - }), - 1, // a single provider is "quorum", but failure will cause failover to the next provider - ); - } - default: { - throw Error(`Unsupported connectionType: ${connectionType}`); - } + let rpcData = chainData.rpcUrls.map((url) => url.http); + if (rpcData.length === 0) { + rpcData = await getSecretRpcEndpoint(environment, chainName, !single); + } + + if (connectionType === RpcConsensusType.Single) { + return HyperlaneSmartProvider.fromRpcUrl(chainId, rpcData[0], defaultRetry); + } else if ( + connectionType === RpcConsensusType.Quorum || + connectionType === RpcConsensusType.Fallback + ) { + return new HyperlaneSmartProvider( + chainId, + rpcData.map((url) => ({ http: url })), + undefined, + // disable retry for quorum + connectionType === RpcConsensusType.Fallback ? defaultRetry : undefined, + ); + } else { + throw Error(`Unsupported connectionType: ${connectionType}`); } } diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index 96f75e5666..3163178592 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -7,9 +7,9 @@ import { HyperlaneEnvironment, IgpConfig, MultiProvider, + OwnableConfig, RpcConsensusType, } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts'; import { environments } from '../../config/environments'; @@ -18,7 +18,6 @@ import { Role } from '../roles'; import { RootAgentConfig } from './agent'; import { KeyFunderConfig } from './funding'; -import { AllStorageGasOracleConfigs } from './gas-oracle'; import { HelloWorldConfig } from './helloworld/types'; import { InfrastructureConfig } from './infrastructure'; import { LiquidityLayerRelayerConfig } from './middleware'; @@ -38,7 +37,7 @@ export type EnvironmentConfig = { agents: Partial>; core: ChainMap; igp: ChainMap; - owners: ChainMap
; + owners: ChainMap; infra: InfrastructureConfig; getMultiProvider: ( context?: Contexts, @@ -55,7 +54,6 @@ export type EnvironmentConfig = { bridgeAdapters: ChainMap; relayer: LiquidityLayerRelayerConfig; }; - storageGasOracleConfig?: AllStorageGasOracleConfigs; }; export const deployEnvToSdkEnv: Record< diff --git a/typescript/infra/src/config/funding.ts b/typescript/infra/src/config/funding.ts index 3ad6f48aa0..c0834abc4b 100644 --- a/typescript/infra/src/config/funding.ts +++ b/typescript/infra/src/config/funding.ts @@ -1,7 +1,7 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMap, RpcConsensusType } from '@hyperlane-xyz/sdk'; import { Contexts } from '../../config/contexts'; -import { Role } from '../roles'; +import { FundableRole, Role } from '../roles'; import { DockerConfig } from './agent'; @@ -10,7 +10,7 @@ export interface ContextAndRoles { roles: Role[]; } -export type ContextAndRolesMap = Partial>; +export type ContextAndRolesMap = Partial>; export interface KeyFunderConfig { docker: DockerConfig; @@ -21,4 +21,6 @@ export interface KeyFunderConfig { cyclesBetweenEthereumMessages?: number; prometheusPushGateway: string; connectionType: RpcConsensusType.Single | RpcConsensusType.Quorum; + desiredBalancePerChain: ChainMap; + desiredKathyBalancePerChain: ChainMap; } diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 812314220a..a7e91e44be 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -1,22 +1,17 @@ import { BigNumber, ethers } from 'ethers'; -import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainName, + StorageGasOracleConfig as DestinationOracleConfig, +} from '@hyperlane-xyz/sdk'; import { convertDecimals } from '@hyperlane-xyz/utils'; import { mustGetChainNativeTokenDecimals } from '../utils/utils'; -export type RemoteGasData = { - tokenExchangeRate: BigNumber; - gasPrice: BigNumber; -}; - -export type RemoteGasDataConfig = RemoteGasData & { - remoteDomain: number; -}; - -// Gas data to configure on a single local chain. Includes RemoteGasData +// Gas data to configure on a single local chain. Includes DestinationOracleConfig // for each remote chain. -export type StorageGasOracleConfig = ChainMap; +export type StorageGasOracleConfig = ChainMap; // StorageGasOracleConfigs for each local chain export type AllStorageGasOracleConfigs = ChainMap; @@ -27,11 +22,8 @@ export const TOKEN_EXCHANGE_RATE_SCALE = ethers.utils.parseUnits( TOKEN_EXCHANGE_RATE_DECIMALS, ); -// Overcharge by 30% to account for market making risk -const TOKEN_EXCHANGE_RATE_MULTIPLIER = ethers.utils.parseUnits( - '1.30', - TOKEN_EXCHANGE_RATE_DECIMALS, -); +// Overcharge by 20% to account for market making risk (when assets are unequal) +const EXCHANGE_RATE_MARGIN_PCT = 20; // Gets the StorageGasOracleConfig for a particular local chain function getLocalStorageGasOracleConfig( @@ -79,9 +71,11 @@ export function getTokenExchangeRateFromValues( remoteValue: BigNumber, ): BigNumber { // This does not yet account for decimals! - const exchangeRate = remoteValue - .mul(TOKEN_EXCHANGE_RATE_MULTIPLIER) - .div(localValue); + let exchangeRate = remoteValue.mul(TOKEN_EXCHANGE_RATE_SCALE).div(localValue); + // use margin if exchange rate is not 1 + if (!exchangeRate.eq(TOKEN_EXCHANGE_RATE_SCALE)) { + exchangeRate = exchangeRate.mul(100 + EXCHANGE_RATE_MARGIN_PCT).div(100); + } return BigNumber.from( convertDecimals( diff --git a/typescript/infra/src/config/grafana_token_config.ts b/typescript/infra/src/config/grafana_token_config.ts deleted file mode 100644 index 9d7e6f4c34..0000000000 --- a/typescript/infra/src/config/grafana_token_config.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { ChainMap, TokenType } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; - -interface NativeTokenConfig { - symbol: string; - name: string; - type: TokenType.native; - decimals: number; - hypNativeAddress: string; - protocolType: - | ProtocolType.Ethereum - | ProtocolType.Sealevel - | ProtocolType.Cosmos; -} - -interface CollateralTokenConfig { - type: TokenType.collateral; - address: string; - decimals: number; - symbol: string; - name: string; - hypCollateralAddress: string; - isSpl2022?: boolean; - protocolType: - | ProtocolType.Ethereum - | ProtocolType.Sealevel - | ProtocolType.Cosmos; -} - -interface SyntheticTokenConfig { - type: TokenType.synthetic; - hypSyntheticAddress: string; - decimals: number; - symbol: string; - name: string; - protocolType: - | ProtocolType.Ethereum - | ProtocolType.Sealevel - | ProtocolType.Cosmos; -} - -// TODO: migrate and dedupe to SDK from infra and Warp UI -export type WarpTokenConfig = ChainMap< - CollateralTokenConfig | NativeTokenConfig | SyntheticTokenConfig ->; - -/// nautilus configs -export const nautilusList: WarpTokenConfig = { - // bsc - bsc: { - type: TokenType.collateral, - address: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303', - hypCollateralAddress: '0xC27980812E2E66491FD457D488509b7E04144b98', - symbol: 'ZBC', - name: 'Zebec', - decimals: 9, - protocolType: ProtocolType.Ethereum, - }, - - // nautilus - nautilus: { - type: TokenType.native, - hypNativeAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7', - symbol: 'ZBC', - name: 'Zebec', - decimals: 18, - protocolType: ProtocolType.Ethereum, - }, - - // solana - solana: { - type: TokenType.collateral, - address: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59', - hypCollateralAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa', - name: 'Zebec', - symbol: 'ZBC', - decimals: 9, - isSpl2022: false, - protocolType: ProtocolType.Sealevel, - }, -}; - -/// neutron configs -export const neutronList: WarpTokenConfig = { - neutron: { - type: TokenType.collateral, - address: - 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', - hypCollateralAddress: - 'neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa', - name: 'Celestia', - symbol: 'TIA', - decimals: 6, - protocolType: ProtocolType.Cosmos, - }, - mantapacific: { - type: TokenType.synthetic, - hypSyntheticAddress: '0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa', - name: 'Celestia', - symbol: 'TIA', - decimals: 6, - protocolType: ProtocolType.Ethereum, - }, -}; diff --git a/typescript/infra/src/config/index.ts b/typescript/infra/src/config/index.ts index f98fa4b3be..97a3b0c81a 100644 --- a/typescript/infra/src/config/index.ts +++ b/typescript/infra/src/config/index.ts @@ -1,10 +1,9 @@ -export { EnvironmentConfig, DeployEnvironment } from './environment'; +export * from './agent'; +export { DeployEnvironment, EnvironmentConfig } from './environment'; export { AllStorageGasOracleConfigs, - RemoteGasData, StorageGasOracleConfig, getAllStorageGasOracleConfigs, } from './gas-oracle'; export { HelloWorldConfig } from './helloworld/types'; export { InfrastructureConfig } from './infrastructure'; -export * from './agent'; diff --git a/typescript/infra/src/deployment/deploy.ts b/typescript/infra/src/deployment/deploy.ts index 89b846876d..5088a672e5 100644 --- a/typescript/infra/src/deployment/deploy.ts +++ b/typescript/infra/src/deployment/deploy.ts @@ -2,6 +2,7 @@ import { ChainMap, ChainName, HyperlaneAddresses, + HyperlaneCore, HyperlaneDeployer, HyperlaneDeploymentArtifacts, MultiProvider, @@ -10,16 +11,16 @@ import { } from '@hyperlane-xyz/sdk'; import { objMap, objMerge, promiseObjAll } from '@hyperlane-xyz/utils'; -import { getAgentConfigDirectory } from '../../scripts/utils'; +import { getAgentConfigDirectory } from '../../scripts/agent-utils'; import { DeployEnvironment } from '../config'; import { readJSONAtPath, - writeJSON, writeJsonAtPath, + writeMergedJSON, writeMergedJSONAtPath, } from '../utils/utils'; -export async function deployWithArtifacts( +export async function deployWithArtifacts( configMap: ChainMap, deployer: HyperlaneDeployer, cache: { @@ -28,7 +29,7 @@ export async function deployWithArtifacts( read: boolean; write: boolean; }, - fork?: ChainName, + targetNetwork?: ChainName, agentConfig?: { multiProvider: MultiProvider; addresses: string; @@ -55,11 +56,9 @@ export async function deployWithArtifacts( }); try { - if (fork) { - deployer.deployedContracts[fork] = await deployer.deployContracts( - fork, - configMap[fork], - ); + if (targetNetwork) { + deployer.deployedContracts[targetNetwork] = + await deployer.deployContracts(targetNetwork, configMap[targetNetwork]); } else { await deployer.deploy(configMap); } @@ -70,7 +69,7 @@ export async function deployWithArtifacts( await postDeploy(deployer, cache, agentConfig); } -export async function postDeploy( +export async function postDeploy( deployer: HyperlaneDeployer, cache: { addresses: string; @@ -89,7 +88,6 @@ export async function postDeploy( const deployedAddresses = serializeContractsMap(deployer.deployedContracts); const cachedAddresses = deployer.cachedAddresses; const addresses = objMerge(deployedAddresses, cachedAddresses); - console.log(addresses); // cache addresses of deployed contracts writeMergedJSONAtPath(cache.addresses, addresses); @@ -126,21 +124,26 @@ export async function writeAgentConfig( } catch (e) { console.error('Failed to load cached addresses'); } - // Write agent config indexing from the deployed or latest block numbers. - // For non-net-new deployments, these changes will need to be - // reverted manually. - const startBlocks = await promiseObjAll( - objMap(addresses, (chain, _) => - multiProvider.getProvider(chain).getBlockNumber(), - ), + + const core = HyperlaneCore.fromAddressesMap(addresses, multiProvider); + // Write agent config indexing from the deployed Mailbox which stores the block number at deployment + const startBlocksBigNumber = await promiseObjAll( + objMap(addresses, (chain, _) => { + const mailbox = core.getContracts(chain).mailbox; + return mailbox.deployedBlock(); + }), + ); + const startBlocks = objMap(startBlocksBigNumber, (_, blockNumber) => + blockNumber.toNumber(), ); + const agentConfig = buildAgentConfig( multiProvider.getKnownChainNames(), multiProvider, addresses as ChainMap, startBlocks, ); - writeJSON( + writeMergedJSON( getAgentConfigDirectory(), `${environment}_config.json`, agentConfig, diff --git a/typescript/infra/src/deployment/testcontracts/testquerysender.ts b/typescript/infra/src/deployment/testcontracts/testquerysender.ts index 17c4c8b28d..e7203856b6 100644 --- a/typescript/infra/src/deployment/testcontracts/testquerysender.ts +++ b/typescript/infra/src/deployment/testcontracts/testquerysender.ts @@ -1,6 +1,7 @@ import { TestQuerySender__factory } from '@hyperlane-xyz/core'; import { ChainName, + ContractVerifier, HyperlaneDeployer, MultiProvider, } from '@hyperlane-xyz/sdk'; @@ -15,8 +16,13 @@ export class TestQuerySenderDeployer extends HyperlaneDeployer< TestQuerySenderConfig, typeof TEST_QUERY_SENDER_FACTORIES > { - constructor(multiProvider: MultiProvider) { - super(multiProvider, TEST_QUERY_SENDER_FACTORIES); + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { + super(multiProvider, TEST_QUERY_SENDER_FACTORIES, { + contractVerifier, + }); } async deployContracts(chain: ChainName, config: TestQuerySenderConfig) { diff --git a/typescript/infra/src/deployment/testcontracts/testrecipient.ts b/typescript/infra/src/deployment/testcontracts/testrecipient.ts deleted file mode 100644 index fbb760c58b..0000000000 --- a/typescript/infra/src/deployment/testcontracts/testrecipient.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - TestRecipient__factory, - TestTokenRecipient__factory, -} from '@hyperlane-xyz/core'; -import { - ChainName, - HyperlaneDeployer, - MultiProvider, -} from '@hyperlane-xyz/sdk'; - -export const TEST_RECIPIENT_DEPLOYER_FACTORIES = { - TestRecipient: new TestRecipient__factory(), - TestTokenRecipient: new TestTokenRecipient__factory(), -}; - -export class TestRecipientDeployer extends HyperlaneDeployer< - never, - typeof TEST_RECIPIENT_DEPLOYER_FACTORIES -> { - constructor(multiProvider: MultiProvider) { - super(multiProvider, TEST_RECIPIENT_DEPLOYER_FACTORIES); - } - - async deployContracts(chain: ChainName) { - const TestRecipient = await this.deployContract(chain, 'TestRecipient', []); - const TestTokenRecipient = await this.deployContract( - chain, - 'TestTokenRecipient', - [], - ); - return { - TestRecipient, - TestTokenRecipient, - }; - } -} diff --git a/typescript/infra/src/deployment/verify.ts b/typescript/infra/src/deployment/verify.ts new file mode 100644 index 0000000000..7314c16fcd --- /dev/null +++ b/typescript/infra/src/deployment/verify.ts @@ -0,0 +1,20 @@ +import { BuildArtifact, ChainMap } from '@hyperlane-xyz/sdk'; + +import { fetchGCPSecret } from '../utils/gcloud'; +import { readJSONAtPath } from '../utils/utils'; + +// read build artifact from given path +export function extractBuildArtifact(buildArtifactPath: string): BuildArtifact { + // check provided artifact is JSON + if (!buildArtifactPath.endsWith('.json')) { + throw new Error('Source must be a JSON file.'); + } + + // return as BuildArtifact + return readJSONAtPath(buildArtifactPath) as BuildArtifact; +} + +// fetch explorer API keys from GCP +export async function fetchExplorerApiKeys(): Promise> { + return (await fetchGCPSecret('explorer-api-keys', true)) as any; +} diff --git a/typescript/infra/src/funding/key-funder.ts b/typescript/infra/src/funding/key-funder.ts index 8f4c8a67f8..78f58e5531 100644 --- a/typescript/infra/src/funding/key-funder.ts +++ b/typescript/infra/src/funding/key-funder.ts @@ -44,10 +44,12 @@ function getKeyFunderHelmValues( hyperlane: { runEnv: agentConfig.runEnv, // Only used for fetching RPC urls as env vars - chains: agentConfig.contextChainNames.relayer, // temporary hack + chains: agentConfig.environmentChainNames, contextFundingFrom: keyFunderConfig.contextFundingFrom, contextsAndRolesToFund: keyFunderConfig.contextsAndRolesToFund, connectionType: keyFunderConfig.connectionType, + desiredBalancePerChain: keyFunderConfig.desiredBalancePerChain, + desiredKathyBalancePerChain: keyFunderConfig.desiredKathyBalancePerChain, }, image: { repository: keyFunderConfig.docker.repo, diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index d7680d821a..8ad934facb 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -5,6 +5,7 @@ import { ChainName, HyperlaneApp, HyperlaneAppChecker, + OwnableConfig, OwnerViolation, } from '@hyperlane-xyz/sdk'; import { Address, CallData, objMap } from '@hyperlane-xyz/utils'; @@ -31,19 +32,14 @@ export type AnnotatedCallData = CallData & { export abstract class HyperlaneAppGovernor< App extends HyperlaneApp, - Config, + Config extends OwnableConfig, > { readonly checker: HyperlaneAppChecker; - private owners: ChainMap
; private calls: ChainMap; private canPropose: ChainMap>; - constructor( - checker: HyperlaneAppChecker, - owners: ChainMap
, - ) { + constructor(checker: HyperlaneAppChecker) { this.checker = checker; - this.owners = owners; this.calls = objMap(this.checker.app.contractsMap, () => []); this.canPropose = objMap(this.checker.app.contractsMap, () => new Map()); } @@ -120,7 +116,11 @@ export abstract class HyperlaneAppGovernor< ); await sendCallsForType( SubmissionType.SAFE, - new SafeMultiSend(this.checker.multiProvider, chain, this.owners[chain]), + new SafeMultiSend( + this.checker.multiProvider, + chain, + this.checker.configMap[chain].owner, + ), ); await sendCallsForType(SubmissionType.MANUAL, new ManualMultiSend(chain)); } @@ -162,7 +162,7 @@ export abstract class HyperlaneAppGovernor< } // 2. Check if the call will succeed via Gnosis Safe. - const safeAddress = this.owners[chain]; + const safeAddress = this.checker.configMap[chain].owner; if (!safeAddress) throw new Error(`Owner address not found for ${chain}`); // 2a. Confirm that the signer is a Safe owner or delegate. // This should implicitly check whether or not the owner is a gnosis diff --git a/typescript/infra/src/govern/HyperlaneCoreGovernor.ts b/typescript/infra/src/govern/HyperlaneCoreGovernor.ts index 019c7321a2..21cd659c87 100644 --- a/typescript/infra/src/govern/HyperlaneCoreGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneCoreGovernor.ts @@ -1,15 +1,14 @@ import { - ChainMap, CoreConfig, CoreViolationType, HyperlaneCore, HyperlaneCoreChecker, + HyperlaneCoreDeployer, MailboxViolation, MailboxViolationType, OwnerViolation, ViolationType, } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; import { HyperlaneAppGovernor } from '../govern/HyperlaneAppGovernor'; @@ -17,11 +16,8 @@ export class HyperlaneCoreGovernor extends HyperlaneAppGovernor< HyperlaneCore, CoreConfig > { - constructor( - readonly checker: HyperlaneCoreChecker, - owners: ChainMap
, - ) { - super(checker, owners); + constructor(readonly checker: HyperlaneCoreChecker) { + super(checker); } protected async handleMailboxViolation(violation: MailboxViolation) { @@ -29,10 +25,15 @@ export class HyperlaneCoreGovernor extends HyperlaneAppGovernor< case MailboxViolationType.DefaultIsm: { let ismAddress: string; if (typeof violation.expected === 'object') { - const ism = await this.checker.ismFactory.deploy( - violation.chain, - violation.expected, + // hack to bind the ISM factory to the deployer for verification + new HyperlaneCoreDeployer( + this.checker.multiProvider, + this.checker.ismFactory, ); + const ism = await this.checker.ismFactory.deploy({ + destination: violation.chain, + config: violation.expected, + }); ismAddress = ism.address; } else if (typeof violation.expected === 'string') { ismAddress = violation.expected; @@ -66,6 +67,10 @@ export class HyperlaneCoreGovernor extends HyperlaneAppGovernor< await this.handleMailboxViolation(violation as MailboxViolation); break; } + case CoreViolationType.ValidatorAnnounce: { + console.warn('Ignoring ValidatorAnnounce violation'); + break; + } default: throw new Error(`Unsupported violation type ${violation.type}`); } diff --git a/typescript/infra/src/govern/HyperlaneIgpGovernor.ts b/typescript/infra/src/govern/HyperlaneIgpGovernor.ts index 46a9ec0ff8..4f1f94ce44 100644 --- a/typescript/infra/src/govern/HyperlaneIgpGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneIgpGovernor.ts @@ -2,18 +2,16 @@ import { BigNumber, ethers } from 'ethers'; import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; import { - ChainMap, ChainName, HyperlaneIgp, - HyperlaneIgpChecker, IgpBeneficiaryViolation, IgpConfig, IgpGasOraclesViolation, IgpOverheadViolation, IgpViolation, IgpViolationType, + OwnerViolation, } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; import { HyperlaneAppGovernor } from '../govern/HyperlaneAppGovernor'; @@ -21,10 +19,6 @@ export class HyperlaneIgpGovernor extends HyperlaneAppGovernor< HyperlaneIgp, IgpConfig > { - constructor(checker: HyperlaneIgpChecker, owners: ChainMap
) { - super(checker, owners); - } - protected async mapViolationsToCalls() { for (const violation of this.checker.violations) { switch (violation.type) { @@ -32,6 +26,10 @@ export class HyperlaneIgpGovernor extends HyperlaneAppGovernor< this.handleIgpViolation(violation as IgpViolation); break; } + case 'Owner': { + super.handleOwnerViolation(violation as OwnerViolation); + break; + } default: throw new Error(`Unsupported violation type ${violation.type}`); } diff --git a/typescript/infra/src/govern/ProxiedRouterGovernor.ts b/typescript/infra/src/govern/ProxiedRouterGovernor.ts index b2e5eccb99..9d421c62fe 100644 --- a/typescript/infra/src/govern/ProxiedRouterGovernor.ts +++ b/typescript/infra/src/govern/ProxiedRouterGovernor.ts @@ -1,8 +1,6 @@ import { - ChainMap, ConnectionClientViolation, ConnectionClientViolationType, - HyperlaneAppChecker, OwnerViolation, RouterApp, RouterConfig, @@ -10,7 +8,6 @@ import { RouterViolationType, ViolationType, } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; import { HyperlaneAppGovernor } from './HyperlaneAppGovernor'; @@ -18,13 +15,6 @@ export class ProxiedRouterGovernor< App extends RouterApp, Config extends RouterConfig, > extends HyperlaneAppGovernor { - constructor( - checker: HyperlaneAppChecker, - owners: ChainMap
, - ) { - super(checker, owners); - } - protected async mapViolationsToCalls() { for (const violation of this.checker.violations) { switch (violation.type) { diff --git a/typescript/infra/src/helloworld/kathy.ts b/typescript/infra/src/helloworld/kathy.ts index ae48434671..c5519682d5 100644 --- a/typescript/infra/src/helloworld/kathy.ts +++ b/typescript/infra/src/helloworld/kathy.ts @@ -38,8 +38,6 @@ export async function runHelloworldKathyHelmCommand( const values = getHelloworldKathyHelmValues(agentConfig, kathyConfig); - console.log('kathy values: ', values); - return execCmd( `helm ${helmCommand} ${getHelmReleaseName( agentConfig.context, @@ -77,9 +75,9 @@ function getHelloworldKathyHelmValues( context: agentConfig.context, // This is just used for fetching secrets, and is not actually // the list of chains that kathy will send to. Because Kathy - // will fetch secrets for all chains, regardless of skipping them or - // not, we pass in all chains - chains: agentConfig.contextChainNames.relayer, + // will fetch secrets for all chains in the environment, regardless + // of skipping them or not, we pass in all chains + chains: agentConfig.environmentChainNames, aws: agentConfig.aws !== undefined, chainsToSkip: kathyConfig.chainsToSkip, diff --git a/typescript/infra/src/infrastructure/external-secrets/helm/README.md b/typescript/infra/src/infrastructure/external-secrets/helm/README.md index b407dc84ec..34c36446f2 100644 --- a/typescript/infra/src/infrastructure/external-secrets/helm/README.md +++ b/typescript/infra/src/infrastructure/external-secrets/helm/README.md @@ -19,7 +19,7 @@ As of now, the GCP service account that's used by the ClusterSecretStore to acce GCP service account credentials are static and long-living, which is really unattractive. The leading alternative is workload identity, which doesn't require static and long-living credentials. For now, the GCP service account approach was used for the following reasons: -1. The existing mainnet cluster does not support workload identity. It doesn't seem like a big lift to change the cluster to support workload identity, but it was desireable to avoid a disruption by making large changes to the infrastructure. +1. The existing mainnet cluster does not support workload identity. It doesn't seem like a big lift to change the cluster to support workload identity, but it was desirable to avoid a disruption by making large changes to the infrastructure. 2. Workload identity has some less-than-attractive features, like [identity sameness](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity), which essentially requires putting sensitive workloads in their own GCP project. Regardless, workload identities are a more attractive long-term option, and moving to them should be relatively easy. @@ -29,4 +29,3 @@ Regardless, workload identities are a more attractive long-term option, and movi The [documentation](https://external-secrets.io/v0.4.4/) is the best source. In short, it allows Kuberenetes Secrets to get their secrets from an external secret provided (like GCP's Secret Manager), all without a developer/deployer needing to touch the secrets themselves. The general idea is there are `SecretStore`s (or `ClusterSecretStore`s, which are the cluster-wide version), that specify how the cluster can authenticate with the external secret provider. `ExternalSecret`s can then be specified in "application" infrastructure, which allow developers to specify a template for a Secret that will be created using the secret values from the external provider (& using the credentials from the SecretStore). - diff --git a/typescript/infra/src/roles.ts b/typescript/infra/src/roles.ts index 4da4bf3c04..4953118f37 100644 --- a/typescript/infra/src/roles.ts +++ b/typescript/infra/src/roles.ts @@ -3,15 +3,15 @@ export enum Role { Relayer = 'relayer', Scraper = 'scraper', Deployer = 'deployer', - Bank = 'bank', Kathy = 'kathy', } +export type FundableRole = Role.Relayer | Role.Kathy; + export const ALL_KEY_ROLES = [ Role.Validator, Role.Relayer, Role.Deployer, - Role.Bank, Role.Kathy, ]; diff --git a/typescript/infra/src/utils/fork.ts b/typescript/infra/src/utils/fork.ts index 46de55d010..3d50f8eac3 100644 --- a/typescript/infra/src/utils/fork.ts +++ b/typescript/infra/src/utils/fork.ts @@ -1,5 +1,4 @@ import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'; -import { ethers } from 'ethers'; import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; @@ -19,10 +18,6 @@ export const impersonateAccount = async ( ): Promise => { const provider = new JsonRpcProvider('http://127.0.0.1:8545'); await provider.send('hardhat_impersonateAccount', [account]); - await provider.send('hardhat_setBalance', [ - account, - ethers.utils.parseEther('42').toHexString(), - ]); return provider.getSigner(account); }; diff --git a/typescript/infra/src/utils/gcloud.ts b/typescript/infra/src/utils/gcloud.ts index c5354875aa..dde9411ed0 100644 --- a/typescript/infra/src/utils/gcloud.ts +++ b/typescript/infra/src/utils/gcloud.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import fs from 'fs'; import { rm, writeFile } from 'fs/promises'; @@ -9,6 +10,8 @@ interface IamCondition { expression: string; } +const debugLog = debug('infra:utils:gcloud'); + // Allows secrets to be overridden via environment variables to avoid // gcloud calls. This is particularly useful for running commands in k8s, // where we can use external-secrets to fetch secrets from GCP secret manager, @@ -23,7 +26,7 @@ export async function fetchGCPSecret( const envVarOverride = tryGCPSecretFromEnvVariable(secretName); if (envVarOverride !== undefined) { - console.log( + debugLog( `Using environment variable instead of GCP secret with name ${secretName}`, ); output = envVarOverride; @@ -41,7 +44,7 @@ export async function fetchGCPSecret( // If the environment variable GCP_SECRET_OVERRIDES_ENABLED is `true`, // this will attempt to find an environment variable of the form: -// `GCP_SECRET_OVERRIDE_${gcpSecretName..replaceAll('-', '_').toUpperCase()}` +// `GCP_SECRET_OVERRIDE_${gcpSecretName.replaceAll('-', '_').toUpperCase()}` // If found, it's returned, otherwise, undefined is returned. function tryGCPSecretFromEnvVariable(gcpSecretName: string) { const overridingEnabled = @@ -58,9 +61,12 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { export async function gcpSecretExists(secretName: string) { const fullName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; + debugLog(`Checking if GCP secret exists for ${fullName}`); + const matches = await execCmdAndParseJson( `gcloud secrets list --filter name=${fullName} --format json`, ); + debugLog(`Matches: ${matches.length}`); return matches.length > 0; } @@ -80,10 +86,12 @@ export async function setGCPSecret( await execCmd( `gcloud secrets create ${secretName} --data-file=${fileName} --replication-policy=automatic --labels=${labelString}`, ); + debugLog(`Created new GCP secret for ${secretName}`); } else { await execCmd( `gcloud secrets versions add ${secretName} --data-file=${fileName}`, ); + debugLog(`Added new version to existing GCP secret for ${secretName}`); } await rm(fileName); } @@ -95,6 +103,9 @@ export async function createServiceAccountIfNotExists( let serviceAccountInfo = await getServiceAccountInfo(serviceAccountName); if (!serviceAccountInfo) { serviceAccountInfo = await createServiceAccount(serviceAccountName); + debugLog(`Created new service account with name ${serviceAccountName}`); + } else { + debugLog(`Service account with name ${serviceAccountName} already exists`); } return serviceAccountInfo.email; } @@ -110,6 +121,7 @@ export async function grantServiceAccountRoleIfNotExists( matchedBinding && iamConditionsEqual(condition, matchedBinding.condition) ) { + debugLog(`Service account ${serviceAccountEmail} already has role ${role}`); return; } await execCmd( @@ -119,6 +131,7 @@ export async function grantServiceAccountRoleIfNotExists( : '' }`, ); + debugLog(`Granted role ${role} to service account ${serviceAccountEmail}`); } export async function createServiceAccountKey(serviceAccountEmail: string) { @@ -128,12 +141,14 @@ export async function createServiceAccountKey(serviceAccountEmail: string) { ); const key = JSON.parse(fs.readFileSync(localKeyFile, 'utf8')); fs.rmSync(localKeyFile); + debugLog(`Created new service account key for ${serviceAccountEmail}`); return key; } // The alphanumeric project name / ID export async function getCurrentProject() { const [result] = await execCmd('gcloud config get-value project'); + debugLog(`Current GCP project ID: ${result.trim()}`); return result.trim(); } @@ -150,10 +165,12 @@ async function getIamMemberPolicyBindings(memberEmail: string) { const unprocessedRoles = await execCmdAndParseJson( `gcloud projects get-iam-policy $(gcloud config get-value project) --format "json(bindings)" --flatten="bindings[].members" --filter="bindings.members:${memberEmail}"`, ); - return unprocessedRoles.map((unprocessedRoleObject: any) => ({ + const bindings = unprocessedRoles.map((unprocessedRoleObject: any) => ({ role: unprocessedRoleObject.bindings.role, condition: unprocessedRoleObject.bindings.condition, })); + debugLog(`Retrieved IAM policy bindings for ${memberEmail}`); + return bindings; } async function createServiceAccount(serviceAccountName: string) { @@ -164,13 +181,15 @@ async function createServiceAccount(serviceAccountName: string) { async function getServiceAccountInfo(serviceAccountName: string) { // By filtering, we get an array with one element upon a match and an empty - // array if there is not a match, which is desireable because it never errors. + // array if there is not a match, which is desirable because it never errors. const matches = await execCmdAndParseJson( `gcloud iam service-accounts list --format json --filter displayName="${serviceAccountName}"`, ); if (matches.length === 0) { + debugLog(`No service account found with name ${serviceAccountName}`); return undefined; } + debugLog(`Found service account with name ${serviceAccountName}`); return matches[0]; } diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index 1a8a79f8e5..84256ba58e 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -3,7 +3,9 @@ import * as asn1 from 'asn1.js'; import { exec } from 'child_process'; import { ethers } from 'ethers'; import fs from 'fs'; +import stringify from 'json-stable-stringify'; import path from 'path'; +import { parse as yamlParse } from 'yaml'; import { AllChains, @@ -11,10 +13,10 @@ import { CoreChainName, chainMetadata, } from '@hyperlane-xyz/sdk'; -import { objMerge } from '@hyperlane-xyz/utils'; +import { ProtocolType, objMerge } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts'; -import { Role } from '../roles'; +import { FundableRole, Role } from '../roles'; export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -69,7 +71,7 @@ export function getEthereumAddress(publicKey: Buffer): string { pubKeyBuffer = pubKeyBuffer.slice(1, pubKeyBuffer.length); const address = ethers.utils.keccak256(pubKeyBuffer); // keccak256 hash of publicKey - const EthAddr = `0x${address.slice(-40)}`; // take last 20 bytes as ethereum adress + const EthAddr = `0x${address.slice(-40)}`; // take last 20 bytes as ethereum address return EthAddr; } @@ -178,7 +180,7 @@ export function writeJsonAtPath(filepath: string, obj: any) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } - fs.writeFileSync(filepath, JSON.stringify(obj, null, 2) + '\n'); + fs.writeFileSync(filepath, stringify(obj, { space: ' ' }) + '\n'); } export function writeJSON(directory: string, filename: string, obj: any) { @@ -200,6 +202,10 @@ export function readJSON(directory: string, filename: string) { return readJSONAtPath(path.join(directory, filename)); } +export function readYaml(filepath: string): T { + return yamlParse(readFileAtPath(filepath)) as T; +} + export function assertRole(roleStr: string) { const role = roleStr as Role; if (!Object.values(Role).includes(role)) { @@ -208,6 +214,14 @@ export function assertRole(roleStr: string) { return role; } +export function assertFundableRole(roleStr: string): FundableRole { + const role = roleStr as Role; + if (role !== Role.Relayer && role !== Role.Kathy) { + throw Error(`Invalid fundable role ${role}`); + } + return role; +} + export function assertChain(chainStr: string) { const chain = chainStr as ChainName; if (!AllChains.includes(chain as CoreChainName)) { @@ -273,3 +287,8 @@ export function mustGetChainNativeTokenDecimals(chain: ChainName): number { } return metadata.nativeToken.decimals; } + +export function isEthereumProtocolChain(chainName: ChainName) { + if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`); + return chainMetadata[chainName].protocol === ProtocolType.Ethereum; +} diff --git a/typescript/infra/tsconfig.json b/typescript/infra/tsconfig.json index 13290d01d9..4a968b40db 100644 --- a/typescript/infra/tsconfig.json +++ b/typescript/infra/tsconfig.json @@ -3,6 +3,7 @@ "outDir": "./dist/", "rootDir": "./", "noUnusedLocals": false, + "resolveJsonModule": true }, "exclude": ["./node_modules/", "./dist/", "./tmp.ts"], "extends": "../tsconfig.json", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 5efc299d90..3fcc771222 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,122 @@ # @hyperlane-xyz/sdk +## 3.7.0 + +### Minor Changes + +- 54aeb6420: Added warp route artifacts type adopting registry schema + +### Patch Changes + +- 6f464eaed: Add logos for injective and nautilus +- 87151c62b: Bumped injective reorg period +- ab17af5f7: Updating HyperlaneIgpDeployer to configure storage gas oracles as part of deployment +- 7b40232af: Remove unhealthy zkevm rpc + - @hyperlane-xyz/core@3.7.0 + - @hyperlane-xyz/utils@3.7.0 + +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/core@3.6.2 +- @hyperlane-xyz/utils@3.6.2 + +## 3.6.1 + +### Patch Changes + +- ae4476ad0: Bumped mantapacific reorgPeriod to 1, a reorg period in chain metadata is now required by infra. +- f3b7ddb69: Add optional grpcUrl field to ChainMetadata +- e4e4f93fc: Support pausable ISM in deployer and checker +- Updated dependencies [3c298d064] +- Updated dependencies [df24eec8b] +- Updated dependencies [78e50e7da] +- Updated dependencies [e4e4f93fc] + - @hyperlane-xyz/utils@3.6.1 + - @hyperlane-xyz/core@3.6.1 + +## 3.6.0 + +### Minor Changes + +- 0488ef31d: Add dsrv, staked and zeeprime as validators +- 8d8ba3f7a: HyperlaneIsmFactory is now wary of (try)getDomainId or (try)getChainName calls which may fail and handles them appropriately. + +### Patch Changes + +- 67a6d971e: Added `shouldRecover` flag to deployContractFromFactory so that the `TestRecipientDeployer` can deploy new contracts if it's not the owner of the prior deployments (We were recovering the SDK artifacts which meant the deployer won't be able to set the ISM as they needed) +- 612d4163a: Add mailbox version const to SDK + - @hyperlane-xyz/core@3.6.0 + - @hyperlane-xyz/utils@3.6.0 + +## 3.5.1 + +### Patch Changes + +- a04454d6d: Use getBalance instead of queryContractSmart for CwTokenAdapter + - @hyperlane-xyz/core@3.5.1 + - @hyperlane-xyz/utils@3.5.1 + +## 3.5.0 + +### Minor Changes + +- 655b6a0cd: Redeploy Routing ISM Factories + +### Patch Changes + +- 08ba0d32b: Remove dead arbitrum goerli explorer link" +- f7d285e3a: Adds Test Recipient addresses to the SDK artifacts + - @hyperlane-xyz/core@3.5.0 + - @hyperlane-xyz/utils@3.5.0 + +## 3.4.0 + +### Minor Changes + +- b832e57ae: Replace Fallback and Retry Providers with new SmartProvider with more effective fallback/retry logic + +### Patch Changes + +- 7919417ec: Granular control of updating predeployed routingIsms based on routing config mismatch + - Add support for routingIsmDelta which filters out the incompatibility between the onchain deployed config and the desired config. + - Based on the above, you either update the deployed Ism with new routes, delete old routes, change owners, etc. + - `moduleMatchesConfig` uses the same +- fd4fc1898: - Upgrade Viem to 1.20.0 + - Add optional restUrls field to ChainMetadata + - Add deepCopy util function + - Add support for cosmos factory token addresses +- e06fe0b32: Supporting DefaultFallbackRoutingIsm through non-factory deployments +- 79c96d718: Remove healthy RPC URLs and remove NeutronTestnet +- Updated dependencies [fd4fc1898] +- Updated dependencies [e06fe0b32] + - @hyperlane-xyz/utils@3.4.0 + - @hyperlane-xyz/core@3.4.0 + +## 3.3.0 + +### Patch Changes + +- 7e620c9df: Allow CLI to accept hook as a config +- 350175581: Rename StaticProtocolFee hook to ProtocolFee for clarity +- 9f2c7ce7c: Removing agentStartBlocks and using mailbox.deployedBlock() instead +- Updated dependencies [350175581] + - @hyperlane-xyz/core@3.3.0 + - @hyperlane-xyz/utils@3.3.0 + +## 3.2.0 + +### Minor Changes + +- df693708b: Add support for all ISM types in CLI interactive config creation + +### Patch Changes + +- Updated dependencies [df34198d4] + - @hyperlane-xyz/core@3.2.0 + - @hyperlane-xyz/utils@3.2.0 + ## 3.1.10 ### Patch Changes diff --git a/typescript/sdk/hardhat.config.ts b/typescript/sdk/hardhat.config.ts index fc40a39315..e65a4508ac 100644 --- a/typescript/sdk/hardhat.config.ts +++ b/typescript/sdk/hardhat.config.ts @@ -7,7 +7,7 @@ import '@typechain/hardhat'; */ module.exports = { solidity: { - version: '0.7.6', + version: '0.8.19', settings: { optimizer: { enabled: true, diff --git a/typescript/sdk/logos/black/cosmwasm.svg b/typescript/sdk/logos/black/cosmwasm.svg index bef563f624..e2bdd24ce9 100644 --- a/typescript/sdk/logos/black/cosmwasm.svg +++ b/typescript/sdk/logos/black/cosmwasm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/typescript/sdk/logos/black/inevm.svg b/typescript/sdk/logos/black/inevm.svg new file mode 100644 index 0000000000..a13721a5cc --- /dev/null +++ b/typescript/sdk/logos/black/inevm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/black/injective.svg b/typescript/sdk/logos/black/injective.svg new file mode 100644 index 0000000000..62a30f4aee --- /dev/null +++ b/typescript/sdk/logos/black/injective.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/black/nautilus.svg b/typescript/sdk/logos/black/nautilus.svg new file mode 100644 index 0000000000..a3d3d1cc76 --- /dev/null +++ b/typescript/sdk/logos/black/nautilus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/black/plume.svg b/typescript/sdk/logos/black/plume.svg new file mode 100644 index 0000000000..a051a4cac0 --- /dev/null +++ b/typescript/sdk/logos/black/plume.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/typescript/sdk/logos/black/viction.svg b/typescript/sdk/logos/black/viction.svg new file mode 100644 index 0000000000..911cc7fb51 --- /dev/null +++ b/typescript/sdk/logos/black/viction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/cosmos.svg b/typescript/sdk/logos/color/cosmos.svg index 6023d57e6a..bdc37656dd 100644 --- a/typescript/sdk/logos/color/cosmos.svg +++ b/typescript/sdk/logos/color/cosmos.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/typescript/sdk/logos/color/cosmwasm.svg b/typescript/sdk/logos/color/cosmwasm.svg index 21df94390a..f9b3558d95 100644 --- a/typescript/sdk/logos/color/cosmwasm.svg +++ b/typescript/sdk/logos/color/cosmwasm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/typescript/sdk/logos/color/inevm.svg b/typescript/sdk/logos/color/inevm.svg new file mode 100644 index 0000000000..da8638a4ac --- /dev/null +++ b/typescript/sdk/logos/color/inevm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/injective.svg b/typescript/sdk/logos/color/injective.svg new file mode 100644 index 0000000000..56a066e6fd --- /dev/null +++ b/typescript/sdk/logos/color/injective.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/manta.svg b/typescript/sdk/logos/color/manta.svg index 65ea376986..3ece718ca3 100644 --- a/typescript/sdk/logos/color/manta.svg +++ b/typescript/sdk/logos/color/manta.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/typescript/sdk/logos/color/nautilus.svg b/typescript/sdk/logos/color/nautilus.svg new file mode 100644 index 0000000000..6b5daa694b --- /dev/null +++ b/typescript/sdk/logos/color/nautilus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/plume.svg b/typescript/sdk/logos/color/plume.svg new file mode 100644 index 0000000000..461e39c54c --- /dev/null +++ b/typescript/sdk/logos/color/plume.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/typescript/sdk/logos/color/viction.svg b/typescript/sdk/logos/color/viction.svg new file mode 100644 index 0000000000..911cc7fb51 --- /dev/null +++ b/typescript/sdk/logos/color/viction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index c0f951df9b..35a16d961a 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,24 +1,24 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.1.10", + "version": "3.7.0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.1.10", - "@hyperlane-xyz/utils": "3.1.10", + "@hyperlane-xyz/core": "3.7.0", + "@hyperlane-xyz/utils": "3.7.0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", - "@wagmi/chains": "^0.2.6", + "@wagmi/chains": "^1.8.0", "bignumber.js": "^9.1.1", "coingecko-api": "^1.0.10", "cosmjs-types": "^0.9.0", "cross-fetch": "^3.1.5", "debug": "^4.3.4", "ethers": "^5.7.2", - "viem": "^1.3.1", + "viem": "^1.20.0", "zod": "^3.21.2" }, "devDependencies": { @@ -38,7 +38,8 @@ "prettier": "^2.8.8", "sinon": "^13.0.2", "ts-node": "^10.8.0", - "typescript": "5.1.6" + "typescript": "5.1.6", + "yaml": "^2.3.1" }, "files": [ "/dist", @@ -60,9 +61,12 @@ "lint": "eslint src --ext .ts", "prepublishOnly": "yarn build", "prettier": "prettier --write ./src", - "test": "yarn test:unit && yarn test:hardhat", - "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts'", - "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")" + "test": "yarn test:unit && yarn test:hardhat && yarn test:foundry", + "test:ci": "yarn test", + "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts' --exit", + "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")", + "test:metadata": "ts-node ./src/test/metadata-check.ts", + "test:foundry": "./scripts/foundry-test.sh" }, "types": "dist/index.d.ts", "peerDependencies": { diff --git a/typescript/sdk/scripts/foundry-test.sh b/typescript/sdk/scripts/foundry-test.sh new file mode 100755 index 0000000000..2eef6dfb60 --- /dev/null +++ b/typescript/sdk/scripts/foundry-test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +function cleanup() { + set +e + pkill -f anvil + rm -rf /tmp/anvil* + set -e +} + +cleanup + +echo "Starting anvil chain" +anvil --chain-id 31337 -p 8545 --state /tmp/anvil1/state --gas-price 1 > /dev/null & + +echo "Running mocha tests" +yarn mocha --config .mocharc.json './src/**/*.foundry-test.ts' + +cleanup + +echo "Done foundry tests" \ No newline at end of file diff --git a/typescript/sdk/src/app/MultiProtocolApp.test.ts b/typescript/sdk/src/app/MultiProtocolApp.test.ts index 68f695ba52..9c69f39fb0 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.test.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.test.ts @@ -26,7 +26,13 @@ describe('MultiProtocolApp', () => { describe('constructs', () => { const multiProvider = new MultiProtocolProvider(); it('creates an app class and gleans types from generic', async () => { - const app = new TestMultiProtocolApp(multiProvider, {}); + const addresses = { + ethereum: {}, + }; + const app = new TestMultiProtocolApp( + multiProvider.intersect(Object.keys(addresses)).result, + addresses, + ); expect(app).to.be.instanceOf(MultiProtocolApp); expect(app.adapter(Chains.ethereum).protocol).to.eql( ProtocolType.Ethereum, diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index daf841dcad..e9c99d3a8e 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -6,6 +6,7 @@ import { ProtocolType, objMap, promiseObjAll, + symmetricDifference, } from '@hyperlane-xyz/utils'; import { ChainMetadata } from '../metadata/chainMetadataTypes'; @@ -120,6 +121,22 @@ export abstract class MultiProtocolApp< public readonly addresses: ChainMap, public readonly logger = debug('hyperlane:MultiProtocolApp'), ) { + const multiProviderChains = multiProvider.getKnownChainNames(); + const addressesChains = Object.keys(addresses); + const setDifference = symmetricDifference( + new Set(multiProviderChains), + new Set(addressesChains), + ); + if (setDifference.size > 0) { + throw new Error( + `MultiProtocolProvider and addresses must have the same chains. Provider chains: ${multiProviderChains.join( + ', ', + )}. Addresses chains: ${addressesChains.join( + ', ', + )}. Difference: ${Array.from(setDifference)}`, + ); + } + super(multiProvider.metadata); } diff --git a/typescript/sdk/src/consts/.eslintrc b/typescript/sdk/src/consts/.eslintrc new file mode 100644 index 0000000000..7242f12412 --- /dev/null +++ b/typescript/sdk/src/consts/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "sort-keys": ["error"] + } +} diff --git a/typescript/sdk/src/consts/agentStartBlocks.ts b/typescript/sdk/src/consts/agentStartBlocks.ts deleted file mode 100644 index 3fe6c9c5c4..0000000000 --- a/typescript/sdk/src/consts/agentStartBlocks.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ChainMap } from '../types'; - -// TODO this was previously in hyp-deploy, but ideally should be integrated -// into the ChainMetadata type and de-duped with agent consts -export const agentStartBlocks: 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/sdk/src/consts/bytecode.ts b/typescript/sdk/src/consts/bytecode.ts index 86a046d0d8..fe5605d4e6 100644 --- a/typescript/sdk/src/consts/bytecode.ts +++ b/typescript/sdk/src/consts/bytecode.ts @@ -1,11 +1,13 @@ export enum BytecodeHash { V3_MAILBOX_BYTECODE_HASH = '0x6e853444a6e38bb1d7ac7608b92a70cee83153c891c70ed882b2432134bb23a0', // without optimizer OPT_V3_MAILBOX_BYTECODE_HASH = '0x7cc944e10fa5597f10265bdac4a808e705711451ee7f117ebf9a97193b796136', // with optimizer - TRANSPARENT_PROXY_BYTECODE_HASH = '0x320bda003dfa31828115be5c01b9f3e7eecaf2532afdb89d2b53559f2e7ab86d', - PROXY_ADMIN_BYTECODE_HASH = '0x13855ae57da3aadecb9259cecece16e1f434b8850fe95531f422e4e262f3f200', // without optimizer - OPT_PROXY_ADMIN_BYTECODE_HASH = '0x30aa3b1506a94c0fe2749af099851623685d9a24a65e2e8b3746c272499979d1', // with optimizer + TRANSPARENT_PROXY_4_9_3_BYTECODE_HASH = '0xae0fb63adc64a29562a3337ed10b8772f89d5241bc3d8f0a82e9462d421e5e4b', // OZ 4.9.3 + TRANSPARENT_PROXY_BYTECODE_HASH = '0x320bda003dfa31828115be5c01b9f3e7eecaf2532afdb89d2b53559f2e7ab86d', // without optimizer + OPT_TRANSPARENT_PROXY_BYTECODE_HASH = '0x30aa3b1506a94c0fe2749af099851623685d9a24a65e2e8b3746c272499979d1', // with optimizer + PROXY_ADMIN_BYTECODE_HASH = '0x13855ae57da3aadecb9259cecece16e1f434b8850fe95531f422e4e262f3f200', V2_PROXY_ADMIN_BYTECODE_HASH = '0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0', // reused from v2 - INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xe995bcd732f4861606036357edb2a4d4c3e9b8d7e599fe548790ac1cf26888f8', + INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xf9c7e93d69bf377a85bce53f6d89a59fc75334d823042014ca32ee6e4d6d8070', // without optimizer + OPT_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0x69325ab0957fcca37e2eac622af6186380f4cddb279183a97c37511459d40f18', // optimized OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xd2c5b00ac2d058117491d581d63c3c4fcf6aeb2667c6cc0c7caed359c9eebea1', OVERHEAD_IGP_BYTECODE_HASH = '0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252', } diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 4644133b28..a09e9569ce 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -11,103 +11,109 @@ import { Chains, Mainnets, Testnets } from './chains'; export const avaxToken = { decimals: 18, name: 'Avalanche', symbol: 'AVAX' }; export const bnbToken = { decimals: 18, name: 'BNB', symbol: 'BNB' }; export const celoToken = { decimals: 18, name: 'CELO', symbol: 'CELO' }; -export const etherToken = { name: 'Ether', symbol: 'ETH', decimals: 18 }; -export const maticToken = { name: 'MATIC', symbol: 'MATIC', decimals: 18 }; -export const xDaiToken = { name: 'xDai', symbol: 'xDai', decimals: 18 }; -export const solToken = { name: 'Sol', symbol: 'SOL', decimals: 9 }; - -/** - * Metadata for Ethereum chains - */ +export const etherToken = { decimals: 18, name: 'Ether', symbol: 'ETH' }; +export const maticToken = { decimals: 18, name: 'MATIC', symbol: 'MATIC' }; +export const xDaiToken = { decimals: 18, name: 'xDai', symbol: 'xDai' }; +export const solToken = { decimals: 9, name: 'Sol', symbol: 'SOL' }; export const alfajores: ChainMetadata = { - chainId: 44787, - domainId: 44787, - name: Chains.alfajores, - protocol: ProtocolType.Ethereum, - displayName: 'Alfajores', - nativeToken: celoToken, - rpcUrls: [{ http: 'https://alfajores-forno.celo-testnet.org' }], blockExplorers: [ { - name: 'CeloScan', - url: 'https://alfajores.celoscan.io', apiUrl: 'https://api-alfajores.celoscan.io/api', family: ExplorerFamily.Etherscan, + name: 'CeloScan', + url: 'https://alfajores.celoscan.io', }, { - name: 'Blockscout', - url: 'https://explorer.celo.org/alfajores', apiUrl: 'https://explorer.celo.org/alfajores/api', family: ExplorerFamily.Blockscout, + name: 'Blockscout', + url: 'https://explorer.celo.org/alfajores', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 5, + reorgPeriod: 0, }, + chainId: 44787, + displayName: 'Alfajores', + domainId: 44787, isTestnet: true, + name: Chains.alfajores, + nativeToken: celoToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://alfajores-forno.celo-testnet.org' }], }; export const arbitrum: ChainMetadata = { - chainId: 42161, - domainId: 42161, - name: Chains.arbitrum, - protocol: ProtocolType.Ethereum, - displayName: 'Arbitrum', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://arb1.arbitrum.io/rpc' }], blockExplorers: [ { - name: 'Arbiscan', - url: 'https://arbiscan.io', apiUrl: 'https://api.arbiscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Arbiscan', + url: 'https://arbiscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 3, + reorgPeriod: 0, }, - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas + chainId: 42161, + displayName: 'Arbitrum', + domainId: 42161, + gasCurrencyCoinGeckoId: 'ethereum', + // ETH is used for gas gnosisSafeTransactionServiceUrl: 'https://safe-transaction-arbitrum.safe.global/', + name: Chains.arbitrum, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://arb1.arbitrum.io/rpc' }], }; export const arbitrumgoerli: ChainMetadata = { + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, + }, chainId: 421613, - domainId: 421613, - name: Chains.arbitrumgoerli, - protocol: ProtocolType.Ethereum, displayName: 'Arbitrum Goerli', displayNameShort: 'Arb. Goerli', + domainId: 421613, + isTestnet: true, + name: Chains.arbitrumgoerli, nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [{ http: 'https://goerli-rollup.arbitrum.io/rpc' }], +}; + +export const avalanche: ChainMetadata = { blockExplorers: [ { - name: 'Arbiscan', - url: 'https://goerli.arbiscan.io', - apiUrl: 'https://api-goerli.arbiscan.io/api', - family: ExplorerFamily.Etherscan, + apiUrl: + 'https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api', + family: ExplorerFamily.Routescan, + name: 'SnowTrace', + url: 'https://snowtrace.io', }, ], blocks: { - confirmations: 1, - reorgPeriod: 1, - estimateBlockTime: 3, + confirmations: 3, + estimateBlockTime: 2, + reorgPeriod: 3, }, - isTestnet: true, -}; - -export const avalanche: ChainMetadata = { chainId: 43114, + displayName: 'Avalanche', domainId: 43114, + gasCurrencyCoinGeckoId: 'avalanche-2', + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction-avalanche.safe.global/', name: Chains.avalanche, - protocol: ProtocolType.Ethereum, - displayName: 'Avalanche', nativeToken: avaxToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://api.avax.network/ext/bc/C/rpc', @@ -117,451 +123,436 @@ export const avalanche: ChainMetadata = { }, }, ], +}; + +export const base: ChainMetadata = { blockExplorers: [ { - name: 'SnowTrace', - url: 'https://snowtrace.io', - apiUrl: 'https://api.snowtrace.io/api', - family: ExplorerFamily.Other, + apiUrl: 'https://api.basescan.org/api', + family: ExplorerFamily.Etherscan, + name: 'BaseScan', + url: 'https://basescan.org', }, ], + // ETH is used for gas blocks: { - confirmations: 3, - reorgPeriod: 3, + confirmations: 1, estimateBlockTime: 2, + reorgPeriod: 1, }, - gasCurrencyCoinGeckoId: 'avalanche-2', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-avalanche.safe.global/', -}; - -export const base: ChainMetadata = { chainId: 8453, + displayName: 'Base', domainId: 8453, + gasCurrencyCoinGeckoId: 'ethereum', + gnosisSafeTransactionServiceUrl: 'https://safe-transaction-base.safe.global/', name: Chains.base, - protocol: ProtocolType.Ethereum, - displayName: 'Base', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://base.publicnode.com/' }, { http: 'https://mainnet.base.org' }, { http: 'https://base.blockpi.network/v1/rpc/public' }, ], - blockExplorers: [ - { - name: 'BaseScan', - url: 'https://basescan.org', - apiUrl: 'https://api.basescan.org/api', - family: ExplorerFamily.Etherscan, - }, - ], - blocks: { - confirmations: 1, - reorgPeriod: 1, - estimateBlockTime: 2, - }, - gnosisSafeTransactionServiceUrl: 'https://safe-transaction-base.safe.global/', }; -export const basegoerli: ChainMetadata = { - chainId: 84531, - domainId: 84531, - name: Chains.basegoerli, - protocol: ProtocolType.Ethereum, - displayName: 'Base Goerli', - nativeToken: etherToken, - rpcUrls: [ - { http: 'https://base-goerli.publicnode.com' }, - { http: 'https://goerli.base.org' }, - ], +export const bsc: ChainMetadata = { blockExplorers: [ { - name: 'BaseScan', - url: 'https://goerli.basescan.org', - apiUrl: 'https://api-goerli.basescan.org/api', + apiUrl: 'https://api.bscscan.com/api', family: ExplorerFamily.Etherscan, + name: 'BscScan', + url: 'https://bscscan.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 15, }, - isTestnet: true, -}; - -export const bsc: ChainMetadata = { chainId: 56, - domainId: 56, - name: Chains.bsc, - protocol: ProtocolType.Ethereum, displayName: 'Binance Smart Chain', displayNameShort: 'Binance', + domainId: 56, + gasCurrencyCoinGeckoId: 'binancecoin', + gnosisSafeTransactionServiceUrl: 'https://safe-transaction-bsc.safe.global/', + name: Chains.bsc, nativeToken: bnbToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ - { http: 'https://bsc-dataseed.binance.org' }, { http: 'https://rpc.ankr.com/bsc' }, + { http: 'https://bsc.drpc.org' }, + { http: 'https://bscrpc.com' }, ], +}; + +export const bsctestnet: ChainMetadata = { blockExplorers: [ { - name: 'BscScan', - url: 'https://bscscan.com', - apiUrl: 'https://api.bscscan.com/api', + apiUrl: 'https://api-testnet.bscscan.com/api', family: ExplorerFamily.Etherscan, + name: 'BscScan', + url: 'https://testnet.bscscan.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 15, estimateBlockTime: 3, + reorgPeriod: 9, }, - gasCurrencyCoinGeckoId: 'binancecoin', - gnosisSafeTransactionServiceUrl: 'https://safe-transaction-bsc.safe.global/', -}; - -export const bsctestnet: ChainMetadata = { chainId: 97, + displayName: 'BSC Testnet', domainId: 97, + isTestnet: true, name: Chains.bsctestnet, - protocol: ProtocolType.Ethereum, - displayName: 'BSC Testnet', nativeToken: bnbToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://bsc-testnet.publicnode.com' }, - { http: 'https://bsc-testnet.public.blastapi.io' }, { http: 'https://bsc-testnet.blockpi.network/v1/rpc/public' }, ], +}; + +export const celo: ChainMetadata = { blockExplorers: [ { - name: 'BscScan', - url: 'https://testnet.bscscan.com', - apiUrl: 'https://api-testnet.bscscan.com/api', + apiUrl: 'https://api.celoscan.io/api', family: ExplorerFamily.Etherscan, + name: 'CeloScan', + url: 'https://celoscan.io', + }, + { + apiUrl: 'https://explorer.celo.org/mainnet/api', + family: ExplorerFamily.Blockscout, + name: 'Blockscout', + url: 'https://explorer.celo.org', }, ], blocks: { confirmations: 1, - reorgPeriod: 9, - estimateBlockTime: 3, + estimateBlockTime: 5, + reorgPeriod: 0, }, - isTestnet: true, + chainId: 42220, + displayName: 'Celo', + domainId: 42220, + // The official Gnosis safe URL `https://safe-transaction-celo.safe.global` doesn't work well + // with delegates on a multisig created with the old unofficial Celo tooling. + gnosisSafeTransactionServiceUrl: + 'https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/', + name: Chains.celo, + nativeToken: celoToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://forno.celo.org' }], }; export const chiado: ChainMetadata = { - chainId: 10200, - domainId: 10200, - name: Chains.chiado, - protocol: ProtocolType.Ethereum, - displayName: 'Chiado', - nativeToken: xDaiToken, - rpcUrls: [{ http: 'https://gnosis-chiado.publicnode.com' }], blockExplorers: [ { - name: 'GnosisScan', - url: 'https://gnosis-chiado.blockscout.com', apiUrl: 'https://gnosis-chiado.blockscout.com/api', family: ExplorerFamily.Blockscout, + name: 'GnosisScan', + url: 'https://gnosis-chiado.blockscout.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 14, estimateBlockTime: 5, + reorgPeriod: 14, }, + chainId: 10200, + displayName: 'Chiado', + domainId: 10200, isTestnet: true, + name: Chains.chiado, + nativeToken: xDaiToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://gnosis-chiado.publicnode.com' }], }; -export const celo: ChainMetadata = { - chainId: 42220, - domainId: 42220, - name: Chains.celo, - protocol: ProtocolType.Ethereum, - displayName: 'Celo', - nativeToken: celoToken, - rpcUrls: [{ http: 'https://forno.celo.org' }], +export const ethereum: ChainMetadata = { blockExplorers: [ { - name: 'CeloScan', - url: 'https://celoscan.io', - apiUrl: 'https://api.celoscan.io/api', + apiUrl: 'https://api.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://etherscan.io', }, { - name: 'Blockscout', - url: 'https://explorer.celo.org', - apiUrl: 'https://explorer.celo.org/mainnet/api', + apiUrl: 'https://eth.blockscout.com/api', family: ExplorerFamily.Blockscout, + name: 'Blockscout', + url: 'https://blockscout.com/eth/mainnet', }, ], blocks: { - confirmations: 1, - reorgPeriod: 0, - estimateBlockTime: 5, + confirmations: 7, + estimateBlockTime: 13, + reorgPeriod: 14, }, - // The official Gnosis safe URL `https://safe-transaction-celo.safe.global` doesn't work well - // with delegates on a multisig created with the old unofficial Celo tooling. - gnosisSafeTransactionServiceUrl: - 'https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/', -}; - -export const ethereum: ChainMetadata = { chainId: 1, + displayName: 'Ethereum', domainId: 1, + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction-mainnet.safe.global/', name: Chains.ethereum, - protocol: ProtocolType.Ethereum, - displayName: 'Ethereum', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ - { http: 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161' }, + { http: 'https://ethereum.publicnode.com' }, { http: 'https://cloudflare-eth.com' }, ], +}; + +export const fuji: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://etherscan.io', - apiUrl: 'https://api.etherscan.io/api', + apiUrl: + 'https://api.routescan.io/v2/network/testnet/evm/43113/etherscan/api', family: ExplorerFamily.Etherscan, - }, - { - name: 'Blockscout', - url: 'https://blockscout.com/eth/mainnet', - apiUrl: 'https://blockscout.com/eth/mainnet/api', - family: ExplorerFamily.Blockscout, + name: 'SnowTrace', + url: 'https://testnet.snowtrace.io', }, ], blocks: { - confirmations: 7, - reorgPeriod: 14, - estimateBlockTime: 13, + confirmations: 3, + estimateBlockTime: 2, + reorgPeriod: 3, }, - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-mainnet.safe.global/', -}; - -export const fuji: ChainMetadata = { chainId: 43113, + displayName: 'Fuji', domainId: 43113, + isTestnet: true, name: Chains.fuji, - protocol: ProtocolType.Ethereum, - displayName: 'Fuji', nativeToken: avaxToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://api.avax-test.network/ext/bc/C/rpc', pagination: { maxBlockRange: 2048 }, }, ], +}; + +export const goerli: ChainMetadata = { blockExplorers: [ { - name: 'SnowTrace', - url: 'https://testnet.snowtrace.io', - apiUrl: 'https://api-testnet.snowtrace.io/api', + apiUrl: 'https://api-goerli.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://goerli.etherscan.io', }, ], blocks: { - confirmations: 3, - reorgPeriod: 3, - estimateBlockTime: 2, + confirmations: 1, + estimateBlockTime: 13, + reorgPeriod: 2, }, - isTestnet: true, -}; - -export const goerli: ChainMetadata = { chainId: 5, + displayName: 'Goerli', domainId: 5, + isTestnet: true, name: Chains.goerli, - protocol: ProtocolType.Ethereum, - displayName: 'Goerli', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161' }, { http: 'https://rpc.ankr.com/eth_goerli' }, - { http: 'https://eth-goerli.public.blastapi.io' }, ], +}; + +export const gnosis: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://goerli.etherscan.io', - apiUrl: 'https://api-goerli.etherscan.io/api', + apiUrl: 'https://api.gnosisscan.io/api', family: ExplorerFamily.Etherscan, + name: 'GnosisScan', + url: 'https://gnosisscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 2, - estimateBlockTime: 13, + estimateBlockTime: 5, + reorgPeriod: 14, }, - isTestnet: true, + chainId: 100, + displayName: 'Gnosis', + domainId: 100, + gasCurrencyCoinGeckoId: 'xdai', + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction-gnosis-chain.safe.global/', + name: Chains.gnosis, + nativeToken: xDaiToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [ + { + http: 'https://rpc.gnosischain.com', + pagination: { + maxBlockRange: 10000, + minBlockNumber: 25997478, + }, + }, + ], }; -export const lineagoerli: ChainMetadata = { - chainId: 59140, - domainId: 59140, - name: Chains.lineagoerli, - protocol: ProtocolType.Ethereum, - displayName: 'Linea Goerli', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://rpc.goerli.linea.build' }], +export const inevm: ChainMetadata = { blockExplorers: [ { - name: 'Linea Explorer', - url: 'https://explorer.goerli.linea.build/', - apiUrl: 'https://explorer.goerli.linea.build/api', + apiUrl: 'https://inevm.calderaexplorer.xyz/api', family: ExplorerFamily.Blockscout, + name: 'Caldera inEVM Explorer', + url: 'https://inevm.calderaexplorer.xyz', }, ], blocks: { confirmations: 1, - reorgPeriod: 2, - estimateBlockTime: 12, + estimateBlockTime: 3, + reorgPeriod: 0, }, - isTestnet: true, + chainId: 2525, + displayName: 'Injective EVM', + displayNameShort: 'inEVM', + domainId: 2525, + gasCurrencyCoinGeckoId: 'injective-protocol', + name: Chains.inevm, + nativeToken: { + decimals: 18, + name: 'Injective', + symbol: 'INJ', + }, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://inevm.calderachain.xyz/http' }], }; -export const sepolia: ChainMetadata = { - chainId: 11155111, - domainId: 11155111, - name: Chains.sepolia, - protocol: ProtocolType.Ethereum, - displayName: 'Sepolia', - nativeToken: etherToken, - rpcUrls: [ - { http: 'https://ethereum-sepolia.blockpi.network/v1/rpc/public' }, - { http: 'https://eth-sepolia.g.alchemy.com/v2/demo' }, - { http: 'https://rpc.sepolia.org' }, - ], - blockExplorers: [ - { - name: 'Etherscan', - url: 'https://sepolia.etherscan.io', - apiUrl: 'https://api-sepolia.etherscan.io/api', - family: ExplorerFamily.Etherscan, - }, - ], +export const injective: ChainMetadata = { + bech32Prefix: 'inj', + blockExplorers: [], blocks: { confirmations: 1, - reorgPeriod: 2, - estimateBlockTime: 13, + estimateBlockTime: 1, + reorgPeriod: 10, }, - isTestnet: true, + chainId: 'injective-1', + displayName: 'Injective', + domainId: 6909546, + gasCurrencyCoinGeckoId: 'injective-protocol', + grpcUrls: [{ http: 'sentry.chain.grpc.injective.network:443' }], + name: Chains.injective, + nativeToken: { + decimals: 18, + denom: 'inj', + name: 'Injective', + symbol: 'INJ', + }, + protocol: ProtocolType.Cosmos, + restUrls: [{ http: 'https://sentry.lcd.injective.network:443' }], + rpcUrls: [{ http: 'https://sentry.tm.injective.network:443' }], + slip44: 118, }; -export const scroll: ChainMetadata = { - chainId: 534352, - domainId: 534352, - name: Chains.scroll, - protocol: ProtocolType.Ethereum, - displayName: 'Scroll', - nativeToken: etherToken, - rpcUrls: [ - { http: 'https://scroll.blockpi.network/v1/rpc/public' }, - { http: 'https://scroll-mainnet.public.blastapi.io' }, - ], +export const lineagoerli: ChainMetadata = { blockExplorers: [ { - name: 'Scroll Explorer', - url: 'https://scrollscan.com/', - apiUrl: 'https://api.scrollscan.com/api', - family: ExplorerFamily.Etherscan, + apiUrl: 'https://explorer.goerli.linea.build/api', + family: ExplorerFamily.Blockscout, + name: 'Linea Explorer', + url: 'https://explorer.goerli.linea.build', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, - estimateBlockTime: 3, + estimateBlockTime: 12, + reorgPeriod: 2, }, + chainId: 59140, + displayName: 'Linea Goerli', + domainId: 59140, + isTestnet: true, + name: Chains.lineagoerli, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://rpc.goerli.linea.build' }], }; -export const scrollsepolia: ChainMetadata = { - chainId: 534351, - domainId: 534351, - name: Chains.scrollsepolia, - protocol: ProtocolType.Ethereum, - displayName: 'Scroll Sepolia', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://sepolia-rpc.scroll.io' }], +export const mantapacific: ChainMetadata = { blockExplorers: [ { - name: 'Scroll Explorer', - url: 'https://sepolia.scrollscan.dev/', - apiUrl: 'https://api-sepolia.scrollscan.com/api', - family: ExplorerFamily.Etherscan, + apiUrl: 'https://pacific-explorer.manta.network/api', + family: ExplorerFamily.Blockscout, + name: 'Manta Pacific Explorer', + url: 'https://pacific-explorer.manta.network', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 1, }, - isTestnet: true, -}; - -export const moonbasealpha: ChainMetadata = { - chainId: 1287, - domainId: 1287, - name: Chains.moonbasealpha, - protocol: ProtocolType.Ethereum, - displayName: 'Moonbase Alpha', - displayNameShort: 'Moonbase', + chainId: 169, + displayName: 'Manta Pacific', + displayNameShort: 'Manta', + domainId: 169, + gasCurrencyCoinGeckoId: 'ethereum', + isTestnet: false, + name: Chains.mantapacific, nativeToken: { decimals: 18, - name: 'DEV', - symbol: 'DEV', + name: 'Ether', + symbol: 'ETH', }, - rpcUrls: [{ http: 'https://rpc.api.moonbase.moonbeam.network' }], + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], +}; + +export const moonbeam: ChainMetadata = { blockExplorers: [ { - name: 'MoonScan', - url: 'https://moonbase.moonscan.io', - apiUrl: 'https://api-moonbase.moonscan.io/api', + apiUrl: 'https://api-moonbeam.moonscan.io/api', family: ExplorerFamily.Etherscan, + name: 'MoonScan', + url: 'https://moonscan.io', }, ], blocks: { - confirmations: 1, - reorgPeriod: 1, + confirmations: 2, estimateBlockTime: 12, + reorgPeriod: 2, }, - isTestnet: true, -}; - -export const moonbeam: ChainMetadata = { chainId: 1284, + displayName: 'Moonbeam', domainId: 1284, + gnosisSafeTransactionServiceUrl: + 'https://transaction.multisig.moonbeam.network', name: Chains.moonbeam, - protocol: ProtocolType.Ethereum, - displayName: 'Moonbeam', nativeToken: { decimals: 18, name: 'GLMR', symbol: 'GLMR', }, + protocol: ProtocolType.Ethereum, rpcUrls: [{ http: 'https://rpc.api.moonbeam.network' }], +}; + +export const mumbai: ChainMetadata = { blockExplorers: [ { - name: 'MoonScan', - url: 'https://moonscan.io', - apiUrl: 'https://api-moonbeam.moonscan.io/api', + apiUrl: 'https://api-testnet.polygonscan.com/api', family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://mumbai.polygonscan.com', }, ], blocks: { - confirmations: 2, - reorgPeriod: 2, - estimateBlockTime: 12, + confirmations: 3, + estimateBlockTime: 5, + reorgPeriod: 32, }, - gnosisSafeTransactionServiceUrl: - 'https://transaction.multisig.moonbeam.network', -}; - -export const mumbai: ChainMetadata = { chainId: 80001, + displayName: 'Mumbai', domainId: 80001, + isTestnet: true, name: Chains.mumbai, - protocol: ProtocolType.Ethereum, - displayName: 'Mumbai', nativeToken: maticToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://rpc.ankr.com/polygon_mumbai', @@ -571,441 +562,511 @@ export const mumbai: ChainMetadata = { minBlockNumber: 22900000, }, }, - { - http: 'https://matic-mumbai.chainstacklabs.com', - }, - ], - blockExplorers: [ - { - name: 'PolygonScan', - url: 'https://mumbai.polygonscan.com', - apiUrl: 'https://api-testnet.polygonscan.com/api', - family: ExplorerFamily.Etherscan, - }, ], - blocks: { - confirmations: 3, - reorgPeriod: 32, - estimateBlockTime: 5, - }, - isTestnet: true, }; -export const optimism: ChainMetadata = { - chainId: 10, - domainId: 10, - name: Chains.optimism, +export const nautilus: ChainMetadata = { + blocks: { + confirmations: 1, + estimateBlockTime: 1, + reorgPeriod: 1, + }, + chainId: 22222, + displayName: 'Nautilus', + domainId: 22222, + name: Chains.nautilus, + nativeToken: { + decimals: 18, + name: 'Zebec', + symbol: 'ZBC', + }, protocol: ProtocolType.Ethereum, - displayName: 'Optimism', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://mainnet.optimism.io' }], + rpcUrls: [ + { + http: 'https://api.nautilus.nautchain.xyz', + }, + ], +}; + +export const neutron: ChainMetadata = { + bech32Prefix: 'neutron', + blockExplorers: [ + { + // TODO API not actually supported, using url to meet validation requirements + apiUrl: 'https://www.mintscan.io/neutron', + family: ExplorerFamily.Other, + name: 'Mintscan', + url: 'https://www.mintscan.io/neutron', + }, + ], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, + }, + chainId: 'neutron-1', + displayName: 'Neutron', + domainId: 1853125230, + gasCurrencyCoinGeckoId: 'neutron-3', + grpcUrls: [{ http: 'grpc-kralum.neutron-1.neutron.org:80' }], + isTestnet: false, + name: Chains.neutron, + nativeToken: { + decimals: 6, + denom: 'untrn', + name: 'Neutron', + symbol: 'NTRN', + }, + protocol: ProtocolType.Cosmos, + restUrls: [{ http: 'https://rest-lb.neutron.org' }], + rpcUrls: [{ http: 'https://rpc-kralum.neutron-1.neutron.org' }], + slip44: 118, +}; + +export const optimism: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://optimistic.etherscan.io', apiUrl: 'https://api-optimistic.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://optimistic.etherscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 3, + reorgPeriod: 0, }, - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas + chainId: 10, + displayName: 'Optimism', + domainId: 10, + gasCurrencyCoinGeckoId: 'ethereum', + // ETH is used for gas gnosisSafeTransactionServiceUrl: 'https://safe-transaction-optimism.safe.global/', + name: Chains.optimism, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://mainnet.optimism.io' }], }; export const optimismgoerli: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api-goerli-optimism.etherscan.io/api', + family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://goerli-optimism.etherscan.io', + }, + ], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, + }, chainId: 420, - domainId: 420, - name: Chains.optimismgoerli, - protocol: ProtocolType.Ethereum, displayName: 'Optimism Goerli', displayNameShort: 'Opt. Goerli', + domainId: 420, + isTestnet: true, + name: Chains.optimismgoerli, nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [{ http: 'https://goerli.optimism.io' }], +}; + +export const plumetestnet: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://goerli-optimism.etherscan.io', - apiUrl: 'https://api-goerli-optimism.etherscan.io/api', - family: ExplorerFamily.Etherscan, + apiUrl: 'https://plume-testnet.explorer.caldera.xyz/api', + family: ExplorerFamily.Blockscout, + name: 'Plume Testnet Explorer', + url: 'https://plume-testnet.explorer.caldera.xyz', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 1, }, + chainId: 161221135, + displayName: 'Plume Testnet', + domainId: 161221135, isTestnet: true, + name: Chains.plumetestnet, + nativeToken: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', + }, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://plume-testnet.rpc.caldera.xyz/http' }], }; export const polygon: ChainMetadata = { - chainId: 137, - domainId: 137, - name: Chains.polygon, - protocol: ProtocolType.Ethereum, - displayName: 'Polygon', - nativeToken: etherToken, - rpcUrls: [ - { - http: 'https://rpc-mainnet.matic.quiknode.pro', - pagination: { - // Needs to be low to avoid RPC timeouts - maxBlockRange: 10000, - minBlockNumber: 19657100, - }, - }, - { http: 'https://polygon-rpc.com' }, - ], blockExplorers: [ { - name: 'PolygonScan', - url: 'https://polygonscan.com', apiUrl: 'https://api.polygonscan.com/api', family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://polygonscan.com', }, ], blocks: { confirmations: 200, - reorgPeriod: 256, estimateBlockTime: 2, + reorgPeriod: 256, }, + chainId: 137, + displayName: 'Polygon', + domainId: 137, gasCurrencyCoinGeckoId: 'matic-network', gnosisSafeTransactionServiceUrl: 'https://safe-transaction-polygon.safe.global/', -}; - -export const gnosis: ChainMetadata = { - chainId: 100, - domainId: 100, - name: Chains.gnosis, + name: Chains.polygon, + nativeToken: etherToken, protocol: ProtocolType.Ethereum, - displayName: 'Gnosis', - nativeToken: xDaiToken, rpcUrls: [ { - http: 'https://rpc.gnosischain.com', - pagination: { - maxBlockRange: 10000, - minBlockNumber: 25997478, - }, + http: 'https://polygon-bor.publicnode.com', }, + { http: 'https://polygon-rpc.com' }, + { http: 'https://rpc.ankr.com/polygon' }, ], +}; + +export const polygonzkevm: ChainMetadata = { blockExplorers: [ { - name: 'GnosisScan', - url: 'https://gnosisscan.io', - apiUrl: 'https://api.gnosisscan.io/api', + apiUrl: 'https://api-zkevm.polygonscan.com/api', family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://zkevm.polygonscan.com', }, ], + // ETH is used for gas blocks: { confirmations: 1, - reorgPeriod: 14, - estimateBlockTime: 5, + estimateBlockTime: 10, + reorgPeriod: 1, }, - gasCurrencyCoinGeckoId: 'xdai', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-gnosis-chain.safe.global/', -}; - -// Testnet for Nautilus -export const proteustestnet: ChainMetadata = { - chainId: 88002, - domainId: 88002, - name: Chains.proteustestnet, + chainId: 1101, + displayName: 'Polygon zkEVM', + displayNameShort: 'zkEVM', + domainId: 1101, + gasCurrencyCoinGeckoId: 'ethereum', + name: Chains.polygonzkevm, + nativeToken: etherToken, protocol: ProtocolType.Ethereum, - displayName: 'Proteus Testnet', - nativeToken: { - name: 'Zebec', - symbol: 'ZBC', - decimals: 18, - }, rpcUrls: [ + { http: 'https://zkevm-rpc.com' }, + { http: 'https://rpc.ankr.com/polygon_zkevm' }, + ], +}; + +export const polygonzkevmtestnet: ChainMetadata = { + blockExplorers: [ { - http: 'https://api.proteus.nautchain.xyz/solana', + apiUrl: 'https://api-testnet-zkevm.polygonscan.com/api', + family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://testnet-zkevm.polygonscan.com', }, ], blocks: { confirmations: 1, + estimateBlockTime: 3, reorgPeriod: 1, - estimateBlockTime: 1, }, -}; - -export const mantapacific: ChainMetadata = { - chainId: 169, - domainId: 169, - name: Chains.mantapacific, + chainId: 1442, + displayName: 'Polygon zkEVM Testnet', + displayNameShort: 'ZkEvm Testnet', + domainId: 1442, + isTestnet: true, + name: Chains.polygonzkevmtestnet, + nativeToken: etherToken, protocol: ProtocolType.Ethereum, - displayName: 'Manta Pacific', - displayNameShort: 'Manta', - nativeToken: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - blocks: { - confirmations: 1, - reorgPeriod: 0, - estimateBlockTime: 3, - }, - blockExplorers: [ - { - name: 'Manta Pacific Explorer', - url: 'https://pacific-explorer.manta.network/', - apiUrl: 'https://pacific-explorer.manta.network/api', - family: ExplorerFamily.Blockscout, - }, - ], - rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], - isTestnet: false, + rpcUrls: [{ http: 'https://rpc.public.zkevm-test.net' }], }; -export const nautilus: ChainMetadata = { - chainId: 22222, - domainId: 22222, - name: Chains.nautilus, - protocol: ProtocolType.Ethereum, - displayName: 'Nautilus', - nativeToken: { - name: 'Zebec', - symbol: 'ZBC', - decimals: 18, - }, - rpcUrls: [ - { - http: 'https://api.nautilus.nautchain.xyz', - }, - ], +// Testnet for Nautilus +export const proteustestnet: ChainMetadata = { blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 1, + reorgPeriod: 1, }, -}; - -export const neutron: ChainMetadata = { - chainId: 'neutron-1', - domainId: 1853125230, - name: Chains.neutron, - protocol: ProtocolType.Cosmos, - displayName: 'Neutron', - bech32Prefix: 'neutron', - slip44: 118, + chainId: 88002, + displayName: 'Proteus Testnet', + domainId: 88002, + name: Chains.proteustestnet, nativeToken: { - name: 'Neutron', - symbol: 'NTRN', - decimals: 6, + decimals: 18, + name: 'Zebec', + symbol: 'ZBC', }, + protocol: ProtocolType.Ethereum, rpcUrls: [ - { http: 'https://rpc-kralum.neutron-1.neutron.org' }, - { http: 'grpc-kralum.neutron-1.neutron.org:80' }, - ], - blocks: { - confirmations: 1, - reorgPeriod: 0, - estimateBlockTime: 3, - }, - blockExplorers: [ { - name: 'Mintscan', - url: 'https://www.mintscan.io/neutron', - // TODO API not actually supported, using url to meet validation requirements - apiUrl: 'https://www.mintscan.io/neutron', - family: ExplorerFamily.Other, + http: 'https://api.proteus.nautchain.xyz/solana', }, ], - isTestnet: false, }; - -/** - * Metadata for local test chains - */ - -export const test1: ChainMetadata = { - chainId: 13371, - domainId: 13371, - name: Chains.test1, - protocol: ProtocolType.Ethereum, - displayName: 'Test 1', - nativeToken: etherToken, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], - blockExplorers: [], + +export const scroll: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api.scrollscan.com/api', + family: ExplorerFamily.Etherscan, + name: 'Scroll Explorer', + url: 'https://scrollscan.com/', + }, + ], + // ETH is used for gas blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 3, + reorgPeriod: 1, }, - isTestnet: true, + chainId: 534352, + displayName: 'Scroll', + domainId: 534352, + gasCurrencyCoinGeckoId: 'ethereum', + name: Chains.scroll, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://scroll.blockpi.network/v1/rpc/public' }], }; -export const test2: ChainMetadata = { - chainId: 13372, - domainId: 13372, - name: Chains.test2, - protocol: ProtocolType.Ethereum, - displayName: 'Test 2', - nativeToken: etherToken, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], - blockExplorers: [], +export const scrollsepolia: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api-sepolia.scrollscan.com/api', + family: ExplorerFamily.Etherscan, + name: 'Scroll Explorer', + url: 'https://sepolia.scrollscan.dev/', + }, + ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 1, }, + chainId: 534351, + displayName: 'Scroll Sepolia', + domainId: 534351, isTestnet: true, + name: Chains.scrollsepolia, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://sepolia-rpc.scroll.io' }], }; -export const test3: ChainMetadata = { - chainId: 13373, - domainId: 13373, - name: Chains.test3, - protocol: ProtocolType.Ethereum, - displayName: 'Test 3', - nativeToken: etherToken, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], - blockExplorers: [], +export const sepolia: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api-sepolia.etherscan.io/api', + family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://sepolia.etherscan.io', + }, + ], blocks: { confirmations: 1, + estimateBlockTime: 13, reorgPeriod: 2, - estimateBlockTime: 3, }, + chainId: 11155111, + displayName: 'Sepolia', + domainId: 11155111, isTestnet: true, + name: Chains.sepolia, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [ + { http: 'https://ethereum-sepolia.publicnode.com' }, + { http: 'https://ethereum-sepolia.blockpi.network/v1/rpc/public' }, + { http: 'https://rpc.sepolia.org' }, + ], }; -/** - * Metadata for Sealevel chains - */ - export const solana: ChainMetadata = { - protocol: ProtocolType.Sealevel, + blockExplorers: [ + { + apiUrl: 'https://explorer.solana.com', + family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com', + }, + ], + blocks: { + confirmations: 1, + estimateBlockTime: 0.4, + reorgPeriod: 0, + }, // Uses the same ChainId as https://www.alchemy.com/chain-connect/chain/solana chainId: 1399811149, + displayName: 'Solana', domainId: 1399811149, name: 'solana', - displayName: 'Solana', nativeToken: solToken, + protocol: ProtocolType.Sealevel, rpcUrls: [{ http: 'https://api.mainnet-beta.solana.com' }], +}; + +export const solanatestnet: ChainMetadata = { blockExplorers: [ { - name: 'Solana Explorer', - url: 'https://explorer.solana.com', apiUrl: 'https://explorer.solana.com', family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 0.4, + reorgPeriod: 0, }, -}; - -export const solanatestnet: ChainMetadata = { - protocol: ProtocolType.Sealevel, chainId: 1399811150, - domainId: 1399811150, - name: 'solanatestnet', displayName: 'Solana Testnet', displayNameShort: 'Sol Testnet', + domainId: 1399811150, + isTestnet: true, + name: 'solanatestnet', nativeToken: solToken, + protocol: ProtocolType.Sealevel, rpcUrls: [{ http: 'https://api.testnet.solana.com' }], +}; + +export const solanadevnet: ChainMetadata = { blockExplorers: [ { - name: 'Solana Explorer', - url: 'https://explorer.solana.com', apiUrl: 'https://explorer.solana.com', family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 0.4, + reorgPeriod: 0, }, - isTestnet: true, -}; - -export const solanadevnet: ChainMetadata = { - protocol: ProtocolType.Sealevel, chainId: 1399811151, - domainId: 1399811151, - name: 'solanadevnet', displayName: 'Solana Devnet', displayNameShort: 'Sol Devnet', + domainId: 1399811151, + isTestnet: true, + name: 'solanadevnet', nativeToken: solToken, + protocol: ProtocolType.Sealevel, rpcUrls: [{ http: 'https://api.devnet.solana.com' }], - blockExplorers: [ - { - name: 'Solana Explorer', - url: 'https://explorer.solana.com', - apiUrl: 'https://explorer.solana.com', - family: ExplorerFamily.Other, - }, - ], +}; + +export const eclipsetestnet: ChainMetadata = { blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 0.4, + reorgPeriod: 0, }, + chainId: 239092742, + displayName: 'Eclipse Testnet', + domainId: 239092742, isTestnet: true, + name: 'eclipsetestnet', + nativeToken: { + ...etherToken, + decimals: 9, + }, + protocol: ProtocolType.Sealevel, + rpcUrls: [{ http: 'https://testnet.dev2.eclipsenetwork.xyz' }], }; -export const polygonzkevmtestnet: ChainMetadata = { - protocol: ProtocolType.Ethereum, - chainId: 1442, - domainId: 1442, - name: Chains.polygonzkevmtestnet, - displayName: 'Polygon zkEVM Testnet', - displayNameShort: 'ZkEvm Testnet', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://rpc.public.zkevm-test.net' }], - blockExplorers: [ - { - name: 'PolygonScan', - url: 'https://testnet-zkevm.polygonscan.com/', - apiUrl: 'https://api-testnet-zkevm.polygonscan.com/api', - family: ExplorerFamily.Etherscan, - }, - ], +export const test1: ChainMetadata = { + blockExplorers: [], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 0, }, + chainId: 13371, + displayName: 'Test 1', + domainId: 13371, isTestnet: true, + name: Chains.test1, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], }; -export const polygonzkevm: ChainMetadata = { +export const test2: ChainMetadata = { + blockExplorers: [], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, + }, + chainId: 13372, + displayName: 'Test 2', + domainId: 13372, + isTestnet: true, + name: Chains.test2, + nativeToken: etherToken, protocol: ProtocolType.Ethereum, - chainId: 1101, - domainId: 1101, - name: Chains.polygonzkevm, - displayName: 'Polygon zkEVM', - displayNameShort: 'zkEVM', + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], +}; + +export const test3: ChainMetadata = { + blockExplorers: [], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 2, + }, + chainId: 13373, + displayName: 'Test 3', + domainId: 13373, + isTestnet: true, + name: Chains.test3, nativeToken: etherToken, - rpcUrls: [ - { http: 'https://polygonzkevm-mainnet.g.alchemy.com/v2/demo' }, - { http: 'https://rpc.ankr.com/polygon_zkevm' }, - { http: 'https://zkevm.polygonscan.com/' }, - ], + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], +}; + +export const viction: ChainMetadata = { blockExplorers: [ { - name: 'PolygonScan', - url: 'https://zkevm.polygonscan.com/', - apiUrl: 'https://api-zkevm.polygonscan.com/api', - family: ExplorerFamily.Etherscan, + apiUrl: 'https://www.vicscan.xyz/api', + family: ExplorerFamily.Other, + name: 'Vicscan', + url: 'https://www.vicscan.xyz', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, - estimateBlockTime: 10, + estimateBlockTime: 2, + reorgPeriod: 0, + }, + chainId: 88, + displayName: 'Viction', + domainId: 88, + gasCurrencyCoinGeckoId: 'tomochain', + name: Chains.viction, + nativeToken: { + decimals: 18, + name: 'Viction', + symbol: 'VIC', }, + protocol: ProtocolType.Ethereum, + rpcUrls: [ + { + http: 'https://rpc.tomochain.com', + }, + { + http: 'https://viction.blockpi.network/v1/rpc/public', + }, + ], }; /** @@ -1020,41 +1081,44 @@ export const chainMetadata: ChainMap = { arbitrumgoerli, avalanche, base, - basegoerli, bsc, bsctestnet, - chiado, celo, + chiado, + eclipsetestnet, ethereum, fuji, + gnosis, goerli, + inevm, + injective, lineagoerli, - scroll, - scrollsepolia, - sepolia, mantapacific, - moonbasealpha, moonbeam, mumbai, + nautilus, neutron, optimism, optimismgoerli, + plumetestnet, polygon, polygonzkevm, polygonzkevmtestnet, - gnosis, proteustestnet, + scroll, + scrollsepolia, + sepolia, + solana, + solanadevnet, + solanatestnet, test1, test2, test3, - solana, - solanatestnet, - solanadevnet, - nautilus, + viction, }; export const chainIdToMetadata = Object.values(chainMetadata).reduce< - ChainMap + Record >((result, chain) => { result[chain.chainId] = chain; return result; @@ -1069,6 +1133,6 @@ export const testnetChainsMetadata: Array = Testnets.map( export const solanaChainToClusterName: ChainMap = { solana: 'mainnet-beta', - solanatestnet: 'testnet', solanadevnet: 'devnet', + solanatestnet: 'testnet', }; diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index b73faf0a0c..8142a2189d 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -8,7 +8,6 @@ export enum Chains { arbitrumgoerli = 'arbitrumgoerli', avalanche = 'avalanche', base = 'base', - basegoerli = 'basegoerli', bsc = 'bsc', bsctestnet = 'bsctestnet', celo = 'celo', @@ -17,24 +16,29 @@ export enum Chains { fuji = 'fuji', gnosis = 'gnosis', goerli = 'goerli', + inevm = 'inevm', + injective = 'injective', lineagoerli = 'lineagoerli', - scroll = 'scroll', - scrollsepolia = 'scrollsepolia', - sepolia = 'sepolia', mantapacific = 'mantapacific', - moonbasealpha = 'moonbasealpha', moonbeam = 'moonbeam', mumbai = 'mumbai', nautilus = 'nautilus', neutron = 'neutron', optimism = 'optimism', optimismgoerli = 'optimismgoerli', + plumetestnet = 'plumetestnet', polygon = 'polygon', polygonzkevm = 'polygonzkevm', polygonzkevmtestnet = 'polygonzkevmtestnet', proteustestnet = 'proteustestnet', + scroll = 'scroll', + scrollsepolia = 'scrollsepolia', + sepolia = 'sepolia', solana = 'solana', solanadevnet = 'solanadevnet', + solanatestnet = 'solanatestnet', + eclipsetestnet = 'eclipsetestnet', + viction = 'viction', test1 = 'test1', test2 = 'test2', test3 = 'test3', @@ -68,25 +72,29 @@ export const Mainnets: Array = [ Chains.base, Chains.scroll, Chains.polygonzkevm, + Chains.injective, + Chains.inevm, + Chains.viction, // Chains.solana, ]; export const Testnets: Array = [ Chains.alfajores, Chains.arbitrumgoerli, - Chains.basegoerli, Chains.bsctestnet, Chains.chiado, Chains.fuji, - Chains.lineagoerli, Chains.goerli, - Chains.moonbasealpha, + Chains.lineagoerli, Chains.mumbai, Chains.optimismgoerli, + Chains.plumetestnet, Chains.polygonzkevmtestnet, Chains.scrollsepolia, Chains.sepolia, Chains.solanadevnet, + Chains.solanatestnet, + Chains.eclipsetestnet, ]; export const TestChains: Array = [ diff --git a/typescript/sdk/src/consts/environments/index.ts b/typescript/sdk/src/consts/environments/index.ts index a1c6d8b525..96a308cbda 100644 --- a/typescript/sdk/src/consts/environments/index.ts +++ b/typescript/sdk/src/consts/environments/index.ts @@ -6,7 +6,7 @@ import { CoreChainName } from '../chains'; import mainnet from './mainnet.json'; import testnet from './testnet.json'; -export const hyperlaneEnvironments = { testnet, mainnet }; +export const hyperlaneEnvironments = { mainnet, testnet }; export type HyperlaneEnvironment = keyof typeof hyperlaneEnvironments; export type HyperlaneEnvironmentChain = Extract< diff --git a/typescript/sdk/src/consts/environments/mainnet.json b/typescript/sdk/src/consts/environments/mainnet.json index f9dea3adb4..82f22b523a 100644 --- a/typescript/sdk/src/consts/environments/mainnet.json +++ b/typescript/sdk/src/consts/environments/mainnet.json @@ -6,13 +6,18 @@ "messageIdMultisigIsmFactory": "0xEa5Be2AD66BB1BA321B7aCf0A079fBE304B09Ca0", "aggregationIsmFactory": "0x81AdDD9Ca89105063DaDEBd5B4408551Ce850E22", "aggregationHookFactory": "0xFeeB86e70e4a640cDd29636CCE19BD6fe8628135", - "routingIsmFactory": "0xF0752A65ffB2153EaE53F6a70c858a87022d5c56", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", "aggregationHook": "0x34dAb05650Cf590088bA18aF9d597f3e081bCc47", "protocolFee": "0xF8F3629e308b4758F8396606405989F8D8C9c578", - "validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5" + "validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x0d0E816eE4557689d34fAd5885C53b9393C1D9fA", + "interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB", + "fallbackRoutingHook": "0xca4cCe24E7e06241846F5EA0cda9947F0507C40C", + "pausableHook": "0x748040afB89B8FdBb992799808215419d36A0930" }, "bsc": { "storageGasOracle": "0x91d23D603d60445411C06e6443d81395593B7940", @@ -21,13 +26,18 @@ "messageIdMultisigIsmFactory": "0x4B1d8352E35e3BDE36dF5ED2e73C24E35c4a96b7", "aggregationIsmFactory": "0x38B3878c4fb44d201DA924c4a04bae3EE728c065", "aggregationHookFactory": "0xe70E86a7D1e001D419D71F960Cb6CaD59b6A3dB6", - "routingIsmFactory": "0xc40481D13419BC8090e6AD07074Ef39E538c09CE", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", "aggregationHook": "0x402Fc106576462a892355d69ACF03D46A888ae88", "protocolFee": "0xA8Aa5f14a5463a78E45CC068F11c867949F3E367", - "validatorAnnounce": "0x7024078130D9c2100fEA474DAD009C2d1703aCcd" + "validatorAnnounce": "0x7024078130D9c2100fEA474DAD009C2d1703aCcd", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xe6Af5720d34213C805C08e2470aea979e3F72F75", + "interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61", + "fallbackRoutingHook": "0x237E81f87F57Badad9e09f13CC676D986cA852e7", + "pausableHook": "0x7DBdAd1b4A922B65d37d7258a4227b6658344b7f" }, "arbitrum": { "storageGasOracle": "0xD3805207b65d99C075ceA938Fa7c0587026a5DF5", @@ -36,13 +46,18 @@ "messageIdMultisigIsmFactory": "0x12Df53079d399a47e9E730df095b712B0FDFA791", "aggregationIsmFactory": "0xD4883084389fC1Eeb4dAfB2ADcFc36B711c310EB", "aggregationHookFactory": "0x9B5f440bBb64Fee337F37e03362b628711Ea09C7", - "routingIsmFactory": "0xC020F8A7b00178dFA0fcC75C159e14b79F8e5c63", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", "aggregationHook": "0xe0cb37cFc47296f1c4eD77EFf92Aed478644d10c", "protocolFee": "0xD0199067DACb8526e7dc524a9a7DCBb57Cd25421", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", - "validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08" + "validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xa2931C37957f3079d3B21b877d56E1db930e02a5", + "pausableHook": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "fallbackRoutingHook": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", + "interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa" }, "optimism": { "storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", @@ -51,13 +66,18 @@ "messageIdMultisigIsmFactory": "0xAa4Be20E9957fE21602c74d7C3cF5CB1112EA9Ef", "aggregationIsmFactory": "0x7491843F3A5Ba24E0f17a22645bDa04A1Ae2c584", "aggregationHookFactory": "0x15DEeAB8dECDe553bb0B1F9C00984cbcae1af3D7", - "routingIsmFactory": "0x89E3530137aD51743536443a3EC838b502E72eb7", "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", "aggregationHook": "0x4ccC6d8eB79f2a1EC9bcb0f211fef7907631F91f", "protocolFee": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", - "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38" + "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xD2e905108c5e44dADA680274740f896Ea96Cf2Fb", + "pausableHook": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "fallbackRoutingHook": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2" }, "moonbeam": { "storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819", @@ -66,13 +86,18 @@ "messageIdMultisigIsmFactory": "0x84Df48F8f241f11d0fA302d09d73030429Bd9C73", "aggregationIsmFactory": "0x40c6Abcb6A2CdC8882d4bEcaC47927005c7Bb8c2", "aggregationHookFactory": "0x59cC3E7A49DdC4893eB8754c7908f96072A7DbE8", - "routingIsmFactory": "0x98Aa6239FfCcEc73A662a5e5e26Bc3fD7c7291B7", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", "aggregationHook": "0x23cca255aE83F57F39EAf9D14fB9FdaDF22D5863", "protocolFee": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", - "validatorAnnounce": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F" + "validatorAnnounce": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x8061Af3A459093540d17823D651BC5E2A92669a7", + "pausableHook": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "fallbackRoutingHook": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9" }, "gnosis": { "storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", @@ -82,19 +107,23 @@ "aggregationIsmFactory": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", "aggregationHookFactory": "0xbC8AA096dabDf4A0200BB9f8D4Cbb644C3D86d7B", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", - "routingIsmFactory": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", "aggregationHook": "0xdD1FA1C12496474c1dDC67a658Ba81437F818861", "protocolFee": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", - "validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c" + "validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0xbB5Df000113e767dE11343A16f83De733e5bCC0F", + "pausableHook": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "fallbackRoutingHook": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", + "interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1" }, "base": { "merkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "messageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "aggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "aggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "routingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", @@ -102,14 +131,17 @@ "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "aggregationHook": "0x13f3d4B0Ee0a713430fded9E18f7fb6c91A6E41F", "protocolFee": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", - "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B" + "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B", + "routingIsmFactory": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", + "pausableHook": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "fallbackRoutingHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F" }, "scroll": { "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", @@ -117,14 +149,17 @@ "aggregationHook": "0x9Bc0FAf446E128a618A88a2F28960Fb2Ca169faE", "protocolFee": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "validatorAnnounce": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638" + "validatorAnnounce": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "routingIsmFactory": "0xe03dad16074BC5EEA9A9311257BF02Eb0B6AAA2b", + "pausableHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "fallbackRoutingHook": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7" }, "polygonzkevm": { "merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "routingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", @@ -132,7 +167,11 @@ "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" + "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "routingIsmFactory": "0xe4057c5B0c43Dc18E36b08C39B419F190D29Ac2d", + "interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141", + "fallbackRoutingHook": "0x01aE937A7B05d187bBCBE80F44F41879D3D335a4", + "pausableHook": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c" }, "celo": { "storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C", @@ -141,13 +180,21 @@ "messageIdMultisigIsmFactory": "0xaB402f227e892Ef37C105bf06619c0fa106a1fB2", "aggregationIsmFactory": "0x1722dd970a1F56040712129f5Eeb76B003fd7500", "aggregationHookFactory": "0xc3745652EFB8555A8b064A0EA78d295133d326D2", - "routingIsmFactory": "0xec748b5623f0B50E4c5eB1CFa7Bd46C3213608b6", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", "aggregationHook": "0xc65890329066FB20c339Bc5C22f1756e9D3a4fF5", "protocolFee": "0x89886d431f9c3eEE64DCD6dAbA3f7D689D98D899", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", - "validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1" + "validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x2A2c22B0a8615ad24839fA6Af302E896Af32d1a3", + "domainRoutingIsm": "0xf18E32428dad0802C5D6F723cB80A6Da889777c4", + "pausableIsm": "0x6Bc4437ce69696C9461Cbc89582c259AC8847A58", + "staticAggregationIsm": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", + "interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", + "fallbackRoutingHook": "0xDC98a856fb9112894c2fE32267DA8bF35645FAF3", + "pausableHook": "0x80672c5D9Fd26B235654C24adc1CFcDeb8d15115" }, "ethereum": { "storageGasOracle": "0xc9a103990A8dB11b4f627bc5CD1D0c2685484Ec5", @@ -156,13 +203,18 @@ "messageIdMultisigIsmFactory": "0xfA21D9628ADce86531854C2B7ef00F07394B0B69", "aggregationIsmFactory": "0x46FA191Ad972D9674Ed752B69f9659A0d7b22846", "aggregationHookFactory": "0x6D2555A8ba483CcF4409C39013F5e9a3285D3C9E", - "routingIsmFactory": "0xCb74c6aE411236CEE6803619916694BE86cF5987", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", "aggregationHook": "0xb87AC8EA4533AE017604E44470F7c1E550AC6F10", "protocolFee": "0x8B05BF30F6247a90006c5837eA63C7905D79e6d8", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", - "validatorAnnounce": "0xCe74905e51497b4adD3639366708b821dcBcff96" + "validatorAnnounce": "0xCe74905e51497b4adD3639366708b821dcBcff96", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x28fA9552F19039b450498B0d8e5DEAe0d0aAc559", + "pausableHook": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "fallbackRoutingHook": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "interchainSecurityModule": "0x43Ce4Eb4aE3585dDe9Ac6967Db5b06f7f6764C8a" }, "avalanche": { "storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21", @@ -171,12 +223,75 @@ "messageIdMultisigIsmFactory": "0x8819D653DF5b1FC0DdB32189a2704E471AF8483c", "aggregationIsmFactory": "0xa5E13796eB7d2EDCc88012c8cfF90D69B51FcF9f", "aggregationHookFactory": "0x3bF6Ac986C7Af9A9Ac356C0e99C0041EFd8D96e7", - "routingIsmFactory": "0xA9Ddc70f50009aF8bDB312aA757B4304b0F7BbB3", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", "aggregationHook": "0x0165a22BA489F7DA37DAf6397781777D9FCB5708", "protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", - "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f" + "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f", + "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", + "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "routingIsmFactory": "0x28F7907911C7E321c596686AE6D1F20516450037", + "pausableHook": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "fallbackRoutingHook": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", + "interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60" + }, + "mantapacific": { + "merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", + "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", + "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "routingIsmFactory": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", + "testRecipient": "0x4E1c88DD261BEe2941e6c1814597e30F53330428", + "testTokenRecipient": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b", + "pausableHook": "0x7556a0E61d577D921Cba8Fca0d7D6299d36E607E", + "fallbackRoutingHook": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", + "interchainSecurityModule": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff" + }, + "inevm": { + "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "aggregationHook": "0xe0dDb5dE7D52918237cC1Ae131F29dcAbcb0F62B", + "protocolFee": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B", + "interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", + "pausableIsm": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "staticAggregationIsm": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", + "pausableHook": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0" + }, + "viction": { + "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "testRecipient": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84", + "testTokenRecipient": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c" } } diff --git a/typescript/sdk/src/consts/environments/testnet.json b/typescript/sdk/src/consts/environments/testnet.json index db04e9078a..e4e6765107 100644 --- a/typescript/sdk/src/consts/environments/testnet.json +++ b/typescript/sdk/src/consts/environments/testnet.json @@ -1,194 +1,202 @@ { - "basegoerli": { - "merkleRootMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "messageIdMultisigIsmFactory": "0x54148470292C24345fb828B003461a9444414517", - "aggregationIsmFactory": "0x589C201a07c26b4725A4A829d772f24423da480B", - "aggregationHookFactory": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "routingIsmFactory": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", - "proxyAdmin": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", - "mailbox": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "validatorAnnounce": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", - "merkleTreeHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "storageGasOracle": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "aggregationHook": "0x168e606fE4A9c8d7F83a3aAA132E831f153e4bAa", - "protocolFee": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "fallbackRoutingHook": "0x2C6dD6768E669EDB7b53f26067C1C4534862c3de" - }, - "arbitrumgoerli": { - "merkleRootMultisigIsmFactory": "0x17D58eBb5Ea0E2d360c877E119FAef4C4052e6B9", - "messageIdMultisigIsmFactory": "0x922CeEe9e8832a047e6aD68Df4F079F271b73Ac3", - "aggregationIsmFactory": "0xC5Bb8CDD44B6c56695df45c7AA8012a97dD6ED13", - "aggregationHookFactory": "0x39a8711BF44165A2292Cb5cB43229659c2Bb11c9", - "routingIsmFactory": "0x735491727b9a1206E16AF4964aF68d5BB9122333", - "proxyAdmin": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", - "mailbox": "0x13dABc0351407d5aAa0A50003a166A73b4febfDc", - "validatorAnnounce": "0x4a01EEBa1CC20F47A2e60aE4ec932051601FcB9e", - "merkleTreeHook": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", - "storageGasOracle": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", - "interchainGasPaymaster": "0x76189acFA212298d7022624a4633411eE0d2f26F", - "aggregationHook": "0xf852EB6b98d84A4296754043a56759a0Ae0E06df", - "protocolFee": "0x0358ba0D90ED2d90fB8cBb610F27C274D8077a0B", - "fallbackRoutingHook": "0xEdA6f85f4761A1f9e42FD40CA5a4E8Ce1C764015" - }, - "optimismgoerli": { - "merkleRootMultisigIsmFactory": "0xAbC25d7daDD748948F5cC912A807b0f8FcBb56a9", - "messageIdMultisigIsmFactory": "0x7868B6026E36C4b6E2ca6a0CaBDb1A6D0CcC443B", - "aggregationIsmFactory": "0xf666A33C451E8371907aD22dd545E1678fCa1582", - "aggregationHookFactory": "0x00cE81F7B02e0673815a8b0A54e62AeabDE78685", - "routingIsmFactory": "0x1807e7d67F00393a49c445E367face82D65d86c7", - "proxyAdmin": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", - "mailbox": "0xB5f021728Ea6223E3948Db2da61d612307945eA2", - "validatorAnnounce": "0x24D31e12E4d3bc2C46C994FcE0c828b218A1aeAb", - "merkleTreeHook": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", - "storageGasOracle": "0x4927C33299091033D935C15DE6b6073164e99BE0", - "interchainGasPaymaster": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", - "aggregationHook": "0x1C8A2588b8038BF9B7b1b60dD0EdF5b995A45599", - "protocolFee": "0x962e30F6A3ECDA85c7fa1FcF38cD04efA991Ee20", - "fallbackRoutingHook": "0xc775c748F8c9F5443151Fd989e8B61375657474d" - }, - "scrollsepolia": { - "merkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "messageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "aggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "aggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "routingIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "proxyAdmin": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", - "validatorAnnounce": "0x527768930D889662Fe7ACF64294871e86e4C2381", - "merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "storageGasOracle": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", - "interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "aggregationHook": "0x7b63Aa270335F8896717c2A809205F4b650E4268", - "protocolFee": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "fallbackRoutingHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96" - }, "alfajores": { - "merkleRootMultisigIsmFactory": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", - "messageIdMultisigIsmFactory": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", - "aggregationIsmFactory": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", + "aggregationHook": "0xdBabD76358897E68E4964647C1fb8Bf524f5EFdB", "aggregationHookFactory": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", - "routingIsmFactory": "0x37308d498bc7B0f002cb02Cf8fA01770dC2169c8", - "proxyAdmin": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", + "aggregationIsmFactory": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", + "fallbackRoutingHook": "0x3528B1aeF3a3d29E0eae90ad777A2b4A6a48aC3F", + "interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297", "mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", - "validatorAnnounce": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", + "merkleRootMultisigIsmFactory": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", "merkleTreeHook": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", - "storageGasOracle": "0x8356113754C7aCa297Db3089b89F87CC125499fb", - "interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297", - "aggregationHook": "0xdBabD76358897E68E4964647C1fb8Bf524f5EFdB", + "messageIdMultisigIsmFactory": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", "protocolFee": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", - "fallbackRoutingHook": "0x3528B1aeF3a3d29E0eae90ad777A2b4A6a48aC3F" + "proxyAdmin": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", + "routingIsmFactory": "0x30d9A03762431F8A917a0C469E7A62Bf55092Ca6", + "storageGasOracle": "0x8356113754C7aCa297Db3089b89F87CC125499fb", + "testRecipient": "0x6489d13AcAd3B8dce4c5B31f375DE4f9451E7b38", + "testTokenRecipient": "0x92dC0a76452a9D9358D2d2dEd8CddA209DF67c45", + "validatorAnnounce": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0" }, - "polygonzkevmtestnet": { - "merkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "messageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "aggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "aggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "routingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "proxyAdmin": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", - "mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "validatorAnnounce": "0x7914A3349107A7295Bbf2374db5A973d73D1b324", - "merkleTreeHook": "0x68311418D79fE8d96599384ED767d225635d88a8", - "storageGasOracle": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", - "interchainGasPaymaster": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "aggregationHook": "0x0Fd2C6F0Ad45e766660b9fDebCF36a2AD69536D1", - "protocolFee": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", - "fallbackRoutingHook": "0xBF2C366530C1269d531707154948494D3fF4AcA7" + "arbitrumgoerli": { + "aggregationHook": "0xf852EB6b98d84A4296754043a56759a0Ae0E06df", + "aggregationHookFactory": "0x39a8711BF44165A2292Cb5cB43229659c2Bb11c9", + "aggregationIsmFactory": "0xC5Bb8CDD44B6c56695df45c7AA8012a97dD6ED13", + "fallbackRoutingHook": "0xEdA6f85f4761A1f9e42FD40CA5a4E8Ce1C764015", + "interchainGasPaymaster": "0x76189acFA212298d7022624a4633411eE0d2f26F", + "mailbox": "0x13dABc0351407d5aAa0A50003a166A73b4febfDc", + "merkleRootMultisigIsmFactory": "0x17D58eBb5Ea0E2d360c877E119FAef4C4052e6B9", + "merkleTreeHook": "0xf0A38e1eEA49dAc7968F470c3aA0BDE2565A5d80", + "messageIdMultisigIsmFactory": "0x922CeEe9e8832a047e6aD68Df4F079F271b73Ac3", + "protocolFee": "0x0358ba0D90ED2d90fB8cBb610F27C274D8077a0B", + "proxyAdmin": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", + "routingIsmFactory": "0x4D6b4fe86cA1B49ea9CcDFA92F97e4EA0C27Cef2", + "storageGasOracle": "0xFc8229ADB46D96056A6e451Fb3c55d60FFeD056f", + "testRecipient": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", + "testTokenRecipient": "0x207db41AB053213451f1a71d936353C9056A0205", + "validatorAnnounce": "0x4a01EEBa1CC20F47A2e60aE4ec932051601FcB9e" }, - "sepolia": { - "merkleRootMultisigIsmFactory": "0x0a71AcC99967829eE305a285750017C4916Ca269", - "messageIdMultisigIsmFactory": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", - "aggregationIsmFactory": "0xC83e12EF2627ACE445C298e6eC418684918a6002", - "aggregationHookFactory": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", - "routingIsmFactory": "0x3603458990BfEb30f99E61B58427d196814D8ce1", - "proxyAdmin": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", - "storageGasOracle": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", - "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", - "aggregationHook": "0xe3147d5618f5e2e100690B50ec923009a4cde14A", - "protocolFee": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5", - "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", - "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", - "validatorAnnounce": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9", - "fallbackRoutingHook": "0x17Dc724B7a2F09141C13b8AC33B396073785c2BC" + "bsctestnet": { + "aggregationHook": "0x3d675bB93250Ab7603F40cbb9194bae210784627", + "aggregationHookFactory": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", + "aggregationIsmFactory": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", + "fallbackRoutingHook": "0x2670ED2EC08cAd135307556685a96bD4c16b007b", + "interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", + "mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", + "merkleRootMultisigIsmFactory": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", + "merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", + "messageIdMultisigIsmFactory": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", + "protocolFee": "0x3eF0a63B8976b838704Bcc93C78C56b6653E5a39", + "proxyAdmin": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", + "storageGasOracle": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", + "testRecipient": "0xfbcD1c00a3d809f36cC1A15918694B17B32c0b6c", + "testTokenRecipient": "0x260f6024119549a40595d0937471e607411E8ea5", + "validatorAnnounce": "0xf09701B0a93210113D175461b6135a96773B5465" }, "fuji": { - "merkleRootMultisigIsmFactory": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", - "messageIdMultisigIsmFactory": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", - "aggregationIsmFactory": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", + "aggregationHook": "0x8E9b4006171c6B75111823e7545Ee5400CEce0B3", "aggregationHookFactory": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", - "routingIsmFactory": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", - "proxyAdmin": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "aggregationIsmFactory": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", + "fallbackRoutingHook": "0xc684f7F50DB4b2563218512e021fBdd0BeD6b57E", + "interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", "mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a", + "merkleRootMultisigIsmFactory": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", "merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", - "storageGasOracle": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", - "interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "aggregationHook": "0x8E9b4006171c6B75111823e7545Ee5400CEce0B3", + "messageIdMultisigIsmFactory": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", "protocolFee": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", - "fallbackRoutingHook": "0xc684f7F50DB4b2563218512e021fBdd0BeD6b57E" - }, - "bsctestnet": { - "merkleRootMultisigIsmFactory": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", - "messageIdMultisigIsmFactory": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", - "aggregationIsmFactory": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", - "aggregationHookFactory": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", - "routingIsmFactory": "0xea12ECFD1f241da323e93F12b4ed936403990190", - "proxyAdmin": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", - "storageGasOracle": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", - "interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", - "aggregationHook": "0x3d675bB93250Ab7603F40cbb9194bae210784627", - "protocolFee": "0x3eF0a63B8976b838704Bcc93C78C56b6653E5a39", - "mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", - "merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", - "validatorAnnounce": "0xf09701B0a93210113D175461b6135a96773B5465", - "fallbackRoutingHook": "0x2670ED2EC08cAd135307556685a96bD4c16b007b" + "proxyAdmin": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", + "routingIsmFactory": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "storageGasOracle": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", + "testRecipient": "0x44a7e1d76fD8AfA244AdE7278336E3D5C658D398", + "testTokenRecipient": "0x9CC10c844B3Bbae2444E39991aB027C4A05D1F2e", + "validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a" }, "goerli": { + "aggregationHook": "0x2dF77b3efe9B8f9aEDf7bFC86f40B048178d8116", + "aggregationHookFactory": "0x6bc243963f80AEa80948e8538bB114d4122DD9c5", + "aggregationIsmFactory": "0x8a176773d54292123d271FA0B9C7C8Def4c3a31b", + "fallbackRoutingHook": "0xd9E546CBB9577dC6346EdB40b24E86aE52487ab8", + "interchainGasPaymaster": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", + "mailbox": "0x49cfd6Ef774AcAb14814D699e3F7eE36Fdfba932", "merkleRootMultisigIsmFactory": "0x8e43aCfb338B137A3befd9b92BfD84E128adE0B8", + "merkleTreeHook": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", "messageIdMultisigIsmFactory": "0xDdB54502A8e2a31C48148C62A8a9E83a693d6173", - "aggregationIsmFactory": "0x8a176773d54292123d271FA0B9C7C8Def4c3a31b", - "aggregationHookFactory": "0x6bc243963f80AEa80948e8538bB114d4122DD9c5", - "routingIsmFactory": "0xd16c3f34d6A2e62185aC61f76F83D3AA1E969018", + "protocolFee": "0x9293B8dAcA7933765de499C992B0Fa86Bb104b0f", "proxyAdmin": "0x0EdB3604D230963ecE9d83963164CFe2fDef576B", + "routingIsmFactory": "0xeB998dC788E2c1e772d198d32e50890544776e75", "storageGasOracle": "0xeC34c715ee6d050b2172E8aF650Db779561266C1", - "interchainGasPaymaster": "0x0cD26594ea6c6526927C0F5225AC09F6288e7140", - "aggregationHook": "0x2dF77b3efe9B8f9aEDf7bFC86f40B048178d8116", - "protocolFee": "0x9293B8dAcA7933765de499C992B0Fa86Bb104b0f", - "merkleTreeHook": "0x28c294C61D3dE053462d2Cfa5d5f8c8D70605A59", - "mailbox": "0x49cfd6Ef774AcAb14814D699e3F7eE36Fdfba932", - "validatorAnnounce": "0x3c182AD9cA8A71bc107Ef440C2667E8360e1158E", - "fallbackRoutingHook": "0xd9E546CBB9577dC6346EdB40b24E86aE52487ab8" - }, - "moonbasealpha": { - "merkleRootMultisigIsmFactory": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", - "messageIdMultisigIsmFactory": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", - "aggregationIsmFactory": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", - "aggregationHookFactory": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", - "routingIsmFactory": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", - "proxyAdmin": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", - "mailbox": "0x76189acFA212298d7022624a4633411eE0d2f26F", - "merkleTreeHook": "0x155B1CD2f7Cbc58d403B9BE341FaB6CD77425175", - "storageGasOracle": "0x62fA20dE68Dbe425f0bc474b12235a4F8449E608", - "interchainGasPaymaster": "0x92F05669A354a032A84FcfABfD13beE1aBc5bFd0", - "aggregationHook": "0xaA9d918C49Cea0D2a877252aFb7976B6e3A48623", - "protocolFee": "0xe2A73F106902983452713F24Bd019F6eb8712986", - "validatorAnnounce": "0x07543860AE9E72aBcF2Bae9827b23621A64Fa416", - "fallbackRoutingHook": "0xf666A33C451E8371907aD22dd545E1678fCa1582" + "testRecipient": "0x4fC0Ac163eFFEb7890937cB89275B2C231880F22", + "testTokenRecipient": "0xd8958706B33E20C88679a22203F0AFa6158c834d", + "validatorAnnounce": "0x3c182AD9cA8A71bc107Ef440C2667E8360e1158E" }, "mumbai": { - "merkleRootMultisigIsmFactory": "0xda0780ed3eE577EfE0B856E00f983bF231603307", - "messageIdMultisigIsmFactory": "0x23c2483ab814177bA79DCDCb5dFA1B105387AAB1", - "aggregationIsmFactory": "0x54b0d9AB6a99E9C9425D20fa4D9eE9dbf067e886", + "aggregationHook": "0xD546273418733AcEC8c7A67EfB881c9Ea83851bf", "aggregationHookFactory": "0x54CA9De95B37365909364672D363D2ecFC4e1Af4", - "routingIsmFactory": "0x276C07098879f44F6C4a6ab91B6AAca6a56AD4B1", + "aggregationIsmFactory": "0x54b0d9AB6a99E9C9425D20fa4D9eE9dbf067e886", + "fallbackRoutingHook": "0x31191BA83143b4745745389fEe64990c65F36829", + "interchainGasPaymaster": "0x8aB67CAF605c6ee83cbFeFb0D8d67FDd3BF7B591", + "mailbox": "0x2d1889fe5B092CD988972261434F7E5f26041115", + "merkleRootMultisigIsmFactory": "0xda0780ed3eE577EfE0B856E00f983bF231603307", "merkleTreeHook": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", + "messageIdMultisigIsmFactory": "0x23c2483ab814177bA79DCDCb5dFA1B105387AAB1", + "protocolFee": "0x244d1F7e30Be144A87602905baBF86630e8f39DC", "proxyAdmin": "0xa99aD6B1c10E92DB8d3510f1865A6d2Ab43EAd58", + "routingIsmFactory": "0x832Ea28749C93C05E5AaF8207E4e61Bd56aE3877", "storageGasOracle": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", - "interchainGasPaymaster": "0x8aB67CAF605c6ee83cbFeFb0D8d67FDd3BF7B591", - "aggregationHook": "0xD546273418733AcEC8c7A67EfB881c9Ea83851bf", - "protocolFee": "0x244d1F7e30Be144A87602905baBF86630e8f39DC", - "mailbox": "0x2d1889fe5B092CD988972261434F7E5f26041115", - "validatorAnnounce": "0x99303EFF09332cDd93E8BC8b2F07b2416e4501e5", - "fallbackRoutingHook": "0x31191BA83143b4745745389fEe64990c65F36829" + "testRecipient": "0xF45A4D54223DA32bf7b5D43a9a460Ef3C94C713B", + "testTokenRecipient": "0x57d098e6952B6C1c85Ce0B68C9Deada3dCf7D05A", + "validatorAnnounce": "0x99303EFF09332cDd93E8BC8b2F07b2416e4501e5" + }, + "optimismgoerli": { + "aggregationHook": "0x1C8A2588b8038BF9B7b1b60dD0EdF5b995A45599", + "aggregationHookFactory": "0x00cE81F7B02e0673815a8b0A54e62AeabDE78685", + "aggregationIsmFactory": "0xf666A33C451E8371907aD22dd545E1678fCa1582", + "fallbackRoutingHook": "0xc775c748F8c9F5443151Fd989e8B61375657474d", + "interchainGasPaymaster": "0x02A7661273528EfF3d78CBE7CbD1a717b28B89fC", + "mailbox": "0xB5f021728Ea6223E3948Db2da61d612307945eA2", + "merkleRootMultisigIsmFactory": "0xAbC25d7daDD748948F5cC912A807b0f8FcBb56a9", + "merkleTreeHook": "0xFEe074B31B5B259eB3109737bE13D39B853b47b9", + "messageIdMultisigIsmFactory": "0x7868B6026E36C4b6E2ca6a0CaBDb1A6D0CcC443B", + "protocolFee": "0x962e30F6A3ECDA85c7fa1FcF38cD04efA991Ee20", + "proxyAdmin": "0x800b4be4Dc91E56DE934D9f16888d113eFf89Ebb", + "routingIsmFactory": "0xce8E9D701A1DFfe672c1d8dB20De2B3fa6F4437D", + "storageGasOracle": "0x4927C33299091033D935C15DE6b6073164e99BE0", + "testRecipient": "0x518eA1802407b4b5AAF3aA92c1A803FfbA9FB7fe", + "testTokenRecipient": "0xB9E45eA920DE14e95A16Ed5e1275F893552f2e32", + "validatorAnnounce": "0x24D31e12E4d3bc2C46C994FcE0c828b218A1aeAb" + }, + "plumetestnet": { + "aggregationHook": "0x31dF0EEE7Dc7565665468698a0da221225619a1B", + "aggregationHookFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "aggregationIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", + "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", + "interchainSecurityModule": "0x7B40deb01A127E3A5eECdbCDF263e41899a90078", + "mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "merkleRootMultisigIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "messageIdMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "pausableHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", + "protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962", + "proxyAdmin": "0x589C201a07c26b4725A4A829d772f24423da480B", + "routingIsmFactory": "0x54148470292C24345fb828B003461a9444414517", + "storageGasOracle": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "testRecipient": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", + "validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9" + }, + "polygonzkevmtestnet": { + "aggregationHook": "0x0Fd2C6F0Ad45e766660b9fDebCF36a2AD69536D1", + "aggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "aggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "fallbackRoutingHook": "0xBF2C366530C1269d531707154948494D3fF4AcA7", + "interchainGasPaymaster": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", + "mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", + "merkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "merkleTreeHook": "0x68311418D79fE8d96599384ED767d225635d88a8", + "messageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "protocolFee": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "proxyAdmin": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", + "routingIsmFactory": "0xc08675806BA844467E559E45E4bB59e66778bDcd", + "storageGasOracle": "0x3707bc8C7342aA6f693bCe1Bd7671Fca146F7F0A", + "testRecipient": "0x11918DC33E067C5DA83EEF58E50F856398b8Df4C", + "testTokenRecipient": "0x04438ef7622f5412f82915F59caD4f704C61eA48", + "validatorAnnounce": "0x7914A3349107A7295Bbf2374db5A973d73D1b324" + }, + "scrollsepolia": { + "aggregationHook": "0x7b63Aa270335F8896717c2A809205F4b650E4268", + "aggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "aggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "fallbackRoutingHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96", + "interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", + "merkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0", + "messageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "protocolFee": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", + "proxyAdmin": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", + "routingIsmFactory": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", + "storageGasOracle": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", + "testRecipient": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", + "testTokenRecipient": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", + "validatorAnnounce": "0x527768930D889662Fe7ACF64294871e86e4C2381" + }, + "sepolia": { + "aggregationHook": "0xe3147d5618f5e2e100690B50ec923009a4cde14A", + "aggregationHookFactory": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", + "aggregationIsmFactory": "0xC83e12EF2627ACE445C298e6eC418684918a6002", + "fallbackRoutingHook": "0x17Dc724B7a2F09141C13b8AC33B396073785c2BC", + "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", + "interchainSecurityModule": "0x958124472b14B7940Ed5317C44a2508791dB1d48", + "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", + "merkleRootMultisigIsmFactory": "0x0a71AcC99967829eE305a285750017C4916Ca269", + "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", + "messageIdMultisigIsmFactory": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", + "pausableHook": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd", + "protocolFee": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5", + "proxyAdmin": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", + "routingIsmFactory": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", + "storageGasOracle": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", + "testRecipient": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07", + "testTokenRecipient": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A", + "validatorAnnounce": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9" } } diff --git a/typescript/sdk/src/consts/igp.ts b/typescript/sdk/src/consts/igp.ts new file mode 100644 index 0000000000..913dc50c73 --- /dev/null +++ b/typescript/sdk/src/consts/igp.ts @@ -0,0 +1 @@ +export const TOKEN_EXCHANGE_RATE_EXPONENT = 10; diff --git a/typescript/sdk/src/consts/mailbox.ts b/typescript/sdk/src/consts/mailbox.ts new file mode 100644 index 0000000000..b1e3d5be48 --- /dev/null +++ b/typescript/sdk/src/consts/mailbox.ts @@ -0,0 +1,2 @@ +// Pairs well with /utils/src/messages.ts#formatMessage +export const MAILBOX_VERSION = 3; diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index 7cda9b5e23..22059db955 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -2,24 +2,35 @@ import { MultisigConfig } from '../ism/types'; import { ChainMap } from '../types'; export const defaultMultisigConfigs: ChainMap = { - // ----------------- Mainnets ----------------- - celo: { + alfajores: { threshold: 2, validators: [ - '0x63478422679303c3e4fc611b771fa4a707ef7f4a', - '0x622e43baf06ad808ca8399360d9a2d9a1a12688b', // dsrv - '0xf2c1e3888eb618f1f1a071ef3618f134715a9a49', // everstake + '0x2233a5ce12f814bd64c9cdd73410bb8693124d40', + '0xba279f965489d90f90490e3c49e860e0b43c2ae6', + '0x86485dcec5f7bb8478dd251676372d054dea6653', ], }, - ethereum: { + + arbitrum: { threshold: 3, validators: [ - '0x03c842db86a6a3e524d4a6615390c1ea8e2b9541', - '0x94438a7de38d4548ae54df5c6010c4ebc5239eae', // dsrv + '0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1', + '0xec68258a7c882ac2fc46b81ce80380054ffb4ef2', // dsrv '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime - '0xce327111035dd38698c92c3778884dbbb0ca8103', // everstake + '0x092e1c19da58e87ea65198785ee83867fe4bb418', // everstake + '0xc2d68e109a7e80e12098d50ac4ef9fa7b3061684', // staked + ], + }, + + arbitrumgoerli: { + threshold: 2, + validators: [ + '0x071c8d135845ae5a2cb73f98d681d519014c0a8b', + '0x1bcf03360989f15cbeb174c188288f2c6d2760d7', + '0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d', ], }, + avalanche: { threshold: 2, validators: [ @@ -28,128 +39,151 @@ export const defaultMultisigConfigs: ChainMap = { '0x716a1d4d3166c6151b05ce0450e0d77d94588eac', // everstake ], }, - polygon: { + + base: { threshold: 2, validators: [ - '0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac', - '0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916', - '0x722aa4d45387009684582bca8281440d16b8b40f', // everstake + '0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9', + '0x64889519ad3ffc8f3ae436fcd63efc6b853fd63f', // staked + '0x41188cb5a5493a961c467ba38a3f8b1f1d35ee63', // everstake + '0xcff391b4e516452d424db66beb9052b041a9ed79', // dsrv + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime ], }, + bsc: { threshold: 2, validators: [ '0x570af9b7b36568c8877eebba6c6727aa9dab7268', - '0x8292b1a53907ece0f76af8a50724e9492bcdc8a3', // bsc + '0x8292b1a53907ece0f76af8a50724e9492bcdc8a3', // dsrv '0xeaf5cf9100f36a4baeea779f8745dda86159103c', // everstake - ], - }, - arbitrum: { - threshold: 3, - validators: [ - '0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1', - '0xec68258a7c882ac2fc46b81ce80380054ffb4ef2', // dsrv '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime - '0x092e1c19da58e87ea65198785ee83867fe4bb418', // everstake ], }, - optimism: { + + bsctestnet: { threshold: 2, validators: [ - '0x20349eadc6c72e94ce38268b96692b1a5c20de4f', - '0x5b7d47b76c69740462432f6a5a0ca5005e014157', // dsrv - '0x22b1ad4322cdb5f2c76ebf4e5a93803d480fcf0d', // everstake + '0x242d8a855a8c932dec51f7999ae7d1e48b10c95e', + '0xf620f5e3d25a3ae848fec74bccae5de3edcd8796', + '0x1f030345963c54ff8229720dd3a711c15c554aeb', ], }, - moonbeam: { + + celo: { threshold: 2, validators: [ - '0x2225e2f4e9221049456da93b71d2de41f3b6b2a8', - '0x645428d198d2e76cbd9c1647f5c80740bb750b97', // dsrv - '0xaed886392df07897743d8e272d438f00c4c9a2ae', // everstake + '0x63478422679303c3e4fc611b771fa4a707ef7f4a', + '0x622e43baf06ad808ca8399360d9a2d9a1a12688b', // dsrv + '0xf2c1e3888eb618f1f1a071ef3618f134715a9a49', // everstake + '0x46ecbc794574727abb8f97f01dacd9db6135f47a', // staked + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime ], }, - gnosis: { + + chiado: { threshold: 2, validators: [ - '0xd4df66a859585678f2ea8357161d896be19cc1ca', - '0x19fb7e04a1be6b39b6966a0b0c60b929a93ed672', // dsrv - '0xdb96116d13a2fadde9742d7cc88474a5ed39a03a', // everstake + '0x06c3757a4b7a912828e523bb8a5f980ddc297356', + '0x0874967a145d70b799ebe9ed861ab7c93faef95a', + '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', ], }, - base: { - threshold: 2, + + eclipsetestnet: { + threshold: 1, + validators: ['0xf344f34abca9a444545b5295066348a0ae22dda3'], + }, + + ethereum: { + threshold: 3, validators: [ - '0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9', - '0x4512985a574cb127b2af2d4bb676876ce804e3f8', - '0x41188cb5a5493a961c467ba38a3f8b1f1d35ee63', // everstake + '0x03c842db86a6a3e524d4a6615390c1ea8e2b9541', + '0x94438a7de38d4548ae54df5c6010c4ebc5239eae', // dsrv + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime + '0xce327111035dd38698c92c3778884dbbb0ca8103', // everstake + '0xb2f5a6a6e6046e2ede213617e989329666a6c4bc', // staked ], }, - scroll: { + + fuji: { threshold: 2, validators: [ - '0xad557170a9f2f21c35e03de07cb30dcbcc3dff63', - '0xb37fe43a9f47b7024c2d5ae22526cc66b5261533', - '0x276de8e2b88e659c4e5ad30d62d9de42c3da3403', // everstake + '0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e', + '0x895ae30bc83ff1493b9cf7781b0b813d23659857', + '0x43e915573d9f1383cbf482049e4a012290759e7f', ], }, - polygonzkevm: { + + gnosis: { threshold: 2, validators: [ - '0x86f2a44592bb98da766e880cfd70d3bbb295e61a', - '0xc84076030bdabaabb9e61161d833dd84b700afda', - '0x57231619fea13d85270ca6943298046c75a6dd01', // everstake + '0xd4df66a859585678f2ea8357161d896be19cc1ca', + '0x19fb7e04a1be6b39b6966a0b0c60b929a93ed672', // dsrv + '0xdb96116d13a2fadde9742d7cc88474a5ed39a03a', // everstake + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime ], }, - solana: { + + goerli: { threshold: 2, validators: [ - '0x3cd1a081f38874bbb075bf10b62adcb858db864c', // abacus - '0x2b0c45f6111ae1c1684d4287792e3bd6ebd1abcc', // ZKV - '0x7b9ec253a8ba38994457eb9dbe386938d545351a', // everstake + '0x05a9b5efe9f61f9142453d8e9f61565f333c6768', + '0x43a96c7dfbd8187c95013d6ee8665650cbdb2673', + '0x7940a12c050e24e1839c21ecb12f65afd84e8c5b', ], }, - // ----------------- Testnets ----------------- - alfajores: { + + inevm: { threshold: 2, validators: [ - '0x2233a5ce12f814bd64c9cdd73410bb8693124d40', - '0xba279f965489d90f90490e3c49e860e0b43c2ae6', - '0x86485dcec5f7bb8478dd251676372d054dea6653', + '0xf9e35ee88e4448a3673b4676a4e153e3584a08eb', + '0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2', + '0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3', ], }, - basegoerli: { + + injective: { threshold: 2, validators: [ - '0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03', - '0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071', - '0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25', + '0xbfb8911b72cfb138c7ce517c57d9c691535dc517', + '0x6faa139c33a7e6f53cb101f6b2ae392298283ed2', + '0x0115e3a66820fb99da30d30e2ce52a453ba99d92', ], }, - fuji: { + + lineagoerli: { threshold: 2, validators: [ - '0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e', - '0x895ae30bc83ff1493b9cf7781b0b813d23659857', - '0x43e915573d9f1383cbf482049e4a012290759e7f', + '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', + '0x4a5d7085ca93c22fbc994dd97857c98fcc745674', + '0x8327779c3c31fa1ffc7f0c9ffae33e4d804bbd8f', ], }, - chiado: { - threshold: 2, + + mantapacific: { + threshold: 5, validators: [ - '0x06c3757a4b7a912828e523bb8a5f980ddc297356', - '0x0874967a145d70b799ebe9ed861ab7c93faef95a', - '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', + '0x8e668c97ad76d0e28375275c41ece4972ab8a5bc', //abacusworks + '0x521a3e6bf8d24809fde1c1fd3494a859a16f132c', //cosmostation + '0x14025fe092f5f8a401dd9819704d9072196d2125', //p2p + '0x25b9a0961c51e74fd83295293bc029131bf1e05a', //neutron + '0xa0eE95e280D46C14921e524B075d0C341e7ad1C8', //cosmos spaces + '0xcc9a0b6de7fe314bd99223687d784730a75bb957', //dsrv + '0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf', //sg-1 ], }, - lineagoerli: { + + moonbeam: { threshold: 2, validators: [ - '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', - '0x4a5d7085ca93c22fbc994dd97857c98fcc745674', - '0x8327779c3c31fa1ffc7f0c9ffae33e4d804bbd8f', + '0x2225e2f4e9221049456da93b71d2de41f3b6b2a8', + '0x645428d198d2e76cbd9c1647f5c80740bb750b97', // dsrv + '0xaed886392df07897743d8e272d438f00c4c9a2ae', // everstake + '0xcf0bb43255849cb3709a96ee166e5c3ce4adc7f9', // staked ], }, + mumbai: { threshold: 2, validators: [ @@ -158,71 +192,101 @@ export const defaultMultisigConfigs: ChainMap = { '0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5', ], }, - bsctestnet: { + + neutron: { + threshold: 4, + validators: [ + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0xb65438a014fb05fbadcfe35bc6e25d372b6ba460', // cosmostation + '0x42fa752defe92459370a052b6387a87f7de9b80c', // p2p + '0xc79503a3e3011535a9c60f6d21f76f59823a38bd', // neutron + '0x47aa126e05933b95c5eb90b26e6b668d84f4b25a', // dsrv + '0x54b2cca5091b098a1a993dec03c4d1ee9af65999', // cosmos spaces + '0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf', // sg-1 + ], + }, + + optimism: { threshold: 2, validators: [ - '0x242d8a855a8c932dec51f7999ae7d1e48b10c95e', - '0xf620f5e3d25a3ae848fec74bccae5de3edcd8796', - '0x1f030345963c54ff8229720dd3a711c15c554aeb', + '0x20349eadc6c72e94ce38268b96692b1a5c20de4f', + '0x5b7d47b76c69740462432f6a5a0ca5005e014157', // dsrv + '0x22b1ad4322cdb5f2c76ebf4e5a93803d480fcf0d', // everstake + '0x9636fbe90b6816438327b0fbde435aa3c8eeda15', // staked + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime ], }, - goerli: { + + optimismgoerli: { threshold: 2, validators: [ - '0x05a9b5efe9f61f9142453d8e9f61565f333c6768', - '0x43a96c7dfbd8187c95013d6ee8665650cbdb2673', - '0x7940a12c050e24e1839c21ecb12f65afd84e8c5b', + '0x79e58546e2faca865c6732ad5f6c4951051c4d67', + '0x7bbfe1bb7146aad7df309c637987d856179ebbc1', + '0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40', ], }, - scrollsepolia: { + + plumetestnet: { + threshold: 1, + validators: ['0xe765a214849f3ecdf00793b97d00422f2d408ea6'], + }, + + polygon: { threshold: 2, validators: [ - '0xbe18dbd758afb367180260b524e6d4bcd1cb6d05', - '0x9a11ed23ae962974018ab45bc133caabff7b3271', - '0x7867bea3c9761fe64e6d124b171f91fd5dd79644', + '0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac', + '0x008f24cbb1cc30ad0f19f2516ca75730e37efb5f', // dsrv + '0x722aa4d45387009684582bca8281440d16b8b40f', // everstake + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime ], }, - sepolia: { + + polygonzkevm: { threshold: 2, validators: [ - '0xb22b65f202558adf86a8bb2847b76ae1036686a5', - '0x469f0940684d147defc44f3647146cb90dd0bc8e', - '0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83', + '0x86f2a44592bb98da766e880cfd70d3bbb295e61a', + '0x865818fe1db986036d5fd0466dcd462562436d1a', // dsrv + '0x57231619fea13d85270ca6943298046c75a6dd01', // everstake ], }, - moonbasealpha: { + + polygonzkevmtestnet: { threshold: 2, validators: [ - '0x521877064bd7ac7500d300f162c8c47c256a2f9c', - '0xbc1c70f58ae0459d4b8a013245420a893837d568', - '0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c', + '0x3f06b725bc9648917eb11c414e9f8d76fd959550', + '0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636', + '0xd476548222f43206d0abaa30e46e28670aa7859c', ], }, - optimismgoerli: { + + scroll: { threshold: 2, validators: [ - '0x79e58546e2faca865c6732ad5f6c4951051c4d67', - '0x7bbfe1bb7146aad7df309c637987d856179ebbc1', - '0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40', + '0xad557170a9f2f21c35e03de07cb30dcbcc3dff63', + '0x37148DE77D9FA915e6F0A9B54bCdF5e6f53ca511', // staked + '0x276de8e2b88e659c4e5ad30d62d9de42c3da3403', // everstake + '0xbac4ac39f1d8b5ef15f26fdb1294a7c9aba3f948', // dsrv ], }, - arbitrumgoerli: { + + scrollsepolia: { threshold: 2, validators: [ - '0x071c8d135845ae5a2cb73f98d681d519014c0a8b', - '0x1bcf03360989f15cbeb174c188288f2c6d2760d7', - '0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d', + '0xbe18dbd758afb367180260b524e6d4bcd1cb6d05', + '0x9a11ed23ae962974018ab45bc133caabff7b3271', + '0x7867bea3c9761fe64e6d124b171f91fd5dd79644', ], }, - polygonzkevmtestnet: { + sepolia: { threshold: 2, validators: [ - '0x3f06b725bc9648917eb11c414e9f8d76fd959550', - '0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636', - '0xd476548222f43206d0abaa30e46e28670aa7859c', + '0xb22b65f202558adf86a8bb2847b76ae1036686a5', + '0x469f0940684d147defc44f3647146cb90dd0bc8e', + '0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83', ], }, + solanadevnet: { threshold: 2, validators: [ @@ -231,4 +295,18 @@ export const defaultMultisigConfigs: ChainMap = { '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', ], }, + + solanatestnet: { + threshold: 1, + validators: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'], + }, + + viction: { + threshold: 2, + validators: [ + '0x4E53dA92cD5Bf0a032b6B4614b986926456756A7', // blockpi + '0xa3f93fe365bf99f431d8fde740b140615e24f99b', // rockx + '0x1f87c368f8e05a85ef9126d984a980a20930cb9c', + ], + }, }; diff --git a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts index 44d2e1d1d0..8bcc44d20f 100644 --- a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts +++ b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts @@ -111,8 +111,10 @@ describe('core', async () => { await deployer.deploy(updatedConfig); - // one aggregation ISM deploy and one set ISM transaction per chain - const numTransactions = 2 * TestChains.length; + // 3x1 for aggregation ISM deploy + // 3x1 for setting ISM transaction for mailbox + // 3x1 for setting ISM transaction for test recipient + const numTransactions = 3 * TestChains.length; const nonceAfter = await signer.getTransactionCount(); expect(nonceAfter).to.equal(nonceBefore + numTransactions); }); diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 00f3f91138..b373874340 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -1,22 +1,27 @@ import { ethers } from 'ethers'; +import type { TransactionReceipt as ViemTxReceipt } from 'viem'; import { Mailbox__factory } from '@hyperlane-xyz/core'; import { Address, AddressBytes32, + ProtocolType, messageId, + objFilter, objMap, parseMessage, pollAsync, } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp'; +import { chainMetadata } from '../consts/chainMetadata'; import { HyperlaneEnvironment, hyperlaneEnvironments, } from '../consts/environments'; import { appFromAddressesMapHelper } from '../contracts/contracts'; import { HyperlaneAddressesMap } from '../contracts/types'; +import { OwnableConfig } from '../deploy/types'; import { MultiProvider } from '../providers/MultiProvider'; import { RouterConfig } from '../router/types'; import { ChainMap, ChainName } from '../types'; @@ -49,14 +54,20 @@ export class HyperlaneCore extends HyperlaneApp { } getRouterConfig = ( - owners: Address | ChainMap
, - ): ChainMap => - objMap(this.contractsMap, (chain, contracts) => { - return { - mailbox: contracts.mailbox.address, - owner: typeof owners === 'string' ? owners : owners[chain], - }; - }); + owners: Address | ChainMap, + ): ChainMap => { + // get config + const config = objMap(this.contractsMap, (chain, contracts) => ({ + mailbox: contracts.mailbox.address, + owner: typeof owners === 'string' ? owners : owners[chain].owner, + })); + // filter for EVM chains + return objFilter( + config, + (chainName, _): _ is RouterConfig => + chainMetadata[chainName].protocol === ProtocolType.Ethereum, + ); + }; quoteGasPayment = ( origin: ChainName, @@ -119,7 +130,7 @@ export class HyperlaneCore extends HyperlaneApp { } waitForMessageProcessing( - sourceTx: ethers.ContractReceipt, + sourceTx: ethers.ContractReceipt | ViemTxReceipt, ): Promise { const messages = HyperlaneCore.getDispatchedMessages(sourceTx); return Promise.all(messages.map((msg) => this.waitForProcessReceipt(msg))); @@ -127,7 +138,7 @@ export class HyperlaneCore extends HyperlaneApp { // TODO consider renaming this, all the waitForMessage* methods are confusing async waitForMessageProcessed( - sourceTx: ethers.ContractReceipt, + sourceTx: ethers.ContractReceipt | ViemTxReceipt, delay?: number, maxAttempts?: number, ): Promise { @@ -146,12 +157,14 @@ export class HyperlaneCore extends HyperlaneApp { } // Redundant with static method but keeping for backwards compatibility - getDispatchedMessages(sourceTx: ethers.ContractReceipt): DispatchedMessage[] { + getDispatchedMessages( + sourceTx: ethers.ContractReceipt | ViemTxReceipt, + ): DispatchedMessage[] { return HyperlaneCore.getDispatchedMessages(sourceTx); } static getDispatchedMessages( - sourceTx: ethers.ContractReceipt, + sourceTx: ethers.ContractReceipt | ViemTxReceipt, ): DispatchedMessage[] { const mailbox = Mailbox__factory.createInterface(); const dispatchLogs = sourceTx.logs diff --git a/typescript/sdk/src/core/HyperlaneCoreChecker.ts b/typescript/sdk/src/core/HyperlaneCoreChecker.ts index 580f732eb7..b1e3fabd67 100644 --- a/typescript/sdk/src/core/HyperlaneCoreChecker.ts +++ b/typescript/sdk/src/core/HyperlaneCoreChecker.ts @@ -5,11 +5,8 @@ import { Address, assert, eqAddress } from '@hyperlane-xyz/utils'; import { BytecodeHash } from '../consts/bytecode'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker'; import { proxyImplementation } from '../deploy/proxy'; -import { - HyperlaneIsmFactory, - collectValidators, - moduleMatchesConfig, -} from '../ism/HyperlaneIsmFactory'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory'; +import { collectValidators, moduleMatchesConfig } from '../ism/utils'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; @@ -150,15 +147,8 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< ); } - await this.checkBytecode( - chain, - 'Mailbox proxy', - contracts.mailbox.address, - [ - BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH, - BytecodeHash.OPT_PROXY_ADMIN_BYTECODE_HASH, - ], - ); + await this.checkProxy(chain, 'Mailbox proxy', contracts.mailbox.address); + await this.checkBytecode( chain, 'ProxyAdmin', diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index a1ee914563..41bc9fef92 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -1,20 +1,28 @@ import debug from 'debug'; -import { Mailbox, ValidatorAnnounce } from '@hyperlane-xyz/core'; +import { + IPostDispatchHook, + Mailbox, + TestRecipient, + ValidatorAnnounce, +} from '@hyperlane-xyz/core'; import { Address } from '@hyperlane-xyz/utils'; import { HyperlaneContracts } from '../contracts/types'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier'; import { HyperlaneHookDeployer } from '../hook/HyperlaneHookDeployer'; import { HookConfig } from '../hook/types'; -import { - HyperlaneIsmFactory, - moduleMatchesConfig, -} from '../ism/HyperlaneIsmFactory'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory'; import { IsmConfig } from '../ism/types'; +import { moduleMatchesConfig } from '../ism/utils'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; +import { + TestRecipientConfig, + TestRecipientDeployer, +} from './TestRecipientDeployer'; import { CoreAddresses, CoreFactories, coreFactories } from './contracts'; import { CoreConfig } from './types'; @@ -22,22 +30,29 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< CoreConfig, CoreFactories > { - startingBlockNumbers: ChainMap = {}; hookDeployer: HyperlaneHookDeployer; + testRecipient: TestRecipientDeployer; constructor( multiProvider: MultiProvider, readonly ismFactory: HyperlaneIsmFactory, + contractVerifier?: ContractVerifier, ) { super(multiProvider, coreFactories, { logger: debug('hyperlane:CoreDeployer'), chainTimeoutMs: 1000 * 60 * 10, // 10 minutes ismFactory, + contractVerifier, }); this.hookDeployer = new HyperlaneHookDeployer( multiProvider, {}, ismFactory, + contractVerifier, + ); + this.testRecipient = new TestRecipientDeployer( + multiProvider, + contractVerifier, ); } @@ -69,8 +84,13 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< ); if (!matches) { this.logger('Deploying default ISM'); - defaultIsm = await this.deployIsm(chain, config.defaultIsm); + defaultIsm = await this.deployIsm( + chain, + config.defaultIsm, + mailbox.address, + ); } + this.cachedAddresses[chain].interchainSecurityModule = defaultIsm; const hookAddresses = { mailbox: mailbox.address, proxyAdmin }; @@ -96,24 +116,33 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< mailbox.initialize( config.owner, defaultIsm, - defaultHook, - requiredHook, + defaultHook.address, + requiredHook.address, this.multiProvider.getTransactionOverrides(chain), ), ); } catch (e: any) { - if (!e.message.includes('already initialized')) { + if ( + !e.message.includes('already initialized') && + // Some RPC providers dont return the revert reason (nor allow ethers to parse it), so we have to check the message + !e.message.includes('Reverted 0x08c379a') && + // Handle situation where the gas estimation fails on the call function, + // then the real error reason is not available in `e.message`, but rather in `e.error.reason` + !e.error?.reason?.includes('already initialized') + ) { throw e; } this.logger('Mailbox already initialized'); + const overrides = this.multiProvider.getTransactionOverrides(chain); await this.configureHook( chain, mailbox, defaultHook, (_mailbox) => _mailbox.defaultHook(), - (_mailbox, _hook) => _mailbox.populateTransaction.setDefaultHook(_hook), + (_mailbox, _hook) => + _mailbox.populateTransaction.setDefaultHook(_hook, { ...overrides }), ); await this.configureHook( @@ -122,7 +151,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< requiredHook, (_mailbox) => _mailbox.requiredHook(), (_mailbox, _hook) => - _mailbox.populateTransaction.setRequiredHook(_hook), + _mailbox.populateTransaction.setRequiredHook(_hook, { ...overrides }), ); await this.configureIsm( @@ -154,7 +183,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< chain: ChainName, config: HookConfig, coreAddresses: Partial, - ): Promise
{ + ): Promise { const hooks = await this.hookDeployer.deployContracts( chain, config, @@ -165,15 +194,38 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< this.hookDeployer.deployedContracts[chain], this.hookDeployer.verificationInputs[chain], ); - return hooks[config.type].address; + return hooks[config.type]; } - async deployIsm(chain: ChainName, config: IsmConfig): Promise
{ - const ism = await this.ismFactory.deploy(chain, config); + async deployIsm( + chain: ChainName, + config: IsmConfig, + mailbox: Address, + ): Promise
{ + const ism = await this.ismFactory.deploy({ + destination: chain, + config, + mailbox, + }); this.addDeployedContracts(chain, this.ismFactory.deployedIsms[chain]); return ism.address; } + async deployTestRecipient( + chain: ChainName, + interchainSecurityModule: Address, + ): Promise { + const config: TestRecipientConfig = { + interchainSecurityModule: interchainSecurityModule, + }; + const testRecipient = await this.testRecipient.deployContracts( + chain, + config, + ); + this.addDeployedContracts(chain, testRecipient); + return testRecipient.testRecipient; + } + async deployContracts( chain: ChainName, config: CoreConfig, @@ -187,32 +239,36 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< const mailbox = await this.deployMailbox(chain, config, proxyAdmin.address); - // TODO: remove once agents fetch deployedBlock from mailbox - const deployedBlock = await mailbox.deployedBlock(); - this.startingBlockNumbers[chain] = deployedBlock.toNumber(); - const validatorAnnounce = await this.deployValidatorAnnounce( chain, mailbox.address, ); - let proxyOwner: string; + const testRecipient = await this.deployTestRecipient( + chain, + this.cachedAddresses[chain].interchainSecurityModule, + ); + if (config.upgrade) { const timelockController = await this.deployTimelock( chain, config.upgrade.timelock, ); - proxyOwner = timelockController.address; - } else { - proxyOwner = config.owner; + config.ownerOverrides = { + ...config.ownerOverrides, + proxyAdmin: timelockController.address, + }; } - await this.transferOwnershipOfContracts(chain, proxyOwner, { proxyAdmin }); - - return { + const contracts = { mailbox, proxyAdmin, validatorAnnounce, + testRecipient, }; + + await this.transferOwnershipOfContracts(chain, config, contracts); + + return contracts; } } diff --git a/typescript/cli/src/deploy/TestRecipientDeployer.ts b/typescript/sdk/src/core/TestRecipientDeployer.ts similarity index 60% rename from typescript/cli/src/deploy/TestRecipientDeployer.ts rename to typescript/sdk/src/core/TestRecipientDeployer.ts index a62505bdd9..90f7c12bb5 100644 --- a/typescript/cli/src/deploy/TestRecipientDeployer.ts +++ b/typescript/sdk/src/core/TestRecipientDeployer.ts @@ -1,13 +1,13 @@ import debug from 'debug'; import { TestRecipient, TestRecipient__factory } from '@hyperlane-xyz/core'; -import { - ChainName, - HyperlaneDeployer, - MultiProvider, -} from '@hyperlane-xyz/sdk'; import { Address, eqAddress } from '@hyperlane-xyz/utils'; +import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier'; +import { MultiProvider } from '../providers/MultiProvider'; +import { ChainName } from '../types'; + export type TestRecipientConfig = { interchainSecurityModule: Address; }; @@ -29,9 +29,13 @@ export class TestRecipientDeployer extends HyperlaneDeployer< TestRecipientConfig, typeof testRecipientFactories > { - constructor(multiProvider: MultiProvider) { + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { super(multiProvider, testRecipientFactories, { logger: debug('hyperlane:TestRecipientDeployer'), + contractVerifier, }); } @@ -39,7 +43,28 @@ export class TestRecipientDeployer extends HyperlaneDeployer< chain: ChainName, config: TestRecipientConfig, ): Promise { - const testRecipient = await this.deployContract(chain, 'testRecipient', []); + const predeployed = this.readCache( + chain, + this.factories['testRecipient'], + 'testRecipient', + ); + let usePreviousDeployment = false; + if ( + predeployed && + eqAddress( + await predeployed.owner(), + await this.multiProvider.getSignerAddress(chain), + ) + ) { + usePreviousDeployment = true; + } + const testRecipient = await this.deployContract( + chain, + 'testRecipient', + [], + undefined, + usePreviousDeployment, + ); try { this.logger(`Checking ISM ${chain}`); const ism = await testRecipient.interchainSecurityModule(); @@ -51,7 +76,11 @@ export class TestRecipientDeployer extends HyperlaneDeployer< const tx = testRecipient.setInterchainSecurityModule( config.interchainSecurityModule, ); - await this.multiProvider.handleTx(chain, tx); + await this.runIfOwner( + chain, + testRecipient, + async () => await this.multiProvider.handleTx(chain, tx), + ); } } catch (error) { this.logger(`Failed to check/update ISM for ${chain}: ${error}`); diff --git a/typescript/sdk/src/core/types.ts b/typescript/sdk/src/core/types.ts index f8da0c8b9d..f8bc7a1aec 100644 --- a/typescript/sdk/src/core/types.ts +++ b/typescript/sdk/src/core/types.ts @@ -2,17 +2,17 @@ import type { Mailbox } from '@hyperlane-xyz/core'; import type { Address, ParsedMessage } from '@hyperlane-xyz/utils'; import type { UpgradeConfig } from '../deploy/proxy'; -import type { CheckerViolation } from '../deploy/types'; +import type { CheckerViolation, OwnableConfig } from '../deploy/types'; import { HookConfig } from '../hook/types'; import type { IsmConfig } from '../ism/types'; import type { ChainName } from '../types'; -export type CoreConfig = { +import { CoreFactories } from './contracts'; + +export type CoreConfig = OwnableConfig & { defaultIsm: IsmConfig; defaultHook: HookConfig; requiredHook: HookConfig; - owner: Address; - ownerOverrides?: Record; remove?: boolean; upgrade?: UpgradeConfig; }; diff --git a/typescript/sdk/src/deploy/HyperlaneAppChecker.ts b/typescript/sdk/src/deploy/HyperlaneAppChecker.ts index f8d5418e05..3f703ef74c 100644 --- a/typescript/sdk/src/deploy/HyperlaneAppChecker.ts +++ b/typescript/sdk/src/deploy/HyperlaneAppChecker.ts @@ -11,6 +11,7 @@ import { } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp'; +import { BytecodeHash } from '../consts/bytecode'; import { filterOwnableContracts } from '../contracts/contracts'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; @@ -186,6 +187,18 @@ export abstract class HyperlaneAppChecker< } } + protected async checkProxy( + chain: ChainName, + name: string, + address: string, + ): Promise { + return this.checkBytecode(chain, name, address, [ + BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH, + BytecodeHash.TRANSPARENT_PROXY_4_9_3_BYTECODE_HASH, + BytecodeHash.OPT_TRANSPARENT_PROXY_BYTECODE_HASH, + ]); + } + async ownables(chain: ChainName): Promise<{ [key: string]: Ownable }> { const contracts = this.app.getContracts(chain); return filterOwnableContracts(contracts); diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index fb3cc16e7f..c0370701ff 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -2,6 +2,8 @@ import { Debugger, debug } from 'debug'; import { Contract, PopulatedTransaction, ethers } from 'ethers'; import { + IPostDispatchHook, + IPostDispatchHook__factory, ITransparentUpgradeableProxy, MailboxClient, Ownable, @@ -11,6 +13,7 @@ import { TimelockController__factory, TransparentUpgradeableProxy__factory, } from '@hyperlane-xyz/core'; +import SdkBuildArtifact from '@hyperlane-xyz/core/buildArtifact.json'; import { Address, ProtocolType, @@ -24,11 +27,9 @@ import { HyperlaneContractsMap, HyperlaneFactories, } from '../contracts/types'; -import { - HyperlaneIsmFactory, - moduleMatchesConfig, -} from '../ism/HyperlaneIsmFactory'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory'; import { IsmConfig } from '../ism/types'; +import { moduleMatchesConfig } from '../ism/utils'; import { MultiProvider } from '../providers/MultiProvider'; import { MailboxClientConfig } from '../router/types'; import { ChainMap, ChainName } from '../types'; @@ -40,7 +41,9 @@ import { proxyConstructorArgs, proxyImplementation, } from './proxy'; -import { ContractVerificationInput } from './verify/types'; +import { OwnableConfig } from './types'; +import { ContractVerifier } from './verify/ContractVerifier'; +import { ContractVerificationInput, ExplorerLicenseType } from './verify/types'; import { buildVerificationInput, getContractVerificationInput, @@ -50,10 +53,11 @@ export interface DeployerOptions { logger?: Debugger; chainTimeoutMs?: number; ismFactory?: HyperlaneIsmFactory; + contractVerifier?: ContractVerifier; } export abstract class HyperlaneDeployer< - Config, + Config extends object, Factories extends HyperlaneFactories, > { public verificationInputs: ChainMap = {}; @@ -67,11 +71,20 @@ export abstract class HyperlaneDeployer< constructor( protected readonly multiProvider: MultiProvider, protected readonly factories: Factories, - protected readonly options?: DeployerOptions, + protected readonly options: DeployerOptions = {}, protected readonly recoverVerificationInputs = false, ) { this.logger = options?.logger ?? debug('hyperlane:deployer'); this.chainTimeoutMs = options?.chainTimeoutMs ?? 5 * 60 * 1000; // 5 minute timeout per chain + this.options.ismFactory?.setDeployer(this); + + // if none provided, instantiate a default verifier with SDK's included build artifact + this.options.contractVerifier ??= new ContractVerifier( + multiProvider, + {}, + SdkBuildArtifact, + ExplorerLicenseType.MIT, + ); } cacheAddressesMap(addressesMap: HyperlaneAddressesMap): void { @@ -208,7 +221,7 @@ export abstract class HyperlaneDeployer< } } else { const ismFactory = - this.options?.ismFactory ?? + this.options.ismFactory ?? (() => { throw new Error('No ISM factory provided'); })(); @@ -220,7 +233,8 @@ export abstract class HyperlaneDeployer< this.multiProvider, ismFactory.getContracts(chain), ); - targetIsm = (await ismFactory.deploy(chain, config)).address; + targetIsm = (await ismFactory.deploy({ destination: chain, config })) + .address; } if (!matches) { await this.runIfOwner(chain, contract, async () => { @@ -239,27 +253,32 @@ export abstract class HyperlaneDeployer< protected async configureHook( chain: ChainName, contract: C, - targetHook: Address, + targetHook: IPostDispatchHook, getHook: (contract: C) => Promise
, setHook: (contract: C, hook: Address) => Promise, ): Promise { const configuredHook = await getHook(contract); - if (!eqAddress(targetHook, configuredHook)) { - await this.runIfOwner(chain, contract, async () => { + if (!eqAddress(targetHook.address, configuredHook)) { + const result = await this.runIfOwner(chain, contract, async () => { this.logger( - `Set hook on ${chain} to ${targetHook}, currently is ${configuredHook}`, + `Set hook on ${chain} to ${targetHook.address}, currently is ${configuredHook}`, ); await this.multiProvider.sendTransaction( chain, - setHook(contract, targetHook), + setHook(contract, targetHook.address), ); const actualHook = await getHook(contract); - if (!eqAddress(targetHook, actualHook)) { + if (!eqAddress(targetHook.address, actualHook)) { throw new Error( - `Set hook failed on ${chain}, wanted ${targetHook}, got ${actualHook}`, + `Set hook failed on ${chain}, wanted ${targetHook.address}, got ${actualHook}`, ); } + return true; }); + // if the signer is not the owner, saving the hook address in the artifacts for later use for sending test messages, etc + if (!result) { + this.addDeployedContracts(chain, { customHook: targetHook }); + } } } @@ -269,12 +288,14 @@ export abstract class HyperlaneDeployer< config: MailboxClientConfig, ): Promise { this.logger(`Initializing mailbox client (if not already) on ${local}...`); - this.logger(`MailboxClient Config: ${JSON.stringify(config)}`); if (config.hook) { await this.configureHook( local, client, - config.hook, + IPostDispatchHook__factory.connect( + config.hook, + this.multiProvider.getSignerOrProvider(local), + ), (_client) => _client.hook(), (_client, _hook) => _client.populateTransaction.setHook(_hook), ); @@ -294,26 +315,29 @@ export abstract class HyperlaneDeployer< this.logger(`Mailbox client on ${local} initialized...`); } - protected async deployContractFromFactory( + public async deployContractFromFactory( chain: ChainName, factory: F, contractName: string, constructorArgs: Parameters, initializeArgs?: Parameters>['initialize']>, + shouldRecover = true, ): Promise> { - const cachedContract = this.readCache(chain, factory, contractName); - if (cachedContract) { - if (this.recoverVerificationInputs) { - const recoveredInputs = await this.recoverVerificationArtifacts( - chain, - contractName, - cachedContract, - constructorArgs, - initializeArgs, - ); - this.addVerificationArtifacts(chain, recoveredInputs); + if (shouldRecover) { + const cachedContract = this.readCache(chain, factory, contractName); + if (cachedContract) { + if (this.recoverVerificationInputs) { + const recoveredInputs = await this.recoverVerificationArtifacts( + chain, + contractName, + cachedContract, + constructorArgs, + initializeArgs, + ); + this.addVerificationArtifacts(chain, recoveredInputs); + } + return cachedContract; } - return cachedContract; } this.logger(`Deploy ${contractName} on ${chain}`); @@ -337,6 +361,17 @@ export abstract class HyperlaneDeployer< ); this.addVerificationArtifacts(chain, [verificationInput]); + // try verifying contract + try { + await this.options.contractVerifier?.verifyContract( + chain, + verificationInput, + ); + } catch (error) { + // log error but keep deploying, can also verify post-deployment if needed + this.logger(`Error verifying contract: ${error}`); + } + return contract; } @@ -347,6 +382,7 @@ export abstract class HyperlaneDeployer< initializeArgs?: Parameters< Awaited>['initialize'] >, + shouldRecover = true, ): Promise[K]> { const contract = await this.deployContractFromFactory( chain, @@ -354,6 +390,7 @@ export abstract class HyperlaneDeployer< contractName.toString(), constructorArgs, initializeArgs, + shouldRecover, ); this.writeCache(chain, contractName, contract.address); return contract; @@ -603,16 +640,21 @@ export abstract class HyperlaneDeployer< return ret; } - protected async transferOwnershipOfContracts( + protected async transferOwnershipOfContracts( chain: ChainName, - owner: Address, - ownables: { [key: string]: Ownable }, + config: OwnableConfig, + ownables: Partial>, ): Promise { const receipts: ethers.ContractReceipt[] = []; - for (const contractName of Object.keys(ownables)) { - const ownable = ownables[contractName]; - const currentOwner = await ownable.owner(); - if (!eqAddress(currentOwner, owner)) { + for (const [contractName, ownable] of Object.entries( + ownables, + )) { + if (!ownable) { + continue; + } + const current = await ownable.owner(); + const owner = config.ownerOverrides?.[contractName as K] ?? config.owner; + if (!eqAddress(current, owner)) { this.logger( `Transferring ownership of ${contractName} to ${owner} on ${chain}`, ); diff --git a/typescript/sdk/src/deploy/HyperlaneProxyFactoryDeployer.ts b/typescript/sdk/src/deploy/HyperlaneProxyFactoryDeployer.ts index 37a0543a08..bf860a9efd 100644 --- a/typescript/sdk/src/deploy/HyperlaneProxyFactoryDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneProxyFactoryDeployer.ts @@ -10,14 +10,19 @@ import { proxyFactoryFactories, proxyFactoryImplementations, } from './contracts'; +import { ContractVerifier } from './verify/ContractVerifier'; export class HyperlaneProxyFactoryDeployer extends HyperlaneDeployer< {}, ProxyFactoryFactories > { - constructor(multiProvider: MultiProvider) { + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { super(multiProvider, proxyFactoryFactories, { logger: debug('hyperlane:IsmFactoryDeployer'), + contractVerifier, }); } diff --git a/typescript/sdk/src/deploy/contracts.ts b/typescript/sdk/src/deploy/contracts.ts index ad4e42c7c2..542c245c13 100644 --- a/typescript/sdk/src/deploy/contracts.ts +++ b/typescript/sdk/src/deploy/contracts.ts @@ -13,9 +13,6 @@ export const proxyFactoryFactories = { aggregationIsmFactory: new StaticAggregationIsmFactory__factory(), aggregationHookFactory: new StaticAggregationHookFactory__factory(), routingIsmFactory: new DomainRoutingIsmFactory__factory(), - // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895 - // defaultFallbackRoutingIsmFactory: - // new DefaultFallbackRoutingIsmFactory__factory(), }; export type ProxyFactoryFactories = typeof proxyFactoryFactories; @@ -29,5 +26,4 @@ export const proxyFactoryImplementations: ProxyFactoryImplementations = { aggregationIsmFactory: 'StaticAggregationIsm', aggregationHookFactory: 'StaticAggregationHook', routingIsmFactory: 'DomaingRoutingIsm', - // defaultFallbackRoutingIsmFactory: 'DefaultFallbackRoutingIsm', }; diff --git a/typescript/sdk/src/deploy/types.ts b/typescript/sdk/src/deploy/types.ts index 49380f19bf..3d2389858f 100644 --- a/typescript/sdk/src/deploy/types.ts +++ b/typescript/sdk/src/deploy/types.ts @@ -5,9 +5,19 @@ import type { Ownable, TimelockController, } from '@hyperlane-xyz/core'; +import { Address } from '@hyperlane-xyz/utils'; import type { ChainName } from '../types'; +export type OwnableConfig = { + owner: Address; + ownerOverrides?: Partial>; +}; + +export function isOwnableConfig(config: object): config is OwnableConfig { + return 'owner' in config; +} + export interface CheckerViolation { chain: ChainName; type: string; diff --git a/typescript/sdk/src/deploy/verify/ContractVerifier.ts b/typescript/sdk/src/deploy/verify/ContractVerifier.ts index 39246746fc..d958ae6eda 100644 --- a/typescript/sdk/src/deploy/verify/ContractVerifier.ts +++ b/typescript/sdk/src/deploy/verify/ContractVerifier.ts @@ -4,196 +4,241 @@ import { ethers } from 'ethers'; import { sleep, strip0x } from '@hyperlane-xyz/utils'; +import { ExplorerFamily } from '../../metadata/chainMetadataTypes'; import { MultiProvider } from '../../providers/MultiProvider'; import { ChainMap, ChainName } from '../../types'; -import { MultiGeneric } from '../../utils/MultiGeneric'; import { + BuildArtifact, CompilerOptions, ContractVerificationInput, - VerificationInput, + EXPLORER_GET_ACTIONS, + ExplorerApiActions, + ExplorerApiErrors, + FormOptions, } from './types'; -enum ExplorerApiActions { - GETSOURCECODE = 'getsourcecode', - VERIFY_IMPLEMENTATION = 'verifysourcecode', - MARK_PROXY = 'verifyproxycontract', - CHECK_STATUS = 'checkverifystatus', - CHECK_PROXY_STATUS = 'checkproxyverification', -} +export class ContractVerifier { + private logger = debug(`hyperlane:ContractVerifier`); -enum ExplorerApiErrors { - ALREADY_VERIFIED = 'Contract source code already verified', - ALREADY_VERIFIED_ALT = 'Already Verified', - VERIFICATION_PENDING = 'Pending in queue', - PROXY_FAILED = 'A corresponding implementation contract was unfortunately not detected for the proxy address.', - BYTECODE_MISMATCH = 'Fail - Unable to verify. Compiled contract deployment bytecode does NOT match the transaction deployment bytecode.', -} + private contractSourceMap: { [contractName: string]: string } = {}; -export class ContractVerifier extends MultiGeneric { - protected logger: Debugger; + protected readonly standardInputJson: string; + protected readonly compilerOptions: CompilerOptions; constructor( - verificationInputs: ChainMap, protected readonly multiProvider: MultiProvider, protected readonly apiKeys: ChainMap, - protected readonly flattenedSource: string, // flattened source code from eg `hardhat flatten` - protected readonly compilerOptions: CompilerOptions, + buildArtifact: BuildArtifact, + licenseType: CompilerOptions['licenseType'], ) { - super(verificationInputs); - this.logger = debug('hyperlane:ContractVerifier'); - } - - verify(targets = this.chains()): Promise[]> { - return Promise.allSettled( - targets.map((chain) => this.verifyChain(chain, this.get(chain))), - ); - } + // Extract the standard input json and compiler version from the build artifact + this.standardInputJson = JSON.stringify(buildArtifact.input); + const compilerversion = `v${buildArtifact.solcLongVersion}`; - async verifyChain( - chain: ChainName, - inputs: VerificationInput, - ): Promise { - this.logger(`Verifying ${chain}...`); - for (const input of inputs) { - await this.verifyContract(chain, input); + // double check compiler version matches expected format + const versionRegex = /v(\d.\d.\d+)\+commit.\w+/; + const matches = versionRegex.exec(compilerversion); + if (!matches) { + throw new Error(`Invalid compiler version ${compilerversion}`); } + + // set compiler options + // only license type is configurable, empty if not provided + this.compilerOptions = { + codeformat: 'solidity-standard-json-input', + compilerversion, + licenseType, + }; + + // process input to create mapping of contract names to source names + // this is required to construct the fully qualified contract name + const contractRegex = /contract\s+([A-Z][a-zA-Z0-9]*)/g; + Object.entries(buildArtifact.input.sources).forEach( + ([sourceName, { content }]) => { + const matches = content.matchAll(contractRegex); + for (const match of matches) { + const contractName = match[1]; + if (contractName) { + this.contractSourceMap[contractName] = sourceName; + } + } + }, + ); } private async submitForm( chain: ChainName, action: ExplorerApiActions, - options?: Record, + verificationLogger: Debugger, + options?: FormOptions, ): Promise { - const apiUrl = new URL(this.multiProvider.getExplorerApiUrl(chain)); - const isGetRequest = - action === ExplorerApiActions.CHECK_STATUS || - action === ExplorerApiActions.CHECK_PROXY_STATUS || - action === ExplorerApiActions.GETSOURCECODE; - const params = new URLSearchParams({ - apikey: this.apiKeys[chain], - module: 'contract', - action, - ...options, - }); - - let response: Response; + const { apiUrl, family } = this.multiProvider.getExplorerApi(chain); + const params = new URLSearchParams(); + params.set('module', 'contract'); + params.set('action', action); + + // no need to provide every argument for every request + for (const [key, value] of Object.entries(options ?? {})) { + params.set(key, value); + } + + // only include apikey if provided & not blockscout + if (family !== ExplorerFamily.Blockscout && this.apiKeys[chain]) { + params.set('apikey', this.apiKeys[chain]); + } + + const url = new URL(apiUrl); + const isGetRequest = EXPLORER_GET_ACTIONS.includes(action); if (isGetRequest) { - response = await fetch(`${apiUrl}?${params}`); + url.search = params.toString(); + } else if (family === ExplorerFamily.Blockscout) { + // Blockscout requires module and action to be query params + url.searchParams.set('module', 'contract'); + url.searchParams.set('action', action); + } + + let response; + if (isGetRequest) { + response = await fetch(url.toString(), { + method: 'GET', + }); } else { - response = await fetch(apiUrl, { + response = await fetch(url.toString(), { method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params, }); } - let result; - let responseText; - try { - responseText = await response.text(); - result = JSON.parse(responseText); - } catch (e) { - this.logger(`Failed to parse response from ${responseText}`, e); - } - if (result.message === 'NOTOK') { + const responseText = await response.text(); + const result = JSON.parse(responseText); + + if (result.message !== 'OK') { + let errorMessage; + switch (result.result) { case ExplorerApiErrors.VERIFICATION_PENDING: await sleep(5000); // wait 5 seconds - return this.submitForm(chain, action, options); + return this.submitForm(chain, action, verificationLogger, options); case ExplorerApiErrors.ALREADY_VERIFIED: case ExplorerApiErrors.ALREADY_VERIFIED_ALT: return; case ExplorerApiErrors.PROXY_FAILED: - this.logger(`Proxy verification failed for, try manually?`); - return; + errorMessage = 'Proxy verification failed, try manually?'; + break; case ExplorerApiErrors.BYTECODE_MISMATCH: - this.logger( - `Compiled bytecode does not match deployed bytecode, check constructor arguments?`, - ); - return; + errorMessage = + 'Compiled bytecode does not match deployed bytecode, check constructor arguments?'; + break; default: - this.logger( - `Verification failed for some unknown reason on ${chain}`, - result, - ); - throw new Error(`Verification failed: ${result.result}`); + errorMessage = `Verification failed. ${ + result.result ?? response.statusText + }`; + break; + } + + if (errorMessage) { + verificationLogger(errorMessage); + throw new Error(`[${chain}] ${errorMessage}`); } } + if (result.result === ExplorerApiErrors.UNKNOWN_UID) { + await sleep(1000); // wait 1 second + return this.submitForm(chain, action, verificationLogger, options); + } + + if (result.result === ExplorerApiErrors.UNABLE_TO_VERIFY) { + const errorMessage = `Verification failed. ${ + result.result ?? response.statusText + }`; + verificationLogger(errorMessage); + throw new Error(`[${chain}] ${errorMessage}`); + } + return result.result; } private async isAlreadyVerified( chain: ChainName, input: ContractVerificationInput, - ) { + verificationLogger: Debugger, + ): Promise { try { const result = await this.submitForm( chain, ExplorerApiActions.GETSOURCECODE, + verificationLogger, { - ...this.compilerOptions, address: input.address, }, ); - return result[0].SourceCode !== ''; + return !!result[0]?.SourceCode; } catch (error) { - this.logger(`Error checking if contract is already verified: ${error}`); + verificationLogger( + `Error checking if contract is already verified: ${error}`, + ); return false; } } - async verifyProxy( + private async verifyProxy( chain: ChainName, input: ContractVerificationInput, + verificationLogger: Debugger, ): Promise { - if (input.isProxy) { - try { - const proxyGuid = await this.submitForm( - chain, - ExplorerApiActions.MARK_PROXY, - { - address: input.address, - }, - ); - - const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl( - chain, - input.address, - ); - - // poll for verified proxy status - if (proxyGuid) { - await this.submitForm(chain, ExplorerApiActions.CHECK_PROXY_STATUS, { - guid: proxyGuid, - }); - this.logger( - `Successfully verified proxy ${addressUrl}#readProxyContract`, - ); - } - } catch (error) { - console.error( - `Verification of proxy at ${input.address} failed on ${chain}`, - ); - throw error; - } + if (!input.isProxy) return; + + try { + const proxyGuid = await this.submitForm( + chain, + ExplorerApiActions.MARK_PROXY, + verificationLogger, + { address: input.address }, + ); + if (!proxyGuid) return; + + await this.submitForm( + chain, + ExplorerApiActions.CHECK_PROXY_STATUS, + verificationLogger, + { + guid: proxyGuid, + }, + ); + const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl( + chain, + input.address, + ); + verificationLogger( + `Successfully verified proxy ${addressUrl}#readProxyContract`, + ); + } catch (error) { + verificationLogger( + `Verification of proxy at ${input.address} failed: ${error}`, + ); + throw error; } } - async verifyImplementation( + private async verifyImplementation( chain: ChainName, input: ContractVerificationInput, + verificationLogger: Debugger, ): Promise { - this.logger( - `Verifying ${input.name} implementation at ${input.address} on ${chain}`, - ); + verificationLogger(`Verifying implementation at ${input.address}`); + + const sourceName = this.contractSourceMap[input.name]; + if (!sourceName) { + const errorMessage = `Contract '${input.name}' not found in provided build artifact`; + verificationLogger(errorMessage); + throw new Error(`[${chain}] ${errorMessage}`); + } const data = { - sourceCode: this.flattenedSource, - contractname: input.name, + sourceCode: this.standardInputJson, + contractname: `${sourceName}:${input.name}`, contractaddress: input.address, // TYPO IS ENFORCED BY API constructorArguements: strip0x(input.constructorArguments ?? ''), @@ -203,55 +248,64 @@ export class ContractVerifier extends MultiGeneric { const guid = await this.submitForm( chain, ExplorerApiActions.VERIFY_IMPLEMENTATION, + verificationLogger, data, ); + if (!guid) return; + await this.submitForm( + chain, + ExplorerApiActions.CHECK_STATUS, + verificationLogger, + { guid }, + ); const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl( chain, input.address, ); - - // poll for verified status - if (guid) { - try { - await this.submitForm(chain, ExplorerApiActions.CHECK_STATUS, { guid }); - this.logger(`Successfully verified ${addressUrl}#code`); - } catch (error) { - console.error( - `Verifying implementation at ${input.address} failed on ${chain}`, - ); - throw error; - } - } + verificationLogger(`Successfully verified ${addressUrl}#code`); } async verifyContract( chain: ChainName, input: ContractVerificationInput, + logger = this.logger, ): Promise { - if (input.address === ethers.constants.AddressZero) { + const verificationLogger = logger.extend(`${chain}:${input.name}`); + + const explorerApi = this.multiProvider.tryGetExplorerApi(chain); + if (!explorerApi) { + verificationLogger('No explorer API set, skipping'); return; } + if (!explorerApi.family) { + verificationLogger(`No explorer family set, skipping`); + return; + } + + if (explorerApi.family === ExplorerFamily.Other) { + verificationLogger(`Unsupported explorer family, skipping`); + return; + } + + if (input.address === ethers.constants.AddressZero) return; if (Array.isArray(input.constructorArguments)) { - this.logger('Constructor arguments in legacy format, skipping'); + verificationLogger('Constructor arguments in legacy format, skipping'); return; } - if (await this.isAlreadyVerified(chain, input)) { + if (await this.isAlreadyVerified(chain, input, verificationLogger)) { const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl( chain, input.address, ); - this.logger( - `Contract ${input.name} already verified on ${chain} at ${addressUrl}#code`, - ); - // There is a rate limit of 5 requests per second - await sleep(200); + verificationLogger(`Contract already verified at ${addressUrl}#code`); + await sleep(200); // There is a rate limit of 5 requests per second return; - } else { - await this.verifyImplementation(chain, input); } - await this.verifyProxy(chain, input); + + await this.verifyImplementation(chain, input, verificationLogger); + await this.verifyProxy(chain, input, verificationLogger); } } diff --git a/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts b/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts new file mode 100644 index 0000000000..22cb991abb --- /dev/null +++ b/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts @@ -0,0 +1,50 @@ +import { debug } from 'debug'; + +import { ExplorerFamily } from '../../metadata/chainMetadataTypes'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { ChainMap } from '../../types'; +import { MultiGeneric } from '../../utils/MultiGeneric'; + +import { ContractVerifier } from './ContractVerifier'; +import { BuildArtifact, CompilerOptions, VerificationInput } from './types'; + +export class PostDeploymentContractVerifier extends MultiGeneric { + protected logger = debug('hyperlane:PostDeploymentContractVerifier'); + protected readonly contractVerifier: ContractVerifier; + + constructor( + verificationInputs: ChainMap, + protected readonly multiProvider: MultiProvider, + apiKeys: ChainMap, + buildArtifact: BuildArtifact, + licenseType: CompilerOptions['licenseType'], + ) { + super(verificationInputs); + this.contractVerifier = new ContractVerifier( + multiProvider, + apiKeys, + buildArtifact, + licenseType, + ); + } + + verify(targets = this.chains()): Promise[]> { + return Promise.allSettled( + targets.map(async (chain) => { + // can check explorer family here to avoid doing these checks for each input in verifier + const { family } = this.multiProvider.getExplorerApi(chain); + if (family === ExplorerFamily.Other) { + this.logger( + `Skipping verification for ${chain} due to unsupported explorer family.`, + ); + return; + } + + this.logger(`Verifying ${chain}...`); + for (const input of this.get(chain)) { + await this.contractVerifier.verifyContract(chain, input, this.logger); + } + }), + ); + } +} diff --git a/typescript/sdk/src/deploy/verify/types.ts b/typescript/sdk/src/deploy/verify/types.ts index 6a1dcd2bb6..442ad5e370 100644 --- a/typescript/sdk/src/deploy/verify/types.ts +++ b/typescript/sdk/src/deploy/verify/types.ts @@ -7,9 +7,89 @@ export type ContractVerificationInput = { export type VerificationInput = ContractVerificationInput[]; +export type SolidityStandardJsonInput = { + sources: { + [sourceName: string]: { + content: string; + }; + }; +}; + +export type BuildArtifact = { + input: SolidityStandardJsonInput; + solcLongVersion: string; +}; + +// see https://etherscan.io/contract-license-types +export enum ExplorerLicenseType { + NO_LICENSE = '1', + UNLICENSED = '2', + MIT = '3', + GPL2 = '4', + GPL3 = '5', + LGPL2 = '6', + LGPL3 = '7', + BSD2 = '8', + BSD3 = '9', + MPL2 = '10', + OSL3 = '11', + APACHE2 = '12', + AGPL3 = '13', + BSL = '14', +} + export type CompilerOptions = { - codeformat: 'solidity-single-file' | 'solidity-standard-json-input'; //solidity-single-file (default) or solidity-standard-json-input (for std-input-json-format support + codeformat: 'solidity-standard-json-input'; compilerversion: string; // see https://etherscan.io/solcversions for list of support versions - optimizationUsed: '0' | '1'; //0 = No Optimization, 1 = Optimization used (applicable when codeformat=solidity-single-file) - runs: string; //set to 200 as default unless otherwise (applicable when codeformat=solidity-single-file) + licenseType?: ExplorerLicenseType; }; + +export enum ExplorerApiActions { + GETSOURCECODE = 'getsourcecode', + VERIFY_IMPLEMENTATION = 'verifysourcecode', + MARK_PROXY = 'verifyproxycontract', + CHECK_STATUS = 'checkverifystatus', + CHECK_PROXY_STATUS = 'checkproxyverification', +} + +export const EXPLORER_GET_ACTIONS = [ + ExplorerApiActions.CHECK_STATUS, + ExplorerApiActions.CHECK_PROXY_STATUS, + ExplorerApiActions.GETSOURCECODE, +]; + +export enum ExplorerApiErrors { + ALREADY_VERIFIED = 'Contract source code already verified', + ALREADY_VERIFIED_ALT = 'Already Verified', + VERIFICATION_PENDING = 'Pending in queue', + PROXY_FAILED = 'A corresponding implementation contract was unfortunately not detected for the proxy address.', + BYTECODE_MISMATCH = 'Fail - Unable to verify. Compiled contract deployment bytecode does NOT match the transaction deployment bytecode.', + UNABLE_TO_VERIFY = 'Fail - Unable to verify', + UNKNOWN_UID = 'Unknown UID', +} + +export type FormOptions = + Action extends ExplorerApiActions.GETSOURCECODE + ? { + address: string; + } + : Action extends ExplorerApiActions.VERIFY_IMPLEMENTATION + ? CompilerOptions & { + contractaddress: string; + sourceCode: string; + contractname: string; + constructorArguements?: string; // TYPO IS ENFORCED BY API + } + : Action extends ExplorerApiActions.MARK_PROXY + ? { + address: string; + } + : Action extends ExplorerApiActions.CHECK_STATUS + ? { + guid: string; + } + : Action extends ExplorerApiActions.CHECK_PROXY_STATUS + ? { + guid: string; + } + : never; diff --git a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts index 2c9467f04b..5245a04356 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts @@ -1,15 +1,15 @@ -import { BigNumber, ethers } from 'ethers'; +import { BigNumber } from 'ethers'; -import { Address, eqAddress } from '@hyperlane-xyz/utils'; +import { eqAddress } from '@hyperlane-xyz/utils'; import { BytecodeHash } from '../consts/bytecode'; +import { chainMetadata } from '../consts/chainMetadata'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker'; import { proxyImplementation } from '../deploy/proxy'; import { ChainName } from '../types'; import { HyperlaneIgp } from './HyperlaneIgp'; import { - GasOracleContractType, IgpBeneficiaryViolation, IgpConfig, IgpGasOraclesViolation, @@ -31,21 +31,11 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< async checkDomainOwnership(chain: ChainName): Promise { const config = this.configMap[chain]; - - const ownableOverrides: Record = { - storageGasOracle: config.oracleKey, - }; - await super.checkOwnership(chain, config.owner, ownableOverrides); + await super.checkOwnership(chain, config.owner, config.ownerOverrides); } async checkBytecodes(chain: ChainName): Promise { const contracts = this.app.getContracts(chain); - await this.checkBytecode( - chain, - 'InterchainGasPaymaster proxy', - contracts.interchainGasPaymaster.address, - [BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH], - ); const implementation = await proxyImplementation( this.multiProvider.getProvider(chain), contracts.interchainGasPaymaster.address, @@ -54,26 +44,23 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< chain, 'InterchainGasPaymaster implementation', implementation, - [BytecodeHash.INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH], - ); - - await this.checkBytecode( - chain, - 'InterchainGasPaymaster proxy', - contracts.interchainGasPaymaster.address, - [BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH], + [ + BytecodeHash.INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH, + BytecodeHash.OPT_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH, + ], (bytecode) => - bytecode - // We persist the block number in the bytecode now too, so we have to strip it + bytecode // We persist the block number in the bytecode now too, so we have to strip it .replaceAll( /(00000000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})81565/g, (match, _offset) => (match.length % 2 === 0 ? '' : '0'), - ) - .replaceAll( - /(0000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})6118123373/g, - (match, _offset) => (match.length % 2 === 0 ? '' : '0'), ), ); + + await this.checkProxy( + chain, + 'InterchainGasPaymaster proxy', + contracts.interchainGasPaymaster.address, + ); } async checkOverheadInterchainGasPaymaster(local: ChainName): Promise { @@ -103,7 +90,9 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< expectedOverhead = 0; } - const remoteId = this.multiProvider.getDomainId(remote); + const remoteId = + chainMetadata[remote]?.domainId ?? + this.multiProvider.getDomainId(remote); const existingOverhead = await defaultIsmIgp.destinationGasLimit( remoteId, 0, @@ -138,17 +127,16 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< expected: {}, }; - // In addition to all remote chains on the app, which are just Ethereum chains, - // also consider what the config says about non-Ethereum chains. - const remotes = new Set([ - ...this.app.remoteChains(local), - ...Object.keys(this.configMap[local].gasOracleType), - ]); + const remotes = new Set( + Object.keys(this.configMap[local].oracleConfig ?? {}), + ); for (const remote of remotes) { - const remoteId = this.multiProvider.getDomainId(remote); + const remoteId = + chainMetadata[remote]?.domainId ?? + this.multiProvider.getDomainId(remote); const destinationGasConfigs = await igp.destinationGasConfigs(remoteId); const actualGasOracle = destinationGasConfigs.gasOracle; - const expectedGasOracle = this.getGasOracleAddress(local, remote); + const expectedGasOracle = coreContracts.storageGasOracle.address; if (!eqAddress(actualGasOracle, expectedGasOracle)) { const remoteChain = remote as ChainName; @@ -175,22 +163,4 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< this.addViolation(violation); } } - - getGasOracleAddress(local: ChainName, remote: ChainName): Address { - const config = this.configMap[local]; - const gasOracleType = config.gasOracleType[remote]; - if (!gasOracleType) { - this.app.logger( - `No gas oracle for local ${local} and remote ${remote}, defaulting to zero address`, - ); - return ethers.constants.AddressZero; - } - const coreContracts = this.app.getContracts(local); - switch (gasOracleType) { - case GasOracleContractType.StorageGasOracle: - return coreContracts.storageGasOracle.address; - default: - throw Error(`Unsupported gas oracle type ${gasOracleType}`); - } - } } diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index f7c09d36d3..fb3f416add 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -7,21 +7,28 @@ import { } from '@hyperlane-xyz/core'; import { eqAddress } from '@hyperlane-xyz/utils'; +import { chainMetadata } from '../consts/chainMetadata'; import { HyperlaneContracts } from '../contracts/types'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainName } from '../types'; import { IgpFactories, igpFactories } from './contracts'; +import { serializeDifference } from './oracle/types'; import { IgpConfig } from './types'; export class HyperlaneIgpDeployer extends HyperlaneDeployer< IgpConfig, IgpFactories > { - constructor(multiProvider: MultiProvider) { + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { super(multiProvider, igpFactories, { logger: debug('hyperlane:IgpDeployer'), + contractVerifier, }); } @@ -31,28 +38,29 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< storageGasOracle: StorageGasOracle, config: IgpConfig, ): Promise { - const owner = config.owner; const beneficiary = config.beneficiary; const igp = await this.deployProxiedContract( chain, 'interchainGasPaymaster', proxyAdmin.address, [], - [owner, beneficiary], + [await this.multiProvider.getSignerAddress(chain), beneficiary], ); const gasParamsToSet: InterchainGasPaymaster.GasParamStruct[] = []; - const remotes = Object.keys(config.gasOracleType); - for (const remote of remotes) { - const remoteId = this.multiProvider.getDomainId(remote); - const newGasOverhead = config.overhead[remote]; + for (const [remote, newGasOverhead] of Object.entries(config.overhead)) { + const remoteId = + chainMetadata[remote]?.domainId ?? + this.multiProvider.getDomainId(remote); const currentGasConfig = await igp.destinationGasConfigs(remoteId); if ( !eqAddress(currentGasConfig.gasOracle, storageGasOracle.address) || !currentGasConfig.gasOverhead.eq(newGasOverhead) ) { - this.logger(`Setting gas params for ${remote} to ${newGasOverhead}`); + this.logger( + `Setting gas params for ${chain} -> ${remote}: gasOverhead = ${newGasOverhead} gasOracle = ${storageGasOracle.address}`, + ); gasParamsToSet.push({ remoteDomain: remoteId, config: { @@ -74,11 +82,58 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< ), ); } + return igp; } - async deployStorageGasOracle(chain: ChainName): Promise { - return this.deployContract(chain, 'storageGasOracle', []); + async deployStorageGasOracle( + chain: ChainName, + config: IgpConfig, + ): Promise { + const gasOracle = await this.deployContract(chain, 'storageGasOracle', []); + + if (!config.oracleConfig) { + this.logger('No oracle config provided, skipping...'); + return gasOracle; + } + + this.logger(`Configuring gas oracle from ${chain}...`); + const configsToSet: Array = []; + + // For each remote, check if the gas oracle has the correct data + for (const [remote, desired] of Object.entries(config.oracleConfig)) { + // check core metadata for non EVMs and fallback to multiprovider for custom EVMs + const remoteDomain = + chainMetadata[remote]?.domainId ?? + this.multiProvider.getDomainId(remote); + + const actual = await gasOracle.remoteGasData(remoteDomain); + + if ( + !actual.gasPrice.eq(desired.gasPrice) || + !actual.tokenExchangeRate.eq(desired.tokenExchangeRate) + ) { + this.logger(`-> ${remote} ${serializeDifference(actual, desired)}`); + configsToSet.push({ + remoteDomain, + ...desired, + }); + } + } + + if (configsToSet.length > 0) { + await this.runIfOwner(chain, gasOracle, async () => + this.multiProvider.handleTx( + chain, + gasOracle.setRemoteGasDataConfigs( + configsToSet, + this.multiProvider.getTransactionOverrides(chain), + ), + ), + ); + } + + return gasOracle; } async deployContracts( @@ -89,27 +144,30 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< // is loaded into the contract cache. const proxyAdmin = await this.deployContract(chain, 'proxyAdmin', []); - const storageGasOracle = await this.deployStorageGasOracle(chain); + const storageGasOracle = await this.deployStorageGasOracle(chain, config); const interchainGasPaymaster = await this.deployInterchainGasPaymaster( chain, proxyAdmin, storageGasOracle, config, ); - await this.transferOwnershipOfContracts(chain, config.owner, { - interchainGasPaymaster, - }); - - // Configure oracle key for StorageGasOracle separately to keep 'hot' - // for updating exchange rates regularly - await this.transferOwnershipOfContracts(chain, config.oracleKey, { - storageGasOracle, - }); - return { + const contracts = { proxyAdmin, storageGasOracle, interchainGasPaymaster, }; + + const ownerConfig = { + ...config, + ownerOverrides: { + ...config.ownerOverrides, + storageGasOracle: config.oracleKey, + }, + }; + + await this.transferOwnershipOfContracts(chain, ownerConfig, contracts); + + return contracts; } } diff --git a/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts new file mode 100644 index 0000000000..3cf0dcc1e0 --- /dev/null +++ b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts @@ -0,0 +1,58 @@ +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; + +import { MultiProvider } from '../../providers/MultiProvider'; +import { testIgpConfig } from '../../test/testUtils'; +import { ChainMap } from '../../types'; +import { HyperlaneIgpDeployer } from '../HyperlaneIgpDeployer'; +import { IgpConfig } from '../types'; + +describe('HyperlaneIgpDeployer', () => { + const local = 'test1'; + const remote = 'test2'; + let remoteId: number; + let deployer: HyperlaneIgpDeployer; + let igp: InterchainGasPaymaster; + let multiProvider: MultiProvider; + let testConfig: ChainMap; + + before(async () => { + const [signer] = await ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + remoteId = multiProvider.getDomainId(remote); + deployer = new HyperlaneIgpDeployer(multiProvider); + testConfig = testIgpConfig([local, remote], signer.address); + const contracts = await deployer.deploy(testConfig); + igp = contracts[local].interchainGasPaymaster; + }); + + it('should deploy storage gas oracle with config given', async () => { + // Assert + const deployedConfig = await igp.getExchangeRateAndGasPrice(remoteId); + expect({ + gasPrice: deployedConfig.gasPrice, + tokenExchangeRate: deployedConfig.tokenExchangeRate, + }).to.deep.equal(testConfig[local].oracleConfig![remote]); + }); + + it('should configure new oracle config', async () => { + testConfig[local].oracleConfig![remote] = { + tokenExchangeRate: ethers.utils.parseUnits('2', 'gwei'), + gasPrice: ethers.utils.parseUnits('3', 'gwei'), + }; + + const localContracts = await deployer.deployContracts( + local, + testConfig[local], + ); + igp = localContracts.interchainGasPaymaster; + + const modifiedConfig = await igp.getExchangeRateAndGasPrice(remoteId); + expect({ + gasPrice: modifiedConfig.gasPrice, + tokenExchangeRate: modifiedConfig.tokenExchangeRate, + }).to.deep.equal(testConfig[local].oracleConfig![remote]); + }); +}); diff --git a/typescript/sdk/src/gas/oracle/types.ts b/typescript/sdk/src/gas/oracle/types.ts new file mode 100644 index 0000000000..238ba019b5 --- /dev/null +++ b/typescript/sdk/src/gas/oracle/types.ts @@ -0,0 +1,60 @@ +import { ethers } from 'ethers'; + +import { StorageGasOracle } from '@hyperlane-xyz/core'; + +import { TOKEN_EXCHANGE_RATE_EXPONENT } from '../../consts/igp'; + +export enum GasOracleContractType { + StorageGasOracle = 'StorageGasOracle', +} + +// Gas data to configure on a single destination chain. +export type StorageGasOracleConfig = Pick< + StorageGasOracle.RemoteGasDataConfigStructOutput, + 'gasPrice' | 'tokenExchangeRate' +>; + +export const formatGasOracleConfig = ( + config: StorageGasOracleConfig, +): { + tokenExchangeRate: string; + gasPrice: string; +} => ({ + tokenExchangeRate: ethers.utils.formatUnits( + config.tokenExchangeRate, + TOKEN_EXCHANGE_RATE_EXPONENT, + ), + gasPrice: ethers.utils.formatUnits(config.gasPrice, 'gwei'), +}); + +const percentDifference = ( + actual: ethers.BigNumber, + expected: ethers.BigNumber, +): ethers.BigNumber => expected.sub(actual).mul(100).div(actual); + +const serializePercentDifference = ( + actual: ethers.BigNumber, + expected: ethers.BigNumber, +): string => { + if (actual.isZero()) { + return 'new'; + } + const diff = percentDifference(actual, expected); + return diff.isNegative() ? `${diff.toString()}%` : `+${diff.toString()}%`; +}; + +export const serializeDifference = ( + actual: StorageGasOracleConfig, + expected: StorageGasOracleConfig, +): string => { + const gasPriceDiff = serializePercentDifference( + actual.gasPrice, + expected.gasPrice, + ); + const tokenExchangeRateDiff = serializePercentDifference( + actual.tokenExchangeRate, + expected.tokenExchangeRate, + ); + const formatted = formatGasOracleConfig(expected); + return `${formatted.tokenExchangeRate} (${tokenExchangeRateDiff}), ${formatted.gasPrice} gwei (${gasPriceDiff})`; +}; diff --git a/typescript/sdk/src/gas/token-prices.test.ts b/typescript/sdk/src/gas/token-prices.test.ts index 82f31c1e69..0751a6d2ed 100644 --- a/typescript/sdk/src/gas/token-prices.test.ts +++ b/typescript/sdk/src/gas/token-prices.test.ts @@ -18,7 +18,11 @@ describe('TokenPriceGetter', () => { mockCoinGecko.setTokenPrice(chainA, priceA); // Destination token mockCoinGecko.setTokenPrice(chainB, priceB); - tokenPriceGetter = new CoinGeckoTokenPriceGetter(mockCoinGecko); + tokenPriceGetter = new CoinGeckoTokenPriceGetter( + mockCoinGecko, + undefined, + 0, + ); }); describe('getTokenPrice', () => { diff --git a/typescript/sdk/src/gas/token-prices.ts b/typescript/sdk/src/gas/token-prices.ts index be525db488..1ef138d3ea 100644 --- a/typescript/sdk/src/gas/token-prices.ts +++ b/typescript/sdk/src/gas/token-prices.ts @@ -1,6 +1,6 @@ import CoinGecko from 'coingecko-api'; -import { warn } from '@hyperlane-xyz/utils'; +import { sleep, warn } from '@hyperlane-xyz/utils'; import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata'; import { CoreChainName, Mainnets } from '../consts/chains'; @@ -70,23 +70,31 @@ class TokenPriceCache { export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { protected coinGecko: CoinGeckoInterface; protected cache: TokenPriceCache; + protected sleepMsBetweenRequests: number; protected metadata: ChainMap; constructor( coinGecko: CoinGeckoInterface, expirySeconds?: number, + sleepMsBetweenRequests = 5000, chainMetadata = defaultChainMetadata, ) { this.coinGecko = coinGecko; this.cache = new TokenPriceCache(expirySeconds); this.metadata = chainMetadata; + this.sleepMsBetweenRequests = sleepMsBetweenRequests; } static withDefaultCoinGecko( expirySeconds?: number, + sleepMsBetweenRequests = 5000, ): CoinGeckoTokenPriceGetter { const coinGecko = new CoinGecko(); - return new CoinGeckoTokenPriceGetter(coinGecko, expirySeconds); + return new CoinGeckoTokenPriceGetter( + coinGecko, + expirySeconds, + sleepMsBetweenRequests, + ); } async getTokenPrice(chain: ChainName): Promise { @@ -136,6 +144,8 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { const ids = chains.map( (chain) => this.metadata[chain].gasCurrencyCoinGeckoId || chain, ); + // Coingecko rate limits, so we are adding this sleep + await sleep(this.sleepMsBetweenRequests); const response = await this.coinGecko.simple.price({ ids, vs_currencies: [currency], diff --git a/typescript/sdk/src/gas/types.ts b/typescript/sdk/src/gas/types.ts index ca0c0485ab..38c6c49707 100644 --- a/typescript/sdk/src/gas/types.ts +++ b/typescript/sdk/src/gas/types.ts @@ -3,19 +3,20 @@ import { BigNumber } from 'ethers'; import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; import type { Address } from '@hyperlane-xyz/utils'; -import type { CheckerViolation } from '../deploy/types'; +import type { CheckerViolation, OwnableConfig } from '../deploy/types'; import { ChainMap } from '../types'; -export enum GasOracleContractType { - StorageGasOracle = 'StorageGasOracle', -} +import { IgpFactories } from './contracts'; +import { GasOracleContractType, StorageGasOracleConfig } from './oracle/types'; -export type IgpConfig = { - owner: Address; +export type IgpConfig = OwnableConfig & { beneficiary: Address; - gasOracleType: ChainMap; oracleKey: Address; overhead: ChainMap; + // TODO: require this + oracleConfig?: ChainMap; + // DEPRECATED + gasOracleType?: ChainMap; }; export enum IgpViolationType { diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index c61f371c51..c87d8777b7 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -7,14 +7,16 @@ import { IL1CrossDomainMessenger__factory, OPStackHook, OPStackIsm, + ProtocolFee, StaticAggregationHook__factory, - StaticProtocolFee, } from '@hyperlane-xyz/core'; import { Address, addressToBytes32 } from '@hyperlane-xyz/utils'; +import { chainMetadata } from '../consts/chainMetadata'; import { HyperlaneContracts } from '../contracts/types'; import { CoreAddresses } from '../core/contracts'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier'; import { HyperlaneIgpDeployer } from '../gas/HyperlaneIgpDeployer'; import { IgpFactories } from '../gas/contracts'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory'; @@ -22,7 +24,7 @@ import { IsmType, OpStackIsmConfig } from '../ism/types'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; -import { HookFactories, hookFactories } from './contracts'; +import { DeployedHook, HookFactories, hookFactories } from './contracts'; import { AggregationHookConfig, DomainRoutingHookConfig, @@ -42,10 +44,15 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< multiProvider: MultiProvider, readonly core: ChainMap>, readonly ismFactory: HyperlaneIsmFactory, - readonly igpDeployer = new HyperlaneIgpDeployer(multiProvider), + contractVerifier?: ContractVerifier, + readonly igpDeployer = new HyperlaneIgpDeployer( + multiProvider, + contractVerifier, + ), ) { super(multiProvider, hookFactories, { logger: debug('hyperlane:HookDeployer'), + contractVerifier, }); } @@ -59,19 +66,23 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< config: HookConfig, coreAddresses = this.core[chain], ): Promise> { - // other simple hooks can go here - let hook; + let hook: DeployedHook; if (config.type === HookType.MERKLE_TREE) { const mailbox = coreAddresses.mailbox; if (!mailbox) { throw new Error(`Mailbox address is required for ${config.type}`); } hook = await this.deployContract(chain, config.type, [mailbox]); - return { [config.type]: hook } as any; } else if (config.type === HookType.INTERCHAIN_GAS_PAYMASTER) { - return this.deployIgp(chain, config, coreAddresses) as any; + const { interchainGasPaymaster } = await this.deployIgp( + chain, + config, + coreAddresses, + ); + hook = interchainGasPaymaster; } else if (config.type === HookType.AGGREGATION) { - return this.deployAggregation(chain, config, coreAddresses); // deploy from factory + hook = (await this.deployAggregation(chain, config, coreAddresses)) + .aggregationHook; // deploy from factory } else if (config.type === HookType.PROTOCOL_FEE) { hook = await this.deployProtocolFee(chain, config); } else if (config.type === HookType.OP_STACK) { @@ -81,8 +92,18 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< config.type === HookType.FALLBACK_ROUTING ) { hook = await this.deployRouting(chain, config, coreAddresses); + } else if (config.type === HookType.PAUSABLE) { + hook = await this.deployContract(chain, config.type, []); + await this.transferOwnershipOfContracts( + chain, + config, + { [HookType.PAUSABLE]: hook }, + ); + } else { + throw new Error(`Unsupported hook config: ${config}`); } - const deployedContracts = { [config.type]: hook } as any; + + const deployedContracts = { [config.type]: hook } as any; // partial this.addDeployedContracts(chain, deployedContracts); return deployedContracts; } @@ -90,8 +111,8 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< async deployProtocolFee( chain: ChainName, config: ProtocolFeeHookConfig, - ): Promise { - this.logger('Deploying StaticProtocolFeeHook for %s', chain); + ): Promise { + this.logger('Deploying ProtocolFeeHook for %s', chain); return this.deployContract(chain, HookType.PROTOCOL_FEE, [ config.maxProtocolFee, config.protocolFee, @@ -147,6 +168,7 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< chain, this.ismFactory.getContracts(chain).aggregationHookFactory, aggregatedHooks, + this.logger, ); hooks[HookType.AGGREGATION] = StaticAggregationHook__factory.connect( address, @@ -182,11 +204,11 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< origin: chain, nativeBridge: l2Messenger, }; - const opstackIsm = (await this.ismFactory.deploy( - config.destinationChain, - ismConfig, - chain, - )) as OPStackIsm; + const opstackIsm = (await this.ismFactory.deploy({ + destination: config.destinationChain, + config: ismConfig, + origin: chain, + })) as OPStackIsm; // deploy opstack hook const hook = await this.deployContract(chain, HookType.OP_STACK, [ mailbox, @@ -266,7 +288,8 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< const routingConfigs: DomainRoutingHook.HookConfigStruct[] = []; for (const [dest, hookConfig] of Object.entries(config.domains)) { - const destDomain = this.multiProvider.getDomainId(dest); + const destDomain = + chainMetadata[dest]?.domainId ?? this.multiProvider.getDomainId(dest); if (typeof hookConfig === 'string') { routingConfigs.push({ destination: destDomain, @@ -285,9 +308,12 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< } } + const overrides = this.multiProvider.getTransactionOverrides(chain); await this.multiProvider.handleTx( chain, - routingHook.setHooks(routingConfigs), + routingHook.setHooks(routingConfigs, { + ...overrides, + }), ); return routingHook; diff --git a/typescript/sdk/src/hook/contracts.ts b/typescript/sdk/src/hook/contracts.ts index aa0651584d..d7fa4995ac 100644 --- a/typescript/sdk/src/hook/contracts.ts +++ b/typescript/sdk/src/hook/contracts.ts @@ -4,20 +4,27 @@ import { InterchainGasPaymaster__factory, MerkleTreeHook__factory, OPStackHook__factory, + PausableHook__factory, + ProtocolFee__factory, StaticAggregationHook__factory, - StaticProtocolFee__factory, } from '@hyperlane-xyz/core'; +import { ValueOf } from '@hyperlane-xyz/utils'; import { HookType } from './types'; export const hookFactories = { [HookType.MERKLE_TREE]: new MerkleTreeHook__factory(), - [HookType.PROTOCOL_FEE]: new StaticProtocolFee__factory(), + [HookType.PROTOCOL_FEE]: new ProtocolFee__factory(), [HookType.INTERCHAIN_GAS_PAYMASTER]: new InterchainGasPaymaster__factory(), // unused [HookType.AGGREGATION]: new StaticAggregationHook__factory(), // unused [HookType.OP_STACK]: new OPStackHook__factory(), [HookType.ROUTING]: new DomainRoutingHook__factory(), [HookType.FALLBACK_ROUTING]: new FallbackDomainRoutingHook__factory(), + [HookType.PAUSABLE]: new PausableHook__factory(), }; export type HookFactories = typeof hookFactories; + +export type DeployedHook = Awaited< + ReturnType['deploy']> +>; diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index a0e60a88b7..01f44cbd04 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -1,7 +1,6 @@ -import { BigNumber } from 'ethers'; - import { Address } from '@hyperlane-xyz/utils'; +import { OwnableConfig } from '../deploy/types'; import { IgpConfig } from '../gas/types'; import { ChainMap, ChainName } from '../types'; @@ -13,6 +12,7 @@ export enum HookType { OP_STACK = 'opStackHook', ROUTING = 'domainRoutingHook', FALLBACK_ROUTING = 'fallbackRoutingHook', + PAUSABLE = 'pausableHook', } export type MerkleTreeHookConfig = { @@ -28,12 +28,15 @@ export type IgpHookConfig = IgpConfig & { type: HookType.INTERCHAIN_GAS_PAYMASTER; }; -export type ProtocolFeeHookConfig = { +export type ProtocolFeeHookConfig = OwnableConfig & { type: HookType.PROTOCOL_FEE; - maxProtocolFee: BigNumber; - protocolFee: BigNumber; + maxProtocolFee: string; + protocolFee: string; beneficiary: Address; - owner: Address; +}; + +export type PausableHookConfig = OwnableConfig & { + type: HookType.PAUSABLE; }; export type OpStackHookConfig = { @@ -42,8 +45,7 @@ export type OpStackHookConfig = { destinationChain: ChainName; }; -type RoutingHookConfig = { - owner: Address; +type RoutingHookConfig = OwnableConfig & { domains: ChainMap; }; @@ -63,4 +65,10 @@ export type HookConfig = | ProtocolFeeHookConfig | OpStackHookConfig | DomainRoutingHookConfig - | FallbackRoutingHookConfig; + | FallbackRoutingHookConfig + | PausableHookConfig; + +export type HooksConfig = { + required: HookConfig; + default: HookConfig; +}; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index cd980c0fbb..4deae7ee17 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -6,7 +6,6 @@ export { BaseSealevelAdapter, MultiProtocolApp, } from './app/MultiProtocolApp'; -export { agentStartBlocks } from './consts/agentStartBlocks'; export { chainIdToMetadata, chainMetadata, @@ -30,6 +29,7 @@ export { hyperlaneContractAddresses, hyperlaneEnvironments, } from './consts/environments'; +export { MAILBOX_VERSION } from './consts/mailbox'; export { defaultMultisigConfigs } from './consts/multisigIsm'; export { SEALEVEL_SPL_NOOP_ADDRESS } from './consts/sealevel'; export { @@ -59,6 +59,10 @@ export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer'; export { MultiProtocolCore } from './core/MultiProtocolCore'; export { TestCoreApp } from './core/TestCoreApp'; export { TestCoreDeployer } from './core/TestCoreDeployer'; +export { + TestRecipientConfig, + TestRecipientDeployer, +} from './core/TestRecipientDeployer'; export { EvmCoreAdapter } from './core/adapters/EvmCoreAdapter'; export { SealevelCoreAdapter } from './core/adapters/SealevelCoreAdapter'; export { ICoreAdapter } from './core/adapters/types'; @@ -78,13 +82,17 @@ export { DeployerOptions, HyperlaneDeployer } from './deploy/HyperlaneDeployer'; export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer'; export { CheckerViolation, + OwnableConfig, OwnerViolation, ViolationType, } from './deploy/types'; export { ContractVerifier } from './deploy/verify/ContractVerifier'; +export { PostDeploymentContractVerifier } from './deploy/verify/PostDeploymentContractVerifier'; export { + BuildArtifact, CompilerOptions, ContractVerificationInput, + ExplorerLicenseType, VerificationInput, } from './deploy/verify/types'; export * as verificationUtils from './deploy/verify/utils'; @@ -100,9 +108,12 @@ export { SealevelOverheadIgpDataSchema, } from './gas/adapters/serialization'; export { IgpFactories, igpFactories } from './gas/contracts'; -export { CoinGeckoTokenPriceGetter } from './gas/token-prices'; export { GasOracleContractType, + StorageGasOracleConfig, +} from './gas/oracle/types'; +export { CoinGeckoTokenPriceGetter } from './gas/token-prices'; +export { IgpBeneficiaryViolation, IgpConfig, IgpGasOraclesViolation, @@ -117,17 +128,18 @@ export { FallbackRoutingHookConfig, HookConfig, HookType, + HooksConfig, IgpHookConfig, MerkleTreeHookConfig, OpStackHookConfig, + PausableHookConfig, ProtocolFeeHookConfig, } from './hook/types'; +export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory'; export { - HyperlaneIsmFactory, - collectValidators, - moduleCanCertainlyVerify, -} from './ism/HyperlaneIsmFactory'; -export { buildMultisigIsmConfigs } from './ism/multisig'; + buildAggregationIsmConfigs, + buildMultisigIsmConfigs, +} from './ism/multisig'; export { AggregationIsmConfig, DeployedIsm, @@ -137,8 +149,10 @@ export { MultisigConfig, MultisigIsmConfig, OpStackIsmConfig, + PausableIsmConfig, RoutingIsmConfig, } from './ism/types'; +export { collectValidators, moduleCanCertainlyVerify } from './ism/utils'; export { ChainMetadataManager, ChainMetadataManagerOptions, @@ -164,6 +178,7 @@ export { buildAgentConfig, } from './metadata/agentConfig'; export { + BlockExplorer, ChainMetadata, ChainMetadataSchema, ChainMetadataSchemaObject, @@ -173,13 +188,19 @@ export { RpcUrlSchema, getChainIdNumber, getDomainId, + getReorgPeriod, isValidChainMetadata, } from './metadata/chainMetadataTypes'; +export { ZHash } from './metadata/customZodTypes'; export { HyperlaneDeploymentArtifacts, HyperlaneDeploymentArtifactsSchema, } from './metadata/deploymentArtifacts'; export { MatchingList } from './metadata/matchingList'; +export { + WarpRouteConfig, + WarpRouteConfigSchema, +} from './metadata/warpRouteConfig'; export { InterchainAccount } from './middleware/account/InterchainAccount'; export { InterchainAccountChecker } from './middleware/account/InterchainAccountChecker'; export { @@ -237,12 +258,26 @@ export { ViemTransaction, ViemTransactionReceipt, } from './providers/ProviderType'; +export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneEtherscanProvider'; +export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider'; +export { + AllProviderMethods, + IProviderMethods, + ProviderMethod, + excludeProviderMethods, +} from './providers/SmartProvider/ProviderMethods'; +export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider'; +export { + ChainMetadataWithRpcConnectionInfo, + ProviderErrorResult, + ProviderPerformResult, + ProviderRetryOptions, + ProviderStatus, + ProviderSuccessResult, + ProviderTimeoutResult, + SmartProviderOptions, +} from './providers/SmartProvider/types'; export { - RetryJsonRpcProvider, - RetryProviderOptions, -} from './providers/RetryProvider'; -export { - DEFAULT_RETRY_OPTIONS, ProviderBuilderFn, ProviderBuilderMap, TypedProviderBuilderFn, @@ -279,7 +314,6 @@ export { GasConfig, GasRouterConfig, MailboxClientConfig, - OwnableConfig, ProxiedFactories, ProxiedRouterConfig, RouterAddress, @@ -288,6 +322,27 @@ export { RouterViolationType, proxiedFactories, } from './router/types'; +export { IToken, TokenArgs, TokenConfigSchema } from './token/IToken'; +export { Token } from './token/Token'; +export { TokenAmount } from './token/TokenAmount'; +export { + HyperlaneTokenConnection, + IbcToHyperlaneTokenConnection, + IbcTokenConnection, + TokenConnection, + TokenConnectionConfigSchema, + TokenConnectionType, +} from './token/TokenConnection'; +export { + PROTOCOL_TO_NATIVE_STANDARD, + TOKEN_COLLATERALIZED_STANDARDS, + TOKEN_COSMWASM_STANDARDS, + TOKEN_HYP_STANDARDS, + TOKEN_MULTI_CHAIN_STANDARDS, + TOKEN_NFT_STANDARDS, + TOKEN_STANDARD_TO_PROTOCOL, + TokenStandard, +} from './token/TokenStandard'; export { CW20Metadata, CwHypCollateralAdapter, @@ -303,11 +358,13 @@ export { } from './token/adapters/CosmosTokenAdapter'; export { EvmHypCollateralAdapter, + EvmHypNativeAdapter, EvmHypSyntheticAdapter, EvmNativeTokenAdapter, EvmTokenAdapter, } from './token/adapters/EvmTokenAdapter'; export { + InterchainGasQuote as AdapterInterchainGasQuote, IHypTokenAdapter, ITokenAdapter, TransferParams, @@ -355,8 +412,8 @@ export { HypERC20Deployer, HypERC721Deployer } from './token/deploy'; export { ChainMap, ChainName, + ChainNameOrId, Connection, - NameOrDomain, TestChainNames, } from './types'; export { MultiGeneric } from './utils/MultiGeneric'; @@ -368,3 +425,12 @@ export { getSealevelAccountDataSchema, } from './utils/sealevelSerialization'; export { chainMetadataToWagmiChain, wagmiChainMetadata } from './utils/wagmi'; +export { WarpCore, WarpCoreOptions } from './warp/WarpCore'; +export { + IgpQuoteConstants, + RouteBlacklist, + WarpCoreConfig, + WarpCoreConfigSchema, + WarpTxCategory, + WarpTypedTransaction, +} from './warp/types'; diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index cdb0ba9edc..79c4b30a5d 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -1,17 +1,17 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; -import { error } from '@hyperlane-xyz/utils'; +import { DomainRoutingIsm } from '@hyperlane-xyz/core'; +import { Address, error } from '@hyperlane-xyz/utils'; import { TestChains } from '../consts/chains'; +import { TestCoreApp } from '../core/TestCoreApp'; +import { TestCoreDeployer } from '../core/TestCoreDeployer'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer'; import { MultiProvider } from '../providers/MultiProvider'; import { randomAddress, randomInt } from '../test/testUtils'; -import { - HyperlaneIsmFactory, - moduleMatchesConfig, -} from './HyperlaneIsmFactory'; +import { HyperlaneIsmFactory } from './HyperlaneIsmFactory'; import { AggregationIsmConfig, IsmConfig, @@ -20,6 +20,7 @@ import { MultisigIsmConfig, RoutingIsmConfig, } from './types'; +import { moduleMatchesConfig } from './utils'; function randomModuleType(): ModuleType { const choices = [ @@ -72,28 +73,51 @@ const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => { }; describe('HyperlaneIsmFactory', async () => { - let factory: HyperlaneIsmFactory; + let ismFactory: HyperlaneIsmFactory; + let coreApp: TestCoreApp; + let multiProvider: MultiProvider; + let ismFactoryDeployer: HyperlaneProxyFactoryDeployer; + let exampleRoutingConfig: RoutingIsmConfig; + let mailboxAddress: Address, newMailboxAddress: Address; const chain = 'test1'; - before(async () => { + beforeEach(async () => { const [signer] = await ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + ismFactory = new HyperlaneIsmFactory( + await ismFactoryDeployer.deploy(multiProvider.mapKnownChains(() => ({}))), + multiProvider, + ); + let coreDeployer = new TestCoreDeployer(multiProvider, ismFactory); + coreApp = await coreDeployer.deployApp(); + mailboxAddress = coreApp.getContracts(chain).mailbox.address; - const multiProvider = MultiProvider.createTestMultiProvider({ signer }); + coreDeployer = new TestCoreDeployer(multiProvider, ismFactory); + coreApp = await coreDeployer.deployApp(); + newMailboxAddress = coreApp.getContracts(chain).mailbox.address; - const deployer = new HyperlaneProxyFactoryDeployer(multiProvider); - const contracts = await deployer.deploy({ [chain]: {} }); - factory = new HyperlaneIsmFactory(contracts, multiProvider); + exampleRoutingConfig = { + type: IsmType.ROUTING, + owner: await multiProvider.getSignerAddress(chain), + domains: Object.fromEntries( + TestChains.filter((c) => c !== 'test1').map((c) => [ + c, + randomMultisigIsmConfig(3, 5), + ]), + ), + }; }); it('deploys a simple ism', async () => { const config = randomMultisigIsmConfig(3, 5); - const ism = await factory.deploy(chain, config); + const ism = await ismFactory.deploy({ destination: chain, config }); const matches = await moduleMatchesConfig( chain, ism.address, config, - factory.multiProvider, - factory.getContracts(chain), + ismFactory.multiProvider, + ismFactory.getContracts(chain), ); expect(matches).to.be.true; }); @@ -103,7 +127,7 @@ describe('HyperlaneIsmFactory', async () => { const config = randomIsmConfig(); let ismAddress: string; try { - const ism = await factory.deploy(chain, config); + const ism = await ismFactory.deploy({ destination: chain, config }); ismAddress = ism.address; } catch (e) { error('Failed to deploy random ism config', e); @@ -116,8 +140,8 @@ describe('HyperlaneIsmFactory', async () => { chain, ismAddress, config, - factory.multiProvider, - factory.getContracts(chain), + ismFactory.multiProvider, + ismFactory.getContracts(chain), ); expect(matches).to.be.true; } catch (e) { @@ -127,4 +151,320 @@ describe('HyperlaneIsmFactory', async () => { } }); } + + for (const type of [IsmType.ROUTING, IsmType.FALLBACK_ROUTING]) { + it(`deploys ${type} routingIsm with correct routes`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + const ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const matches = await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + ); + expect(matches).to.be.true; + }); + + it(`update route in an existing ${type}`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + // changing the type of a domain should enroll the domain + (exampleRoutingConfig.domains['test2'] as MultisigIsmConfig).type = + IsmType.MESSAGE_ID_MULTISIG; + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + matches = + matches && + existingIsm === ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(matches).to.be.true; + }); + + it(`should skip deployment with warning if no chain metadata configured ${type}`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + exampleRoutingConfig.domains['test4'] = { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: [randomAddress()], + }; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + matches = + matches && + existingIsm === ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + + exampleRoutingConfig.domains['test5'] = { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: [randomAddress()], + }; + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + matches = + matches && + existingIsm === ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(matches).to.be.true; + }); + + it(`deletes route in an existing ${type}`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + // deleting the domain should unenroll the domain + delete exampleRoutingConfig.domains['test3']; + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + matches = + matches && + existingIsm == ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(matches).to.be.true; + }); + + it(`deletes route in an existing ${type} even if not in multiprovider`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + const domainsBefore = await (ism as DomainRoutingIsm).domains(); + + // deleting the domain and removing from multiprovider should unenroll the domain + // NB: we'll deploy new multisigIsms for the domains bc of new factories but the routingIsm address should be the same because of existingIsmAddress + delete exampleRoutingConfig.domains['test3']; + multiProvider = multiProvider.intersect(['test1', 'test2']).result; + ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + ismFactory = new HyperlaneIsmFactory( + await ismFactoryDeployer.deploy( + multiProvider.mapKnownChains(() => ({})), + ), + multiProvider, + ); + new TestCoreDeployer(multiProvider, ismFactory); + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + const domainsAfter = await (ism as DomainRoutingIsm).domains(); + + matches = + matches && + existingIsm == ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(domainsBefore.length - 1).to.equal(domainsAfter.length); + expect(matches).to.be.true; + }); + + it(`updates owner in an existing ${type}`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + // change the owner + exampleRoutingConfig.owner = randomAddress(); + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + matches = + matches && + existingIsm == ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(matches).to.be.true; + }); + + it(`no changes to an existing ${type} means no redeployment or updates`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + // using the same config should not change anything + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + matches = + matches && + existingIsm === ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(matches).to.be.true; + }); + + it(`redeploy same config if the deployer doesn't have ownership of ${type}`, async () => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + let matches = true; + exampleRoutingConfig.owner = randomAddress(); + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: mailboxAddress, + }); + matches = + matches && + existingIsm !== ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + mailboxAddress, + )); + expect(matches).to.be.true; + }); + } + + it(`redeploy same config if the mailbox address changes for defaultFallbackRoutingIsm`, async () => { + exampleRoutingConfig.type = IsmType.FALLBACK_ROUTING; + let matches = true; + let ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + mailbox: mailboxAddress, + }); + const existingIsm = ism.address; + ism = await ismFactory.deploy({ + destination: chain, + config: exampleRoutingConfig, + existingIsmAddress: ism.address, + mailbox: newMailboxAddress, + }); + matches = + matches && + existingIsm !== ism.address && + (await moduleMatchesConfig( + chain, + ism.address, + exampleRoutingConfig, + ismFactory.multiProvider, + ismFactory.getContracts(chain), + newMailboxAddress, + )); + expect(matches).to.be.true; + }); }); diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 3d36e8fde0..b51c1131f1 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -1,19 +1,30 @@ -import { debug } from 'debug'; +import debug, { Debugger } from 'debug'; import { ethers } from 'ethers'; import { + DefaultFallbackRoutingIsm, + DefaultFallbackRoutingIsm__factory, + DomainRoutingIsm, DomainRoutingIsm__factory, + IAggregationIsm, IAggregationIsm__factory, IInterchainSecurityModule__factory, + IMultisigIsm, IMultisigIsm__factory, - IRoutingIsm__factory, + IRoutingIsm, OPStackIsm__factory, + PausableIsm__factory, StaticAddressSetFactory, - StaticAggregationIsm__factory, StaticThresholdAddressSetFactory, TestIsm__factory, } from '@hyperlane-xyz/core'; -import { Address, eqAddress, formatMessage, warn } from '@hyperlane-xyz/utils'; +import { + Address, + Domain, + eqAddress, + objFilter, + warn, +} from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp'; import { @@ -21,7 +32,8 @@ import { hyperlaneEnvironments, } from '../consts/environments'; import { appFromAddressesMapHelper } from '../contracts/contracts'; -import { HyperlaneAddressesMap, HyperlaneContracts } from '../contracts/types'; +import { HyperlaneAddressesMap } from '../contracts/types'; +import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer'; import { ProxyFactoryFactories, proxyFactoryFactories, @@ -35,12 +47,11 @@ import { DeployedIsmType, IsmConfig, IsmType, - ModuleType, MultisigIsmConfig, - OpStackIsmConfig, RoutingIsmConfig, - ismTypeToModuleType, + RoutingIsmDelta, } from './types'; +import { routingModuleDelta } from './utils'; export class HyperlaneIsmFactory extends HyperlaneApp { // The shape of this object is `ChainMap
`, @@ -48,6 +59,11 @@ export class HyperlaneIsmFactory extends HyperlaneApp { // TODO: fix this in the next refactoring public deployedIsms: ChainMap = {}; + protected deployer?: HyperlaneDeployer; + setDeployer(deployer: HyperlaneDeployer): void { + this.deployer = deployer; + } + static fromEnvironment( env: Env, multiProvider: MultiProvider, @@ -71,27 +87,32 @@ export class HyperlaneIsmFactory extends HyperlaneApp { ); return new HyperlaneIsmFactory( helper.contractsMap, - helper.multiProvider, + multiProvider, debug('hyperlane:IsmFactoryApp'), ); } - async deploy( - chain: ChainName, - config: C, - origin?: ChainName, - ): Promise { + async deploy(params: { + destination: ChainName; + config: C; + origin?: ChainName; + mailbox?: Address; + existingIsmAddress?: Address; + }): Promise { + const { destination, config, origin, mailbox, existingIsmAddress } = params; if (typeof config === 'string') { // @ts-ignore return IInterchainSecurityModule__factory.connect( config, - this.multiProvider.getSignerOrProvider(chain), + this.multiProvider.getSignerOrProvider(destination), ); } const ismType = config.type; - this.logger( - `Deploying ${ismType} to ${chain} ${ + const logger = this.logger.extend(`${destination}:${ismType}`); + + logger( + `Deploying ${ismType} to ${destination} ${ origin ? `(for verifying ${origin})` : '' }`, ); @@ -100,21 +121,58 @@ export class HyperlaneIsmFactory extends HyperlaneApp { switch (ismType) { case IsmType.MESSAGE_ID_MULTISIG: case IsmType.MERKLE_ROOT_MULTISIG: - contract = await this.deployMultisigIsm(chain, config); + contract = await this.deployMultisigIsm(destination, config, logger); break; case IsmType.ROUTING: - contract = await this.deployRoutingIsm(chain, config); + case IsmType.FALLBACK_ROUTING: + contract = await this.deployRoutingIsm({ + destination, + config, + origin, + mailbox, + existingIsmAddress, + logger, + }); break; case IsmType.AGGREGATION: - contract = await this.deployAggregationIsm(chain, config, origin); + contract = await this.deployAggregationIsm({ + destination, + config, + origin, + mailbox, + logger, + }); break; case IsmType.OP_STACK: - contract = await this.deployOpStackIsm(chain, config); + if (!this.deployer) { + throw new Error(`HyperlaneDeployer must be set to deploy ${ismType}`); + } + contract = await this.deployer?.deployContractFromFactory( + destination, + new OPStackIsm__factory(), + IsmType.OP_STACK, + [config.nativeBridge], + ); + break; + case IsmType.PAUSABLE: + if (!this.deployer) { + throw new Error(`HyperlaneDeployer must be set to deploy ${ismType}`); + } + contract = await this.deployer?.deployContractFromFactory( + destination, + new PausableIsm__factory(), + IsmType.PAUSABLE, + [config.owner], + ); break; case IsmType.TEST_ISM: - contract = await this.multiProvider.handleDeploy( - chain, + if (!this.deployer) { + throw new Error(`HyperlaneDeployer must be set to deploy ${ismType}`); + } + contract = await this.deployer?.deployContractFromFactory( + destination, new TestIsm__factory(), + IsmType.TEST_ISM, [], ); break; @@ -122,125 +180,247 @@ export class HyperlaneIsmFactory extends HyperlaneApp { throw new Error(`Unsupported ISM type ${ismType}`); } - if (!this.deployedIsms[chain]) { - this.deployedIsms[chain] = {}; + if (!this.deployedIsms[destination]) { + this.deployedIsms[destination] = {}; } if (origin) { // if we're deploying network-specific contracts (e.g. ISMs), store them as sub-entry // under that network's key (`origin`) - if (!this.deployedIsms[chain][origin]) { - this.deployedIsms[chain][origin] = {}; + if (!this.deployedIsms[destination][origin]) { + this.deployedIsms[destination][origin] = {}; } - this.deployedIsms[chain][origin][ismType] = contract; + this.deployedIsms[destination][origin][ismType] = contract; } else { // otherwise store the entry directly - this.deployedIsms[chain][ismType] = contract; + this.deployedIsms[destination][ismType] = contract; } return contract; } - private async deployMultisigIsm(chain: ChainName, config: MultisigIsmConfig) { - const signer = this.multiProvider.getSigner(chain); + protected async deployMultisigIsm( + destination: ChainName, + config: MultisigIsmConfig, + logger: Debugger, + ): Promise { + const signer = this.multiProvider.getSigner(destination); const multisigIsmFactory = config.type === IsmType.MERKLE_ROOT_MULTISIG - ? this.getContracts(chain).merkleRootMultisigIsmFactory - : this.getContracts(chain).messageIdMultisigIsmFactory; + ? this.getContracts(destination).merkleRootMultisigIsmFactory + : this.getContracts(destination).messageIdMultisigIsmFactory; const address = await this.deployStaticAddressSet( - chain, + destination, multisigIsmFactory, config.validators, + logger, config.threshold, ); return IMultisigIsm__factory.connect(address, signer); } - private async deployRoutingIsm(chain: ChainName, config: RoutingIsmConfig) { - const signer = this.multiProvider.getSigner(chain); - const routingIsmFactory = this.getContracts(chain).routingIsmFactory; - // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895 - // config.defaultFallback - // ? this.getContracts(chain).defaultFallbackRoutingIsmFactory - // : this.getContracts(chain).routingIsmFactory; - const isms: ChainMap
= {}; - for (const origin in config.domains) { - const ism = await this.deploy(chain, config.domains[origin], origin); - isms[origin] = ism.address; - } - const domains = Object.keys(isms).map((chain) => - this.multiProvider.getDomainId(chain), - ); - const submoduleAddresses = Object.values(isms); - const overrides = this.multiProvider.getTransactionOverrides(chain); - const tx = await routingIsmFactory.deploy( - domains, - submoduleAddresses, - overrides, - ); - const receipt = await this.multiProvider.handleTx(chain, tx); - // TODO: Break this out into a generalized function - const dispatchLogs = receipt.logs - .map((log) => { - try { - return routingIsmFactory.interface.parseLog(log); - } catch (e) { - return undefined; + protected async deployRoutingIsm(params: { + destination: ChainName; + config: RoutingIsmConfig; + origin?: ChainName; + mailbox?: Address; + existingIsmAddress?: Address; + logger: Debugger; + }): Promise { + const { destination, config, mailbox, existingIsmAddress } = params; + const overrides = this.multiProvider.getTransactionOverrides(destination); + const routingIsmFactory = this.getContracts(destination).routingIsmFactory; + let routingIsm: DomainRoutingIsm | DefaultFallbackRoutingIsm; + // filtering out domains which are not part of the multiprovider + config.domains = objFilter( + config.domains, + (domain, config): config is IsmConfig => { + const domainId = this.multiProvider.tryGetDomainId(domain); + if (domainId === null) { + warn( + `Domain ${domain} doesn't have chain metadata provided, skipping ...`, + ); } - }) - .filter( - (log): log is ethers.utils.LogDescription => - !!log && log.name === 'ModuleDeployed', - ); - const moduleAddress = dispatchLogs[0].args['module']; - const routingIsm = DomainRoutingIsm__factory.connect( - moduleAddress, - this.multiProvider.getSigner(chain), + return domainId !== null; + }, ); - this.logger(`Transferring ownership of routing ISM to ${config.owner}`); - await this.multiProvider.handleTx( - chain, - await routingIsm.transferOwnership(config.owner, overrides), + const safeConfigDomains = Object.keys(config.domains).map((domain) => + this.multiProvider.getDomainId(domain), ); - const address = dispatchLogs[0].args['module']; - return IRoutingIsm__factory.connect(address, signer); + const delta: RoutingIsmDelta = existingIsmAddress + ? await routingModuleDelta( + destination, + existingIsmAddress, + config, + this.multiProvider, + this.getContracts(destination), + mailbox, + ) + : { + domainsToUnenroll: [], + domainsToEnroll: safeConfigDomains, + }; + + const signer = this.multiProvider.getSigner(destination); + const provider = this.multiProvider.getProvider(destination); + let isOwner = false; + if (existingIsmAddress) { + const owner = await DomainRoutingIsm__factory.connect( + existingIsmAddress, + provider, + ).owner(); + isOwner = eqAddress(await signer.getAddress(), owner); + } + // reconfiguring existing routing ISM + if (existingIsmAddress && isOwner && !delta.mailbox) { + const isms: Record = {}; + routingIsm = DomainRoutingIsm__factory.connect( + existingIsmAddress, + this.multiProvider.getSigner(destination), + ); + // deploying all the ISMs which have to be updated + for (const originDomain of delta.domainsToEnroll) { + const origin = this.multiProvider.getChainName(originDomain); // already filtered to only include domains in the multiprovider + params.logger( + `Reconfiguring preexisting routing ISM at for origin ${origin}...`, + ); + const ism = await this.deploy({ + destination, + config: config.domains[origin], + origin, + mailbox, + }); + isms[originDomain] = ism.address; + const tx = await routingIsm.set( + originDomain, + isms[originDomain], + overrides, + ); + await this.multiProvider.handleTx(destination, tx); + } + // unenrolling domains if needed + for (const originDomain of delta.domainsToUnenroll) { + params.logger( + `Unenrolling originDomain ${originDomain} from preexisting routing ISM at ${existingIsmAddress}...`, + ); + const tx = await routingIsm.remove(originDomain, overrides); + await this.multiProvider.handleTx(destination, tx); + } + // transfer ownership if needed + if (delta.owner) { + params.logger(`Transferring ownership of routing ISM...`); + const tx = await routingIsm.transferOwnership(delta.owner, overrides); + await this.multiProvider.handleTx(destination, tx); + } + } else { + const isms: ChainMap
= {}; + for (const origin of Object.keys(config.domains)) { + const ism = await this.deploy({ + destination, + config: config.domains[origin], + origin, + mailbox, + }); + isms[origin] = ism.address; + } + const submoduleAddresses = Object.values(isms); + let receipt: ethers.providers.TransactionReceipt; + if (config.type === IsmType.FALLBACK_ROUTING) { + // deploying new fallback routing ISM + if (!mailbox) { + throw new Error( + 'Mailbox address is required for deploying fallback routing ISM', + ); + } + params.logger('Deploying fallback routing ISM ...'); + routingIsm = await this.multiProvider.handleDeploy( + destination, + new DefaultFallbackRoutingIsm__factory(), + [mailbox], + ); + params.logger('Initialising fallback routing ISM ...'); + receipt = await this.multiProvider.handleTx( + destination, + routingIsm['initialize(address,uint32[],address[])']( + config.owner, + safeConfigDomains, + submoduleAddresses, + overrides, + ), + ); + } else { + // deploying new domain routing ISM + const tx = await routingIsmFactory.deploy( + config.owner, + safeConfigDomains, + submoduleAddresses, + overrides, + ); + receipt = await this.multiProvider.handleTx(destination, tx); + + // TODO: Break this out into a generalized function + const dispatchLogs = receipt.logs + .map((log) => { + try { + return routingIsmFactory.interface.parseLog(log); + } catch (e) { + return undefined; + } + }) + .filter( + (log): log is ethers.utils.LogDescription => + !!log && log.name === 'ModuleDeployed', + ); + if (dispatchLogs.length === 0) { + throw new Error('No ModuleDeployed event found'); + } + const moduleAddress = dispatchLogs[0].args['module']; + routingIsm = DomainRoutingIsm__factory.connect( + moduleAddress, + this.multiProvider.getSigner(destination), + ); + } + } + return routingIsm; } - private async deployAggregationIsm( - chain: ChainName, - config: AggregationIsmConfig, - origin?: ChainName, - ) { - const signer = this.multiProvider.getSigner(chain); + protected async deployAggregationIsm(params: { + destination: ChainName; + config: AggregationIsmConfig; + origin?: ChainName; + mailbox?: Address; + logger: Debugger; + }): Promise { + const { destination, config, origin, mailbox } = params; + const signer = this.multiProvider.getSigner(destination); const aggregationIsmFactory = - this.getContracts(chain).aggregationIsmFactory; + this.getContracts(destination).aggregationIsmFactory; const addresses: Address[] = []; for (const module of config.modules) { - const submodule = await this.deploy(chain, module, origin); + const submodule = await this.deploy({ + destination, + config: module, + origin, + mailbox, + }); addresses.push(submodule.address); } const address = await this.deployStaticAddressSet( - chain, + destination, aggregationIsmFactory, addresses, + params.logger, config.threshold, ); return IAggregationIsm__factory.connect(address, signer); } - private async deployOpStackIsm(chain: ChainName, config: OpStackIsmConfig) { - return await this.multiProvider.handleDeploy( - chain, - new OPStackIsm__factory(), - [config.nativeBridge], - ); - } - async deployStaticAddressSet( chain: ChainName, factory: StaticThresholdAddressSetFactory | StaticAddressSetFactory, values: Address[], + logger: Debugger, threshold = values.length, ): Promise
{ const sorted = [...values].sort(); @@ -251,7 +431,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { ); const code = await this.multiProvider.getProvider(chain).getCode(address); if (code === '0x') { - this.logger( + logger( `Deploying new ${threshold} of ${values.length} address set to ${chain}`, ); const overrides = this.multiProvider.getTransactionOverrides(chain); @@ -263,305 +443,10 @@ export class HyperlaneIsmFactory extends HyperlaneApp { await this.multiProvider.handleTx(chain, hash); // TODO: add proxy verification artifact? } else { - this.logger( + logger( `Recovered ${threshold} of ${values.length} address set on ${chain}`, ); } return address; } } - -// Note that this function may return false negatives, but should -// not return false positives. -// This can happen if, for example, the module has sender, recipient, or -// body specific logic, as the sample message used when querying the ISM -// sets all of these to zero. -export async function moduleCanCertainlyVerify( - destModule: Address | IsmConfig, - multiProvider: MultiProvider, - origin: ChainName, - destination: ChainName, -): Promise { - const message = formatMessage( - 0, - 0, - multiProvider.getDomainId(origin), - ethers.constants.AddressZero, - multiProvider.getDomainId(destination), - ethers.constants.AddressZero, - '0x', - ); - const provider = multiProvider.getSignerOrProvider(destination); - - if (typeof destModule === 'string') { - const module = IInterchainSecurityModule__factory.connect( - destModule, - provider, - ); - - try { - const moduleType = await module.moduleType(); - if ( - moduleType === ModuleType.MERKLE_ROOT_MULTISIG || - moduleType === ModuleType.MESSAGE_ID_MULTISIG - ) { - const multisigModule = IMultisigIsm__factory.connect( - destModule, - provider, - ); - - const [, threshold] = await multisigModule.validatorsAndThreshold( - message, - ); - return threshold > 0; - } else if (moduleType === ModuleType.ROUTING) { - const routingIsm = IRoutingIsm__factory.connect(destModule, provider); - const subModule = await routingIsm.route(message); - return moduleCanCertainlyVerify( - subModule, - multiProvider, - origin, - destination, - ); - } else if (moduleType === ModuleType.AGGREGATION) { - const aggregationIsm = IAggregationIsm__factory.connect( - destModule, - provider, - ); - const [subModules, threshold] = - await aggregationIsm.modulesAndThreshold(message); - let verified = 0; - for (const subModule of subModules) { - const canVerify = await moduleCanCertainlyVerify( - subModule, - multiProvider, - origin, - destination, - ); - if (canVerify) { - verified += 1; - } - } - return verified >= threshold; - } else { - throw new Error(`Unsupported module type: ${moduleType}`); - } - } catch (e) { - warn(`Error checking module ${destModule}: ${e}`); - return false; - } - } else { - // destModule is an IsmConfig - switch (destModule.type) { - case IsmType.MERKLE_ROOT_MULTISIG: - case IsmType.MESSAGE_ID_MULTISIG: - return destModule.threshold > 0; - case IsmType.ROUTING: { - const checking = moduleCanCertainlyVerify( - destModule.domains[destination], - multiProvider, - origin, - destination, - ); - return checking; - } - case IsmType.AGGREGATION: { - let verified = 0; - for (const subModule of destModule.modules) { - const canVerify = await moduleCanCertainlyVerify( - subModule, - multiProvider, - origin, - destination, - ); - if (canVerify) { - verified += 1; - } - } - return verified >= destModule.threshold; - } - case IsmType.OP_STACK: - return destModule.nativeBridge !== ethers.constants.AddressZero; - case IsmType.TEST_ISM: { - return true; - } - default: - throw new Error(`Unsupported module type: ${(destModule as any).type}`); - } - } -} - -export async function moduleMatchesConfig( - chain: ChainName, - moduleAddress: Address, - config: IsmConfig, - multiProvider: MultiProvider, - contracts: HyperlaneContracts, - _origin?: ChainName, -): Promise { - if (typeof config === 'string') { - return eqAddress(moduleAddress, config); - } - - // If the module address is zero, it can't match any object-based config. - // The subsequent check of what moduleType it is will throw, so we fail here. - if (eqAddress(moduleAddress, ethers.constants.AddressZero)) { - return false; - } - - const provider = multiProvider.getProvider(chain); - const module = IInterchainSecurityModule__factory.connect( - moduleAddress, - provider, - ); - const actualType = await module.moduleType(); - if (actualType !== ismTypeToModuleType(config.type)) return false; - let matches = true; - switch (config.type) { - case IsmType.MERKLE_ROOT_MULTISIG: { - // A MerkleRootMultisigIsm matches if validators and threshold match the config - const expectedAddress = - await contracts.merkleRootMultisigIsmFactory.getAddress( - config.validators.sort(), - config.threshold, - ); - matches = eqAddress(expectedAddress, module.address); - break; - } - case IsmType.MESSAGE_ID_MULTISIG: { - // A MessageIdMultisigIsm matches if validators and threshold match the config - const expectedAddress = - await contracts.messageIdMultisigIsmFactory.getAddress( - config.validators.sort(), - config.threshold, - ); - matches = eqAddress(expectedAddress, module.address); - break; - } - case IsmType.ROUTING: { - // A RoutingIsm matches if: - // 1. The set of domains in the config equals those on-chain - // 2. The modules for each domain match the config - // TODO: Check (1) - const routingIsm = DomainRoutingIsm__factory.connect( - moduleAddress, - provider, - ); - // Check that the RoutingISM owner matches the config - const owner = await routingIsm.owner(); - matches = matches && eqAddress(owner, config.owner); - // Recursively check that the submodule for each configured - // domain matches the submodule config. - for (const [origin, subConfig] of Object.entries(config.domains)) { - const subModule = await routingIsm.module( - multiProvider.getDomainId(origin), - ); - const subModuleMatches = await moduleMatchesConfig( - chain, - subModule, - subConfig, - multiProvider, - contracts, - origin, - ); - matches = matches && subModuleMatches; - } - break; - } - case IsmType.AGGREGATION: { - // An AggregationIsm matches if: - // 1. The threshold matches the config - // 2. There is a bijection between on and off-chain configured modules - const aggregationIsm = StaticAggregationIsm__factory.connect( - moduleAddress, - provider, - ); - const [subModules, threshold] = await aggregationIsm.modulesAndThreshold( - '0x', - ); - matches = matches && threshold === config.threshold; - matches = matches && subModules.length === config.modules.length; - - const configIndexMatched = new Map(); - for (const subModule of subModules) { - const subModuleMatchesConfig = await Promise.all( - config.modules.map((c) => - moduleMatchesConfig(chain, subModule, c, multiProvider, contracts), - ), - ); - // The submodule returned by the ISM must match exactly one - // entry in the config. - const count = subModuleMatchesConfig.filter(Boolean).length; - matches = matches && count === 1; - - // That entry in the config should not have been matched already. - subModuleMatchesConfig.forEach((matched, index) => { - if (matched) { - matches = matches && !configIndexMatched.has(index); - configIndexMatched.set(index, true); - } - }); - } - break; - } - case IsmType.OP_STACK: { - const opStackIsm = OPStackIsm__factory.connect(moduleAddress, provider); - const type = await opStackIsm.moduleType(); - matches = matches && type === ModuleType.NULL; - break; - } - case IsmType.TEST_ISM: { - // This is just a TestISM - matches = true; - break; - } - default: { - throw new Error('Unsupported ModuleType'); - } - } - - return matches; -} - -export function collectValidators( - origin: ChainName, - config: IsmConfig, -): Set { - // TODO: support address configurations in collectValidators - if (typeof config === 'string') { - debug('hyperlane:IsmFactory')( - 'Address config unimplemented in collectValidators', - ); - return new Set([]); - } - - let validators: string[] = []; - if ( - config.type === IsmType.MERKLE_ROOT_MULTISIG || - config.type === IsmType.MESSAGE_ID_MULTISIG - ) { - validators = config.validators; - } else if (config.type === IsmType.ROUTING) { - if (Object.keys(config.domains).includes(origin)) { - const domainValidators = collectValidators( - origin, - config.domains[origin], - ); - validators = [...domainValidators]; - } - } else if (config.type === IsmType.AGGREGATION) { - const aggregatedValidators = config.modules.map((c) => - collectValidators(origin, c), - ); - aggregatedValidators.forEach((set) => { - validators = validators.concat([...set]); - }); - } else if (config.type === IsmType.TEST_ISM) { - // This is just a TestISM - return new Set([]); - } else { - throw new Error('Unsupported ModuleType'); - } - - return new Set(validators); -} diff --git a/typescript/sdk/src/ism/multisig.ts b/typescript/sdk/src/ism/multisig.ts index 2846f006ce..370f23221e 100644 --- a/typescript/sdk/src/ism/multisig.ts +++ b/typescript/sdk/src/ism/multisig.ts @@ -2,7 +2,12 @@ import { objFilter, objMap } from '@hyperlane-xyz/utils'; import { ChainMap, ChainName } from '../types'; -import { MultisigConfig, MultisigIsmConfig } from './types'; +import { + AggregationIsmConfig, + IsmType, + MultisigConfig, + MultisigIsmConfig, +} from './types'; // build multisigIsmConfig from multisigConfig // eg. for { sepolia (local), arbitrumsepolia, scrollsepolia } @@ -25,3 +30,31 @@ export const buildMultisigIsmConfigs = ( }), ); }; + +export const buildAggregationIsmConfigs = ( + local: ChainName, + chains: ChainName[], + multisigConfigs: ChainMap, +): ChainMap => { + return objMap( + objFilter( + multisigConfigs, + (chain, config): config is MultisigConfig => + chain !== local && chains.includes(chain), + ), + (_, config) => ({ + type: IsmType.AGGREGATION, + modules: [ + { + ...config, + type: IsmType.MESSAGE_ID_MULTISIG, + }, + { + ...config, + type: IsmType.MERKLE_ROOT_MULTISIG, + }, + ], + threshold: 1, + }), + ) as ChainMap; +}; diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index 0f7e17cd8d..12c84a2a9c 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -3,10 +3,12 @@ import { IMultisigIsm, IRoutingIsm, OPStackIsm, + PausableIsm, TestIsm, } from '@hyperlane-xyz/core'; -import type { Address, ValueOf } from '@hyperlane-xyz/utils'; +import type { Address, Domain, ValueOf } from '@hyperlane-xyz/utils'; +import { OwnableConfig } from '../deploy/types'; import { ChainMap } from '../types'; // this enum should match the IInterchainSecurityModule.sol enum @@ -26,19 +28,23 @@ export enum ModuleType { export enum IsmType { OP_STACK = 'opStackIsm', ROUTING = 'domainRoutingIsm', + FALLBACK_ROUTING = 'defaultFallbackRoutingIsm', AGGREGATION = 'staticAggregationIsm', MERKLE_ROOT_MULTISIG = 'merkleRootMultisigIsm', MESSAGE_ID_MULTISIG = 'messageIdMultisigIsm', TEST_ISM = 'testIsm', + PAUSABLE = 'pausableIsm', } -// mapping betweent the two enums +// mapping between the two enums export function ismTypeToModuleType(ismType: IsmType): ModuleType { switch (ismType) { case IsmType.OP_STACK: return ModuleType.NULL; case IsmType.ROUTING: return ModuleType.ROUTING; + case IsmType.FALLBACK_ROUTING: + return ModuleType.ROUTING; case IsmType.AGGREGATION: return ModuleType.AGGREGATION; case IsmType.MERKLE_ROOT_MULTISIG: @@ -47,6 +53,8 @@ export function ismTypeToModuleType(ismType: IsmType): ModuleType { return ModuleType.MESSAGE_ID_MULTISIG; case IsmType.TEST_ISM: return ModuleType.NULL; + case IsmType.PAUSABLE: + return ModuleType.NULL; } } @@ -63,12 +71,14 @@ export type TestIsmConfig = { type: IsmType.TEST_ISM; }; -export type RoutingIsmConfig = { - type: IsmType.ROUTING; - owner: Address; +export type PausableIsmConfig = OwnableConfig & { + type: IsmType.PAUSABLE; + paused?: boolean; +}; + +export type RoutingIsmConfig = OwnableConfig & { + type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING; domains: ChainMap; - // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2895 - // defaultFallback?: boolean; }; export type AggregationIsmConfig = { @@ -89,15 +99,26 @@ export type IsmConfig = | MultisigIsmConfig | AggregationIsmConfig | OpStackIsmConfig - | TestIsmConfig; + | TestIsmConfig + | PausableIsmConfig; export type DeployedIsmType = { [IsmType.ROUTING]: IRoutingIsm; + [IsmType.FALLBACK_ROUTING]: IRoutingIsm; [IsmType.AGGREGATION]: IAggregationIsm; [IsmType.MERKLE_ROOT_MULTISIG]: IMultisigIsm; [IsmType.MESSAGE_ID_MULTISIG]: IMultisigIsm; [IsmType.OP_STACK]: OPStackIsm; [IsmType.TEST_ISM]: TestIsm; + [IsmType.PAUSABLE]: PausableIsm; }; export type DeployedIsm = ValueOf; + +// for finding the difference between the onchain deployment and the config provided +export type RoutingIsmDelta = { + domainsToUnenroll: Domain[]; // new or updated isms for the domain + domainsToEnroll: Domain[]; // isms to remove + owner?: Address; // is the owner different + mailbox?: Address; // is the mailbox different (only for fallback routing) +}; diff --git a/typescript/sdk/src/ism/utils.ts b/typescript/sdk/src/ism/utils.ts new file mode 100644 index 0000000000..1a94a08f3c --- /dev/null +++ b/typescript/sdk/src/ism/utils.ts @@ -0,0 +1,419 @@ +import debug from 'debug'; +import { ethers } from 'ethers'; + +import { + DomainRoutingIsm__factory, + IAggregationIsm__factory, + IInterchainSecurityModule__factory, + IMultisigIsm__factory, + IRoutingIsm__factory, + MailboxClient__factory, + OPStackIsm__factory, + PausableIsm__factory, + StaticAggregationIsm__factory, +} from '@hyperlane-xyz/core'; +import { + Address, + eqAddress, + formatMessage, + normalizeAddress, + objMap, +} from '@hyperlane-xyz/utils'; + +import { chainMetadata } from '../consts/chainMetadata'; +import { HyperlaneContracts } from '../contracts/types'; +import { ProxyFactoryFactories } from '../deploy/contracts'; +import { MultiProvider } from '../providers/MultiProvider'; +import { ChainName } from '../types'; + +import { + IsmConfig, + IsmType, + ModuleType, + RoutingIsmConfig, + RoutingIsmDelta, + ismTypeToModuleType, +} from './types'; + +const logger = debug('hyperlane:IsmUtils'); + +// Note that this function may return false negatives, but should +// not return false positives. +// This can happen if, for example, the module has sender, recipient, or +// body specific logic, as the sample message used when querying the ISM +// sets all of these to zero. +export async function moduleCanCertainlyVerify( + destModule: Address | IsmConfig, + multiProvider: MultiProvider, + origin: ChainName, + destination: ChainName, +): Promise { + const originDomainId = multiProvider.tryGetDomainId(origin); + const destinationDomainId = multiProvider.tryGetDomainId(destination); + if (!originDomainId || !destinationDomainId) { + return false; + } + const message = formatMessage( + 0, + 0, + originDomainId, + ethers.constants.AddressZero, + destinationDomainId, + ethers.constants.AddressZero, + '0x', + ); + const provider = multiProvider.getSignerOrProvider(destination); + + if (typeof destModule === 'string') { + const module = IInterchainSecurityModule__factory.connect( + destModule, + provider, + ); + + try { + const moduleType = await module.moduleType(); + if ( + moduleType === ModuleType.MERKLE_ROOT_MULTISIG || + moduleType === ModuleType.MESSAGE_ID_MULTISIG + ) { + const multisigModule = IMultisigIsm__factory.connect( + destModule, + provider, + ); + + const [, threshold] = await multisigModule.validatorsAndThreshold( + message, + ); + return threshold > 0; + } else if (moduleType === ModuleType.ROUTING) { + const routingIsm = IRoutingIsm__factory.connect(destModule, provider); + const subModule = await routingIsm.route(message); + return moduleCanCertainlyVerify( + subModule, + multiProvider, + origin, + destination, + ); + } else if (moduleType === ModuleType.AGGREGATION) { + const aggregationIsm = IAggregationIsm__factory.connect( + destModule, + provider, + ); + const [subModules, threshold] = + await aggregationIsm.modulesAndThreshold(message); + let verified = 0; + for (const subModule of subModules) { + const canVerify = await moduleCanCertainlyVerify( + subModule, + multiProvider, + origin, + destination, + ); + if (canVerify) { + verified += 1; + } + } + return verified >= threshold; + } else { + throw new Error(`Unsupported module type: ${moduleType}`); + } + } catch (e) { + logger(`Error checking module ${destModule}: ${e}`); + return false; + } + } else { + // destModule is an IsmConfig + switch (destModule.type) { + case IsmType.MERKLE_ROOT_MULTISIG: + case IsmType.MESSAGE_ID_MULTISIG: + return destModule.threshold > 0; + case IsmType.ROUTING: { + const checking = moduleCanCertainlyVerify( + destModule.domains[destination], + multiProvider, + origin, + destination, + ); + return checking; + } + case IsmType.AGGREGATION: { + let verified = 0; + for (const subModule of destModule.modules) { + const canVerify = await moduleCanCertainlyVerify( + subModule, + multiProvider, + origin, + destination, + ); + if (canVerify) { + verified += 1; + } + } + return verified >= destModule.threshold; + } + case IsmType.OP_STACK: + return destModule.nativeBridge !== ethers.constants.AddressZero; + case IsmType.TEST_ISM: { + return true; + } + default: + throw new Error(`Unsupported module type: ${(destModule as any).type}`); + } + } +} + +export async function moduleMatchesConfig( + chain: ChainName, + moduleAddress: Address, + config: IsmConfig, + multiProvider: MultiProvider, + contracts: HyperlaneContracts, + mailbox?: Address, +): Promise { + if (typeof config === 'string') { + return eqAddress(moduleAddress, config); + } + + // If the module address is zero, it can't match any object-based config. + // The subsequent check of what moduleType it is will throw, so we fail here. + if (eqAddress(moduleAddress, ethers.constants.AddressZero)) { + return false; + } + + const provider = multiProvider.getProvider(chain); + const module = IInterchainSecurityModule__factory.connect( + moduleAddress, + provider, + ); + const actualType = await module.moduleType(); + if (actualType !== ismTypeToModuleType(config.type)) return false; + let matches = true; + switch (config.type) { + case IsmType.MERKLE_ROOT_MULTISIG: { + // A MerkleRootMultisigIsm matches if validators and threshold match the config + const expectedAddress = + await contracts.merkleRootMultisigIsmFactory.getAddress( + config.validators.sort(), + config.threshold, + ); + matches = eqAddress(expectedAddress, module.address); + break; + } + case IsmType.MESSAGE_ID_MULTISIG: { + // A MessageIdMultisigIsm matches if validators and threshold match the config + const expectedAddress = + await contracts.messageIdMultisigIsmFactory.getAddress( + config.validators.sort(), + config.threshold, + ); + matches = eqAddress(expectedAddress, module.address); + break; + } + case IsmType.FALLBACK_ROUTING: + case IsmType.ROUTING: { + // A RoutingIsm matches if: + // 1. The set of domains in the config equals those on-chain + // 2. The modules for each domain match the config + // TODO: Check (1) + const routingIsm = DomainRoutingIsm__factory.connect( + moduleAddress, + provider, + ); + // Check that the RoutingISM owner matches the config + const owner = await routingIsm.owner(); + matches &&= eqAddress(owner, config.owner); + // check if the mailbox matches the config for fallback routing + if (config.type === IsmType.FALLBACK_ROUTING) { + const client = MailboxClient__factory.connect(moduleAddress, provider); + const mailboxAddress = await client.mailbox(); + matches = + matches && + mailbox !== undefined && + eqAddress(mailboxAddress, mailbox); + } + const delta = await routingModuleDelta( + chain, + moduleAddress, + config, + multiProvider, + contracts, + mailbox, + ); + matches = + matches && + delta.domainsToEnroll.length === 0 && + delta.domainsToUnenroll.length === 0 && + !delta.mailbox && + !delta.owner; + break; + } + case IsmType.AGGREGATION: { + // An AggregationIsm matches if: + // 1. The threshold matches the config + // 2. There is a bijection between on and off-chain configured modules + const aggregationIsm = StaticAggregationIsm__factory.connect( + moduleAddress, + provider, + ); + const [subModules, threshold] = await aggregationIsm.modulesAndThreshold( + '0x', + ); + matches &&= threshold === config.threshold; + matches &&= subModules.length === config.modules.length; + + const configIndexMatched = new Map(); + for (const subModule of subModules) { + const subModuleMatchesConfig = await Promise.all( + config.modules.map((c) => + moduleMatchesConfig(chain, subModule, c, multiProvider, contracts), + ), + ); + // The submodule returned by the ISM must match exactly one + // entry in the config. + const count = subModuleMatchesConfig.filter(Boolean).length; + matches &&= count === 1; + + // That entry in the config should not have been matched already. + subModuleMatchesConfig.forEach((matched, index) => { + if (matched) { + matches &&= !configIndexMatched.has(index); + configIndexMatched.set(index, true); + } + }); + } + break; + } + case IsmType.OP_STACK: { + const opStackIsm = OPStackIsm__factory.connect(moduleAddress, provider); + const type = await opStackIsm.moduleType(); + matches &&= type === ModuleType.NULL; + break; + } + case IsmType.TEST_ISM: { + // This is just a TestISM + matches = true; + break; + } + case IsmType.PAUSABLE: { + const pausableIsm = PausableIsm__factory.connect(moduleAddress, provider); + const owner = await pausableIsm.owner(); + matches &&= eqAddress(owner, config.owner); + + if (config.paused) { + const isPaused = await pausableIsm.paused(); + matches &&= config.paused === isPaused; + } + break; + } + default: { + throw new Error('Unsupported ModuleType'); + } + } + + return matches; +} + +export async function routingModuleDelta( + destination: ChainName, + moduleAddress: Address, + config: RoutingIsmConfig, + multiProvider: MultiProvider, + contracts: HyperlaneContracts, + mailbox?: Address, +): Promise { + const provider = multiProvider.getProvider(destination); + const routingIsm = DomainRoutingIsm__factory.connect(moduleAddress, provider); + const owner = await routingIsm.owner(); + const deployedDomains = (await routingIsm.domains()).map((domain) => + domain.toNumber(), + ); + // config.domains is already filtered to only include domains in the multiprovider + const safeConfigDomains = objMap( + config.domains, + (chainName) => + chainMetadata[chainName]?.domainId ?? + multiProvider.getDomainId(chainName), + ); + + const delta: RoutingIsmDelta = { + domainsToUnenroll: [], + domainsToEnroll: [], + }; + + // if owners don't match, we need to transfer ownership + if (!eqAddress(owner, normalizeAddress(config.owner))) + delta.owner = config.owner; + if (config.type === IsmType.FALLBACK_ROUTING) { + const client = MailboxClient__factory.connect(moduleAddress, provider); + const mailboxAddress = await client.mailbox(); + if (mailbox && !eqAddress(mailboxAddress, mailbox)) delta.mailbox = mailbox; + } + // check for exclusion of domains in the config + delta.domainsToUnenroll = deployedDomains.filter( + (domain) => !Object.values(safeConfigDomains).includes(domain), + ); + // check for inclusion of domains in the config + for (const [origin, subConfig] of Object.entries(config.domains)) { + const originDomain = safeConfigDomains[origin]; + if (!deployedDomains.includes(originDomain)) { + delta.domainsToEnroll.push(originDomain); + } else { + const subModule = await routingIsm.module(originDomain); + // Recursively check that the submodule for each configured + // domain matches the submodule config. + const subModuleMatches = await moduleMatchesConfig( + destination, + subModule, + subConfig, + multiProvider, + contracts, + mailbox, + ); + if (!subModuleMatches) delta.domainsToEnroll.push(originDomain); + } + } + return delta; +} + +export function collectValidators( + origin: ChainName, + config: IsmConfig, +): Set { + // TODO: support address configurations in collectValidators + if (typeof config === 'string') { + logger.extend(origin)('Address config unimplemented in collectValidators'); + return new Set([]); + } + + let validators: string[] = []; + if ( + config.type === IsmType.MERKLE_ROOT_MULTISIG || + config.type === IsmType.MESSAGE_ID_MULTISIG + ) { + validators = config.validators; + } else if (config.type === IsmType.ROUTING) { + if (Object.keys(config.domains).includes(origin)) { + const domainValidators = collectValidators( + origin, + config.domains[origin], + ); + validators = [...domainValidators]; + } + } else if (config.type === IsmType.AGGREGATION) { + const aggregatedValidators = config.modules.map((c) => + collectValidators(origin, c), + ); + aggregatedValidators.forEach((set) => { + validators = validators.concat([...set]); + }); + } else if ( + config.type === IsmType.TEST_ISM || + config.type === IsmType.PAUSABLE + ) { + return new Set([]); + } else { + throw new Error('Unsupported ModuleType'); + } + + return new Set(validators); +} diff --git a/typescript/sdk/src/logger.ts b/typescript/sdk/src/logger.ts new file mode 100644 index 0000000000..0e0a2c910f --- /dev/null +++ b/typescript/sdk/src/logger.ts @@ -0,0 +1,5 @@ +import debug from 'debug'; + +// Default logger for use in utils/scripts +// For classes, prefer to create loggers with more specific namespaces +export const logger = debug('hyperlane'); diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index c9bf172fcc..2ff6899349 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -2,16 +2,21 @@ import { Debugger, debug } from 'debug'; import { ProtocolType, exclude, pick } from '@hyperlane-xyz/utils'; -import { - chainMetadata as defaultChainMetadata, - solanaChainToClusterName, -} from '../consts/chainMetadata'; -import { ChainMap, ChainName } from '../types'; +import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata'; +import { ChainMap, ChainName, ChainNameOrId } from '../types'; +import { + getExplorerAddressUrl, + getExplorerApi, + getExplorerApiUrl, + getExplorerBaseUrl, + getExplorerTxUrl, +} from './blockExplorer'; import { ChainMetadata, + ExplorerFamily, getDomainId, - isValidChainMetadata, + safeParseChainMetadata, } from './chainMetadataTypes'; export interface ChainMetadataManagerOptions { @@ -52,8 +57,14 @@ export class ChainMetadataManager { * @throws if chain's name or domain/chain ID collide */ addChain(metadata: ChainMetadata): void { - if (!isValidChainMetadata(metadata)) - throw new Error(`Invalid chain metadata for ${metadata.name}`); + const parseResult = safeParseChainMetadata(metadata); + if (!parseResult.success) { + throw new Error( + `Invalid chain metadata for ${ + metadata.name + }: ${parseResult.error.format()}`, + ); + } // Ensure no two chains have overlapping names/domainIds/chainIds for (const chainMetadata of Object.values(this.metadata)) { const { name, chainId, domainId } = chainMetadata; @@ -78,7 +89,7 @@ export class ChainMetadataManager { * @throws if chain's metadata has not been set */ tryGetChainMetadata( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, ): ChainMetadata | null { // First check if it's a chain name if (this.metadata[chainNameOrId]) return this.metadata[chainNameOrId]; @@ -93,7 +104,7 @@ export class ChainMetadataManager { * Get the metadata for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - getChainMetadata(chainNameOrId: ChainName | number): ChainMetadata { + getChainMetadata(chainNameOrId: ChainNameOrId): ChainMetadata { const chainMetadata = this.tryGetChainMetadata(chainNameOrId); if (!chainMetadata) { throw new Error(`No chain metadata set for ${chainNameOrId}`); @@ -101,10 +112,18 @@ export class ChainMetadataManager { return chainMetadata; } + /** + * Returns true if the given chain name, chain id, or domain id is + * include in this manager's metadata, false otherwise + */ + hasChain(chainNameOrId: ChainNameOrId): boolean { + return !!this.tryGetChainMetadata(chainNameOrId); + } + /** * Get the name for a given chain name, chain id, or domain id */ - tryGetChainName(chainNameOrId: ChainName | number): string | null { + tryGetChainName(chainNameOrId: ChainNameOrId): string | null { return this.tryGetChainMetadata(chainNameOrId)?.name ?? null; } @@ -112,7 +131,7 @@ export class ChainMetadataManager { * Get the name for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - getChainName(chainNameOrId: ChainName | number): string { + getChainName(chainNameOrId: ChainNameOrId): string { return this.getChainMetadata(chainNameOrId).name; } @@ -126,7 +145,7 @@ export class ChainMetadataManager { /** * Get the id for a given chain name, chain id, or domain id */ - tryGetChainId(chainNameOrId: ChainName | number): number | string | null { + tryGetChainId(chainNameOrId: ChainNameOrId): number | string | null { return this.tryGetChainMetadata(chainNameOrId)?.chainId ?? null; } @@ -134,7 +153,7 @@ export class ChainMetadataManager { * Get the id for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - getChainId(chainNameOrId: ChainName | number): number | string { + getChainId(chainNameOrId: ChainNameOrId): number | string { return this.getChainMetadata(chainNameOrId).chainId; } @@ -148,7 +167,7 @@ export class ChainMetadataManager { /** * Get the domain id for a given chain name, chain id, or domain id */ - tryGetDomainId(chainNameOrId: ChainName | number): number | null { + tryGetDomainId(chainNameOrId: ChainNameOrId): number | null { const metadata = this.tryGetChainMetadata(chainNameOrId); if (!metadata) return null; return getDomainId(metadata) ?? null; @@ -158,7 +177,7 @@ export class ChainMetadataManager { * Get the domain id for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - getDomainId(chainNameOrId: ChainName | number): number { + getDomainId(chainNameOrId: ChainNameOrId): number { const domainId = this.tryGetDomainId(chainNameOrId); if (!domainId) throw new Error(`No domain id set for ${chainNameOrId}`); return domainId; @@ -167,7 +186,7 @@ export class ChainMetadataManager { /** * Get the protocol type for a given chain name, chain id, or domain id */ - tryGetProtocol(chainNameOrId: ChainName | number): ProtocolType | null { + tryGetProtocol(chainNameOrId: ChainNameOrId): ProtocolType | null { return this.tryGetChainMetadata(chainNameOrId)?.protocol ?? null; } @@ -175,7 +194,7 @@ export class ChainMetadataManager { * 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 { + getProtocol(chainNameOrId: ChainNameOrId): ProtocolType { return this.getChainMetadata(chainNameOrId).protocol; } @@ -216,7 +235,7 @@ export class ChainMetadataManager { * Get an RPC URL for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - getRpcUrl(chainNameOrId: ChainName | number): string { + getRpcUrl(chainNameOrId: ChainNameOrId): string { const { rpcUrls } = this.getChainMetadata(chainNameOrId); if (!rpcUrls?.length || !rpcUrls[0].http) throw new Error(`No RPC URl configured for ${chainNameOrId}`); @@ -226,51 +245,64 @@ export class ChainMetadataManager { /** * Get a block explorer URL for a given chain name, chain id, or domain id */ - tryGetExplorerUrl(chainNameOrId: ChainName | number): string | null { + tryGetExplorerUrl(chainNameOrId: ChainNameOrId): string | null { const metadata = this.tryGetChainMetadata(chainNameOrId); - if (!metadata?.blockExplorers?.length) return null; - const url = new URL(metadata.blockExplorers[0].url); - // TODO move handling of these chain/protocol specific quirks to ChainMetadata - if ( - metadata.protocol === ProtocolType.Sealevel && - solanaChainToClusterName[metadata.name] - ) { - url.searchParams.set('cluster', solanaChainToClusterName[metadata.name]); - } - // TODO cosmos support here - return url.toString(); + if (!metadata) return null; + return getExplorerBaseUrl(metadata); } /** * Get a block explorer URL for a given chain name, chain id, or domain id * @throws if chain's metadata or block explorer data has no been set */ - getExplorerUrl(chainNameOrId: ChainName | number): string { + getExplorerUrl(chainNameOrId: ChainNameOrId): string { const url = this.tryGetExplorerUrl(chainNameOrId); if (!url) throw new Error(`No explorer url set for ${chainNameOrId}`); return url; } + /** + * Get a block explorer's API for a given chain name, chain id, or domain id + */ + tryGetExplorerApi(chainNameOrId: ChainName | number): { + apiUrl: string; + apiKey?: string; + family?: ExplorerFamily; + } | null { + const metadata = this.tryGetChainMetadata(chainNameOrId); + if (!metadata) return null; + return getExplorerApi(metadata); + } + + /** + * Get a block explorer API for a given chain name, chain id, or domain id + * @throws if chain's metadata or block explorer data has no been set + */ + getExplorerApi(chainNameOrId: ChainName | number): { + apiUrl: string; + apiKey?: string; + family?: ExplorerFamily; + } { + const explorerApi = this.tryGetExplorerApi(chainNameOrId); + if (!explorerApi) + throw new Error(`No supported explorer api set for ${chainNameOrId}`); + return explorerApi; + } + /** * Get a block explorer's API URL for a given chain name, chain id, or domain id */ - tryGetExplorerApiUrl(chainNameOrId: ChainName | number): string | null { + tryGetExplorerApiUrl(chainNameOrId: ChainNameOrId): string | null { const metadata = this.tryGetChainMetadata(chainNameOrId); - const { protocol, blockExplorers } = metadata || {}; - if (protocol !== ProtocolType.Ethereum) return null; - if (!blockExplorers?.length || !blockExplorers[0].apiUrl) return null; - const { apiUrl, apiKey } = blockExplorers[0]; - if (!apiKey) return apiUrl; - const url = new URL(apiUrl); - url.searchParams.set('apikey', apiKey); - return url.toString(); + if (!metadata) return null; + return getExplorerApiUrl(metadata); } /** * Get a block explorer API URL for a given chain name, chain id, or domain id * @throws if chain's metadata or block explorer data has no been set */ - getExplorerApiUrl(chainNameOrId: ChainName | number): string { + getExplorerApiUrl(chainNameOrId: ChainNameOrId): string { const url = this.tryGetExplorerApiUrl(chainNameOrId); if (!url) throw new Error(`No explorer api url set for ${chainNameOrId}`); return url; @@ -280,33 +312,35 @@ export class ChainMetadataManager { * Get a block explorer URL for given chain's tx */ tryGetExplorerTxUrl( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, response: { hash: string }, ): string | null { - const baseUrl = this.tryGetExplorerUrl(chainNameOrId); - if (!baseUrl) return null; - const chainName = this.getChainName(chainNameOrId); - const urlPathStub = ['nautilus', 'proteustestnet'].includes(chainName) - ? 'transaction' - : 'tx'; - const url = new URL(baseUrl); - url.pathname += `/${urlPathStub}/${response.hash}`; - return url.toString(); + const metadata = this.tryGetChainMetadata(chainNameOrId); + if (!metadata) return null; + return getExplorerTxUrl(metadata, response.hash); + } + + /** + * Get a block explorer URL for given chain's tx + * @throws if chain's metadata or block explorer data has no been set + */ + getExplorerTxUrl( + chainNameOrId: ChainNameOrId, + response: { hash: string }, + ): string { + return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`; } /** * Get a block explorer URL for given chain's address */ async tryGetExplorerAddressUrl( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, address?: string, ): Promise { - if (!address) return null; - const baseUrl = this.tryGetExplorerUrl(chainNameOrId); - if (!baseUrl) return null; - const url = new URL(baseUrl); - url.pathname += `/address/${address}`; - return url.toString(); + const metadata = this.tryGetChainMetadata(chainNameOrId); + if (!metadata || !address) return null; + return getExplorerAddressUrl(metadata, address); } /** @@ -314,7 +348,7 @@ export class ChainMetadataManager { * @throws if address or the chain's block explorer data has no been set */ async getExplorerAddressUrl( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, address?: string, ): Promise { const url = await this.tryGetExplorerAddressUrl(chainNameOrId, address); @@ -323,17 +357,6 @@ export class ChainMetadataManager { return url; } - /** - * Get a block explorer URL for given chain's tx - * @throws if chain's metadata or block explorer data has no been set - */ - getExplorerTxUrl( - chainNameOrId: ChainName | number, - response: { hash: string }, - ): string { - return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`; - } - /** * Creates a new ChainMetadataManager with the extended metadata * @param additionalMetadata extra fields to add to the metadata for each chain diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index 8431ea7385..5aa89687ff 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -4,6 +4,8 @@ */ import { z } from 'zod'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; @@ -46,6 +48,7 @@ export enum AgentSignerKeyType { Aws = 'aws', Hex = 'hexKey', Node = 'node', + Cosmos = 'cosmosKey', } const AgentSignerHexKeySchema = z @@ -63,6 +66,13 @@ const AgentSignerAwsKeySchema = z .describe( 'An AWS signer. Note that AWS credentials must be inserted into the env separately.', ); +const AgentSignerCosmosKeySchema = z + .object({ + type: z.literal(AgentSignerKeyType.Cosmos), + prefix: z.string().describe('The bech32 prefix for the cosmos address'), + key: ZHash, + }) + .describe('Cosmos key'); const AgentSignerNodeSchema = z .object({ type: z.literal(AgentSignerKeyType.Node), @@ -72,47 +82,129 @@ const AgentSignerNodeSchema = z const AgentSignerSchema = z.union([ AgentSignerHexKeySchema, AgentSignerAwsKeySchema, + AgentSignerCosmosKeySchema, AgentSignerNodeSchema, ]); export type AgentSignerHexKey = z.infer; export type AgentSignerAwsKey = z.infer; +export type AgentSignerCosmosKey = z.infer; export type AgentSignerNode = z.infer; export type AgentSigner = z.infer; -export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( - HyperlaneDeploymentArtifactsSchema, -).extend({ - customRpcUrls: z +// Additional chain metadata for Cosmos chains required by the agents. +const AgentCosmosChainMetadataSchema = z.object({ + canonicalAsset: z .string() - .optional() .describe( - 'Specify a comma seperated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.', + 'The name of the canonical asset for this chain, usually in "micro" form, e.g. untrn', ), - rpcConsensusType: z - .nativeEnum(RpcConsensusType) - .describe('The consensus type to use when multiple RPCs are configured.') - .optional(), - signer: AgentSignerSchema.optional().describe( - 'The signer to use for this chain', - ), - index: z - .object({ - from: ZUint.optional().describe( - 'The starting block from which to index events.', - ), - chunk: ZNzUint.optional().describe( - 'The number of blocks to index at a time.', + gasPrice: z.object({ + denom: z + .string() + .describe('The coin denom, usually in "micro" form, e.g. untrn'), + amount: z + .string() + .regex(/^(\d*[.])?\d+$/) + .describe('The the gas price, in denom, to pay for each unit of gas'), + }), + contractAddressBytes: z + .number() + .int() + .positive() + .lte(32) + .describe('The number of bytes used to represent a contract address.'), +}); + +export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( + HyperlaneDeploymentArtifactsSchema, +) + .extend({ + customRpcUrls: z + .string() + .optional() + .describe( + 'Specify a comma separated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.', ), - mode: z - .nativeEnum(AgentIndexMode) - .optional() - .describe( - 'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.', + rpcConsensusType: z + .nativeEnum(RpcConsensusType) + .describe('The consensus type to use when multiple RPCs are configured.') + .optional(), + signer: AgentSignerSchema.optional().describe( + 'The signer to use for this chain', + ), + index: z + .object({ + from: ZUint.optional().describe( + 'The starting block from which to index events.', ), - }) - .optional(), -}); + chunk: ZNzUint.optional().describe( + 'The number of blocks to index at a time.', + ), + mode: z + .nativeEnum(AgentIndexMode) + .optional() + .describe( + 'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.', + ), + }) + .optional(), + }) + .merge(AgentCosmosChainMetadataSchema.partial()) + .refine((metadata) => { + // Make sure that the signer is valid for the protocol + + const signerType = metadata.signer?.type; + + // If no signer is specified, no validation is needed + if (signerType === undefined) { + return true; + } + + switch (metadata.protocol) { + case ProtocolType.Ethereum: + if ( + ![ + AgentSignerKeyType.Hex, + signerType === AgentSignerKeyType.Aws, + signerType === AgentSignerKeyType.Node, + ].includes(signerType) + ) { + return false; + } + break; + + case ProtocolType.Cosmos: + if (![AgentSignerKeyType.Cosmos].includes(signerType)) { + return false; + } + break; + + case ProtocolType.Sealevel: + if (![AgentSignerKeyType.Hex].includes(signerType)) { + return false; + } + break; + + case ProtocolType.Fuel: + if (![AgentSignerKeyType.Hex].includes(signerType)) { + return false; + } + break; + + default: + // Just accept it if we don't know the protocol + } + + // If the protocol type is Cosmos, require everything in AgentCosmosChainMetadataSchema + if (metadata.protocol === ProtocolType.Cosmos) { + if (!AgentCosmosChainMetadataSchema.safeParse(metadata).success) { + return false; + } + } + + return true; + }); export type AgentChainMetadata = z.infer; @@ -190,29 +282,36 @@ const GasPaymentEnforcementSchema = z.union([ ]); export type GasPaymentEnforcement = z.infer; +const MetricAppContextSchema = z.object({ + name: z.string().min(1), + matchingList: MatchingListSchema.describe( + 'A matching list, any message that matches will be classified as this app context.', + ), +}); + export const RelayerAgentConfigSchema = AgentConfigSchema.extend({ db: z .string() - .nonempty() + .min(1) .optional() .describe('The path to the relayer database.'), relayChains: CommaSeperatedChainList.describe( 'Comma seperated list of chains to relay messages between.', ), gasPaymentEnforcement: z - .union([z.array(GasPaymentEnforcementSchema), z.string().nonempty()]) + .union([z.array(GasPaymentEnforcementSchema), z.string().min(1)]) .optional() .describe( 'The gas payment enforcement configuration as JSON. Expects an ordered array of `GasPaymentEnforcementConfig`.', ), whitelist: z - .union([MatchingListSchema, z.string().nonempty()]) + .union([MatchingListSchema, z.string().min(1)]) .optional() .describe( 'If no whitelist is provided ALL messages will be considered on the whitelist.', ), blacklist: z - .union([MatchingListSchema, z.string().nonempty()]) + .union([MatchingListSchema, z.string().min(1)]) .optional() .describe( 'If no blacklist is provided ALL will be considered to not be on the blacklist.', @@ -229,12 +328,18 @@ export const RelayerAgentConfigSchema = AgentConfigSchema.extend({ .describe( 'If true, allows local storage based checkpoint syncers. Not intended for production use.', ), + metricAppContexts: z + .union([z.array(MetricAppContextSchema), z.string().min(1)]) + .optional() + .describe( + 'A list of app contexts and their matching lists to use for metrics. A message will be classified as the first matching app context.', + ), }); export type RelayerConfig = z.infer; export const ScraperAgentConfigSchema = AgentConfigSchema.extend({ - db: z.string().nonempty().describe('Database connection string'), + db: z.string().min(1).describe('Database connection string'), chainsToScrape: CommaSeperatedChainList.describe( 'Comma separated list of chain names to scrape', ), @@ -245,32 +350,29 @@ export type ScraperConfig = z.infer; export const ValidatorAgentConfigSchema = AgentConfigSchema.extend({ db: z .string() - .nonempty() + .min(1) .optional() .describe('The path to the validator database.'), originChainName: z .string() - .nonempty() + .min(1) .describe('Name of the chain to validate messages on'), validator: AgentSignerSchema.describe('The validator attestation signer'), checkpointSyncer: z.discriminatedUnion('type', [ z .object({ type: z.literal('localStorage'), - path: z - .string() - .nonempty() - .describe('Path to the local storage location'), + path: z.string().min(1).describe('Path to the local storage location'), }) .describe('A local checkpoint syncer'), z .object({ type: z.literal('s3'), - bucket: z.string().nonempty(), - region: z.string().nonempty(), + bucket: z.string().min(1), + region: z.string().min(1), folder: z .string() - .nonempty() + .min(1) .optional() .describe( 'The folder/key-prefix to use, defaults to the root of the bucket', @@ -287,6 +389,8 @@ export type ValidatorConfig = z.infer; export type AgentConfig = z.infer; +// Note this works well for EVM chains only, and likely needs some love +// before being useful for non-EVM chains. export function buildAgentConfig( chains: ChainName[], multiProvider: MultiProvider, diff --git a/typescript/sdk/src/metadata/blockExplorer.ts b/typescript/sdk/src/metadata/blockExplorer.ts new file mode 100644 index 0000000000..ccd8a0388c --- /dev/null +++ b/typescript/sdk/src/metadata/blockExplorer.ts @@ -0,0 +1,77 @@ +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { solanaChainToClusterName } from '../consts/chainMetadata'; + +import { ChainMetadata, ExplorerFamily } from './chainMetadataTypes'; + +export function getExplorerBaseUrl(metadata: ChainMetadata): string | null { + if (!metadata?.blockExplorers?.length) return null; + const url = new URL(metadata.blockExplorers[0].url); + // TODO consider move handling of these chain/protocol specific quirks to ChainMetadata + if ( + metadata.protocol === ProtocolType.Sealevel && + solanaChainToClusterName[metadata.name] + ) { + url.searchParams.set('cluster', solanaChainToClusterName[metadata.name]); + } + return url.toString(); +} + +export function getExplorerApi(metadata: ChainMetadata): { + apiUrl: string; + apiKey?: string | undefined; + family?: ExplorerFamily | undefined; +} | null { + const { protocol, blockExplorers } = metadata; + // TODO solana + cosmos support here as needed + if (protocol !== ProtocolType.Ethereum) return null; + if (!blockExplorers?.length || !blockExplorers[0].apiUrl) return null; + return { + apiUrl: blockExplorers[0].apiUrl, + apiKey: blockExplorers[0].apiKey, + family: blockExplorers[0].family, + }; +} + +export function getExplorerApiUrl(metadata: ChainMetadata): string | null { + const { protocol, blockExplorers } = metadata; + // TODO solana + cosmos support here as needed + if (protocol !== ProtocolType.Ethereum) return null; + if (!blockExplorers?.length || !blockExplorers[0].apiUrl) return null; + const { apiUrl, apiKey } = blockExplorers[0]; + if (!apiKey) return apiUrl; + const url = new URL(apiUrl); + url.searchParams.set('apikey', apiKey); + return url.toString(); +} + +export function getExplorerTxUrl( + metadata: ChainMetadata, + hash: string, +): string | null { + const baseUrl = getExplorerBaseUrl(metadata); + if (!baseUrl) return null; + const chainName = metadata.name; + // TODO consider move handling of these chain/protocol specific quirks to ChainMetadata + const urlPathStub = ['nautilus', 'proteustestnet'].includes(chainName) + ? 'transaction' + : 'tx'; + return appendToPath(baseUrl, `${urlPathStub}/${hash}`).toString(); +} + +export function getExplorerAddressUrl( + metadata: ChainMetadata, + address: string, +): string | null { + const baseUrl = getExplorerBaseUrl(metadata); + if (!baseUrl) return null; + return appendToPath(baseUrl, `address/${address}`).toString(); +} + +function appendToPath(baseUrl: string, pathExtension: string) { + const base = new URL(baseUrl); + let currentPath = base.pathname; + if (currentPath.endsWith('/')) currentPath = currentPath.slice(0, -1); + const newPath = `${currentPath}/${pathExtension}`; + return new URL(newPath, base); +} diff --git a/typescript/sdk/src/metadata/chainMetadata.test.ts b/typescript/sdk/src/metadata/chainMetadata.test.ts index 16de256ba1..ed0c403fd9 100644 --- a/typescript/sdk/src/metadata/chainMetadata.test.ts +++ b/typescript/sdk/src/metadata/chainMetadata.test.ts @@ -2,6 +2,8 @@ import { expect } from 'chai'; import { ProtocolType } from '@hyperlane-xyz/utils'; +import { chainMetadata } from '../consts/chainMetadata'; + import { ChainMetadata, isValidChainMetadata } from './chainMetadataTypes'; const minimalSchema: ChainMetadata = { @@ -58,9 +60,12 @@ describe('ChainMetadataSchema', () => { chainId: 'cosmos', bech32Prefix: 'cosmos', slip44: 118, + restUrls: [], + grpcUrls: [], }), ).to.eq(true); }); + it('Rejects invalid schemas', () => { expect( //@ts-ignore @@ -106,4 +111,13 @@ describe('ChainMetadataSchema', () => { }), ).to.eq(false); }); + + it('Works for all SDK chain metadata consts', () => { + for (const chain of Object.keys(chainMetadata)) { + const isValid = isValidChainMetadata(chainMetadata[chain]); + // eslint-disable-next-line no-console + if (!isValid) console.error(`Invalid chain metadata for ${chain}`); + expect(isValid).to.eq(true); + } + }); }); diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index 7e185878ee..484b2b6e8d 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -2,15 +2,16 @@ * The types defined here are the source of truth for chain metadata. * ANY CHANGES HERE NEED TO BE REFLECTED IN HYPERLANE-BASE CONFIG PARSING. */ -import { z } from 'zod'; +import { SafeParseReturnType, z } from 'zod'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { ZNzUint, ZUint } from './customZodTypes'; +import { ZChainName, ZNzUint, ZUint } from './customZodTypes'; export enum ExplorerFamily { Etherscan = 'etherscan', Blockscout = 'blockscout', + Routescan = 'routescan', Other = 'other', } @@ -60,12 +61,9 @@ export type RpcUrl = z.infer; * Specified as a Zod schema */ export const ChainMetadataSchemaObject = z.object({ - name: z - .string() - .regex(/^[a-z][a-z0-9]*$/) - .describe( - 'The unique string identifier of the chain, used as the key in ChainMap dictionaries.', - ), + name: ZChainName.describe( + 'The unique string identifier of the chain, used as the key in ChainMap dictionaries.', + ), protocol: z .nativeEnum(ProtocolType) .describe( @@ -98,6 +96,7 @@ export const ChainMetadataSchemaObject = z.object({ name: z.string(), symbol: z.string(), decimals: ZUint.lt(256), + denom: z.string().optional(), }) .optional() .describe( @@ -107,6 +106,20 @@ export const ChainMetadataSchemaObject = z.object({ .array(RpcUrlSchema) .nonempty() .describe('The list of RPC endpoints for interacting with the chain.'), + restUrls: z + .array(RpcUrlSchema) + .describe('For cosmos chains only, a list of Rest API URLs') + .optional(), + grpcUrls: z + .array(RpcUrlSchema) + .describe('For cosmos chains only, a list of gRPC API URLs') + .optional(), + customGrpcUrls: z + .string() + .optional() + .describe( + 'Specify a comma separated list of custom GRPC URLs to use for this chain. If not specified, the default GRPC urls will be used.', + ), blockExplorers: z .array( z.object({ @@ -212,11 +225,51 @@ export const ChainMetadataSchema = ChainMetadataSchemaObject.refine( message: 'Bech32Prefix and Slip44 required for Cosmos chains', path: ['bech32Prefix', 'slip44'], }, + ) + .refine( + (metadata) => { + if ( + metadata.protocol === ProtocolType.Cosmos && + (!metadata.restUrls || !metadata.grpcUrls) + ) + return false; + else return true; + }, + { + message: 'Rest and gRPC URLs required for Cosmos chains', + path: ['restUrls', 'grpcUrls'], + }, + ) + .refine( + (metadata) => { + if ( + metadata.protocol === ProtocolType.Cosmos && + metadata.nativeToken && + !metadata.nativeToken.denom + ) + return false; + else return true; + }, + { + message: 'Denom values are required for Cosmos native tokens', + path: ['nativeToken', 'denom'], + }, ); export type ChainMetadata = z.infer & Ext; +export type BlockExplorer = Exclude< + ChainMetadata['blockExplorers'], + undefined +>[number]; + +export function safeParseChainMetadata( + c: ChainMetadata, +): SafeParseReturnType { + return ChainMetadataSchema.safeParse(c); +} + export function isValidChainMetadata(c: ChainMetadata): boolean { return ChainMetadataSchema.safeParse(c).success; } @@ -232,3 +285,9 @@ export function getChainIdNumber(chainMetadata: ChainMetadata): number { if (typeof chainMetadata.chainId === 'number') return chainMetadata.chainId; else throw new Error('ChainId is not a number, chain may be of Cosmos type'); } + +export function getReorgPeriod(chainMetadata: ChainMetadata): number { + if (chainMetadata.blocks?.reorgPeriod !== undefined) + return chainMetadata.blocks.reorgPeriod; + else throw new Error('Chain has no reorg period'); +} diff --git a/typescript/sdk/src/metadata/customZodTypes.ts b/typescript/sdk/src/metadata/customZodTypes.ts index 6f2841cd0d..bfabed4866 100644 --- a/typescript/sdk/src/metadata/customZodTypes.ts +++ b/typescript/sdk/src/metadata/customZodTypes.ts @@ -16,3 +16,5 @@ export const ZHash = z .regex( /^(0x([0-9a-fA-F]{32}|[0-9a-fA-F]{40}|[0-9a-fA-F]{64}|[0-9a-fA-F]{128}))|([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{32})$/, ); +/** Zod ChainName schema */ +export const ZChainName = z.string().regex(/^[a-z][a-z0-9]*$/); diff --git a/typescript/sdk/src/metadata/health.ts b/typescript/sdk/src/metadata/health.ts new file mode 100644 index 0000000000..d11ed7dd62 --- /dev/null +++ b/typescript/sdk/src/metadata/health.ts @@ -0,0 +1,152 @@ +import { Mailbox__factory } from '@hyperlane-xyz/core'; +import { Address, ProtocolType, timeout } from '@hyperlane-xyz/utils'; + +import { chainIdToMetadata } from '../consts/chainMetadata'; +import { CoreChainName } from '../consts/chains'; +import { hyperlaneContractAddresses } from '../consts/environments'; +import { logger } from '../logger'; +import { + CosmJsProvider, + CosmJsWasmProvider, + EthersV5Provider, + ProviderType, + SolanaWeb3Provider, +} from '../providers/ProviderType'; +import { protocolToDefaultProviderBuilder } from '../providers/providerBuilders'; + +import { + getExplorerAddressUrl, + getExplorerBaseUrl, + getExplorerTxUrl, +} from './blockExplorer'; +import { ChainMetadata, RpcUrl } from './chainMetadataTypes'; + +const HEALTH_CHECK_TIMEOUT = 5000; // 5s + +export async function isRpcHealthy( + rpc: RpcUrl, + chainId: string | number, + protocol: ProtocolType, +): Promise { + try { + const builder = protocolToDefaultProviderBuilder[protocol]; + const provider = builder([rpc], chainId); + let resultPromise; + if (provider.type === ProviderType.EthersV5) + resultPromise = isEthersV5ProviderHealthy(provider.provider, chainId); + else if (provider.type === ProviderType.SolanaWeb3) + resultPromise = isSolanaWeb3ProviderHealthy(provider.provider, chainId); + else if ( + provider.type === ProviderType.CosmJsWasm || + provider.type === ProviderType.CosmJs + ) + resultPromise = isCosmJsProviderHealthy(provider.provider, chainId); + else + throw new Error( + `Unsupported provider type ${provider.type}, new health check required`, + ); + const result = await timeout( + resultPromise, + HEALTH_CHECK_TIMEOUT, + 'RPC health check timed out', + ); + return result; + } catch (error) { + logger(`Provider error for ${rpc.http}`, error); + return false; + } +} + +export async function isEthersV5ProviderHealthy( + provider: EthersV5Provider['provider'], + chainId: string | number, +): Promise { + logger(`Checking ethers provider for ${chainId}`); + const blockNumber = await provider.getBlockNumber(); + if (!blockNumber || blockNumber < 0) return false; + logger(`Block number is okay for ${chainId}`); + + const chainName = chainIdToMetadata[chainId]?.name as CoreChainName; + if (chainName && hyperlaneContractAddresses[chainName]) { + const mailboxAddr = hyperlaneContractAddresses[chainName].mailbox; + const mailbox = Mailbox__factory.createInterface(); + const topics = mailbox.encodeFilterTopics( + mailbox.events['DispatchId(bytes32)'], + [], + ); + logger(`Checking mailbox logs for ${chainId}`); + const mailboxLogs = await provider.getLogs({ + address: mailboxAddr, + topics, + fromBlock: blockNumber - 99, + toBlock: blockNumber, + }); + if (!mailboxLogs) return false; + logger(`Mailbox logs okay for ${chainId}`); + } + return true; +} + +export async function isSolanaWeb3ProviderHealthy( + provider: SolanaWeb3Provider['provider'], + chainId: string | number, +): Promise { + logger(`Checking solana provider for ${chainId}`); + const blockNumber = await provider.getBlockHeight(); + if (!blockNumber || blockNumber < 0) return false; + logger(`Block number is okay for ${chainId}`); + return true; +} + +export async function isCosmJsProviderHealthy( + provider: CosmJsProvider['provider'] | CosmJsWasmProvider['provider'], + chainId: string | number, +): Promise { + const readyProvider = await provider; + const blockNumber = await readyProvider.getHeight(); + if (!blockNumber || blockNumber < 0) return false; + logger(`Block number is okay for ${chainId}`); + return true; +} + +export async function isBlockExplorerHealthy( + chainMetadata: ChainMetadata, + address?: Address, + txHash?: string, +): Promise { + try { + const baseUrl = getExplorerBaseUrl(chainMetadata); + if (!baseUrl) return false; + logger(`Got base url: ${baseUrl}`); + + logger(`Checking explorer home for ${chainMetadata.name}`); + const homeReq = await fetch(baseUrl); + if (!homeReq.ok) return false; + logger(`Explorer home okay for ${chainMetadata.name}`); + + if (address) { + logger(`Checking explorer address page for ${chainMetadata.name}`); + const addressUrl = getExplorerAddressUrl(chainMetadata, address); + if (!addressUrl) return false; + logger(`Got address url: ${addressUrl}`); + const addressReq = await fetch(addressUrl); + if (!addressReq.ok && addressReq.status !== 404) return false; + logger(`Explorer address page okay for ${chainMetadata.name}`); + } + + if (txHash) { + logger(`Checking explorer tx page for ${chainMetadata.name}`); + const txUrl = getExplorerTxUrl(chainMetadata, txHash); + if (!txUrl) return false; + logger(`Got tx url: ${txUrl}`); + const txReq = await fetch(txUrl); + if (!txReq.ok && txReq.status !== 404) return false; + logger(`Explorer tx page okay for ${chainMetadata.name}`); + } + + return true; + } catch (error) { + logger(`Explorer error for ${chainMetadata.name}`, error); + return false; + } +} diff --git a/typescript/sdk/src/metadata/warpRouteConfig.ts b/typescript/sdk/src/metadata/warpRouteConfig.ts new file mode 100644 index 0000000000..e8028c8155 --- /dev/null +++ b/typescript/sdk/src/metadata/warpRouteConfig.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; + +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { TokenType } from '../token/config'; +import { ChainMap } from '../types'; + +const TokenConfigSchema = z.object({ + protocolType: z.nativeEnum(ProtocolType), + type: z.nativeEnum(TokenType), + hypAddress: z.string(), // HypERC20Collateral, HypERC20Synthetic, HypNativeToken address + tokenAddress: z.string().optional(), // external token address needed for collateral type eg tokenAddress.balanceOf(hypAddress) + name: z.string(), + symbol: z.string(), + decimals: z.number(), + isSpl2022: z.boolean().optional(), // Solana Program Library 2022, sealevel specific + ibcDenom: z.string().optional(), // IBC denom for cosmos native token +}); + +export const WarpRouteConfigSchema = z.object({ + description: z.string().optional(), + timeStamp: z.string().optional(), // can make it non-optional if we make it part of the warp route deployment progress + deployer: z.string().optional(), + data: z.object({ config: z.record(TokenConfigSchema) }), +}); + +export type WarpRouteConfig = ChainMap>; diff --git a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts index f564342de7..329539150f 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; import { HyperlaneContracts } from '../../contracts/types'; +import { ContractVerifier } from '../../deploy/verify/ContractVerifier'; import { MultiProvider } from '../../providers/MultiProvider'; import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer'; import { ProxiedRouterConfig, RouterConfig } from '../../router/types'; @@ -20,8 +21,13 @@ export class InterchainAccountDeployer extends ProxiedRouterDeployer< > { readonly routerContractName = 'interchainAccountRouter'; - constructor(multiProvider: MultiProvider) { - super(multiProvider, interchainAccountFactories); + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { + super(multiProvider, interchainAccountFactories, { + contractVerifier, + }); } async constructorArgs(_: string, config: RouterConfig): Promise<[string]> { diff --git a/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts b/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts index 6e338f1167..1662fc4403 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts @@ -11,6 +11,7 @@ import { HyperlaneContracts, HyperlaneContractsMap, } from '../../contracts/types'; +import { ContractVerifier } from '../../deploy/verify/ContractVerifier'; import { MultiProvider } from '../../providers/MultiProvider'; import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer'; import { RouterConfig } from '../../router/types'; @@ -57,8 +58,13 @@ export class LiquidityLayerDeployer extends ProxiedRouterDeployer< > { readonly routerContractName = 'liquidityLayerRouter'; - constructor(multiProvider: MultiProvider) { - super(multiProvider, liquidityLayerFactories); + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { + super(multiProvider, liquidityLayerFactories, { + contractVerifier, + }); } async constructorArgs( diff --git a/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts b/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts index 588890c33f..51af12bb9e 100644 --- a/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts +++ b/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts @@ -1,5 +1,6 @@ import { ethers } from 'ethers'; +import { ContractVerifier } from '../../deploy/verify/ContractVerifier'; import { MultiProvider } from '../../providers/MultiProvider'; import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer'; import { RouterConfig } from '../../router/types'; @@ -18,8 +19,13 @@ export class InterchainQueryDeployer extends ProxiedRouterDeployer< > { readonly routerContractName = 'interchainQueryRouter'; - constructor(multiProvider: MultiProvider) { - super(multiProvider, interchainQueryFactories); + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { + super(multiProvider, interchainQueryFactories, { + contractVerifier, + }); } async constructorArgs(_: string, config: RouterConfig): Promise<[string]> { diff --git a/typescript/sdk/src/providers/MultiProtocolProvider.ts b/typescript/sdk/src/providers/MultiProtocolProvider.ts index 868c65bb6a..a304bfd033 100644 --- a/typescript/sdk/src/providers/MultiProtocolProvider.ts +++ b/typescript/sdk/src/providers/MultiProtocolProvider.ts @@ -1,17 +1,18 @@ import { Debugger, debug } from 'debug'; -import { ProtocolType, objFilter, objMap, pick } from '@hyperlane-xyz/utils'; +import { objFilter, objMap, pick } from '@hyperlane-xyz/utils'; import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager'; import type { ChainMetadata } from '../metadata/chainMetadataTypes'; -import type { ChainMap, ChainName } from '../types'; +import type { ChainMap, ChainName, ChainNameOrId } from '../types'; import { MultiProvider, MultiProviderOptions } from './MultiProvider'; import { CosmJsProvider, CosmJsWasmProvider, EthersV5Provider, + PROTOCOL_TO_DEFAULT_PROVIDER_TYPE, ProviderMap, ProviderType, SolanaWeb3Provider, @@ -23,14 +24,6 @@ import { defaultProviderBuilderMap, } from './providerBuilders'; -export const PROTOCOL_DEFAULT_PROVIDER_TYPE: Partial< - Record -> = { - [ProtocolType.Ethereum]: ProviderType.EthersV5, - [ProtocolType.Sealevel]: ProviderType.SolanaWeb3, - [ProtocolType.Cosmos]: ProviderType.CosmJsWasm, -}; - export interface MultiProtocolProviderOptions { loggerName?: string; providers?: ChainMap>; @@ -116,13 +109,13 @@ export class MultiProtocolProvider< } tryGetProvider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, type?: ProviderType, ): TypedProvider | null { const metadata = this.tryGetChainMetadata(chainNameOrId); if (!metadata) return null; const { protocol, name, chainId, rpcUrls } = metadata; - type = type || PROTOCOL_DEFAULT_PROVIDER_TYPE[protocol]; + type = type || PROTOCOL_TO_DEFAULT_PROVIDER_TYPE[protocol]; if (!type) return null; if (this.providers[name]?.[type]) return this.providers[name][type]!; @@ -137,7 +130,7 @@ export class MultiProtocolProvider< } getProvider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, type?: ProviderType, ): TypedProvider { const provider = this.tryGetProvider(chainNameOrId, type); @@ -147,7 +140,7 @@ export class MultiProtocolProvider< } protected getSpecificProvider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, type: ProviderType, ): T { const provider = this.getProvider(chainNameOrId, type); @@ -159,7 +152,7 @@ export class MultiProtocolProvider< } getEthersV5Provider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, ): EthersV5Provider['provider'] { return this.getSpecificProvider( chainNameOrId, @@ -167,7 +160,7 @@ export class MultiProtocolProvider< ); } - getViemProvider(chainNameOrId: ChainName | number): ViemProvider['provider'] { + getViemProvider(chainNameOrId: ChainNameOrId): ViemProvider['provider'] { return this.getSpecificProvider( chainNameOrId, ProviderType.Viem, @@ -175,7 +168,7 @@ export class MultiProtocolProvider< } getSolanaWeb3Provider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, ): SolanaWeb3Provider['provider'] { return this.getSpecificProvider( chainNameOrId, @@ -183,9 +176,7 @@ export class MultiProtocolProvider< ); } - getCosmJsProvider( - chainNameOrId: ChainName | number, - ): CosmJsProvider['provider'] { + getCosmJsProvider(chainNameOrId: ChainNameOrId): CosmJsProvider['provider'] { return this.getSpecificProvider( chainNameOrId, ProviderType.CosmJs, @@ -193,7 +184,7 @@ export class MultiProtocolProvider< } getCosmJsWasmProvider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, ): CosmJsWasmProvider['provider'] { return this.getSpecificProvider( chainNameOrId, @@ -202,7 +193,7 @@ export class MultiProtocolProvider< } setProvider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, provider: TypedProvider, ): TypedProvider { const chainName = this.getChainName(chainNameOrId); diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index bef14b9994..85e04c06e6 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -15,13 +15,9 @@ import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata'; import { CoreChainName, TestChains } from '../consts/chains'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager'; import { ChainMetadata } from '../metadata/chainMetadataTypes'; -import { ChainMap, ChainName } from '../types'; +import { ChainMap, ChainName, ChainNameOrId } from '../types'; -import { - DEFAULT_RETRY_OPTIONS, - ProviderBuilderFn, - defaultProviderBuilder, -} from './providerBuilders'; +import { ProviderBuilderFn, defaultProviderBuilder } from './providerBuilders'; type Provider = providers.Provider; @@ -78,7 +74,7 @@ export class MultiProvider extends ChainMetadataManager { /** * Get an Ethers provider for a given chain name, chain id, or domain id */ - tryGetProvider(chainNameOrId: ChainName | number): Provider | null { + tryGetProvider(chainNameOrId: ChainNameOrId): Provider | null { const metadata = this.tryGetChainMetadata(chainNameOrId); if (!metadata) return null; const { name, chainId, rpcUrls } = metadata; @@ -91,11 +87,7 @@ export class MultiProvider extends ChainMetadataManager { 31337, ); } else if (rpcUrls.length) { - this.providers[name] = this.providerBuilder( - rpcUrls, - chainId, - DEFAULT_RETRY_OPTIONS, - ); + this.providers[name] = this.providerBuilder(rpcUrls, chainId); } else { return null; } @@ -107,7 +99,7 @@ export class MultiProvider extends ChainMetadataManager { * Get an Ethers provider for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - getProvider(chainNameOrId: ChainName | number): Provider { + getProvider(chainNameOrId: ChainNameOrId): Provider { const provider = this.tryGetProvider(chainNameOrId); if (!provider) throw new Error(`No chain metadata set for ${chainNameOrId}`); @@ -118,7 +110,7 @@ export class MultiProvider extends ChainMetadataManager { * Sets an Ethers provider for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set */ - setProvider(chainNameOrId: ChainName | number, provider: Provider): Provider { + setProvider(chainNameOrId: ChainNameOrId, provider: Provider): Provider { const chainName = this.getChainName(chainNameOrId); this.providers[chainName] = provider; const signer = this.signers[chainName]; @@ -143,7 +135,7 @@ export class MultiProvider extends ChainMetadataManager { * Get an Ethers signer for a given chain name, chain id, or domain id * If signer is not yet connected, it will be connected */ - tryGetSigner(chainNameOrId: ChainName | number): Signer | null { + tryGetSigner(chainNameOrId: ChainNameOrId): Signer | null { const chainName = this.tryGetChainName(chainNameOrId); if (!chainName) return null; const signer = this.signers[chainName]; @@ -159,7 +151,7 @@ export class MultiProvider extends ChainMetadataManager { * If signer is not yet connected, it will be connected * @throws if chain's metadata or signer has not been set */ - getSigner(chainNameOrId: ChainName | number): Signer { + getSigner(chainNameOrId: ChainNameOrId): Signer { const signer = this.tryGetSigner(chainNameOrId); if (!signer) throw new Error(`No chain signer set for ${chainNameOrId}`); return signer; @@ -169,7 +161,7 @@ export class MultiProvider extends ChainMetadataManager { * Get an Ethers signer for a given chain name, chain id, or domain id * @throws if chain's metadata or signer has not been set */ - async getSignerAddress(chainNameOrId: ChainName | number): Promise
{ + async getSignerAddress(chainNameOrId: ChainNameOrId): Promise
{ const signer = this.getSigner(chainNameOrId); const address = await signer.getAddress(); return address; @@ -179,7 +171,7 @@ export class MultiProvider extends ChainMetadataManager { * Sets an Ethers Signer for a given chain name, chain id, or domain id * @throws if chain's metadata has not been set or shared signer has already been set */ - setSigner(chainNameOrId: ChainName | number, signer: Signer): Signer { + setSigner(chainNameOrId: ChainNameOrId, signer: Signer): Signer { if (this.useSharedSigner) { throw new Error('MultiProvider already set to use a shared signer'); } @@ -209,7 +201,7 @@ export class MultiProvider extends ChainMetadataManager { * Gets the Signer if it's been set, otherwise the provider */ tryGetSignerOrProvider( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, ): Signer | Provider | null { return ( this.tryGetSigner(chainNameOrId) || this.tryGetProvider(chainNameOrId) @@ -220,7 +212,7 @@ export class MultiProvider extends ChainMetadataManager { * Gets the Signer if it's been set, otherwise the provider * @throws if chain metadata has not been set */ - getSignerOrProvider(chainNameOrId: ChainName | number): Signer | Provider { + getSignerOrProvider(chainNameOrId: ChainNameOrId): Signer | Provider { return this.tryGetSigner(chainNameOrId) || this.getProvider(chainNameOrId); } @@ -266,7 +258,7 @@ export class MultiProvider extends ChainMetadataManager { * Get a block explorer URL for given chain's address */ override async tryGetExplorerAddressUrl( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, address?: string, ): Promise { if (address) return super.tryGetExplorerAddressUrl(chainNameOrId, address); @@ -283,7 +275,7 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata has not been set */ getTransactionOverrides( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, ): Partial { return this.getChainMetadata(chainNameOrId)?.transactionOverrides ?? {}; } @@ -293,14 +285,29 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata or signer has not been set or tx fails */ async handleDeploy( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, factory: F, params: Parameters, ): Promise>> { + // setup contract factory const overrides = this.getTransactionOverrides(chainNameOrId); const signer = this.getSigner(chainNameOrId); - const contract = await factory.connect(signer).deploy(...params, overrides); + const contractFactory = await factory.connect(signer); + + // estimate gas + const deployTx = contractFactory.getDeployTransaction(...params, overrides); + const gasEstimated = await signer.estimateGas(deployTx); + + // deploy with 10% buffer on gas limit + const contract = await contractFactory.deploy(...params, { + ...overrides, + gasLimit: gasEstimated.add(gasEstimated.div(10)), // 10% buffer + }); + + // wait for deploy tx to be confirmed await this.handleTx(chainNameOrId, contract.deployTransaction); + + // return deployed contract return contract as Awaited>; } @@ -309,11 +316,11 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata or signer has not been set or tx fails */ async handleTx( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, tx: ContractTransaction | Promise, ): Promise { const confirmations = - this.getChainMetadata(chainNameOrId).blocks?.confirmations || 1; + this.getChainMetadata(chainNameOrId).blocks?.confirmations ?? 1; const response = await tx; const txUrl = this.tryGetExplorerTxUrl(chainNameOrId, response); this.logger( @@ -329,7 +336,7 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata has not been set or tx fails */ async prepareTx( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, tx: PopulatedTransaction, from?: string, ): Promise { @@ -347,7 +354,7 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata has not been set or tx fails */ async estimateGas( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, tx: PopulatedTransaction, from?: string, ): Promise { @@ -368,7 +375,7 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata or signer has not been set or tx fails */ async sendTransaction( - chainNameOrId: ChainName | number, + chainNameOrId: ChainNameOrId, tx: PopulatedTransaction | Promise, ): Promise { const txReq = await this.prepareTx(chainNameOrId, await tx); diff --git a/typescript/sdk/src/providers/ProviderType.ts b/typescript/sdk/src/providers/ProviderType.ts index b59a71e1bc..5463b82bf4 100644 --- a/typescript/sdk/src/providers/ProviderType.ts +++ b/typescript/sdk/src/providers/ProviderType.ts @@ -23,6 +23,8 @@ import type { TransactionReceipt as VTransactionReceipt, } from 'viem'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + export enum ProviderType { EthersV5 = 'ethers-v5', // EthersV6 = 'ethers-v6', Disabled for now to simplify build tooling @@ -30,8 +32,20 @@ export enum ProviderType { SolanaWeb3 = 'solana-web3', CosmJs = 'cosmjs', CosmJsWasm = 'cosmjs-wasm', + // TODO fuel provider types not yet defined below + Fuel = 'fuel', } +export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record< + ProtocolType, + ProviderType +> = { + [ProtocolType.Ethereum]: ProviderType.EthersV5, + [ProtocolType.Sealevel]: ProviderType.SolanaWeb3, + [ProtocolType.Cosmos]: ProviderType.CosmJsWasm, + [ProtocolType.Fuel]: ProviderType.Fuel, +}; + export type ProviderMap = Partial>; /** @@ -172,7 +186,7 @@ export interface CosmJsTransaction extends TypedTransactionBase { export interface CosmJsWasmTransaction extends TypedTransactionBase { - type: ProviderType.CosmJs; + type: ProviderType.CosmJsWasm; transaction: ExecuteInstruction; } diff --git a/typescript/sdk/src/providers/RetryProvider.ts b/typescript/sdk/src/providers/RetryProvider.ts deleted file mode 100644 index 3f7950f1cf..0000000000 --- a/typescript/sdk/src/providers/RetryProvider.ts +++ /dev/null @@ -1,42 +0,0 @@ -// RetryProvider Mostly taken from the removed version that was in ethers.js -// See: https://github.com/ethers-io/ethers.js/discussions/3006 -import { ethers } from 'ethers'; - -import { assert, retryAsync } from '@hyperlane-xyz/utils'; - -export type RetryProviderOptions = { - // Maximum number of times to make the RPC - maxRequests: number; - - // Exponential backoff base value - baseRetryMs: number; -}; - -export class RetryJsonRpcProvider extends ethers.providers - .StaticJsonRpcProvider { - public readonly retryOptions: RetryProviderOptions; - constructor( - retryOptions: RetryProviderOptions, - url?: ethers.utils.ConnectionInfo | string, - network?: ethers.providers.Networkish, - ) { - super(url, network); - assert( - retryOptions.maxRequests >= 1, - 'RetryOptions.maxRequests must be >= 1', - ); - assert( - retryOptions.baseRetryMs >= 1, - 'RetryOptions.baseRetryMs must be >= 1', - ); - this.retryOptions = retryOptions; - } - - async send(method: string, params: Array): Promise { - return retryAsync( - () => super.send(method, params), - this.retryOptions.maxRequests, - this.retryOptions.baseRetryMs, - ); - } -} diff --git a/typescript/sdk/src/providers/SmartProvider/HyperlaneEtherscanProvider.ts b/typescript/sdk/src/providers/SmartProvider/HyperlaneEtherscanProvider.ts new file mode 100644 index 0000000000..25acd5344b --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/HyperlaneEtherscanProvider.ts @@ -0,0 +1,142 @@ +import debug from 'debug'; +import { providers } from 'ethers'; + +import { objFilter, sleep } from '@hyperlane-xyz/utils'; + +import { BlockExplorer } from '../../metadata/chainMetadataTypes'; + +import { + IProviderMethods, + ProviderMethod, + excludeProviderMethods, +} from './ProviderMethods'; + +// Used for crude rate-limiting of explorer queries without API keys +const hostToLastQueried: Record = {}; +const ETHERSCAN_THROTTLE_TIME = 6000; // 6.0 seconds + +export class HyperlaneEtherscanProvider + extends providers.EtherscanProvider + implements IProviderMethods +{ + protected readonly logger = debug('hyperlane:EtherscanProvider'); + // Seeing problems with these two methods even though etherscan api claims to support them + public readonly supportedMethods = excludeProviderMethods([ + ProviderMethod.Call, + ProviderMethod.EstimateGas, + ProviderMethod.SendTransaction, + ]); + + constructor( + public readonly explorerConfig: BlockExplorer, + network: providers.Networkish, + public readonly options?: { debug?: boolean }, + ) { + super(network, explorerConfig.apiKey); + if (!explorerConfig.apiKey) { + this.logger( + 'HyperlaneEtherscanProviders created without an API key will be severely rate limited. Consider using an API key for better reliability.', + ); + } + } + + getBaseUrl(): string { + if (!this.explorerConfig) return ''; // Constructor net yet finished + const apiUrl = this.explorerConfig?.apiUrl; + if (!apiUrl) throw new Error('Explorer config missing apiUrl'); + if (apiUrl.endsWith('/api')) return apiUrl.slice(0, -4); + return apiUrl; + } + + getUrl(module: string, params: Record): string { + const combinedParams = objFilter(params, (k, v): v is string => !!k && !!v); + combinedParams['module'] = module; + if (this.apiKey) combinedParams['apikey'] = this.apiKey; + const parsedParams = new URLSearchParams(combinedParams); + return `${this.getBaseUrl()}/api?${parsedParams.toString()}`; + } + + getPostUrl(): string { + return `${this.getBaseUrl()}/api`; + } + + getHostname(): string { + return new URL(this.getBaseUrl()).hostname; + } + + getQueryWaitTime(): number { + if (!this.isCommunityResource()) return 0; + const hostname = this.getHostname(); + const lastExplorerQuery = hostToLastQueried[hostname] || 0; + return ETHERSCAN_THROTTLE_TIME - (Date.now() - lastExplorerQuery); + } + + async fetch( + module: string, + params: Record, + post?: boolean, + ): Promise { + if (!this.isCommunityResource()) return super.fetch(module, params, post); + + const hostname = this.getHostname(); + let waitTime = this.getQueryWaitTime(); + while (waitTime > 0) { + if (this.options?.debug) + this.logger( + `HyperlaneEtherscanProvider waiting ${waitTime}ms to avoid rate limit`, + ); + await sleep(waitTime); + waitTime = this.getQueryWaitTime(); + } + + hostToLastQueried[hostname] = Date.now(); + return super.fetch(module, params, post); + } + + async perform(method: string, params: any, reqId?: number): Promise { + if (this.options?.debug) + this.logger( + `HyperlaneEtherscanProvider performing method ${method} for reqId ${reqId}`, + ); + if (!this.supportedMethods.includes(method as ProviderMethod)) + throw new Error(`Unsupported method ${method}`); + + if (method === ProviderMethod.GetLogs) { + return this.performGetLogs(params); + } else { + return super.perform(method, params); + } + } + + // Overriding to allow more than one topic value + async performGetLogs(params: { filter: providers.Filter }): Promise { + const args: Record = { action: 'getLogs' }; + if (params.filter.fromBlock) + args.fromBlock = checkLogTag(params.filter.fromBlock); + if (params.filter.toBlock) + args.toBlock = checkLogTag(params.filter.toBlock); + if (params.filter.address) args.address = params.filter.address; + const topics = params.filter.topics; + if (topics?.length) { + if (topics.length > 2) + throw new Error(`Unsupported topic count ${topics.length} (max 2)`); + for (let i = 0; i < topics.length; i++) { + const topic = topics[i]; + if (!topic || typeof topic !== 'string' || topic.length !== 66) + throw new Error(`Unsupported topic format: ${topic}`); + args[`topic${i}`] = topic; + if (i < topics.length - 1) args[`topic${i}_${i + 1}_opr`] = 'and'; + } + } + + return this.fetch('logs', args); + } +} + +// From ethers/providers/src.ts/providers/etherscan-provider.ts +function checkLogTag(blockTag: providers.BlockTag): number | 'latest' { + if (typeof blockTag === 'number') return blockTag; + if (blockTag === 'pending') throw new Error('pending not supported'); + if (blockTag === 'latest') return blockTag; + return parseInt(blockTag.substring(2), 16); +} diff --git a/typescript/sdk/src/providers/SmartProvider/HyperlaneJsonRpcProvider.ts b/typescript/sdk/src/providers/SmartProvider/HyperlaneJsonRpcProvider.ts new file mode 100644 index 0000000000..1a3ffbe6b5 --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/HyperlaneJsonRpcProvider.ts @@ -0,0 +1,151 @@ +import debug from 'debug'; +import { BigNumber, providers, utils } from 'ethers'; + +import { chunk, isBigNumberish, isNullish } from '@hyperlane-xyz/utils'; + +import { + AllProviderMethods, + IProviderMethods, + ProviderMethod, +} from './ProviderMethods'; +import { RpcConfigWithConnectionInfo } from './types'; + +const NUM_LOG_BLOCK_RANGES_TO_QUERY = 10; +const NUM_PARALLEL_LOG_QUERIES = 5; + +export class HyperlaneJsonRpcProvider + extends providers.StaticJsonRpcProvider + implements IProviderMethods +{ + protected readonly logger = debug('hyperlane:JsonRpcProvider'); + public readonly supportedMethods = AllProviderMethods; + + constructor( + public readonly rpcConfig: RpcConfigWithConnectionInfo, + network: providers.Networkish, + public readonly options?: { debug?: boolean }, + ) { + super(rpcConfig.connection ?? rpcConfig.http, network); + } + + async perform(method: string, params: any, reqId?: number): Promise { + if (this.options?.debug) + this.logger( + `HyperlaneJsonRpcProvider performing method ${method} for reqId ${reqId}`, + ); + if (method === ProviderMethod.GetLogs) { + return this.performGetLogs(params); + } + + const result = await super.perform(method, params); + if ( + result === '0x' && + [ + ProviderMethod.Call, + ProviderMethod.GetBalance, + ProviderMethod.GetBlock, + ProviderMethod.GetBlockNumber, + ].includes(method as ProviderMethod) + ) { + this.logger(`Received 0x result from ${method} for reqId ${reqId}.`); + throw new Error('Invalid response from provider'); + } + return result; + } + + async performGetLogs(params: { filter: providers.Filter }): Promise { + const superPerform = () => super.perform(ProviderMethod.GetLogs, params); + + const paginationOptions = this.rpcConfig.pagination; + if (!paginationOptions || !params.filter) return superPerform(); + + const { fromBlock, toBlock, address, topics } = params.filter; + const { maxBlockRange, minBlockNumber, maxBlockAge } = paginationOptions; + + if (!maxBlockRange && !maxBlockAge && isNullish(minBlockNumber)) + return superPerform(); + + const currentBlockNumber = await super.perform( + ProviderMethod.GetBlockNumber, + null, + ); + + let endBlock: number; + if (isNullish(toBlock) || toBlock === 'latest') { + endBlock = currentBlockNumber; + } else if (isBigNumberish(toBlock)) { + endBlock = BigNumber.from(toBlock).toNumber(); + } else { + return superPerform(); + } + + let startBlock: number; + if (isNullish(fromBlock) || fromBlock === 'earliest') { + startBlock = 0; + } else if (isBigNumberish(fromBlock)) { + startBlock = BigNumber.from(fromBlock).toNumber(); + } else { + return superPerform(); + } + + if (startBlock > endBlock) { + this.logger( + `Start block ${startBlock} greater than end block. Using ${endBlock} instead`, + ); + startBlock = endBlock; + } + const minForBlockRange = maxBlockRange + ? endBlock - maxBlockRange * NUM_LOG_BLOCK_RANGES_TO_QUERY + 1 + : 0; + if (startBlock < minForBlockRange) { + this.logger( + `Start block ${startBlock} requires too many queries, using ${minForBlockRange}.`, + ); + startBlock = minForBlockRange; + } + const minForBlockAge = maxBlockAge ? currentBlockNumber - maxBlockAge : 0; + if (startBlock < minForBlockAge) { + this.logger( + `Start block ${startBlock} below max block age, increasing to ${minForBlockAge}`, + ); + startBlock = minForBlockAge; + } + if (minBlockNumber && startBlock < minBlockNumber) { + this.logger( + `Start block ${startBlock} below config min, increasing to ${minBlockNumber}`, + ); + startBlock = minBlockNumber; + } + + const blockChunkRange = maxBlockRange || endBlock - startBlock; + const blockChunks: [number, number][] = []; + for (let from = startBlock; from <= endBlock; from += blockChunkRange) { + const to = Math.min(from + blockChunkRange - 1, endBlock); + blockChunks.push([from, to]); + } + + let combinedResults: Array = []; + const requestChunks = chunk(blockChunks, NUM_PARALLEL_LOG_QUERIES); + for (const reqChunk of requestChunks) { + const resultPromises = reqChunk.map( + (blockChunk) => + super.perform(ProviderMethod.GetLogs, { + filter: { + address, + topics, + fromBlock: utils.hexValue(BigNumber.from(blockChunk[0])), + toBlock: utils.hexValue(BigNumber.from(blockChunk[1])), + }, + }) as Promise>, + ); + const results = await Promise.all(resultPromises); + combinedResults = [...combinedResults, ...results.flat()]; + } + + return combinedResults; + } + + getBaseUrl(): string { + return this.connection.url; + } +} diff --git a/typescript/sdk/src/providers/SmartProvider/ProviderMethods.ts b/typescript/sdk/src/providers/SmartProvider/ProviderMethods.ts new file mode 100644 index 0000000000..2a36c65990 --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/ProviderMethods.ts @@ -0,0 +1,27 @@ +export interface IProviderMethods { + readonly supportedMethods: ProviderMethod[]; +} + +export enum ProviderMethod { + Call = 'call', + EstimateGas = 'estimateGas', + GetBalance = 'getBalance', + GetBlock = 'getBlock', + GetBlockNumber = 'getBlockNumber', + GetCode = 'getCode', + GetGasPrice = 'getGasPrice', + GetStorageAt = 'getStorageAt', + GetTransaction = 'getTransaction', + GetTransactionCount = 'getTransactionCount', + GetTransactionReceipt = 'getTransactionReceipt', + GetLogs = 'getLogs', + SendTransaction = 'sendTransaction', +} + +export const AllProviderMethods = Object.values(ProviderMethod); + +export function excludeProviderMethods( + exclude: ProviderMethod[], +): ProviderMethod[] { + return AllProviderMethods.filter((m) => !exclude.includes(m)); +} diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.foundry-test.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.foundry-test.ts new file mode 100644 index 0000000000..8b55e33fd3 --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.foundry-test.ts @@ -0,0 +1,84 @@ +import { expect } from 'chai'; +import { Wallet, constants } from 'ethers'; + +import { ERC20__factory } from '@hyperlane-xyz/core'; + +import { HyperlaneSmartProvider } from './SmartProvider'; + +const PK = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; +const NETWORK = 31337; +const URL = 'http://127.0.0.1:8545'; + +describe('SmartProvider', async () => { + let signer: Wallet; + let smartProvider: HyperlaneSmartProvider; + let contractAddress: string; + + before(async () => { + smartProvider = HyperlaneSmartProvider.fromRpcUrl(NETWORK, URL, { + maxRetries: 3, + }); + signer = new Wallet(PK, smartProvider); + }); + + it('Sends transactions', async () => { + const transferTx = await signer.populateTransaction({ + to: signer.address, + value: 1, + }); + const signedTx = await signer.signTransaction(transferTx); + const response = await smartProvider.sendTransaction(signedTx); + expect(response.hash.substring(0, 2)).to.equal('0x'); + expect(response.hash.length).to.equal(66); + }); + + it('Deploys contracts', async () => { + const factory = new ERC20__factory(signer); + const contract = await factory.deploy('fake', 'FAKE'); + contractAddress = contract.address; + expect(contractAddress.substring(0, 2)).to.equal('0x'); + expect(contractAddress.length).to.equal(42); + }); + + it('Handles multiple requests', async () => { + const [ + isHealthy, + blockNum, + block, + balance, + gasPrice, + feeData, + code, + txCount, + network, + logs, + ] = await Promise.all([ + smartProvider.isHealthy(), + smartProvider.getBlockNumber(), + smartProvider.getBlock(1), + smartProvider.getBalance(signer.address), + smartProvider.getGasPrice(), + smartProvider.getFeeData(), + smartProvider.getCode(contractAddress), + smartProvider.getTransactionCount(signer.address), + smartProvider.getNetwork(), + smartProvider.getLogs({ + fromBlock: 0, + address: constants.AddressZero, + topics: [], + }), + ]); + + expect(isHealthy).to.be.true; + expect(blockNum).to.greaterThan(0); + expect(block.number).to.equal(1); + expect(balance.toBigInt() > 0).to.be.true; + expect(gasPrice.toBigInt() > 0).to.be.true; + expect(feeData.maxFeePerGas && feeData.maxFeePerGas.toBigInt() > 0).to.be + .true; + expect(code.length).to.greaterThan(10); + expect(txCount).to.be.greaterThan(0); + expect(network.chainId).to.equal(NETWORK); + expect(Array.isArray(logs)).to.be.true; + }); +}); diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.test.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.test.ts new file mode 100644 index 0000000000..64f2ec6a72 --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.test.ts @@ -0,0 +1,197 @@ +/* eslint-disable no-console */ +import { expect } from 'chai'; +import { ethers } from 'ethers'; + +import { eqAddress } from '@hyperlane-xyz/utils'; + +import { chainMetadata } from '../../consts/chainMetadata'; +import { ChainMetadata } from '../../metadata/chainMetadataTypes'; + +import { ProviderMethod } from './ProviderMethods'; +import { HyperlaneSmartProvider } from './SmartProvider'; + +const DEFAULT_ACCOUNT = '0x9d525E28Fe5830eE92d7Aa799c4D21590567B595'; +const WETH_CONTRACT = '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6'; +const WETH_TRANSFER_TOPIC0 = + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; +const WETH_CALL_DATA = + '0x70a082310000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38e'; +const TRANSFER_TX_HASH = + '0x45a586f90ffd5d0f8e618f0f3703b14c2c9e4611af6231d6fed32c62776b6c1b'; + +const pagination = { maxBlockRange: 1000 }; +const goerliRpcConfig1 = { ...chainMetadata.goerli.rpcUrls[0], pagination }; +const goerliRpcConfig2 = { ...chainMetadata.goerli.rpcUrls[1], pagination }; +const justExplorersConfig: ChainMetadata = { + ...chainMetadata.goerli, + rpcUrls: [] as any, +}; +const justRpcsConfig: ChainMetadata = { + ...chainMetadata.goerli, + rpcUrls: [goerliRpcConfig1, goerliRpcConfig2], + blockExplorers: [], +}; +const combinedConfig: ChainMetadata = { + ...chainMetadata.goerli, + rpcUrls: [goerliRpcConfig1], +}; +const configs: [string, ChainMetadata][] = [ + ['Just Explorers', justExplorersConfig], + ['Just RPCs', justRpcsConfig], + ['Combined configs', combinedConfig], +]; + +describe('SmartProvider', () => { + let provider: HyperlaneSmartProvider; + + const itDoesIfSupported = (method: ProviderMethod, fn: () => any) => { + it(method, () => { + if (provider.supportedMethods.includes(method)) { + return fn(); + } + }).timeout(30_000); + }; + + for (const [description, config] of configs) { + describe(description, () => { + provider = HyperlaneSmartProvider.fromChainMetadata(config, { + debug: true, + baseRetryDelayMs: 1000, + fallbackStaggerMs: 3000, + maxRetries: 3, + }); + + itDoesIfSupported(ProviderMethod.GetBlock, async () => { + const latestBlock = await provider.getBlock('latest'); + console.debug('Latest block #', latestBlock.number); + expect(latestBlock.number).to.be.greaterThan(0); + expect(latestBlock.timestamp).to.be.greaterThan( + Date.now() / 1000 - 60 * 60 * 24, + ); + const firstBlock = await provider.getBlock(1); + expect(firstBlock.number).to.equal(1); + }); + + itDoesIfSupported(ProviderMethod.GetBlockNumber, async () => { + const result = await provider.getBlockNumber(); + console.debug('Latest block #', result); + expect(result).to.be.greaterThan(0); + }); + + itDoesIfSupported(ProviderMethod.GetGasPrice, async () => { + const result = await provider.getGasPrice(); + console.debug('Gas price', result.toString()); + expect(result.toNumber()).to.be.greaterThan(0); + }); + + itDoesIfSupported(ProviderMethod.GetBalance, async () => { + const result = await provider.getBalance(DEFAULT_ACCOUNT); + console.debug('Balance', result.toString()); + expect(parseFloat(ethers.utils.formatEther(result))).to.be.greaterThan( + 1, + ); + }); + + itDoesIfSupported(ProviderMethod.GetCode, async () => { + const result = await provider.getCode(WETH_CONTRACT); + console.debug('Weth code snippet', result.substring(0, 12)); + expect(result.length).to.be.greaterThan(100); + }); + + itDoesIfSupported(ProviderMethod.GetStorageAt, async () => { + const result = await provider.getStorageAt(WETH_CONTRACT, 0); + console.debug('Weth storage', result); + expect(result.length).to.be.greaterThan(20); + }); + + itDoesIfSupported(ProviderMethod.GetTransactionCount, async () => { + const result = await provider.getTransactionCount( + DEFAULT_ACCOUNT, + 'latest', + ); + console.debug('Tx Count', result); + expect(result).to.be.greaterThan(40); + }); + + itDoesIfSupported(ProviderMethod.GetTransaction, async () => { + const result = await provider.getTransaction(TRANSFER_TX_HASH); + console.debug('Transaction confirmations', result.confirmations); + expect(result.confirmations).to.be.greaterThan(1000); + }); + + itDoesIfSupported(ProviderMethod.GetTransactionReceipt, async () => { + const result = await provider.getTransactionReceipt(TRANSFER_TX_HASH); + console.debug('Transaction receipt', result.confirmations); + expect(result.confirmations).to.be.greaterThan(1000); + }); + + itDoesIfSupported(ProviderMethod.GetLogs, async () => { + const latestBlockNumber = await provider.getBlockNumber(); + const minBlockNumber = latestBlockNumber - 10_000; + + console.debug('Testing logs with small from/to range'); + const result1 = await provider.getLogs({ + address: WETH_CONTRACT, + topics: [WETH_TRANSFER_TOPIC0], + fromBlock: minBlockNumber, + toBlock: minBlockNumber + 100, + }); + expect(result1.length).to.be.greaterThan(0); + expect(eqAddress(result1[0].address, WETH_CONTRACT)).to.be.true; + + console.debug('Testing logs with large from/to range'); + const result2 = await provider.getLogs({ + address: WETH_CONTRACT, + topics: [WETH_TRANSFER_TOPIC0], + fromBlock: minBlockNumber, + toBlock: 'latest', + }); + expect(result2.length).to.be.greaterThan(0); + expect(eqAddress(result2[0].address, WETH_CONTRACT)).to.be.true; + }); + + itDoesIfSupported(ProviderMethod.EstimateGas, async () => { + const result = await provider.estimateGas({ + to: DEFAULT_ACCOUNT, + from: DEFAULT_ACCOUNT, + value: 1, + }); + expect(result.toNumber()).to.be.greaterThan(10_000); + }); + + itDoesIfSupported(ProviderMethod.Call, async () => { + const result = await provider.call({ + to: WETH_CONTRACT, + from: DEFAULT_ACCOUNT, + data: WETH_CALL_DATA, + }); + expect(result).to.equal( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ); + }); + + it('Handles parallel requests', async () => { + const result1Promise = provider.call({ + to: WETH_CONTRACT, + from: DEFAULT_ACCOUNT, + data: WETH_CALL_DATA, + }); + const result2Promise = provider.getBlockNumber(); + const result3Promise = provider.getTransaction(TRANSFER_TX_HASH); + const [result1, result2, result3] = await Promise.all([ + result1Promise, + result2Promise, + result3Promise, + ]); + expect(result1.length).to.be.greaterThan(0); + expect(result2).to.be.greaterThan(0); + expect(!!result3).to.be.true; + }).timeout(15_000); + }); + + it('Reports as healthy', async () => { + const result = await provider.isHealthy(); + expect(result).to.be.true; + }); + } +}); diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts new file mode 100644 index 0000000000..29c1eb0511 --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts @@ -0,0 +1,384 @@ +import debug, { Debugger } from 'debug'; +import { providers } from 'ethers'; + +import { + raceWithContext, + retryAsync, + runWithTimeout, + sleep, +} from '@hyperlane-xyz/utils'; + +import { + BlockExplorer, + ChainMetadata, + ExplorerFamily, + RpcUrl, +} from '../../metadata/chainMetadataTypes'; + +import { HyperlaneEtherscanProvider } from './HyperlaneEtherscanProvider'; +import { HyperlaneJsonRpcProvider } from './HyperlaneJsonRpcProvider'; +import { IProviderMethods, ProviderMethod } from './ProviderMethods'; +import { + ChainMetadataWithRpcConnectionInfo, + ProviderPerformResult, + ProviderStatus, + ProviderTimeoutResult, + SmartProviderOptions, +} from './types'; + +const DEFAULT_MAX_RETRIES = 1; +const DEFAULT_BASE_RETRY_DELAY_MS = 250; // 0.25 seconds +const DEFAULT_STAGGER_DELAY_MS = 1000; // 1 seconds + +type HyperlaneProvider = HyperlaneEtherscanProvider | HyperlaneJsonRpcProvider; + +export class HyperlaneSmartProvider + extends providers.BaseProvider + implements IProviderMethods +{ + protected logger: Debugger; + + // TODO also support blockscout here + public readonly explorerProviders: HyperlaneEtherscanProvider[]; + public readonly rpcProviders: HyperlaneJsonRpcProvider[]; + public readonly supportedMethods: ProviderMethod[]; + public requestCount = 0; + + constructor( + network: providers.Networkish, + rpcUrls?: RpcUrl[], + blockExplorers?: BlockExplorer[], + public readonly options?: SmartProviderOptions, + ) { + super(network); + const supportedMethods = new Set(); + + this.logger = debug(`hyperlane:SmartProvider:${this.network.chainId}`); + + if (!rpcUrls?.length && !blockExplorers?.length) + throw new Error('At least one RPC URL or block explorer is required'); + + if (blockExplorers?.length) { + this.explorerProviders = blockExplorers + .map((explorerConfig) => { + if ( + !explorerConfig.family || + explorerConfig.family === ExplorerFamily.Etherscan + ) { + const newProvider = new HyperlaneEtherscanProvider( + explorerConfig, + network, + ); + newProvider.supportedMethods.forEach((m) => + supportedMethods.add(m), + ); + return newProvider; + // TODO also support blockscout here + } else return null; + }) + .filter((e): e is HyperlaneEtherscanProvider => !!e); + } else { + this.explorerProviders = []; + } + + if (rpcUrls?.length) { + this.rpcProviders = rpcUrls.map((rpcConfig) => { + const newProvider = new HyperlaneJsonRpcProvider(rpcConfig, network); + newProvider.supportedMethods.forEach((m) => supportedMethods.add(m)); + return newProvider; + }); + } else { + this.rpcProviders = []; + } + + this.supportedMethods = [...supportedMethods.values()]; + } + + static fromChainMetadata( + chainMetadata: ChainMetadataWithRpcConnectionInfo, + options?: SmartProviderOptions, + ): HyperlaneSmartProvider { + const network = chainMetadataToProviderNetwork(chainMetadata); + return new HyperlaneSmartProvider( + network, + chainMetadata.rpcUrls, + chainMetadata.blockExplorers, + options, + ); + } + + static fromRpcUrl( + network: providers.Networkish, + rpcUrl: string, + options?: SmartProviderOptions, + ): HyperlaneSmartProvider { + return new HyperlaneSmartProvider( + network, + [{ http: rpcUrl }], + undefined, + options, + ); + } + + async detectNetwork(): Promise { + // For simplicity, efficiency, and better compat with new networks, this assumes + // the provided RPC urls are correct and returns static data here instead of + // querying each sub-provider for network info + return this.network; + } + + async perform(method: string, params: { [name: string]: any }): Promise { + const allProviders = [...this.explorerProviders, ...this.rpcProviders]; + if (!allProviders.length) throw new Error('No providers available'); + + const supportedProviders = allProviders.filter((p) => + p.supportedMethods.includes(method as ProviderMethod), + ); + if (!supportedProviders.length) + throw new Error(`No providers available for method ${method}`); + + this.requestCount += 1; + const reqId = this.requestCount; + + return retryAsync( + () => this.performWithFallback(method, params, supportedProviders, reqId), + this.options?.maxRetries || DEFAULT_MAX_RETRIES, + this.options?.baseRetryDelayMs || DEFAULT_BASE_RETRY_DELAY_MS, + ); + } + + /** + * Checks if this SmartProvider is healthy by checking for new blocks + * @param numBlocks The number of sequential blocks to check for. Default 1 + * @param timeoutMs The maximum time to wait for the full check. Default 3000ms + * @returns true if the provider is healthy, false otherwise + */ + async isHealthy(numBlocks = 1, timeoutMs = 3_000): Promise { + try { + await runWithTimeout(timeoutMs, async () => { + let previousBlockNumber = 0; + let i = 1; + while (i <= numBlocks) { + const block = await this.getBlock('latest'); + if (block.number > previousBlockNumber) { + i += 1; + previousBlockNumber = block.number; + } else { + await sleep(500); + } + } + return true; + }); + return true; + } catch (error) { + this.logger('Provider is unhealthy', error); + return false; + } + } + + isExplorerProvider(p: HyperlaneProvider): p is HyperlaneEtherscanProvider { + return this.explorerProviders.includes(p as any); + } + + /** + * This perform method will trigger any providers that support the method + * one at a time in preferential order. If one is slow to respond, the next is triggered. + * TODO: Consider adding a quorum option that requires a certain number of providers to agree + */ + protected async performWithFallback( + method: string, + params: { [name: string]: any }, + providers: Array, + reqId: number, + ): Promise { + let pIndex = 0; + const providerResultPromises: Promise[] = []; + const providerResultErrors: unknown[] = []; + while (true) { + // Trigger the next provider in line + if (pIndex < providers.length) { + const provider = providers[pIndex]; + const isLastProvider = pIndex === providers.length - 1; + + // Skip the explorer provider if it's currently in a cooldown period + if ( + this.isExplorerProvider(provider) && + provider.getQueryWaitTime() > 0 && + !isLastProvider && + method !== ProviderMethod.GetLogs // never skip GetLogs + ) { + pIndex += 1; + continue; + } + + const resultPromise = this.wrapProviderPerform( + provider, + pIndex, + method, + params, + reqId, + ); + const timeoutPromise = timeoutResult( + this.options?.fallbackStaggerMs || DEFAULT_STAGGER_DELAY_MS, + ); + const result = await Promise.race([resultPromise, timeoutPromise]); + + if (result.status === ProviderStatus.Success) { + return result.value; + } else if (result.status === ProviderStatus.Timeout) { + this.logger( + `Slow response from provider #${pIndex}.${ + !isLastProvider ? ' Triggering next provider.' : '' + }`, + ); + providerResultPromises.push(resultPromise); + pIndex += 1; + } else if (result.status === ProviderStatus.Error) { + this.logger( + `Error from provider #${pIndex}.${ + !isLastProvider ? ' Triggering next provider.' : '' + }`, + ); + providerResultErrors.push(result.error); + pIndex += 1; + } else { + throw new Error('Unexpected result from provider'); + } + + // All providers already triggered, wait for one to complete or all to fail/timeout + } else if (providerResultPromises.length > 0) { + const timeoutPromise = timeoutResult( + this.options?.fallbackStaggerMs || DEFAULT_STAGGER_DELAY_MS, + 20, + ); + const resultPromise = this.waitForProviderSuccess( + providerResultPromises, + ); + const result = await Promise.race([resultPromise, timeoutPromise]); + + if (result.status === ProviderStatus.Success) { + return result.value; + } else if (result.status === ProviderStatus.Timeout) { + this.throwCombinedProviderErrors( + providerResultErrors, + `All providers timed out for method ${method}`, + ); + } else if (result.status === ProviderStatus.Error) { + this.throwCombinedProviderErrors( + [result.error, ...providerResultErrors], + `All providers failed for method ${method} and params ${JSON.stringify( + params, + null, + 2, + )}`, + ); + } else { + throw new Error('Unexpected result from provider'); + } + + // All providers have already failed, all hope is lost + } else { + this.throwCombinedProviderErrors( + providerResultErrors, + `All providers failed for method ${method} and params ${JSON.stringify( + params, + null, + 2, + )}`, + ); + } + } + } + + // Warp for additional logging and error handling + protected async wrapProviderPerform( + provider: HyperlaneProvider, + pIndex: number, + method: string, + params: any, + reqId: number, + ): Promise { + try { + if (this.options?.debug) + this.logger( + `Provider #${pIndex} performing method ${method} for reqId ${reqId}`, + ); + const result = await provider.perform(method, params, reqId); + return { status: ProviderStatus.Success, value: result }; + } catch (error) { + if (this.options?.debug) + this.logger( + `Error performing ${method} on provider #${pIndex} for reqId ${reqId}`, + error, + ); + return { status: ProviderStatus.Error, error }; + } + } + + // Returns the first success from a list a promises, or an error if all fail + protected async waitForProviderSuccess( + resultPromises: Promise[], + ): Promise { + const combinedErrors: unknown[] = []; + const resolvedPromises = new Set>(); + while (resolvedPromises.size < resultPromises.length) { + const unresolvedPromises = resultPromises.filter( + (p) => !resolvedPromises.has(p), + ); + const winner = await raceWithContext(unresolvedPromises); + resolvedPromises.add(winner.promise); + const result = winner.resolved; + if (result.status === ProviderStatus.Success) { + return result; + } else if (result.status === ProviderStatus.Error) { + combinedErrors.push(result.error); + } else { + return { + status: ProviderStatus.Error, + error: new Error('Unexpected result format from provider'), + }; + } + } + // If reached, all providers finished unsuccessfully + return { + status: ProviderStatus.Error, + // TODO combine errors + error: combinedErrors.length + ? combinedErrors[0] + : new Error('Unknown error from provider'), + }; + } + + protected throwCombinedProviderErrors( + errors: unknown[], + fallbackMsg: string, + ): void { + this.logger(fallbackMsg); + // TODO inspect the errors in some clever way to choose which to throw + if (errors.length > 0) throw errors[0]; + else throw new Error(fallbackMsg); + } +} + +function chainMetadataToProviderNetwork( + chainMetadata: ChainMetadata | ChainMetadataWithRpcConnectionInfo, +): providers.Network { + return { + name: chainMetadata.name, + chainId: chainMetadata.chainId as number, + // @ts-ignore add ensAddress to ChainMetadata + ensAddress: chainMetadata.ensAddress, + }; +} + +function timeoutResult(staggerDelay: number, multiplier = 1) { + return new Promise((resolve) => + setTimeout( + () => + resolve({ + status: ProviderStatus.Timeout, + }), + staggerDelay * multiplier, + ), + ); +} diff --git a/typescript/sdk/src/providers/SmartProvider/types.ts b/typescript/sdk/src/providers/SmartProvider/types.ts new file mode 100644 index 0000000000..487d378934 --- /dev/null +++ b/typescript/sdk/src/providers/SmartProvider/types.ts @@ -0,0 +1,54 @@ +import type { utils } from 'ethers'; + +import { ChainMetadata, RpcUrl } from '../../metadata/chainMetadataTypes'; + +export type RpcConfigWithConnectionInfo = RpcUrl & { + connection?: utils.ConnectionInfo; +}; + +export interface ChainMetadataWithRpcConnectionInfo + extends Omit { + rpcUrls: Array; +} + +export enum ProviderStatus { + Success = 'success', + Error = 'error', + Timeout = 'timeout', +} + +export interface ProviderPerformResultBase { + status: ProviderStatus; +} + +export interface ProviderSuccessResult extends ProviderPerformResultBase { + status: ProviderStatus.Success; + value: any; +} + +export interface ProviderErrorResult extends ProviderPerformResultBase { + status: ProviderStatus.Error; + error: unknown; +} + +export interface ProviderTimeoutResult extends ProviderPerformResultBase { + status: ProviderStatus.Timeout; +} + +export type ProviderPerformResult = + | ProviderSuccessResult + | ProviderErrorResult + | ProviderTimeoutResult; + +export interface ProviderRetryOptions { + // Maximum number of times to make the re-query the RPC/explorer + maxRetries?: number; + // Exponential backoff base value for retries + baseRetryDelayMs?: number; +} + +export interface SmartProviderOptions extends ProviderRetryOptions { + // The time to wait before attempting the next provider + fallbackStaggerMs?: number; + debug?: boolean; +} diff --git a/typescript/sdk/src/providers/providerBuilders.ts b/typescript/sdk/src/providers/providerBuilders.ts index 5cab494530..74edb30e1c 100644 --- a/typescript/sdk/src/providers/providerBuilders.ts +++ b/typescript/sdk/src/providers/providerBuilders.ts @@ -6,7 +6,7 @@ import { createPublicClient, http } from 'viem'; import { ProtocolType, isNumeric } from '@hyperlane-xyz/utils'; -import { ChainMetadata } from '../metadata/chainMetadataTypes'; +import { ChainMetadata, RpcUrl } from '../metadata/chainMetadataTypes'; import { CosmJsProvider, @@ -17,56 +17,37 @@ import { TypedProvider, ViemProvider, } from './ProviderType'; -import { RetryJsonRpcProvider, RetryProviderOptions } from './RetryProvider'; +import { HyperlaneSmartProvider } from './SmartProvider/SmartProvider'; +import { ProviderRetryOptions } from './SmartProvider/types'; export type ProviderBuilderFn

= ( rpcUrls: ChainMetadata['rpcUrls'], network: number | string, - retryOverride?: RetryProviderOptions, + retryOverride?: ProviderRetryOptions, ) => P; export type TypedProviderBuilderFn = ProviderBuilderFn; -export const DEFAULT_RETRY_OPTIONS: RetryProviderOptions = { - maxRequests: 3, - baseRetryMs: 250, +const DEFAULT_RETRY_OPTIONS: ProviderRetryOptions = { + maxRetries: 3, + baseRetryDelayMs: 250, }; export function defaultEthersV5ProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], network: number | string, - retryOverride?: RetryProviderOptions, + retryOverride?: ProviderRetryOptions, ): EthersV5Provider { - const createProvider = (r: ChainMetadata['rpcUrls'][number]) => { - const retry = r.retry || retryOverride; - return retry - ? new RetryJsonRpcProvider(retry, r.http, network) - : new providers.StaticJsonRpcProvider(r.http, network); - }; - let provider: providers.Provider; - if (rpcUrls.length > 1) { - provider = new providers.FallbackProvider(rpcUrls.map(createProvider), 1); - } else if (rpcUrls.length === 1) { - provider = createProvider(rpcUrls[0]); - } else { - throw new Error('No RPC URLs provided'); - } + const provider = new HyperlaneSmartProvider( + network, + rpcUrls, + undefined, + retryOverride || DEFAULT_RETRY_OPTIONS, + ); return { type: ProviderType.EthersV5, provider }; } -// export function defaultEthersV6ProviderBuilder( -// rpcUrls: ChainMetadata['rpcUrls'], -// network: number | string, -// ): EthersV6Provider { -// // TODO add support for retry providers here -// if (!rpcUrls.length) throw new Error('No RPC URLs provided'); -// return { -// type: ProviderType.EthersV6, -// provider: new Ev6JsonRpcProvider(rpcUrls[0].http, network), -// }; -// } - export function defaultViemProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], network: number | string, ): ViemProvider { if (!rpcUrls.length) throw new Error('No RPC URLs provided'); @@ -88,7 +69,7 @@ export function defaultViemProviderBuilder( } export function defaultSolProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], _network: number | string, ): SolanaWeb3Provider { if (!rpcUrls.length) throw new Error('No RPC URLs provided'); @@ -99,7 +80,7 @@ export function defaultSolProviderBuilder( } export function defaultFuelProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], _network: number | string, ): EthersV5Provider { if (!rpcUrls.length) throw new Error('No RPC URLs provided'); @@ -107,7 +88,7 @@ export function defaultFuelProviderBuilder( } export function defaultCosmJsProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], _network: number | string, ): CosmJsProvider { if (!rpcUrls.length) throw new Error('No RPC URLs provided'); @@ -118,7 +99,7 @@ export function defaultCosmJsProviderBuilder( } export function defaultCosmJsWasmProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], _network: number | string, ): CosmJsWasmProvider { if (!rpcUrls.length) throw new Error('No RPC URLs provided'); @@ -130,7 +111,7 @@ export function defaultCosmJsWasmProviderBuilder( // Kept for backwards compatibility export function defaultProviderBuilder( - rpcUrls: ChainMetadata['rpcUrls'], + rpcUrls: RpcUrl[], _network: number | string, ): providers.Provider { return defaultEthersV5ProviderBuilder(rpcUrls, _network).provider; @@ -142,11 +123,11 @@ export type ProviderBuilderMap = Record< >; export const defaultProviderBuilderMap: ProviderBuilderMap = { [ProviderType.EthersV5]: defaultEthersV5ProviderBuilder, - // [ProviderType.EthersV6]: defaultEthersV6ProviderBuilder, [ProviderType.Viem]: defaultViemProviderBuilder, [ProviderType.SolanaWeb3]: defaultSolProviderBuilder, [ProviderType.CosmJs]: defaultCosmJsProviderBuilder, [ProviderType.CosmJsWasm]: defaultCosmJsWasmProviderBuilder, + [ProtocolType.Fuel]: defaultFuelProviderBuilder, }; export const protocolToDefaultProviderBuilder: Record< diff --git a/typescript/sdk/src/router/HyperlaneRouterChecker.ts b/typescript/sdk/src/router/HyperlaneRouterChecker.ts index 7d3ded9a17..9fbe4fa551 100644 --- a/typescript/sdk/src/router/HyperlaneRouterChecker.ts +++ b/typescript/sdk/src/router/HyperlaneRouterChecker.ts @@ -7,10 +7,8 @@ import { addressToBytes32, eqAddress } from '@hyperlane-xyz/utils'; import { HyperlaneFactories } from '../contracts/types'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker'; -import { - HyperlaneIsmFactory, - moduleMatchesConfig, -} from '../ism/HyperlaneIsmFactory'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory'; +import { moduleMatchesConfig } from '../ism/utils'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; @@ -19,7 +17,6 @@ import { ClientViolation, ClientViolationType, MailboxClientConfig, - OwnableConfig, RouterConfig, RouterViolation, RouterViolationType, @@ -49,7 +46,7 @@ export class HyperlaneRouterChecker< const router = this.app.router(this.app.getContracts(chain)); const checkMailboxClientProperty = async ( - property: keyof (MailboxClientConfig & OwnableConfig), + property: keyof MailboxClientConfig, actual: string, violationType: ClientViolationType, ) => { diff --git a/typescript/sdk/src/router/HyperlaneRouterDeployer.ts b/typescript/sdk/src/router/HyperlaneRouterDeployer.ts index e9d501c43c..1878ec2665 100644 --- a/typescript/sdk/src/router/HyperlaneRouterDeployer.ts +++ b/typescript/sdk/src/router/HyperlaneRouterDeployer.ts @@ -1,4 +1,4 @@ -import { Router } from '@hyperlane-xyz/core'; +import { Ownable, Router } from '@hyperlane-xyz/core'; import { Address, addressToBytes32, @@ -102,9 +102,14 @@ export abstract class HyperlaneRouterDeployer< this.logger(`Transferring ownership of ownables...`); for (const chain of Object.keys(contractsMap)) { const contracts = contractsMap[chain]; - const owner = configMap[chain].owner; - const ownables = await filterOwnableContracts(contracts); - await this.transferOwnershipOfContracts(chain, owner, ownables); + const ownables = (await filterOwnableContracts(contracts)) as Partial< + Record + >; + await this.transferOwnershipOfContracts( + chain, + configMap[chain], + ownables, + ); } } diff --git a/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts b/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts index c075da32f9..e00b49e989 100644 --- a/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts +++ b/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts @@ -12,9 +12,13 @@ describe('MultiProtocolRouterApp', () => { describe('constructs', () => { const multiProvider = new MultiProtocolProvider(); it('creates an app class', async () => { - const app = new MultiProtocolRouterApp(multiProvider, { + const addresses = { ethereum: { router: ethers.constants.AddressZero }, - }); + }; + const app = new MultiProtocolRouterApp( + multiProvider.intersect(Object.keys(addresses)).result, + addresses, + ); expect(app).to.be.instanceOf(MultiProtocolRouterApp); const ethAdapter = app.adapter(Chains.ethereum); expect(ethAdapter).to.be.instanceOf(EvmRouterAdapter); diff --git a/typescript/sdk/src/router/types.ts b/typescript/sdk/src/router/types.ts index 4026ea8f96..9dd5e86ceb 100644 --- a/typescript/sdk/src/router/types.ts +++ b/typescript/sdk/src/router/types.ts @@ -8,17 +8,13 @@ import type { Address } from '@hyperlane-xyz/utils'; import { HyperlaneFactories } from '../contracts/types'; import { UpgradeConfig } from '../deploy/proxy'; -import { CheckerViolation } from '../deploy/types'; +import { CheckerViolation, OwnableConfig } from '../deploy/types'; import { IsmConfig } from '../ism/types'; export type RouterAddress = { router: Address; }; -export type OwnableConfig = { - owner: Address; -}; - export type ForeignDeploymentConfig = { foreignDeployment?: Address; }; diff --git a/typescript/sdk/src/test/metadata-check.ts b/typescript/sdk/src/test/metadata-check.ts new file mode 100644 index 0000000000..042dd1234b --- /dev/null +++ b/typescript/sdk/src/test/metadata-check.ts @@ -0,0 +1,110 @@ +/* eslint-disable no-console */ +import { ethers } from 'ethers'; + +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; + +import { chainMetadata } from '../consts/chainMetadata'; +import { CoreChainName, TestChains } from '../consts/chains'; +import { isBlockExplorerHealthy, isRpcHealthy } from '../metadata/health'; +import { ChainMap } from '../types'; + +const PROTOCOL_TO_ADDRESS: Record = { + [ProtocolType.Ethereum]: ethers.constants.AddressZero, + [ProtocolType.Sealevel]: '11111111111111111111111111111111', + [ProtocolType.Cosmos]: 'cosmos100000000000000000000000000000000000000', + [ProtocolType.Fuel]: '', +}; + +const PROTOCOL_TO_TX_HASH: Record = { + [ProtocolType.Ethereum]: ethers.constants.HashZero, + [ProtocolType.Sealevel]: + '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111', + [ProtocolType.Cosmos]: + '0000000000000000000000000000000000000000000000000000000000000000', + [ProtocolType.Fuel]: '', +}; + +// Note: run with DEBUG=hyperlane for more detailed logs +async function main() { + const results: ChainMap<{ + goodRpcs: number; + badRpcs: number; + goodExplorers: number; + badExplorers: number; + }> = {}; + const badList: string[] = []; + + for (const metadata of Object.values(chainMetadata)) { + if (TestChains.includes(metadata.name as CoreChainName)) continue; + + console.log(`Checking metadata health for ${metadata.name}`); + if (!metadata.rpcUrls) { + console.error(`No rpcUrls for ${metadata.name}, invalid chain metadata`); + } + if (!metadata.blockExplorers?.length) { + console.warn( + `No block explorers for ${metadata.name}, consider adding one`, + ); + } + + results[metadata.name] = { + goodRpcs: 0, + badRpcs: 0, + goodExplorers: 0, + badExplorers: 0, + }; + + for (const rpc of metadata.rpcUrls) { + const isHealthy = await isRpcHealthy( + rpc, + metadata.chainId, + metadata.protocol, + ); + if (!isHealthy) { + console.error(`RPC ${rpc.http} for ${metadata.name} is not healthy`); + results[metadata.name].badRpcs += 1; + badList.push(rpc.http); + } else { + results[metadata.name].goodRpcs += 1; + } + } + + if (!metadata.blockExplorers?.length) continue; + // This only tests the first explorer since that's + // what the related utils use anyway + const firstExplorerUrl = metadata.blockExplorers[0].url; + const isHealthy = await isBlockExplorerHealthy( + metadata, + PROTOCOL_TO_ADDRESS[metadata.protocol], + PROTOCOL_TO_TX_HASH[metadata.protocol], + ); + if (!isHealthy) { + console.error( + `Explorer ${firstExplorerUrl} for ${metadata.name} is not healthy`, + ); + results[metadata.name].badExplorers += 1; + badList.push(firstExplorerUrl); + } else { + results[metadata.name].goodExplorers += 1; + } + } + + console.table(results); + console.log('The bad ones:\n==============='); + console.log(badList); + + if (badList.length) { + console.error('Some RPCs or block explorers are unhealthy'); + process.exit(1); + } +} + +main() + .then(() => { + console.log('Done'); + process.exit(0); + }) + .catch((err) => { + console.error('Unhandled error running test:', err); + process.exit(1); + }); diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index 0ce5da89dc..020017adf4 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -1,6 +1,6 @@ import { BigNumber, ethers } from 'ethers'; -import { Address, objMap } from '@hyperlane-xyz/utils'; +import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; import { chainMetadata } from '../consts/chainMetadata'; import { HyperlaneContractsMap } from '../contracts/types'; @@ -13,6 +13,7 @@ import { CoinGeckoSimpleInterface, CoinGeckoSimplePriceParams, } from '../gas/token-prices'; +import { IgpConfig } from '../gas/types'; import { HookType } from '../hook/types'; import { IsmType } from '../ism/types'; import { RouterConfig } from '../router/types'; @@ -58,8 +59,8 @@ export function testCoreConfig( }, requiredHook: { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(1), // 1 wei + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(1).toString(), // 1 wei beneficiary: nonZeroAddress, owner, }, @@ -68,6 +69,34 @@ export function testCoreConfig( return Object.fromEntries(chains.map((local) => [local, chainConfig])); } +const TEST_ORACLE_CONFIG = { + gasPrice: ethers.utils.parseUnits('1', 'gwei'), + tokenExchangeRate: ethers.utils.parseUnits('1', 10), +}; + +export function testIgpConfig( + chains: ChainName[], + owner = nonZeroAddress, +): ChainMap { + return Object.fromEntries( + chains.map((local) => [ + local, + { + owner, + oracleKey: owner, + beneficiary: owner, + // TODO: these should be one map + overhead: Object.fromEntries( + exclude(local, chains).map((remote) => [remote, 60000]), + ), + oracleConfig: Object.fromEntries( + exclude(local, chains).map((remote) => [remote, TEST_ORACLE_CONFIG]), + ), + }, + ]), + ); +} + // A mock CoinGecko intended to be used by tests export class MockCoinGecko implements CoinGeckoInterface { // Prices keyed by coingecko id diff --git a/typescript/sdk/src/token/IToken.ts b/typescript/sdk/src/token/IToken.ts new file mode 100644 index 0000000000..dfd50729e1 --- /dev/null +++ b/typescript/sdk/src/token/IToken.ts @@ -0,0 +1,88 @@ +import { z } from 'zod'; + +import { Address, Numberish, ProtocolType } from '@hyperlane-xyz/utils'; + +import { ZChainName, ZUint } from '../metadata/customZodTypes'; +import type { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; +import type { ChainName } from '../types'; + +import type { TokenAmount } from './TokenAmount'; +import { + type TokenConnection, + TokenConnectionConfigSchema, +} from './TokenConnection'; +import { TokenStandard } from './TokenStandard'; +import type { IHypTokenAdapter, ITokenAdapter } from './adapters/ITokenAdapter'; + +export const TokenConfigSchema = z.object({ + chainName: ZChainName.describe( + 'The name of the chain, must correspond to a chain in the multiProvider chainMetadata', + ), + standard: z + .nativeEnum(TokenStandard) + .describe('The type of token. See TokenStandard for valid values.'), + decimals: ZUint.lt(256).describe('The decimals value (e.g. 18 for Eth)'), + symbol: z.string().min(1).describe('The symbol of the token'), + name: z.string().min(1).describe('The name of the token'), + addressOrDenom: z + .string() + .min(1) + .or(z.null()) + .describe('The address or denom, or null for native tokens'), + collateralAddressOrDenom: z + .string() + .min(1) + .optional() + .describe('The address or denom of the collateralized token'), + igpTokenAddressOrDenom: z + .string() + .min(1) + .optional() + .describe('The address or denom of the token for IGP payments'), + logoURI: z.string().optional().describe('The URI of the token logo'), + connections: z + .array(TokenConnectionConfigSchema) + .optional() + .describe('The list of token connections (e.g. warp or IBC)'), +}); + +export type TokenArgs = Omit< + z.infer, + 'addressOrDenom' | 'connections' +> & { + addressOrDenom: Address | string; + connections?: Array; +}; + +export interface IToken extends TokenArgs { + protocol: ProtocolType; + + getAdapter(multiProvider: MultiProtocolProvider): ITokenAdapter; + getHypAdapter( + multiProvider: MultiProtocolProvider<{ mailbox?: Address }>, + destination?: ChainName, + ): IHypTokenAdapter; + + getBalance( + multiProvider: MultiProtocolProvider, + address: Address, + ): Promise; + + amount(amount: Numberish): TokenAmount; + + isNft(): boolean; + isNative(): boolean; + isHypToken(): boolean; + isIbcToken(): boolean; + isMultiChainToken(): boolean; + + getConnections(): TokenConnection[]; + + getConnectionForChain(chain: ChainName): TokenConnection | undefined; + addConnection(connection: TokenConnection): IToken; + removeConnection(token: IToken): IToken; + + equals(token: IToken): boolean; + + collateralizes(token: IToken): boolean; +} diff --git a/typescript/sdk/src/token/Token.test.ts b/typescript/sdk/src/token/Token.test.ts new file mode 100644 index 0000000000..41ac8170b9 --- /dev/null +++ b/typescript/sdk/src/token/Token.test.ts @@ -0,0 +1,176 @@ +/* eslint-disable no-console */ +import { expect } from 'chai'; +import { ethers } from 'ethers'; + +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; + +import { chainMetadata } from '../consts/chainMetadata'; +import { Chains } from '../consts/chains'; +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; + +import { TokenArgs } from './IToken'; +import { Token } from './Token'; +import { TokenStandard } from './TokenStandard'; + +// null values represent TODOs here, ideally all standards should be tested +const STANDARD_TO_TOKEN: Record = { + // EVM + [TokenStandard.ERC20]: { + chainName: Chains.ethereum, + standard: TokenStandard.ERC20, + addressOrDenom: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + decimals: 6, + symbol: 'USDC', + name: 'USDC', + }, + [TokenStandard.ERC721]: null, + [TokenStandard.EvmNative]: Token.FromChainMetadataNativeToken( + chainMetadata.optimism, + ), + [TokenStandard.EvmHypNative]: { + chainName: Chains.inevm, + standard: TokenStandard.EvmHypNative, + addressOrDenom: '0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4', + decimals: 18, + symbol: 'INJ', + name: 'Injective Coin', + }, + [TokenStandard.EvmHypCollateral]: { + chainName: Chains.goerli, + standard: TokenStandard.EvmHypCollateral, + addressOrDenom: '0x145de8760021c4ac6676376691b78038d3DE9097', + collateralAddressOrDenom: '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + decimals: 18, + symbol: 'WETH', + name: 'Weth', + }, + [TokenStandard.EvmHypSynthetic]: { + chainName: Chains.inevm, + standard: TokenStandard.EvmHypSynthetic, + addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147', + decimals: 6, + symbol: 'USDC', + name: 'USDC', + }, + + // Sealevel + [TokenStandard.SealevelSpl]: { + chainName: Chains.solana, + standard: TokenStandard.SealevelSpl, + addressOrDenom: 'So11111111111111111111111111111111111111112', + decimals: 9, + symbol: 'Wrapped SOL', + name: 'SOL', + }, + [TokenStandard.SealevelSpl2022]: { + chainName: Chains.solana, + standard: TokenStandard.SealevelSpl2022, + addressOrDenom: '21zHSATJqhNkcpoNkhFzPJW9LARSmoinLEeDtdygGuWh', + decimals: 6, + symbol: 'SOLMAX', + name: 'Solana Maxi', + }, + [TokenStandard.SealevelNative]: Token.FromChainMetadataNativeToken( + chainMetadata.solana, + ), + [TokenStandard.SealevelHypNative]: null, + [TokenStandard.SealevelHypCollateral]: null, + [TokenStandard.SealevelHypSynthetic]: null, + + // Cosmos + [TokenStandard.CosmosIcs20]: null, + [TokenStandard.CosmosIcs721]: null, + [TokenStandard.CosmosNative]: Token.FromChainMetadataNativeToken( + chainMetadata.neutron, + ), + [TokenStandard.CosmosIbc]: { + chainName: Chains.neutron, + standard: TokenStandard.CosmosIbc, + addressOrDenom: + 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', + decimals: 6, + symbol: 'TIA', + name: 'TIA', + }, + [TokenStandard.CW20]: null, + [TokenStandard.CWNative]: { + chainName: Chains.neutron, + standard: TokenStandard.CWNative, + addressOrDenom: + 'ibc/5751B8BCDA688FD0A8EC0B292EEF1CDEAB4B766B63EC632778B196D317C40C3A', + decimals: 6, + symbol: 'ASTRO', + name: 'ASTRO', + }, + [TokenStandard.CW721]: null, + [TokenStandard.CwHypNative]: { + chainName: Chains.injective, + standard: TokenStandard.CwHypNative, + addressOrDenom: 'inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k', + igpTokenAddressOrDenom: 'inj', + decimals: 18, + symbol: 'INJ', + name: 'Injective Coin', + }, + [TokenStandard.CwHypCollateral]: { + chainName: Chains.neutron, + standard: TokenStandard.CwHypCollateral, + addressOrDenom: + 'neutron1jyyjd3x0jhgswgm6nnctxvzla8ypx50tew3ayxxwkrjfxhvje6kqzvzudq', + collateralAddressOrDenom: + 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', + decimals: 6, + symbol: 'TIA.n', + name: 'TIA.n', + }, + [TokenStandard.CwHypSynthetic]: null, + + // Fuel + [TokenStandard.FuelNative]: null, +}; + +const PROTOCOL_TO_ADDRESS: Partial> = { + [ProtocolType.Ethereum]: ethers.constants.AddressZero, + [ProtocolType.Cosmos]: + 'neutron13we0myxwzlpx8l5ark8elw5gj5d59dl6cjkzmt80c5q5cv5rt54qvzkv2a', + [ProtocolType.Fuel]: '', +}; + +const STANDARD_TO_ADDRESS: Partial> = { + [TokenStandard.SealevelSpl]: 'HVSZJ2juJnMxd6yCNarTL56YmgUqzfUiwM7y7LtTXKHR', + [TokenStandard.SealevelSpl2022]: + 'EK6cs8jNnu2d9pmKTGf1Bvre9oW2xNhcCKNdLKx6t74w', + [TokenStandard.SealevelNative]: + 'EK6cs8jNnu2d9pmKTGf1Bvre9oW2xNhcCKNdLKx6t74w', + [TokenStandard.CwHypNative]: 'inj1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3lj7tt0', +}; + +describe('Token', () => { + it('Handles all standards', async () => { + const multiProvider = new MultiProtocolProvider(); + for (const tokenArgs of Object.values(STANDARD_TO_TOKEN)) { + if (!tokenArgs) continue; + console.debug('Testing token standard', tokenArgs.standard); + const token = new Token(tokenArgs); + expect(token.standard).to.eql(tokenArgs.standard); + const adapter = token.getAdapter(multiProvider); + const adddress = + STANDARD_TO_ADDRESS[token.standard] ?? + PROTOCOL_TO_ADDRESS[token.protocol]; + if (!adddress) + throw new Error(`No address for standard ${tokenArgs.standard}`); + const balance = await adapter.getBalance(adddress); + expect(typeof balance).to.eql('bigint'); + } + }) + .timeout(120_000) + .retries(3); + + it('Constructs from ChainMetadata', () => { + for (const metadata of Object.values(chainMetadata)) { + if (!metadata.nativeToken) continue; + const token = Token.FromChainMetadataNativeToken(metadata); + expect(token.symbol).to.eql(metadata.nativeToken.symbol); + } + }); +}); diff --git a/typescript/sdk/src/token/Token.ts b/typescript/sdk/src/token/Token.ts new file mode 100644 index 0000000000..1283281344 --- /dev/null +++ b/typescript/sdk/src/token/Token.ts @@ -0,0 +1,394 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ +import { MsgTransferEncodeObject } from '@cosmjs/stargate'; + +import { + Address, + Numberish, + ProtocolType, + assert, + eqAddress, +} from '@hyperlane-xyz/utils'; + +import { ChainMetadata } from '../metadata/chainMetadataTypes'; +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; +import { ChainName } from '../types'; + +import type { IToken, TokenArgs } from './IToken'; +import { TokenAmount } from './TokenAmount'; +import { TokenConnection, TokenConnectionType } from './TokenConnection'; +import { + PROTOCOL_TO_NATIVE_STANDARD, + TOKEN_COLLATERALIZED_STANDARDS, + TOKEN_HYP_STANDARDS, + TOKEN_MULTI_CHAIN_STANDARDS, + TOKEN_NFT_STANDARDS, + TOKEN_STANDARD_TO_PROTOCOL, + TokenStandard, +} from './TokenStandard'; +import { + CwHypCollateralAdapter, + CwHypNativeAdapter, + CwHypSyntheticAdapter, + CwNativeTokenAdapter, + CwTokenAdapter, +} from './adapters/CosmWasmTokenAdapter'; +import { + CosmIbcToWarpTokenAdapter, + CosmIbcTokenAdapter, + CosmNativeTokenAdapter, +} from './adapters/CosmosTokenAdapter'; +import { + EvmHypCollateralAdapter, + EvmHypNativeAdapter, + EvmHypSyntheticAdapter, + EvmNativeTokenAdapter, + EvmTokenAdapter, +} from './adapters/EvmTokenAdapter'; +import type { IHypTokenAdapter, ITokenAdapter } from './adapters/ITokenAdapter'; +import { + SealevelHypCollateralAdapter, + SealevelHypNativeAdapter, + SealevelHypSyntheticAdapter, + SealevelNativeTokenAdapter, + SealevelTokenAdapter, +} from './adapters/SealevelTokenAdapter'; + +// Declaring the interface in addition to class allows +// Typescript to infer the members vars from TokenArgs +export interface Token extends TokenArgs {} + +export class Token implements IToken { + public readonly protocol: ProtocolType; + + constructor(args: TokenArgs) { + Object.assign(this, args); + this.protocol = TOKEN_STANDARD_TO_PROTOCOL[this.standard]; + } + + static FromChainMetadataNativeToken(chainMetadata: ChainMetadata): Token { + const { protocol, name: chainName, nativeToken, logoURI } = chainMetadata; + assert( + nativeToken, + `ChainMetadata for ${chainMetadata.name} missing nativeToken`, + ); + + return new Token({ + chainName, + standard: PROTOCOL_TO_NATIVE_STANDARD[protocol], + addressOrDenom: nativeToken.denom ?? '', + decimals: nativeToken.decimals, + symbol: nativeToken.symbol, + name: nativeToken.name, + logoURI, + }); + } + + /** + * Returns a TokenAdapter for the token and multiProvider + * @throws If multiProvider does not contain this token's chain. + * @throws If token is an NFT (TODO NFT Adapter support) + */ + getAdapter(multiProvider: MultiProtocolProvider): ITokenAdapter { + const { standard, chainName, addressOrDenom } = this; + + assert(!this.isNft(), 'NFT adapters not yet supported'); + assert( + multiProvider.tryGetChainMetadata(chainName), + `Token chain ${chainName} not found in multiProvider`, + ); + + if (standard === TokenStandard.ERC20) { + return new EvmTokenAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); + } else if (standard === TokenStandard.EvmNative) { + return new EvmNativeTokenAdapter(chainName, multiProvider, {}); + } else if (standard === TokenStandard.SealevelSpl) { + return new SealevelTokenAdapter( + chainName, + multiProvider, + { token: addressOrDenom }, + false, + ); + } else if (standard === TokenStandard.SealevelSpl2022) { + return new SealevelTokenAdapter( + chainName, + multiProvider, + { token: addressOrDenom }, + true, + ); + } else if (standard === TokenStandard.SealevelNative) { + return new SealevelNativeTokenAdapter(chainName, multiProvider, {}); + } else if (standard === TokenStandard.CosmosIcs20) { + throw new Error('Cosmos ICS20 token adapter not yet supported'); + } else if (standard === TokenStandard.CosmosNative) { + return new CosmNativeTokenAdapter( + chainName, + multiProvider, + {}, + { ibcDenom: addressOrDenom }, + ); + } else if (standard === TokenStandard.CW20) { + return new CwTokenAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); + } else if (standard === TokenStandard.CWNative) { + return new CwNativeTokenAdapter( + chainName, + multiProvider, + {}, + addressOrDenom, + ); + } else if (this.isHypToken()) { + return this.getHypAdapter(multiProvider); + } else if (this.isIbcToken()) { + // Passing in a stub connection here because it's not required + // for an IBC adapter to fulfill the ITokenAdapter interface + return this.getIbcAdapter(multiProvider, { + token: this, + sourcePort: 'transfer', + sourceChannel: 'channel-0', + type: TokenConnectionType.Ibc, + }); + } else { + throw new Error(`No adapter found for token standard: ${standard}`); + } + } + + /** + * Returns a HypTokenAdapter for the token and multiProvider + * @throws If not applicable to this token's standard. + * @throws If multiProvider does not contain this token's chain. + * @throws If token is an NFT (TODO NFT Adapter support) + */ + getHypAdapter( + multiProvider: MultiProtocolProvider<{ mailbox?: Address }>, + destination?: ChainName, + ): IHypTokenAdapter { + const { + protocol, + standard, + chainName, + addressOrDenom, + collateralAddressOrDenom, + } = this; + const chainMetadata = multiProvider.tryGetChainMetadata(chainName); + const mailbox = chainMetadata?.mailbox; + + assert( + this.isMultiChainToken(), + `Token standard ${standard} not applicable to hyp adapter`, + ); + assert(!this.isNft(), 'NFT adapters not yet supported'); + assert( + chainMetadata, + `Token chain ${chainName} not found in multiProvider`, + ); + + let sealevelAddresses; + if (protocol === ProtocolType.Sealevel) { + assert(mailbox, `Mailbox required for Sealevel hyp tokens`); + assert( + collateralAddressOrDenom, + `collateralAddressOrDenom required for Sealevel hyp tokens`, + ); + sealevelAddresses = { + warpRouter: addressOrDenom, + token: collateralAddressOrDenom, + mailbox, + }; + } + if (standard === TokenStandard.EvmHypNative) { + return new EvmHypNativeAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); + } else if (standard === TokenStandard.EvmHypCollateral) { + return new EvmHypCollateralAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); + } else if (standard === TokenStandard.EvmHypSynthetic) { + return new EvmHypSyntheticAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); + } else if (standard === TokenStandard.SealevelHypNative) { + return new SealevelHypNativeAdapter( + chainName, + multiProvider, + sealevelAddresses!, + false, + ); + } else if (standard === TokenStandard.SealevelHypCollateral) { + return new SealevelHypCollateralAdapter( + chainName, + multiProvider, + sealevelAddresses!, + false, + ); + } else if (standard === TokenStandard.SealevelHypSynthetic) { + return new SealevelHypSyntheticAdapter( + chainName, + multiProvider, + sealevelAddresses!, + false, + ); + } else if (standard === TokenStandard.CwHypNative) { + return new CwHypNativeAdapter(chainName, multiProvider, { + warpRouter: addressOrDenom, + }); + } else if (standard === TokenStandard.CwHypCollateral) { + assert( + collateralAddressOrDenom, + 'collateralAddressOrDenom required for CwHypCollateral', + ); + return new CwHypCollateralAdapter(chainName, multiProvider, { + warpRouter: addressOrDenom, + token: collateralAddressOrDenom, + }); + } else if (standard === TokenStandard.CwHypSynthetic) { + assert( + collateralAddressOrDenom, + 'collateralAddressOrDenom required for CwHypSyntheticAdapter', + ); + return new CwHypSyntheticAdapter(chainName, multiProvider, { + warpRouter: addressOrDenom, + token: collateralAddressOrDenom, + }); + } else if (standard === TokenStandard.CosmosIbc) { + assert(destination, 'destination required for IBC token adapters'); + const connection = this.getConnectionForChain(destination); + assert(connection, `No connection found for chain ${destination}`); + return this.getIbcAdapter(multiProvider, connection); + } else { + throw new Error(`No hyp adapter found for token standard: ${standard}`); + } + } + + protected getIbcAdapter( + multiProvider: MultiProtocolProvider, + connection: TokenConnection, + ): IHypTokenAdapter { + if (connection.type === TokenConnectionType.Ibc) { + const { sourcePort, sourceChannel } = connection; + return new CosmIbcTokenAdapter( + this.chainName, + multiProvider, + {}, + { ibcDenom: this.addressOrDenom, sourcePort, sourceChannel }, + ); + } else if (connection.type === TokenConnectionType.IbcHyperlane) { + const { + sourcePort, + sourceChannel, + intermediateChainName, + intermediateIbcDenom, + intermediateRouterAddress, + } = connection; + const destinationRouterAddress = connection.token.addressOrDenom; + return new CosmIbcToWarpTokenAdapter( + this.chainName, + multiProvider, + { + intermediateRouterAddress, + destinationRouterAddress, + }, + { + ibcDenom: this.addressOrDenom, + sourcePort, + sourceChannel, + intermediateIbcDenom, + intermediateChainName, + }, + ); + } else { + throw new Error(`Unsupported IBC connection type: ${connection.type}`); + } + } + + /** + * Convenience method to create an adapter and return an account balance + */ + async getBalance( + multiProvider: MultiProtocolProvider, + address: Address, + ): Promise { + const adapter = this.getAdapter(multiProvider); + const balance = await adapter.getBalance(address); + return new TokenAmount(balance, this); + } + + amount(amount: Numberish): TokenAmount { + return new TokenAmount(amount, this); + } + + isNft(): boolean { + return TOKEN_NFT_STANDARDS.includes(this.standard); + } + + isNative(): boolean { + return Object.values(PROTOCOL_TO_NATIVE_STANDARD).includes(this.standard); + } + + isHypToken(): boolean { + return TOKEN_HYP_STANDARDS.includes(this.standard); + } + + isIbcToken(): boolean { + return this.standard === TokenStandard.CosmosIbc; + } + + isMultiChainToken(): boolean { + return TOKEN_MULTI_CHAIN_STANDARDS.includes(this.standard); + } + + getConnections(): TokenConnection[] { + return this.connections || []; + } + + getConnectionForChain(chain: ChainName): TokenConnection | undefined { + // A token cannot have > 1 connected token for the same chain + return this.getConnections().filter((t) => t.token.chainName === chain)[0]; + } + + addConnection(connection: TokenConnection): Token { + this.connections = [...(this.connections || []), connection]; + return this; + } + + removeConnection(token: Token): Token { + const index = this.connections?.findIndex((t) => t.token.equals(token)); + if (index && index >= 0) this.connections?.splice(index, 1); + return this; + } + + /** + * Returns true if tokens refer to the same asset + */ + equals(token: Token): boolean { + return ( + this.protocol === token.protocol && + this.chainName === token.chainName && + this.standard === token.standard && + this.decimals === token.decimals && + this.addressOrDenom.toLowerCase() === + token.addressOrDenom.toLowerCase() && + this.collateralAddressOrDenom?.toLowerCase() === + token.collateralAddressOrDenom?.toLowerCase() + ); + } + + /** + * Checks if this token is both: + * 1) Of a TokenStandard that uses other tokens as collateral (eg. EvmHypCollateral) + * 2) Has a collateralAddressOrDenom address that matches the given token + * E.g. ERC20 Token ABC, EvmHypCollateral DEF that wraps ABC, DEF.collateralizes(ABC) === true + */ + collateralizes(token: Token): boolean { + if (token.chainName !== this.chainName) return false; + if (!TOKEN_COLLATERALIZED_STANDARDS.includes(this.standard)) return false; + const isCollateralWrapper = + this.collateralAddressOrDenom && + eqAddress(this.collateralAddressOrDenom, token.addressOrDenom); + const isNativeWrapper = !this.collateralAddressOrDenom && token.isNative(); + return isCollateralWrapper || isNativeWrapper; + } +} diff --git a/typescript/sdk/src/token/TokenAmount.test.ts b/typescript/sdk/src/token/TokenAmount.test.ts new file mode 100644 index 0000000000..e4f154275f --- /dev/null +++ b/typescript/sdk/src/token/TokenAmount.test.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai'; +import { ethers } from 'ethers'; + +import { chainMetadata } from '../consts/chainMetadata'; +import { Chains } from '../consts/chains'; + +import { Token } from './Token'; +import { TokenAmount } from './TokenAmount'; +import { TokenStandard } from './TokenStandard'; + +const token1 = new Token({ + chainName: Chains.ethereum, + standard: TokenStandard.ERC20, + addressOrDenom: ethers.constants.AddressZero, + decimals: 4, + symbol: 'FAKE', + name: 'Fake Token', +}); +const token2 = Token.FromChainMetadataNativeToken( + chainMetadata[Chains.neutron], +); + +describe('TokenAmount', () => { + let tokenAmount1: TokenAmount; + let tokenAmount2: TokenAmount; + + it('Constructs', () => { + tokenAmount1 = new TokenAmount(123456789, token1); + tokenAmount2 = new TokenAmount('1', token2); + expect(!!tokenAmount1).to.eq(true); + expect(!!tokenAmount2).to.eq(true); + }); + + it('Formats human readable string', () => { + expect(tokenAmount1.getDecimalFormattedAmount()).to.eq(12345.6789); + expect(tokenAmount2.getDecimalFormattedAmount()).to.eq(0.000001); + }); + + it('Does arithmetic', () => { + expect(tokenAmount1.plus(1).amount).to.eq(123456790n); + expect(tokenAmount2.minus(1).amount).to.eq(0n); + }); + + it('Checks equality', () => { + expect(tokenAmount1.equals(tokenAmount2)).to.be.false; + expect(tokenAmount1.equals(new TokenAmount(123456789n, token1))).to.true; + }); +}); diff --git a/typescript/sdk/src/token/TokenAmount.ts b/typescript/sdk/src/token/TokenAmount.ts new file mode 100644 index 0000000000..2f3fe63dab --- /dev/null +++ b/typescript/sdk/src/token/TokenAmount.ts @@ -0,0 +1,29 @@ +import { Numberish, fromWei } from '@hyperlane-xyz/utils'; + +import type { IToken } from './IToken'; + +export class TokenAmount { + public readonly amount: bigint; + + constructor(_amount: Numberish, public readonly token: IToken) { + this.amount = BigInt(_amount); + } + + getDecimalFormattedAmount(): number { + return Number(fromWei(this.amount.toString(), this.token.decimals)); + } + + plus(amount: Numberish): TokenAmount { + return new TokenAmount(this.amount + BigInt(amount), this.token); + } + + minus(amount: Numberish): TokenAmount { + return new TokenAmount(this.amount - BigInt(amount), this.token); + } + + equals(tokenAmount: TokenAmount): boolean { + return ( + this.amount === tokenAmount.amount && this.token.equals(tokenAmount.token) + ); + } +} diff --git a/typescript/sdk/src/token/TokenConnection.ts b/typescript/sdk/src/token/TokenConnection.ts new file mode 100644 index 0000000000..a15e5867f6 --- /dev/null +++ b/typescript/sdk/src/token/TokenConnection.ts @@ -0,0 +1,73 @@ +import { z } from 'zod'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { ZChainName } from '../metadata/customZodTypes'; +import { ChainName } from '../types'; + +import type { IToken } from './IToken'; + +export enum TokenConnectionType { + Hyperlane = 'hyperlane', + Ibc = 'ibc', + IbcHyperlane = 'ibc-hyperlane', // a.k.a. one-click two-hop +} + +interface TokenConnectionBase { + type?: TokenConnectionType; + token: IToken; // the token that is being connected to +} + +export interface HyperlaneTokenConnection extends TokenConnectionBase { + type?: TokenConnectionType.Hyperlane; +} + +export interface IbcTokenConnection extends TokenConnectionBase { + type: TokenConnectionType.Ibc; + sourcePort: string; + sourceChannel: string; +} + +export interface IbcToHyperlaneTokenConnection extends TokenConnectionBase { + type: TokenConnectionType.IbcHyperlane; + sourcePort: string; + sourceChannel: string; + intermediateChainName: ChainName; + intermediateIbcDenom: string; + intermediateRouterAddress: Address; +} + +export type TokenConnection = + | HyperlaneTokenConnection + | IbcTokenConnection + | IbcToHyperlaneTokenConnection; + +const TokenConnectionRegex = /^(.+)|(.+)|(.+)$/; + +// Distinct from type above in that it uses a +// serialized representation of the tokens instead +// of the possibly recursive Token references +export const TokenConnectionConfigSchema = z + .object({ + type: z.literal(TokenConnectionType.Hyperlane).optional(), + token: z.string().regex(TokenConnectionRegex), + }) + .or( + z.object({ + type: z.literal(TokenConnectionType.Ibc), + token: z.string().regex(TokenConnectionRegex), + sourcePort: z.string(), + sourceChannel: z.string(), + }), + ) + .or( + z.object({ + type: z.literal(TokenConnectionType.IbcHyperlane), + token: z.string().regex(TokenConnectionRegex), + sourcePort: z.string(), + sourceChannel: z.string(), + intermediateChainName: ZChainName, + intermediateIbcDenom: z.string(), + intermediateRouterAddress: z.string(), + }), + ); diff --git a/typescript/sdk/src/token/TokenStandard.ts b/typescript/sdk/src/token/TokenStandard.ts new file mode 100644 index 0000000000..55269e33bc --- /dev/null +++ b/typescript/sdk/src/token/TokenStandard.ts @@ -0,0 +1,137 @@ +import { ProtocolType, objMap } from '@hyperlane-xyz/utils'; + +import { + PROTOCOL_TO_DEFAULT_PROVIDER_TYPE, + ProviderType, +} from '../providers/ProviderType'; + +export enum TokenStandard { + // EVM + ERC20 = 'ERC20', + ERC721 = 'ERC721', + EvmNative = 'EvmNative', + EvmHypNative = 'EvmHypNative', + EvmHypCollateral = 'EvmHypCollateral', + EvmHypSynthetic = 'EvmHypSynthetic', + + // Sealevel (Solana) + SealevelSpl = 'SealevelSpl', + SealevelSpl2022 = 'SealevelSpl2022', + SealevelNative = 'SealevelNative', + SealevelHypNative = 'SealevelHypNative', + SealevelHypCollateral = 'SealevelHypCollateral', + SealevelHypSynthetic = 'SealevelHypSynthetic', + + // Cosmos + CosmosIcs20 = 'CosmosIcs20', + CosmosIcs721 = 'CosmosIcs721', + CosmosNative = 'CosmosNative', + CosmosIbc = 'CosmosIbc', + + // CosmWasm + CW20 = 'CW20', + CWNative = 'CWNative', + CW721 = 'CW721', + CwHypNative = 'CwHypNative', + CwHypCollateral = 'CwHypCollateral', + CwHypSynthetic = 'CwHypSynthetic', + + // Fuel (TODO) + FuelNative = 'FuelNative', +} + +// Allows for omission of protocol field in token args +export const TOKEN_STANDARD_TO_PROTOCOL: Record = { + // EVM + ERC20: ProtocolType.Ethereum, + ERC721: ProtocolType.Ethereum, + EvmNative: ProtocolType.Ethereum, + EvmHypNative: ProtocolType.Ethereum, + EvmHypCollateral: ProtocolType.Ethereum, + EvmHypSynthetic: ProtocolType.Ethereum, + + // Sealevel (Solana) + SealevelSpl: ProtocolType.Sealevel, + SealevelSpl2022: ProtocolType.Sealevel, + SealevelNative: ProtocolType.Sealevel, + SealevelHypNative: ProtocolType.Sealevel, + SealevelHypCollateral: ProtocolType.Sealevel, + SealevelHypSynthetic: ProtocolType.Sealevel, + + // Cosmos + CosmosIcs20: ProtocolType.Cosmos, + CosmosIcs721: ProtocolType.Cosmos, + CosmosNative: ProtocolType.Cosmos, + CosmosIbc: ProtocolType.Cosmos, + + // CosmWasm + CW20: ProtocolType.Cosmos, + CWNative: ProtocolType.Cosmos, + CW721: ProtocolType.Cosmos, + CwHypNative: ProtocolType.Cosmos, + CwHypCollateral: ProtocolType.Cosmos, + CwHypSynthetic: ProtocolType.Cosmos, + + // Fuel (TODO) + FuelNative: ProtocolType.Fuel, +}; + +export const TOKEN_STANDARD_TO_PROVIDER_TYPE: Record< + TokenStandard, + ProviderType +> = objMap(TOKEN_STANDARD_TO_PROTOCOL, (k, v) => { + if (k.startsWith('Cosmos')) return ProviderType.CosmJs; + return PROTOCOL_TO_DEFAULT_PROVIDER_TYPE[v]; +}); + +export const TOKEN_NFT_STANDARDS = [ + TokenStandard.ERC721, + TokenStandard.CosmosIcs721, + TokenStandard.CW721, + // TODO solana here +]; + +export const TOKEN_COLLATERALIZED_STANDARDS = [ + TokenStandard.EvmHypCollateral, + TokenStandard.EvmHypNative, + TokenStandard.SealevelHypCollateral, + TokenStandard.SealevelHypNative, + TokenStandard.CwHypCollateral, + TokenStandard.CwHypNative, +]; + +export const TOKEN_HYP_STANDARDS = [ + TokenStandard.EvmHypNative, + TokenStandard.EvmHypCollateral, + TokenStandard.EvmHypSynthetic, + TokenStandard.SealevelHypNative, + TokenStandard.SealevelHypCollateral, + TokenStandard.SealevelHypSynthetic, + TokenStandard.CwHypNative, + TokenStandard.CwHypCollateral, + TokenStandard.CwHypSynthetic, +]; + +export const TOKEN_MULTI_CHAIN_STANDARDS = [ + ...TOKEN_HYP_STANDARDS, + TokenStandard.CosmosIbc, +]; + +// Useful for differentiating from norma Cosmos standards +// (e.g. for determining the appropriate cosmos client) +export const TOKEN_COSMWASM_STANDARDS = [ + TokenStandard.CW20, + TokenStandard.CWNative, + TokenStandard.CW721, + TokenStandard.CwHypNative, + TokenStandard.CwHypCollateral, + TokenStandard.CwHypSynthetic, +]; + +export const PROTOCOL_TO_NATIVE_STANDARD: Record = + { + [ProtocolType.Ethereum]: TokenStandard.EvmNative, + [ProtocolType.Cosmos]: TokenStandard.CosmosNative, + [ProtocolType.Sealevel]: TokenStandard.SealevelNative, + [ProtocolType.Fuel]: TokenStandard.FuelNative, + }; diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 10249bca11..2d464886a4 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -1,11 +1,11 @@ import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; import { Coin } from '@cosmjs/stargate'; -import BigNumber from 'bignumber.js'; import { Address, Domain, addressToBytes32, + assert, strip0x, } from '@hyperlane-xyz/utils'; @@ -16,6 +16,7 @@ import { QueryMsg as Cw20Query, TokenInfoResponse, } from '../../cw-types/Cw20Base.types'; +import { QuoteDispatchResponse } from '../../cw-types/Mailbox.types'; import { DomainsResponse, InterchainSecurityModuleResponse, @@ -34,6 +35,7 @@ import { ERC20Metadata } from '../config'; import { IHypTokenAdapter, ITokenAdapter, + InterchainGasQuote, TransferParams, TransferRemoteParams, } from './ITokenAdapter'; @@ -41,27 +43,31 @@ import { // Interacts with IBC denom tokens in CosmWasm export class CwNativeTokenAdapter extends BaseCosmWasmAdapter - implements ITokenAdapter + implements ITokenAdapter { constructor( public readonly chainName: string, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: Record, - public readonly ibcDenom: string = 'untrn', + public readonly denom: string, ) { super(chainName, multiProvider, addresses); } - async getBalance(address: Address): Promise { + async getBalance(address: Address): Promise { const provider = await this.getProvider(); - const balance = await provider.getBalance(address, this.ibcDenom); - return balance.amount; + const balance = await provider.getBalance(address, this.denom); + return BigInt(balance.amount); } async getMetadata(): Promise { throw new Error('Metadata not available to native tokens'); } + async isApproveRequired(): Promise { + return false; + } + async populateApproveTx( _params: TransferParams, ): Promise { @@ -79,7 +85,7 @@ export class CwNativeTokenAdapter funds: [ { amount: weiAmountOrId.toString(), - denom: this.ibcDenom, + denom: this.denom, }, ], }; @@ -92,13 +98,12 @@ type CW20Response = TokenInfoResponse | BalanceResponse; // Interacts with CW20/721 contracts export class CwTokenAdapter extends BaseCosmWasmAdapter - implements ITokenAdapter + implements ITokenAdapter { constructor( public readonly chainName: string, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address }, - public readonly denom = 'untrn', ) { super(chainName, multiProvider, addresses); } @@ -120,13 +125,10 @@ export class CwTokenAdapter }; } - async getBalance(address: Address): Promise { - const resp = await this.queryToken({ - balance: { - address, - }, - }); - return resp.balance; + async getBalance(address: Address): Promise { + const provider = await this.getProvider(); + const balance = await provider.getBalance(address, this.addresses.token); + return BigInt(balance.amount); } async getMetadata(): Promise { @@ -139,6 +141,10 @@ export class CwTokenAdapter }; } + async isApproveRequired(): Promise { + return false; + } + async populateApproveTx({ weiAmountOrId, recipient, @@ -174,17 +180,17 @@ type TokenRouterResponse = | DomainsResponse | OwnerResponse | RouteResponseForHexBinary - | RoutesResponseForHexBinary; + | RoutesResponseForHexBinary + | QuoteDispatchResponse; export class CwHypSyntheticAdapter extends CwTokenAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { constructor( public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address; warpRouter: Address }, - public readonly gasDenom = 'untrn', ) { super(chainName, multiProvider, addresses); } @@ -208,7 +214,7 @@ export class CwHypSyntheticAdapter }; } - async tokenType(): Promise { + async getTokenType(): Promise { const resp = await this.queryRouter({ token_default: { token_type: {}, @@ -217,11 +223,11 @@ export class CwHypSyntheticAdapter return resp.type; } - async interchainSecurityModule(): Promise

{ + async getInterchainSecurityModule(): Promise
{ throw new Error('Router does not support ISM config yet.'); } - async owner(): Promise
{ + async getOwner(): Promise
{ const resp = await this.queryRouter({ ownable: { get_owner: {}, @@ -268,19 +274,32 @@ export class CwHypSyntheticAdapter })); } - quoteGasPayment(_destination: number): Promise { - throw new Error('Method not implemented.'); + async quoteGasPayment(_destination: Domain): Promise { + // TODO this may require separate queries to get the hook and/or mailbox + // before making a query for the QuoteDispatchResponse + // Punting on this given that only static quotes are used for now + // const resp = await this.queryRouter({ + // router: { + // TODO: {}, + // }, + // }); + // return { + // amount: BigInt(resp.gas_amount?.amount || 0), + // addressOrDenom: resp.gas_amount?.denom, + // }; + throw new Error('CW adpater quoteGasPayment method not implemented'); } - populateTransferRemoteTx({ + async populateTransferRemoteTx({ destination, recipient, weiAmountOrId, - txValue, - }: TransferRemoteParams): ExecuteInstruction { - if (!txValue) { - throw new Error('txValue is required for native tokens'); - } + interchainGas, + }: TransferRemoteParams): Promise { + if (!interchainGas) interchainGas = await this.quoteGasPayment(destination); + const { addressOrDenom: igpDenom, amount: igpAmount } = interchainGas; + assert(igpDenom, 'Interchain gas denom required for Cosmos'); + return this.prepareRouter( { transfer_remote: { @@ -291,8 +310,8 @@ export class CwHypSyntheticAdapter }, [ { - amount: txValue.toString(), - denom: this.gasDenom, + amount: igpAmount.toString(), + denom: igpDenom, }, ], ); @@ -301,7 +320,7 @@ export class CwHypSyntheticAdapter export class CwHypNativeAdapter extends CwNativeTokenAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { private readonly cw20adapter: CwHypSyntheticAdapter; @@ -309,30 +328,27 @@ export class CwHypNativeAdapter public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { warpRouter: Address }, - public readonly gasDenom = 'untrn', ) { - super(chainName, multiProvider, addresses, gasDenom); - this.cw20adapter = new CwHypSyntheticAdapter( - chainName, - multiProvider, - { token: '', warpRouter: addresses.warpRouter }, - gasDenom, - ); + super(chainName, multiProvider, addresses, ''); + this.cw20adapter = new CwHypSyntheticAdapter(chainName, multiProvider, { + token: '', + warpRouter: addresses.warpRouter, + }); } - async getBalance(address: string): Promise { + async getBalance(address: string): Promise { const provider = await this.getProvider(); - const denom = await this.denom(); + const denom = await this.getDenom(); const balance = await provider.getBalance(address, denom); - return balance.amount; + return BigInt(balance.amount); } - async interchainSecurityModule(): Promise
{ - return this.cw20adapter.interchainSecurityModule(); + async getInterchainSecurityModule(): Promise
{ + return this.cw20adapter.getInterchainSecurityModule(); } - async owner(): Promise
{ - return this.cw20adapter.owner(); + async getOwner(): Promise
{ + return this.cw20adapter.getOwner(); } async getDomains(): Promise { @@ -347,12 +363,12 @@ export class CwHypNativeAdapter return this.cw20adapter.getAllRouters(); } - quoteGasPayment(destination: number): Promise { + quoteGasPayment(destination: Domain): Promise { return this.cw20adapter.quoteGasPayment(destination); } - async denom(): Promise { - const tokenType = await this.cw20adapter.tokenType(); + async getDenom(): Promise { + const tokenType = await this.cw20adapter.getTokenType(); if ('native' in tokenType) { if ('fungible' in tokenType.native) { return tokenType.native.fungible.denom; @@ -366,18 +382,19 @@ export class CwHypNativeAdapter destination, recipient, weiAmountOrId, - txValue, + interchainGas, }: TransferRemoteParams): Promise { - if (!txValue) { - throw new Error('txValue is required for native tokens'); - } - const collateralDenom = await this.denom(); + const collateralDenom = await this.getDenom(); + + if (!interchainGas) interchainGas = await this.quoteGasPayment(destination); + const { addressOrDenom: igpDenom, amount: igpAmount } = interchainGas; + assert(igpDenom, 'Interchain gas denom required for Cosmos'); const funds: Coin[] = - collateralDenom === this.gasDenom + collateralDenom === igpDenom ? [ { - amount: new BigNumber(weiAmountOrId).plus(txValue).toFixed(0), + amount: (BigInt(weiAmountOrId) + igpAmount).toString(), denom: collateralDenom, }, ] @@ -387,8 +404,8 @@ export class CwHypNativeAdapter denom: collateralDenom, }, { - amount: txValue.toString(), - denom: this.gasDenom, + amount: igpAmount.toString(), + denom: igpDenom, }, ]; @@ -407,14 +424,13 @@ export class CwHypNativeAdapter export class CwHypCollateralAdapter extends CwHypNativeAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { constructor( public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { warpRouter: Address; token: Address }, - public readonly gasDenom = 'untrn', ) { - super(chainName, multiProvider, addresses, gasDenom); + super(chainName, multiProvider, addresses); } } diff --git a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts index ab482fdbfa..6f43e58c44 100644 --- a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts @@ -1,7 +1,7 @@ import { MsgTransferEncodeObject } from '@cosmjs/stargate'; import Long from 'long'; -import { Address, Domain } from '@hyperlane-xyz/utils'; +import { Address, Domain, assert } from '@hyperlane-xyz/utils'; import { BaseCosmosAdapter } from '../../app/MultiProtocolApp'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; @@ -12,6 +12,7 @@ import { CwHypCollateralAdapter } from './CosmWasmTokenAdapter'; import { IHypTokenAdapter, ITokenAdapter, + InterchainGasQuote, TransferParams, TransferRemoteParams, } from './ITokenAdapter'; @@ -21,7 +22,7 @@ const COSMOS_IBC_TRANSFER_TIMEOUT = 600_000; // 10 minutes // Interacts with native tokens on a Cosmos chain (e.g TIA on Celestia) export class CosmNativeTokenAdapter extends BaseCosmosAdapter - implements ITokenAdapter + implements ITokenAdapter { constructor( public readonly chainName: ChainName, @@ -36,17 +37,23 @@ export class CosmNativeTokenAdapter super(chainName, multiProvider, addresses); } - async getBalance(address: string): Promise { + async getBalance(address: string): Promise { const provider = await this.getProvider(); const coin = await provider.getBalance(address, this.properties.ibcDenom); - return coin.amount; + return BigInt(coin.amount); } getMetadata(): Promise { throw new Error('Metadata not available to native tokens'); } - populateApproveTx(_transferParams: TransferParams): unknown { + async isApproveRequired(): Promise { + return false; + } + + populateApproveTx( + _transferParams: TransferParams, + ): Promise { throw new Error('Approve not required for native tokens'); } @@ -62,7 +69,7 @@ export class CosmNativeTokenAdapter // methods don't apply to IBC transfers the way they do for Warp transfers export class CosmIbcTokenAdapter extends CosmNativeTokenAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { constructor( public readonly chainName: ChainName, @@ -97,8 +104,9 @@ export class CosmIbcTokenAdapter > { throw new Error('Method not applicable to IBC adapters'); } - quoteGasPayment(_destination: Domain): Promise { - throw new Error('Method not applicable to IBC adapters'); + async quoteGasPayment(_destination: Domain): Promise { + // TODO implement IBC interchain transfer gas estimation here + return { amount: 0n, addressOrDenom: this.properties.ibcDenom }; } async populateTransferRemoteTx( @@ -134,7 +142,7 @@ export class CosmIbcTokenAdapter // A.k.a. 'One-Click' cosmos to evm transfers export class CosmIbcToWarpTokenAdapter extends CosmIbcTokenAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { constructor( public readonly chainName: ChainName, @@ -144,13 +152,18 @@ export class CosmIbcToWarpTokenAdapter destinationRouterAddress: Address; }, public readonly properties: CosmIbcTokenAdapter['properties'] & { - derivedIbcDenom: string; + intermediateIbcDenom: string; intermediateChainName: ChainName; }, ) { super(chainName, multiProvider, addresses, properties); } + async quoteGasPayment(_destination: Domain): Promise { + // TODO implement IBC interchain transfer gas estimation here + return { amount: 0n, addressOrDenom: this.properties.intermediateIbcDenom }; + } + async populateTransferRemoteTx( transferParams: TransferRemoteParams, ): Promise { @@ -158,12 +171,24 @@ export class CosmIbcToWarpTokenAdapter this.properties.intermediateChainName, this.multiProvider, { - token: this.properties.derivedIbcDenom, + token: this.properties.intermediateIbcDenom, warpRouter: this.addresses.intermediateRouterAddress, }, - this.properties.derivedIbcDenom, ); - const transfer = await cwAdapter.populateTransferRemoteTx(transferParams); + assert( + transferParams.interchainGas?.addressOrDenom === this.properties.ibcDenom, + 'Only same-denom interchain gas is supported for IBC to Warp transfers', + ); + // This transformation is necessary to ensure the CW adapter recognizes the gas + // denom is the same as this adapter's denom (e.g. utia & igp/77...) + const intermediateInterchainGas = { + addressOrDenom: this.properties.intermediateIbcDenom, + amount: transferParams.interchainGas?.amount || 0n, + }; + const transfer = await cwAdapter.populateTransferRemoteTx({ + ...transferParams, + interchainGas: intermediateInterchainGas, + }); const cwMemo = { wasm: { contract: transfer.contractAddress, diff --git a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts index 338c484378..f4f99dd7be 100644 --- a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts @@ -4,12 +4,14 @@ import { ERC20, ERC20__factory, HypERC20, + HypERC20Collateral, HypERC20Collateral__factory, HypERC20__factory, } from '@hyperlane-xyz/core'; import { Address, Domain, + Numberish, addressToByteHexString, addressToBytes32, bytes32ToAddress, @@ -24,6 +26,7 @@ import { MinimalTokenMetadata } from '../config'; import { IHypTokenAdapter, ITokenAdapter, + InterchainGasQuote, TransferParams, TransferRemoteParams, } from './ITokenAdapter'; @@ -31,11 +34,11 @@ import { // Interacts with native currencies export class EvmNativeTokenAdapter extends BaseEvmAdapter - implements ITokenAdapter + implements ITokenAdapter { - async getBalance(address: Address): Promise { + async getBalance(address: Address): Promise { const balance = await this.getProvider().getBalance(address); - return balance.toString(); + return BigInt(balance.toString()); } async getMetadata(): Promise { @@ -43,6 +46,14 @@ export class EvmNativeTokenAdapter throw new Error('Metadata not available to native tokens'); } + async isApproveRequired( + _owner: Address, + _spender: Address, + _weiAmountOrId: Numberish, + ): Promise { + return false; + } + async populateApproveTx( _params: TransferParams, ): Promise { @@ -53,7 +64,7 @@ export class EvmNativeTokenAdapter weiAmountOrId, recipient, }: TransferParams): Promise { - const value = BigNumber.from(weiAmountOrId); + const value = BigNumber.from(weiAmountOrId.toString()); return { value, to: recipient }; } } @@ -61,7 +72,7 @@ export class EvmNativeTokenAdapter // Interacts with ERC20/721 contracts export class EvmTokenAdapter extends EvmNativeTokenAdapter - implements ITokenAdapter + implements ITokenAdapter { public readonly contract: T; @@ -78,9 +89,9 @@ export class EvmTokenAdapter ); } - override async getBalance(address: Address): Promise { + override async getBalance(address: Address): Promise { const balance = await this.contract.balanceOf(address); - return balance.toString(); + return BigInt(balance.toString()); } override async getMetadata(isNft?: boolean): Promise { @@ -92,25 +103,40 @@ export class EvmTokenAdapter return { decimals, symbol, name }; } + override async isApproveRequired( + owner: Address, + spender: Address, + weiAmountOrId: Numberish, + ): Promise { + const allowance = await this.contract.allowance(owner, spender); + return allowance.lt(weiAmountOrId); + } + override populateApproveTx({ weiAmountOrId, recipient, }: TransferParams): Promise { - return this.contract.populateTransaction.approve(recipient, weiAmountOrId); + return this.contract.populateTransaction.approve( + recipient, + weiAmountOrId.toString(), + ); } override populateTransferTx({ weiAmountOrId, recipient, }: TransferParams): Promise { - return this.contract.populateTransaction.transfer(recipient, weiAmountOrId); + return this.contract.populateTransaction.transfer( + recipient, + weiAmountOrId.toString(), + ); } } // Interacts with Hyp Synthetic token contracts (aka 'HypTokens') -export class EvmHypSyntheticAdapter - extends EvmTokenAdapter - implements IHypTokenAdapter +export class EvmHypSyntheticAdapter + extends EvmTokenAdapter + implements IHypTokenAdapter { constructor( public readonly chainName: ChainName, @@ -121,6 +147,14 @@ export class EvmHypSyntheticAdapter super(chainName, multiProvider, addresses, contractFactory); } + override async isApproveRequired( + _owner: Address, + _spender: Address, + _weiAmountOrId: Numberish, + ): Promise { + return false; + } + getDomains(): Promise { return this.contract.domains(); } @@ -147,64 +181,130 @@ export class EvmHypSyntheticAdapter return domains.map((d, i) => ({ domain: d, address: routers[i] })); } - async quoteGasPayment(destination: Domain): Promise { + async quoteGasPayment(destination: Domain): Promise { const gasPayment = await this.contract.quoteGasPayment(destination); - return gasPayment.toString(); + // If EVM hyp contracts eventually support alternative IGP tokens, + // this would need to determine the correct token address + return { amount: BigInt(gasPayment.toString()) }; } - populateTransferRemoteTx({ + async populateTransferRemoteTx({ weiAmountOrId, destination, recipient, - txValue, + interchainGas, }: TransferRemoteParams): Promise { + if (!interchainGas) interchainGas = await this.quoteGasPayment(destination); + const recipBytes32 = addressToBytes32(addressToByteHexString(recipient)); return this.contract.populateTransaction.transferRemote( destination, recipBytes32, weiAmountOrId, - { - // Note, typically the value is the gas payment as quoted by IGP - value: txValue, - }, + { value: interchainGas.amount.toString() }, ); } } -// Interacts with HypCollateral and HypNative contracts +// Interacts with HypCollateral contracts export class EvmHypCollateralAdapter extends EvmHypSyntheticAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { + public readonly collateralContract: HypERC20Collateral; + protected wrappedTokenAddress?: Address; + constructor( public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address }, - public readonly contractFactory: any = HypERC20Collateral__factory, ) { - super(chainName, multiProvider, addresses, contractFactory); + super(chainName, multiProvider, addresses); + this.collateralContract = HypERC20Collateral__factory.connect( + addresses.token, + this.getProvider(), + ); + } + + protected async getWrappedTokenAddress(): Promise
{ + if (!this.wrappedTokenAddress) { + this.wrappedTokenAddress = await this.collateralContract.wrappedToken(); + } + return this.wrappedTokenAddress; + } + + protected async getWrappedTokenAdapter(): Promise< + ITokenAdapter + > { + return new EvmTokenAdapter(this.chainName, this.multiProvider, { + token: await this.getWrappedTokenAddress(), + }); + } + + override getMetadata(isNft?: boolean): Promise { + return this.getWrappedTokenAdapter().then((t) => t.getMetadata(isNft)); } - override getMetadata(): Promise { - // TODO pass through metadata from wrapped token or chainMetadata config - throw new Error( - 'Metadata not available for HypCollateral/HypNative contract.', + override isApproveRequired( + owner: Address, + spender: Address, + weiAmountOrId: Numberish, + ): Promise { + return this.getWrappedTokenAdapter().then((t) => + t.isApproveRequired(owner, spender, weiAmountOrId), ); } override populateApproveTx( - _params: TransferParams, + params: TransferParams, ): Promise { - throw new Error( - 'Approve not applicable to HypCollateral/HypNative contract.', + return this.getWrappedTokenAdapter().then((t) => + t.populateApproveTx(params), ); } override populateTransferTx( - _params: TransferParams, + params: TransferParams, ): Promise { - throw new Error( - 'Local transfer not supported for HypCollateral/HypNative contract.', + return this.getWrappedTokenAdapter().then((t) => + t.populateTransferTx(params), + ); + } +} + +// Interacts HypNative contracts +export class EvmHypNativeAdapter + extends EvmHypCollateralAdapter + implements IHypTokenAdapter +{ + override async isApproveRequired(): Promise { + return false; + } + + override async populateTransferRemoteTx({ + weiAmountOrId, + destination, + recipient, + interchainGas, + }: TransferRemoteParams): Promise { + if (!interchainGas) interchainGas = await this.quoteGasPayment(destination); + + let txValue: bigint | undefined = undefined; + const { addressOrDenom: igpAddressOrDenom, amount: igpAmount } = + interchainGas; + // If the igp token is native Eth + if (!igpAddressOrDenom) { + txValue = igpAmount + BigInt(weiAmountOrId); + } else { + txValue = igpAmount; + } + + const recipBytes32 = addressToBytes32(addressToByteHexString(recipient)); + return this.contract.populateTransaction.transferRemote( + destination, + recipBytes32, + weiAmountOrId, + { value: txValue?.toString() }, ); } } diff --git a/typescript/sdk/src/token/adapters/ITokenAdapter.ts b/typescript/sdk/src/token/adapters/ITokenAdapter.ts index b41c42ebae..038c86dc91 100644 --- a/typescript/sdk/src/token/adapters/ITokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/ITokenAdapter.ts @@ -1,37 +1,42 @@ -import { Address, Domain } from '@hyperlane-xyz/utils'; +import { Address, Domain, Numberish } from '@hyperlane-xyz/utils'; import { MinimalTokenMetadata } from '../config'; export interface TransferParams { - weiAmountOrId: string | number; + weiAmountOrId: Numberish; recipient: Address; - - // Solana-specific params - // Included here optionally to keep Adapter types simple - fromTokenAccount?: Address; + // Required for Cosmos + Solana fromAccountOwner?: Address; + // Required for Solana + fromTokenAccount?: Address; } export interface TransferRemoteParams extends TransferParams { destination: Domain; - txValue?: string; + interchainGas?: InterchainGasQuote; +} + +export interface InterchainGasQuote { + addressOrDenom?: string; // undefined values represent default native tokens + amount: bigint; } -export interface ITokenAdapter { - getBalance(address: Address): Promise; +export interface ITokenAdapter { + getBalance(address: Address): Promise; getMetadata(isNft?: boolean): Promise; - populateApproveTx(TransferParams: TransferParams): unknown | Promise; - populateTransferTx( - TransferParams: TransferParams, - ): unknown | Promise; + isApproveRequired( + owner: Address, + spender: Address, + weiAmountOrId: Numberish, + ): Promise; + populateApproveTx(params: TransferParams): Promise; + populateTransferTx(params: TransferParams): Promise; } -export interface IHypTokenAdapter extends ITokenAdapter { +export interface IHypTokenAdapter extends ITokenAdapter { getDomains(): Promise; getRouterAddress(domain: Domain): Promise; getAllRouters(): Promise>; - quoteGasPayment(destination: Domain): Promise; - populateTransferRemoteTx( - TransferParams: TransferRemoteParams, - ): unknown | Promise; + quoteGasPayment(destination: Domain): Promise; + populateTransferRemoteTx(p: TransferRemoteParams): Promise; } diff --git a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts index b6d3b00244..db951525af 100644 --- a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts @@ -13,7 +13,6 @@ import { Transaction, TransactionInstruction, } from '@solana/web3.js'; -import BigNumber from 'bignumber.js'; import { deserializeUnchecked, serialize } from 'borsh'; import { @@ -39,6 +38,7 @@ import { MinimalTokenMetadata } from '../config'; import { IHypTokenAdapter, ITokenAdapter, + InterchainGasQuote, TransferParams, TransferRemoteParams, } from './ITokenAdapter'; @@ -54,33 +54,37 @@ import { // Interacts with native currencies export class SealevelNativeTokenAdapter extends BaseSealevelAdapter - implements ITokenAdapter + implements ITokenAdapter { - async getBalance(address: Address): Promise { + async getBalance(address: Address): Promise { const balance = await this.getProvider().getBalance(new PublicKey(address)); - return balance.toString(); + return BigInt(balance.toString()); } async getMetadata(): Promise { throw new Error('Metadata not available to native tokens'); } - populateApproveTx(_params: TransferParams): Transaction { + async isApproveRequired(): Promise { + return false; + } + + async populateApproveTx(): Promise { throw new Error('Approve not required for native tokens'); } - populateTransferTx({ + async populateTransferTx({ weiAmountOrId, recipient, fromAccountOwner, - }: TransferParams): Transaction { + }: TransferParams): Promise { if (!fromAccountOwner) throw new Error('fromAccountOwner required for Sealevel'); return new Transaction().add( SystemProgram.transfer({ fromPubkey: new PublicKey(fromAccountOwner), toPubkey: new PublicKey(recipient), - lamports: new BigNumber(weiAmountOrId).toNumber(), + lamports: BigInt(weiAmountOrId), }), ); } @@ -89,7 +93,7 @@ export class SealevelNativeTokenAdapter // Interacts with SPL token programs export class SealevelTokenAdapter extends BaseSealevelAdapter - implements ITokenAdapter + implements ITokenAdapter { public readonly tokenProgramPubKey: PublicKey; @@ -103,12 +107,12 @@ export class SealevelTokenAdapter this.tokenProgramPubKey = new PublicKey(addresses.token); } - async getBalance(owner: Address): Promise { + async getBalance(owner: Address): Promise { const tokenPubKey = this.deriveAssociatedTokenAccount(new PublicKey(owner)); const response = await this.getProvider().getTokenAccountBalance( tokenPubKey, ); - return response.value.amount; + return BigInt(response.value.amount); } async getMetadata(_isNft?: boolean): Promise { @@ -116,16 +120,20 @@ export class SealevelTokenAdapter return { decimals: 9, symbol: 'SPL', name: 'SPL Token' }; } + async isApproveRequired(): Promise { + return false; + } + populateApproveTx(_params: TransferParams): Promise { throw new Error('Approve not required for sealevel tokens'); } - populateTransferTx({ + async populateTransferTx({ weiAmountOrId, recipient, fromAccountOwner, fromTokenAccount, - }: TransferParams): Transaction { + }: TransferParams): Promise { if (!fromTokenAccount) throw new Error('fromTokenAccount required for Sealevel'); if (!fromAccountOwner) @@ -135,7 +143,7 @@ export class SealevelTokenAdapter new PublicKey(fromTokenAccount), new PublicKey(recipient), new PublicKey(fromAccountOwner), - new BigNumber(weiAmountOrId).toNumber(), + BigInt(weiAmountOrId), ), ); } @@ -164,7 +172,7 @@ const TRANSFER_REMOTE_COMPUTE_LIMIT = 1_000_000; export abstract class SealevelHypTokenAdapter extends SealevelTokenAdapter - implements IHypTokenAdapter + implements IHypTokenAdapter { public readonly warpProgramPubKey: PublicKey; protected cachedTokenAccountData: SealevelHyperlaneTokenData | undefined; @@ -235,9 +243,9 @@ export abstract class SealevelHypTokenAdapter })); } - async quoteGasPayment(_destination: Domain): Promise { + async quoteGasPayment(_destination: Domain): Promise { // TODO Solana support - return '0'; + return { amount: 0n }; } async populateTransferRemoteTx({ @@ -264,7 +272,7 @@ export abstract class SealevelHypTokenAdapter data: new SealevelTransferRemoteInstruction({ destination_domain: destination, recipient: addressToBytes(recipient), - amount_or_id: new BigNumber(weiAmountOrId).toNumber(), + amount_or_id: BigInt(weiAmountOrId), }), }); const serializedData = serialize(SealevelTransferRemoteSchema, value); @@ -302,7 +310,7 @@ export abstract class SealevelHypTokenAdapter return tx; } - async getIgpKeys() { + async getIgpKeys(): Promise { const tokenData = await this.getTokenAccountData(); if (!tokenData.interchain_gas_paymaster) return undefined; const igpConfig = tokenData.interchain_gas_paymaster; @@ -492,7 +500,7 @@ export class SealevelHypNativeAdapter extends SealevelHypTokenAdapter { ); } - override async getBalance(owner: Address): Promise { + override async getBalance(owner: Address): Promise { return this.wrappedNative.getBalance(owner); } @@ -525,7 +533,7 @@ export class SealevelHypNativeAdapter extends SealevelHypTokenAdapter { // Interacts with Hyp Collateral token programs export class SealevelHypCollateralAdapter extends SealevelHypTokenAdapter { - async getBalance(owner: Address): Promise { + async getBalance(owner: Address): Promise { // Special case where the owner is the warp route program ID. // This is because collateral warp routes don't hold escrowed collateral // tokens in their associated token account - instead, they hold them in @@ -535,7 +543,7 @@ export class SealevelHypCollateralAdapter extends SealevelHypTokenAdapter { const response = await this.getProvider().getTokenAccountBalance( collateralAccount, ); - return response.value.amount; + return BigInt(response.value.amount); } return super.getBalance(owner); @@ -593,12 +601,12 @@ export class SealevelHypSyntheticAdapter extends SealevelHypTokenAdapter { ]; } - override async getBalance(owner: Address): Promise { + override async getBalance(owner: Address): Promise { const tokenPubKey = this.deriveAssociatedTokenAccount(new PublicKey(owner)); const response = await this.getProvider().getTokenAccountBalance( tokenPubKey, ); - return response.value.amount; + return BigInt(response.value.amount); } deriveMintAuthorityAccount(): PublicKey { diff --git a/typescript/sdk/src/token/deploy.ts b/typescript/sdk/src/token/deploy.ts index 23ac7db353..a158d03c01 100644 --- a/typescript/sdk/src/token/deploy.ts +++ b/typescript/sdk/src/token/deploy.ts @@ -24,6 +24,7 @@ import { import { objMap } from '@hyperlane-xyz/utils'; import { HyperlaneContracts } from '../contracts/types'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory'; import { MultiProvider } from '../providers/MultiProvider'; import { GasRouterDeployer } from '../router/GasRouterDeployer'; @@ -56,10 +57,15 @@ export class HypERC20Deployer extends GasRouterDeployer< ERC20RouterConfig, HypERC20Factories > { - constructor(multiProvider: MultiProvider, ismFactory?: HyperlaneIsmFactory) { + constructor( + multiProvider: MultiProvider, + ismFactory?: HyperlaneIsmFactory, + contractVerifier?: ContractVerifier, + ) { super(multiProvider, {} as HypERC20Factories, { logger: debug('hyperlane:HypERC20Deployer'), ismFactory, + contractVerifier, }); // factories not used in deploy } @@ -183,10 +189,17 @@ export class HypERC20Deployer extends GasRouterDeployer< config.mailbox, ]); - await this.multiProvider.handleTx( - chain, - router.initialize(config.totalSupply, config.name, config.symbol), - ); + try { + await this.multiProvider.handleTx( + chain, + router.initialize(config.totalSupply, config.name, config.symbol), + ); + } catch (e: any) { + if (!e.message.includes('already initialized')) { + throw e; + } + this.logger(`${name} already initialized`); + } return router; } @@ -273,9 +286,13 @@ export class HypERC721Deployer extends GasRouterDeployer< ERC721RouterConfig, HypERC721Factories > { - constructor(multiProvider: MultiProvider) { + constructor( + multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, + ) { super(multiProvider, {} as HypERC721Factories, { logger: debug('hyperlane:HypERC721Deployer'), + contractVerifier, }); // factories not used in deploy } diff --git a/typescript/sdk/src/types.ts b/typescript/sdk/src/types.ts index 3b10b97103..8eabb6a74b 100644 --- a/typescript/sdk/src/types.ts +++ b/typescript/sdk/src/types.ts @@ -1,5 +1,7 @@ import type { ethers } from 'ethers'; +import type { ChainId, Domain } from '@hyperlane-xyz/utils'; + // An alias for string to clarify type is a chain name export type ChainName = string; // A map of chain names to a value type @@ -7,6 +9,6 @@ export type ChainMap = Record; // The names of test chains, should be kept up to date if new are added export type TestChainNames = 'test1' | 'test2' | 'test3'; -export type NameOrDomain = ChainName | number; +export type ChainNameOrId = ChainName | ChainId | Domain; export type Connection = ethers.providers.Provider | ethers.Signer; diff --git a/typescript/sdk/src/warp/WarpCore.test.ts b/typescript/sdk/src/warp/WarpCore.test.ts new file mode 100644 index 0000000000..fd2e9bbddf --- /dev/null +++ b/typescript/sdk/src/warp/WarpCore.test.ts @@ -0,0 +1,264 @@ +import { expect } from 'chai'; +import { ethers } from 'ethers'; +import fs from 'fs'; +import path from 'path'; +import sinon from 'sinon'; +import { parse as yamlParse } from 'yaml'; + +import { chainMetadata } from '../consts/chainMetadata'; +import { Chains } from '../consts/chains'; +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; +import { ProviderType } from '../providers/ProviderType'; +import { Token } from '../token/Token'; +import { TokenStandard } from '../token/TokenStandard'; +import { InterchainGasQuote } from '../token/adapters/ITokenAdapter'; +import { ChainName } from '../types'; + +import { WarpCore } from './WarpCore'; +import { WarpTxCategory } from './types'; + +const MOCK_QUOTE = { amount: 20_000n }; +const TRANSFER_AMOUNT = BigInt('1000000000000000000'); // 1 units @ 18 decimals +const BIG_TRANSFER_AMOUNT = BigInt('100000000000000000000'); // 100 units @ 18 decimals +const MOCK_BALANCE = BigInt('10000000000000000000'); // 10 units @ 18 decimals + +describe('WarpCore', () => { + const multiProvider = new MultiProtocolProvider(); + let warpCore: WarpCore; + let evmHypNative: Token; + let evmHypSynthetic: Token; + let sealevelHypSynthetic: Token; + let cwHypCollateral: Token; + let cw20: Token; + let cosmosIbc: Token; + + it('Constructs', () => { + const fromArgs = new WarpCore(multiProvider, [ + Token.FromChainMetadataNativeToken(chainMetadata[Chains.ethereum]), + ]); + const exampleConfig = yamlParse( + fs.readFileSync( + path.join(__dirname, './example-warp-core-config.yaml'), + 'utf-8', + ), + ); + const fromConfig = WarpCore.FromConfig(multiProvider, exampleConfig); + expect(fromArgs).to.be.instanceOf(WarpCore); + expect(fromConfig).to.be.instanceOf(WarpCore); + expect(fromConfig.tokens.length).to.equal(exampleConfig.tokens.length); + + warpCore = fromConfig; + [ + evmHypNative, + evmHypSynthetic, + sealevelHypSynthetic, + cwHypCollateral, + cw20, + cosmosIbc, + ] = warpCore.tokens; + }); + + it('Finds tokens', () => { + expect( + warpCore.findToken(Chains.ethereum, evmHypNative.addressOrDenom), + ).to.be.instanceOf(Token); + expect( + warpCore.findToken(Chains.ethereum, sealevelHypSynthetic.addressOrDenom), + ).to.be.null; + expect( + warpCore.findToken(Chains.neutron, cw20.addressOrDenom), + ).to.be.instanceOf(Token); + }); + + it('Gets transfer gas quote', async () => { + const stubs = warpCore.tokens.map((t) => + sinon.stub(t, 'getHypAdapter').returns({ + quoteGasPayment: () => Promise.resolve(MOCK_QUOTE), + } as any), + ); + + const testQuote = async ( + token: Token, + chain: ChainName, + standard: TokenStandard, + quote: InterchainGasQuote = MOCK_QUOTE, + ) => { + const result = await warpCore.getTransferGasQuote(token, chain); + expect( + result.token.standard, + `token standard check for ${token.chainName} to ${chain}`, + ).equals(standard); + expect( + result.amount, + `token amount check for ${token.chainName} to ${chain}`, + ).to.equal(quote.amount); + }; + + await testQuote(evmHypNative, Chains.arbitrum, TokenStandard.EvmNative); + await testQuote(evmHypNative, Chains.neutron, TokenStandard.EvmNative); + await testQuote(evmHypNative, Chains.solana, TokenStandard.EvmNative); + await testQuote(evmHypSynthetic, Chains.ethereum, TokenStandard.EvmNative); + await testQuote( + sealevelHypSynthetic, + Chains.ethereum, + TokenStandard.SealevelNative, + ); + await testQuote(cosmosIbc, Chains.arbitrum, TokenStandard.CosmosNative); + // Note, this route uses an igp quote const config + await testQuote( + cwHypCollateral, + Chains.arbitrum, + TokenStandard.CosmosNative, + { + amount: 1n, + addressOrDenom: 'untrn', + }, + ); + + stubs.forEach((s) => s.restore()); + }); + + it('Checks for destination collateral', async () => { + const stubs = warpCore.tokens.map((t) => + sinon.stub(t, 'getHypAdapter').returns({ + getBalance: () => Promise.resolve(MOCK_BALANCE), + } as any), + ); + + const testCollateral = async ( + token: Token, + chain: ChainName, + expectedBigResult = true, + ) => { + const smallResult = await warpCore.isDestinationCollateralSufficient( + token.amount(TRANSFER_AMOUNT), + chain, + ); + expect( + smallResult, + `small collateral check for ${token.chainName} to ${chain}`, + ).to.be.true; + const bigResult = await warpCore.isDestinationCollateralSufficient( + token.amount(BIG_TRANSFER_AMOUNT), + chain, + ); + expect( + bigResult, + `big collateral check for ${token.chainName} to ${chain}`, + ).to.equal(expectedBigResult); + }; + + await testCollateral(evmHypNative, Chains.arbitrum); + await testCollateral(evmHypNative, Chains.neutron, false); + await testCollateral(evmHypNative, Chains.solana); + await testCollateral(cwHypCollateral, Chains.arbitrum); + + stubs.forEach((s) => s.restore()); + }); + + it('Validates transfers', async () => { + const balanceStubs = warpCore.tokens.map((t) => + sinon + .stub(t, 'getBalance') + .returns(Promise.resolve({ amount: MOCK_BALANCE } as any)), + ); + const quoteStubs = warpCore.tokens.map((t) => + sinon.stub(t, 'getHypAdapter').returns({ + quoteGasPayment: () => Promise.resolve(MOCK_QUOTE), + } as any), + ); + + const validResult = await warpCore.validateTransfer( + evmHypNative.amount(TRANSFER_AMOUNT), + Chains.arbitrum, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ); + expect(validResult).to.be.null; + + const invalidChain = await warpCore.validateTransfer( + evmHypNative.amount(TRANSFER_AMOUNT), + 'fakechain', + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ); + expect(Object.keys(invalidChain || {})[0]).to.equal('destination'); + + const invalidRecipient = await warpCore.validateTransfer( + evmHypNative.amount(TRANSFER_AMOUNT), + Chains.neutron, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ); + expect(Object.keys(invalidRecipient || {})[0]).to.equal('recipient'); + + const invalidAmount = await warpCore.validateTransfer( + evmHypNative.amount(-10), + Chains.arbitrum, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ); + expect(Object.keys(invalidAmount || {})[0]).to.equal('amount'); + + const insufficientBalance = await warpCore.validateTransfer( + evmHypNative.amount(BIG_TRANSFER_AMOUNT), + Chains.arbitrum, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ); + expect(Object.keys(insufficientBalance || {})[0]).to.equal('amount'); + + balanceStubs.forEach((s) => s.restore()); + quoteStubs.forEach((s) => s.restore()); + }); + + it('Gets transfer remote txs', async () => { + const coreStub = sinon + .stub(warpCore, 'isApproveRequired') + .returns(Promise.resolve(false)); + + const adapterStubs = warpCore.tokens.map((t) => + sinon.stub(t, 'getHypAdapter').returns({ + quoteGasPayment: () => Promise.resolve(MOCK_QUOTE), + populateTransferRemoteTx: () => Promise.resolve({}), + } as any), + ); + + const testGetTxs = async ( + token: Token, + chain: ChainName, + providerType = ProviderType.EthersV5, + ) => { + const result = await warpCore.getTransferRemoteTxs( + token.amount(TRANSFER_AMOUNT), + chain, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ); + expect(result.length).to.equal(1); + expect( + result[0], + `transfer tx for ${token.chainName} to ${chain}`, + ).to.eql({ + category: WarpTxCategory.Transfer, + transaction: {}, + type: providerType, + }); + }; + + await testGetTxs(evmHypNative, Chains.arbitrum); + await testGetTxs(evmHypNative, Chains.neutron); + await testGetTxs(evmHypNative, Chains.solana); + await testGetTxs(evmHypSynthetic, Chains.ethereum); + await testGetTxs( + sealevelHypSynthetic, + Chains.ethereum, + ProviderType.SolanaWeb3, + ); + await testGetTxs(cwHypCollateral, Chains.arbitrum, ProviderType.CosmJsWasm); + await testGetTxs(cosmosIbc, Chains.arbitrum, ProviderType.CosmJs); + + coreStub.restore(); + adapterStubs.forEach((s) => s.restore()); + }); +}); diff --git a/typescript/sdk/src/warp/WarpCore.ts b/typescript/sdk/src/warp/WarpCore.ts new file mode 100644 index 0000000000..758ffcdd92 --- /dev/null +++ b/typescript/sdk/src/warp/WarpCore.ts @@ -0,0 +1,445 @@ +import debug, { Debugger } from 'debug'; + +import { + Address, + ProtocolType, + assert, + convertDecimals, + isValidAddress, +} from '@hyperlane-xyz/utils'; + +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; +import { IToken } from '../token/IToken'; +import { Token } from '../token/Token'; +import { TokenAmount } from '../token/TokenAmount'; +import { + TOKEN_COLLATERALIZED_STANDARDS, + TOKEN_STANDARD_TO_PROVIDER_TYPE, +} from '../token/TokenStandard'; +import { ChainName, ChainNameOrId } from '../types'; + +import { + IgpQuoteConstants, + RouteBlacklist, + WarpCoreConfigSchema, + WarpTxCategory, + WarpTypedTransaction, +} from './types'; + +export interface WarpCoreOptions { + loggerName?: string; + igpQuoteConstants?: IgpQuoteConstants; + routeBlacklist?: RouteBlacklist; +} + +export class WarpCore { + public readonly multiProvider: MultiProtocolProvider<{ mailbox?: Address }>; + public readonly tokens: Token[]; + public readonly igpQuoteConstants: IgpQuoteConstants; + public readonly routeBlacklist: RouteBlacklist; + public readonly logger: Debugger; + + constructor( + multiProvider: MultiProtocolProvider<{ mailbox?: Address }>, + tokens: Token[], + options?: WarpCoreOptions, + ) { + this.multiProvider = multiProvider; + this.tokens = tokens; + this.igpQuoteConstants = options?.igpQuoteConstants || []; + this.routeBlacklist = options?.routeBlacklist || []; + this.logger = debug(options?.loggerName || 'hyperlane:WarpCore'); + } + + // Takes the serialized representation of a complete warp config and returns a WarpCore instance + static FromConfig( + multiProvider: MultiProtocolProvider<{ mailbox?: Address }>, + config: unknown, + ): WarpCore { + // Validate and parse config data + const parsedConfig = WarpCoreConfigSchema.parse(config); + // Instantiate all tokens + const tokens = parsedConfig.tokens.map( + (t) => + new Token({ + ...t, + addressOrDenom: t.addressOrDenom || '', + connections: undefined, + }), + ); + // Connect tokens together + parsedConfig.tokens.forEach((config, i) => { + for (const connection of config.connections || []) { + const token1 = tokens[i]; + // TODO see https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3298 + const [_protocol, chainName, addrOrDenom] = connection.token.split('|'); + const token2 = tokens.find( + (t) => t.chainName === chainName && t.addressOrDenom === addrOrDenom, + ); + assert( + token2, + `Connected token not found: ${chainName} ${addrOrDenom}`, + ); + token1.addConnection({ + ...connection, + token: token2, + }); + } + }); + // Create new Warp + return new WarpCore(multiProvider, tokens, { + igpQuoteConstants: parsedConfig.options?.igpQuoteConstants, + routeBlacklist: parsedConfig.options?.routeBlacklist, + }); + } + + async getTransferGasQuote( + originToken: IToken, + destination: ChainNameOrId, + ): Promise { + const { chainName: originName } = originToken; + const destinationName = this.multiProvider.getChainName(destination); + + let gasAmount: bigint; + let gasAddressOrDenom: string | undefined; + // Check constant quotes first + const defaultQuote = this.igpQuoteConstants.find( + (q) => q.origin === originName && q.destination === destinationName, + ); + if (defaultQuote) { + gasAmount = BigInt(defaultQuote.amount.toString()); + gasAddressOrDenom = defaultQuote.addressOrDenom; + } else { + // Otherwise, compute IGP quote via the adapter + const hypAdapter = originToken.getHypAdapter( + this.multiProvider, + destinationName, + ); + const destinationDomainId = this.multiProvider.getDomainId(destination); + const quote = await hypAdapter.quoteGasPayment(destinationDomainId); + gasAmount = BigInt(quote.amount); + gasAddressOrDenom = quote.addressOrDenom; + } + + let igpToken: Token; + if (!gasAddressOrDenom) { + // An empty/undefined addressOrDenom indicates the native token + igpToken = Token.FromChainMetadataNativeToken( + this.multiProvider.getChainMetadata(originName), + ); + } else { + const searchResult = this.findToken(originName, gasAddressOrDenom); + assert(searchResult, `IGP token ${gasAddressOrDenom} is unknown`); + igpToken = searchResult; + } + + this.logger(`Quoted igp gas payment: ${gasAmount} ${igpToken.symbol}`); + return new TokenAmount(gasAmount, igpToken); + } + + async getTransferRemoteTxs( + originTokenAmount: TokenAmount, + destination: ChainNameOrId, + sender: Address, + recipient: Address, + ): Promise> { + const transactions: Array = []; + + const { token, amount } = originTokenAmount; + const destinationName = this.multiProvider.getChainName(destination); + const destinationDomainId = this.multiProvider.getDomainId(destination); + const providerType = TOKEN_STANDARD_TO_PROVIDER_TYPE[token.standard]; + const hypAdapter = token.getHypAdapter(this.multiProvider, destinationName); + + if (await this.isApproveRequired(originTokenAmount, sender)) { + this.logger(`Approval required for transfer of ${token.symbol}`); + const approveTxReq = await hypAdapter.populateApproveTx({ + weiAmountOrId: amount.toString(), + recipient: token.addressOrDenom, + }); + this.logger(`Approval tx for ${token.symbol} populated`); + + const approveTx = { + category: WarpTxCategory.Approval, + type: providerType, + transaction: approveTxReq, + } as WarpTypedTransaction; + transactions.push(approveTx); + } + + const interchainGasAmount = await this.getTransferGasQuote( + token, + destination, + ); + + const transferTxReq = await hypAdapter.populateTransferRemoteTx({ + weiAmountOrId: amount.toString(), + destination: destinationDomainId, + fromAccountOwner: sender, + recipient, + interchainGas: { + amount: interchainGasAmount.amount, + addressOrDenom: interchainGasAmount.token.addressOrDenom, + }, + }); + this.logger(`Remote transfer tx for ${token.symbol} populated`); + + const transferTx = { + category: WarpTxCategory.Transfer, + type: providerType, + transaction: transferTxReq, + } as WarpTypedTransaction; + transactions.push(transferTx); + + return transactions; + } + + /** + * Checks if destination chain's collateral is sufficient to cover the transfer + */ + async isDestinationCollateralSufficient( + originTokenAmount: TokenAmount, + destination: ChainNameOrId, + ): Promise { + const { token: originToken, amount } = originTokenAmount; + const destinationName = this.multiProvider.getChainName(destination); + this.logger( + `Checking collateral for ${originToken.symbol} to ${destination}`, + ); + + const destinationToken = + originToken.getConnectionForChain(destinationName)?.token; + assert(destinationToken, `No connection found for ${destinationName}`); + + if (!TOKEN_COLLATERALIZED_STANDARDS.includes(destinationToken.standard)) { + this.logger(`${destinationToken.symbol} is not collateralized, skipping`); + return true; + } + + const adapter = destinationToken.getAdapter(this.multiProvider); + const destinationBalance = await adapter.getBalance( + destinationToken.addressOrDenom, + ); + const destinationBalanceInOriginDecimals = convertDecimals( + destinationToken.decimals, + originToken.decimals, + destinationBalance.toString(), + ); + + const isSufficient = BigInt(destinationBalanceInOriginDecimals) >= amount; + this.logger( + `${originTokenAmount.token.symbol} to ${destination} has ${ + isSufficient ? 'sufficient' : 'INSUFFICIENT' + } collateral`, + ); + return isSufficient; + } + + /** + * Checks if a token transfer requires an approval tx first + */ + async isApproveRequired( + originTokenAmount: TokenAmount, + owner: Address, + ): Promise { + const { token, amount } = originTokenAmount; + const adapter = token.getAdapter(this.multiProvider); + const isRequired = await adapter.isApproveRequired( + owner, + token.addressOrDenom, + amount, + ); + this.logger( + `Approval is${isRequired ? '' : ' not'} required for transfer of ${ + token.symbol + }`, + ); + return isRequired; + } + + /** + * Ensure the remote token transfer would be valid for the given chains, amount, sender, and recipient + */ + async validateTransfer( + originTokenAmount: TokenAmount, + destination: ChainNameOrId, + sender: Address, + recipient: Address, + ): Promise | null> { + const chainError = this.validateChains( + originTokenAmount.token.chainName, + destination, + ); + if (chainError) return chainError; + + const recipientError = this.validateRecipient(recipient, destination); + if (recipientError) return recipientError; + + const amountError = this.validateAmount(originTokenAmount); + if (amountError) return amountError; + + const balancesError = await this.validateTokenBalances( + originTokenAmount, + destination, + sender, + ); + if (balancesError) return balancesError; + + return null; + } + + /** + * Ensure the origin and destination chains are valid and known by this WarpCore + */ + protected validateChains( + origin: ChainNameOrId, + destination: ChainNameOrId, + ): Record | null { + if (!origin) return { origin: 'Origin chain required' }; + if (!destination) return { destination: 'Destination chain required' }; + const originMetadata = this.multiProvider.tryGetChainMetadata(origin); + const destinationMetadata = + this.multiProvider.tryGetChainMetadata(destination); + if (!originMetadata) return { origin: 'Origin chain metadata missing' }; + if (!destinationMetadata) + return { destination: 'Destination chain metadata missing' }; + if ( + this.routeBlacklist.some( + (bl) => + bl.origin === originMetadata.name && + bl.destination === destinationMetadata.name, + ) + ) { + return { destination: 'Route is not currently allowed' }; + } + return null; + } + + /** + * Ensure recipient address is valid for the destination chain + */ + protected validateRecipient( + recipient: Address, + destination: ChainNameOrId, + ): Record | null { + const destinationMetadata = + this.multiProvider.getChainMetadata(destination); + const { protocol, bech32Prefix } = destinationMetadata; + // Ensure recip address is valid for the destination chain's protocol + if (!isValidAddress(recipient, protocol)) + return { recipient: 'Invalid recipient' }; + // Also ensure the address denom is correct if the dest protocol is Cosmos + if (protocol === ProtocolType.Cosmos) { + if (!bech32Prefix) { + this.logger(`No bech32 prefix found for chain ${destination}`); + return { destination: 'Invalid chain data' }; + } else if (!recipient.startsWith(bech32Prefix)) { + this.logger(`Recipient prefix should be ${bech32Prefix}`); + return { recipient: `Invalid recipient prefix` }; + } + } + return null; + } + + /** + * Ensure token amount is valid + */ + protected validateAmount( + originTokenAmount: TokenAmount, + ): Record | null { + if (!originTokenAmount.amount || originTokenAmount.amount < 0n) { + const isNft = originTokenAmount.token.isNft(); + return { amount: isNft ? 'Invalid Token Id' : 'Invalid amount' }; + } + return null; + } + + /** + * Ensure the sender has sufficient balances for transfer and interchain gas + */ + protected async validateTokenBalances( + originTokenAmount: TokenAmount, + destination: ChainNameOrId, + sender: Address, + ): Promise | null> { + const { token, amount } = originTokenAmount; + const { amount: senderBalance } = await token.getBalance( + this.multiProvider, + sender, + ); + + // First check basic token balance + if (amount > senderBalance) return { amount: 'Insufficient balance' }; + + // Next, ensure balances can cover IGP fees + const igpQuote = await this.getTransferGasQuote(token, destination); + if (token.equals(igpQuote.token) || token.collateralizes(igpQuote.token)) { + const total = amount + igpQuote.amount; + if (senderBalance < total) + return { amount: 'Insufficient balance for gas and transfer' }; + } else { + const igpTokenBalance = await igpQuote.token.getBalance( + this.multiProvider, + sender, + ); + if (igpTokenBalance.amount < igpQuote.amount) + return { amount: `Insufficient ${igpQuote.token.symbol} for gas` }; + } + + return null; + } + + /** + * Search through token list to find token with matching chain and address + */ + findToken( + chainName: ChainName, + addressOrDenom?: Address | string, + ): Token | null { + if (!addressOrDenom) return null; + + const results = this.tokens.filter( + (token) => + token.chainName === chainName && + token.addressOrDenom.toLowerCase() === addressOrDenom.toLowerCase(), + ); + + if (results.length === 1) return results[0]; + + if (results.length > 1) + throw new Error(`Ambiguous token search results for ${addressOrDenom}`); + + // If the token is not found, check to see if it matches the denom of chain's native token + // This is a convenience so WarpConfigs don't need to include definitions for native tokens + const chainMetadata = this.multiProvider.getChainMetadata(chainName); + if (chainMetadata.nativeToken?.denom === addressOrDenom) { + return Token.FromChainMetadataNativeToken(chainMetadata); + } + + return null; + } + + /** + * Get the list of chains referenced by the tokens in this WarpCore + */ + getTokenChains(): ChainName[] { + return [...new Set(this.tokens.map((t) => t.chainName)).values()]; + } + + /** + * Get the subset of tokens whose chain matches the given chainName + */ + getTokensForChain(chainName: ChainName): Token[] { + return this.tokens.filter((t) => t.chainName === chainName); + } + + /** + * Get the subset of tokens whose chain matches the given chainName + * and which are connected to a token on the given destination chain + */ + getTokensForRoute(origin: ChainName, destination: ChainName): Token[] { + return this.tokens.filter( + (t) => t.chainName === origin && t.getConnectionForChain(destination), + ); + } +} diff --git a/typescript/sdk/src/warp/example-warp-core-config.yaml b/typescript/sdk/src/warp/example-warp-core-config.yaml new file mode 100644 index 0000000000..82b99cdcec --- /dev/null +++ b/typescript/sdk/src/warp/example-warp-core-config.yaml @@ -0,0 +1,73 @@ +# An example Warp Core config +# Contains the token + route data needed to create a Warp Core +--- +tokens: + # Eth Mainnet HypNative token + - chainName: ethereum + standard: EvmHypNative + decimals: 18 + symbol: ETH + name: Ether + addressOrDenom: '0x1234567890123456789012345678901234567890' + connections: + - { token: ethereum|arbitrum|0x9876543210987654321098765432109876543210 } + - { token: cosmos|neutron|neutron1abcdefghijklmnopqrstuvwxyz1234567890ab } + - { token: sealevel|solana|s0LaBcEeFgHiJkLmNoPqRsTuVwXyZ456789012345678 } + # Arbitrum HypSynthetic token + - chainName: arbitrum + standard: EvmHypSynthetic + decimals: 18 + symbol: ETH + name: Ether + addressOrDenom: '0x9876543210987654321098765432109876543210' + connections: + - { token: ethereum|ethereum|0x1234567890123456789012345678901234567890 } + - { token: cosmos|neutron|neutron1abcdefghijklmnopqrstuvwxyz1234567890ab } + # Solana HypSynthetic + - chainName: solana + standard: SealevelHypSynthetic + decimals: 9 + symbol: ETH.sol + name: Ether on Solana + addressOrDenom: s0LaBcEeFgHiJkLmNoPqRsTuVwXyZ456789012345678 + connections: + - { token: ethereum|ethereum|0x1234567890123456789012345678901234567890 } + # Cosmos Neutron HypCollateral token + - chainName: neutron + standard: CwHypCollateral + decimals: 18 + symbol: ETH.in + name: Ether on Neutron + addressOrDenom: neutron1abcdefghijklmnopqrstuvwxyz1234567890ab + collateralAddressOrDenom: neutron1c0ll4t3r4lc0ll4t3r4lc0ll4t3r4lc0ll4t3r + connections: + - { token: ethereum|ethereum|0x1234567890123456789012345678901234567890 } + - { token: ethereum|arbitrum|0x9876543210987654321098765432109876543210 } + # Cosmos Neutron Collateralized token + - chainName: neutron + standard: CW20 + decimals: 18 + symbol: ETH.in + name: Ether on Neutron + addressOrDenom: neutron1c0ll4t3r4lc0ll4t3r4lc0ll4t3r4lc0ll4t3r + # Cosmos Injective token with IBC two-hop + - chainName: injective + standard: CosmosIbc + decimals: 18 + symbol: INJ + name: Injective + addressOrDenom: inj + connections: + - token: ethereum|arbitrum|0x9876543210987654321098765432109876543210 + type: ibc + sourcePort: transfer + sourceChannel: channel-1 + intermediateChainName: neutron + intermediateIbcDenom: untrn + intermediateRouterAddress: neutron1abcdefghijklmnopqrstuvwxyz1234567890ab +options: + igpQuoteConstants: + - origin: neutron + destination: arbitrum + amount: 1 + addressOrDenom: untrn diff --git a/typescript/sdk/src/warp/types.ts b/typescript/sdk/src/warp/types.ts new file mode 100644 index 0000000000..db490714de --- /dev/null +++ b/typescript/sdk/src/warp/types.ts @@ -0,0 +1,62 @@ +import { z } from 'zod'; + +import { ZChainName } from '../metadata/customZodTypes'; +import { TypedTransaction } from '../providers/ProviderType'; +import { TokenConfigSchema } from '../token/IToken'; +import { ChainName } from '../types'; + +// Map of protocol to either quote constant or to a map of chain name to quote constant +export type IgpQuoteConstants = Array<{ + origin: ChainName; + destination: ChainName; + amount: string | number | bigint; + addressOrDenom?: string; +}>; + +// List of chain pairs to blacklist for warp routes +export type RouteBlacklist = Array<{ + origin: ChainName; + destination: ChainName; +}>; + +// Transaction types for warp core remote transfers +export enum WarpTxCategory { + Approval = 'approval', + Transfer = 'transfer', +} + +export type WarpTypedTransaction = TypedTransaction & { + category: WarpTxCategory; +}; + +/** + * Configuration used for instantiating a WarpCore + * Contains the relevant tokens and their connections + */ +export const WarpCoreConfigSchema = z.object({ + tokens: z.array(TokenConfigSchema), + options: z + .object({ + igpQuoteConstants: z + .array( + z.object({ + origin: ZChainName, + destination: ZChainName, + amount: z.union([z.string(), z.number(), z.bigint()]), + addressOrDenom: z.string().optional(), + }), + ) + .optional(), + routeBlacklist: z + .array( + z.object({ + origin: ZChainName, + destination: ZChainName, + }), + ) + .optional(), + }) + .optional(), +}); + +export type WarpCoreConfig = z.infer; diff --git a/typescript/sdk/tsconfig.json b/typescript/sdk/tsconfig.json index 9bf7368a74..72d5a04621 100644 --- a/typescript/sdk/tsconfig.json +++ b/typescript/sdk/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "./dist/", - "rootDir": "./src/" + "rootDir": "./src/", + "resolveJsonModule": true }, - "include": ["./src/**/*.ts", "./src/*.d.ts"], + "include": ["./src/**/*.ts", "./src/*.d.ts"] } diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index e83b639edc..781d871fff 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,3 +1,34 @@ # @hyperlane-xyz/utils +## 3.7.0 + +## 3.6.2 + +## 3.6.1 + +### Patch Changes + +- 3c298d064: Add isAddress function to check if string matches EVM, Cosmos, or Solana address formats +- df24eec8b: Fix for address utils falsy fallbacks +- 78e50e7da: addressToBytes32 changed to work for all protocol types + +## 3.6.0 + +## 3.5.1 + +## 3.5.0 + +## 3.4.0 + +### Patch Changes + +- fd4fc1898: - Upgrade Viem to 1.20.0 + - Add optional restUrls field to ChainMetadata + - Add deepCopy util function + - Add support for cosmos factory token addresses + +## 3.3.0 + +## 3.2.0 + ## 3.1.10 diff --git a/typescript/utils/index.ts b/typescript/utils/index.ts index 08abee53a1..015268a3e9 100644 --- a/typescript/utils/index.ts +++ b/typescript/utils/index.ts @@ -18,6 +18,7 @@ export { eqAddressEvm, eqAddressSol, getAddressProtocolType, + isAddress, isAddressCosmos, isAddressEvm, isAddressSealevel, @@ -48,6 +49,7 @@ export { export { chunk, exclude } from './src/arrays'; export { pollAsync, + raceWithContext, retryAsync, runWithTimeout, sleep, @@ -83,6 +85,7 @@ export { export { ValueOf, arrayToObject, + deepCopy, deepEquals, invertKeysAndValues, isObject, @@ -107,12 +110,14 @@ export { AddressBytes32, CallData, ChainCaip2Id, + ChainId, Checkpoint, Domain, HexString, InterchainSecurityModuleType, MerkleProof, MessageStatus, + Numberish, ParsedLegacyMultisigIsmMetadata, ParsedMessage, ProtocolSmallestUnit, diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 1effa5a2dd..8e73026953 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": "3.1.10", + "version": "3.7.0", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", @@ -30,7 +30,8 @@ "clean": "rm -rf ./dist", "check": "tsc --noEmit", "prettier": "prettier --write ./src", - "test": "mocha --config .mocharc.json './src/**/*.test.ts'" + "test": "mocha --config .mocharc.json './src/**/*.test.ts'", + "test:ci": "yarn test" }, "sideEffects": false, "types": "dist/index.d.ts", diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 131c12c1c6..47852c4525 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -2,13 +2,23 @@ import { fromBech32, normalizeBech32, toBech32 } from '@cosmjs/encoding'; import { PublicKey } from '@solana/web3.js'; import { utils as ethersUtils } from 'ethers'; +import { isNullish } from './typeof'; import { Address, HexString, ProtocolType } from './types'; const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; const SEALEVEL_ADDRESS_REGEX = /^[a-zA-Z0-9]{36,44}$/; -const COSMOS_ADDRESS_REGEX = - /^[a-z]{1,10}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,58}$/; // Bech32 -export const IBC_DENOM_REGEX = /^ibc\/([A-Fa-f0-9]{64})$/; + +const HEX_BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/; + +// https://github.com/cosmos/cosmos-sdk/blob/84c33215658131d87daf3c629e909e12ed9370fa/types/coin.go#L601C17-L601C44 +const COSMOS_DENOM_PATTERN = `[a-zA-Z][a-zA-Z0-9]{2,127}`; +// https://en.bitcoin.it/wiki/BIP_0173 +const BECH32_ADDRESS_PATTERN = `[a-zA-Z]{1,83}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,58}`; +const COSMOS_ADDRESS_REGEX = new RegExp(`^${BECH32_ADDRESS_PATTERN}$`); +const IBC_DENOM_REGEX = new RegExp(`^ibc/([A-Fa-f0-9]{64})$`); +const COSMOS_FACTORY_TOKEN_REGEX = new RegExp( + `^factory/(${BECH32_ADDRESS_PATTERN})/${COSMOS_DENOM_PATTERN}$`, +); const EVM_TX_HASH_REGEX = /^0x([A-Fa-f0-9]{64})$/; const SEALEVEL_TX_HASH_REGEX = /^[a-zA-Z1-9]{88}$/; @@ -26,7 +36,11 @@ export function isAddressSealevel(address: Address) { } export function isAddressCosmos(address: Address) { - return COSMOS_ADDRESS_REGEX.test(address) || IBC_DENOM_REGEX.test(address); + return ( + COSMOS_ADDRESS_REGEX.test(address) || + IBC_DENOM_REGEX.test(address) || + COSMOS_FACTORY_TOKEN_REGEX.test(address) + ); } export function getAddressProtocolType(address: Address) { @@ -42,6 +56,10 @@ export function getAddressProtocolType(address: Address) { } } +export function isAddress(address: Address) { + return !!getAddressProtocolType(address); +} + function routeAddressUtil( fns: Partial T>>, param: string, @@ -50,7 +68,7 @@ function routeAddressUtil( ) { protocol ||= getAddressProtocolType(param); if (protocol && fns[protocol]) return fns[protocol]!(param); - else if (fallback) return fallback; + else if (!isNullish(fallback)) return fallback; else throw new Error(`Unsupported protocol ${protocol}`); } @@ -79,7 +97,10 @@ export function isValidAddressSealevel(address: Address) { export function isValidAddressCosmos(address: Address) { try { const isValid = - address && (IBC_DENOM_REGEX.test(address) || fromBech32(address)); + address && + (IBC_DENOM_REGEX.test(address) || + COSMOS_FACTORY_TOKEN_REGEX.test(address) || + fromBech32(address)); return !!isValid; } catch (error) { return false; @@ -215,8 +236,7 @@ export function capitalizeAddress(address: Address) { else return address.toUpperCase(); } -// For EVM addresses only, kept for backwards compatibility and convenience -export function addressToBytes32(address: Address): string { +export function addressToBytes32Evm(address: Address): string { return ethersUtils .hexZeroPad(ethersUtils.hexStripZeros(address), 32) .toLowerCase(); @@ -228,7 +248,7 @@ export function bytes32ToAddress(bytes32: HexString): Address { } export function addressToBytesEvm(address: Address): Uint8Array { - const addrBytes32 = addressToBytes32(address); + const addrBytes32 = addressToBytes32Evm(address); return Buffer.from(strip0x(addrBytes32), 'hex'); } @@ -240,7 +260,10 @@ export function addressToBytesCosmos(address: Address): Uint8Array { return fromBech32(address).data; } -export function addressToBytes(address: Address, protocol?: ProtocolType) { +export function addressToBytes( + address: Address, + protocol?: ProtocolType, +): Uint8Array { return routeAddressUtil( { [ProtocolType.Ethereum]: addressToBytesEvm, @@ -262,6 +285,29 @@ export function addressToByteHexString( ); } +export function addressToBytes32( + address: Address, + protocol?: ProtocolType, +): string { + // If the address is already bytes32, just return, avoiding a regression + // where an already bytes32 address cannot be categorized as a protocol address. + if (HEX_BYTES32_REGEX.test(ensure0x(address))) return ensure0x(address); + + const bytes = addressToBytes(address, protocol); + return bytesToBytes32(bytes); +} + +export function bytesToBytes32(bytes: Uint8Array): string { + if (bytes.length > 32) { + throw new Error('bytes must be 32 bytes or less'); + } + // This 0x-prefixes the hex string + return ethersUtils.hexZeroPad( + ensure0x(Buffer.from(bytes).toString('hex')), + 32, + ); +} + export function bytesToAddressEvm(bytes: Uint8Array): Address { return bytes32ToAddress(Buffer.from(bytes).toString('hex')); } diff --git a/typescript/utils/src/async.ts b/typescript/utils/src/async.ts index f57514d684..2665f816bf 100644 --- a/typescript/utils/src/async.ts +++ b/typescript/utils/src/async.ts @@ -102,3 +102,17 @@ export async function pollAsync( } throw saveError; } + +/** + * An enhanced Promise.race that returns + * objects with the promise itself and index + * instead of just the resolved value. + */ +export async function raceWithContext( + promises: Array>, +): Promise<{ resolved: T; promise: Promise; index: number }> { + const promisesWithContext = promises.map((p, i) => + p.then((resolved) => ({ resolved, promise: p, index: i })), + ); + return Promise.race(promisesWithContext); +} diff --git a/typescript/utils/src/objects.ts b/typescript/utils/src/objects.ts index a99e7ff1d2..bff85958b2 100644 --- a/typescript/utils/src/objects.ts +++ b/typescript/utils/src/objects.ts @@ -6,6 +6,10 @@ export function deepEquals(v1: any, v2: any) { return JSON.stringify(v1) === JSON.stringify(v2); } +export function deepCopy(v: any) { + return JSON.parse(JSON.stringify(v)); +} + export type ValueOf = T[keyof T]; export function objMapEntries< diff --git a/typescript/utils/src/typeof.ts b/typescript/utils/src/typeof.ts index eb08c4f15a..a204981dbb 100644 --- a/typescript/utils/src/typeof.ts +++ b/typescript/utils/src/typeof.ts @@ -1,6 +1,7 @@ -export function isNullish(val: T) { - if (val === null || val === undefined) return true; - else return false; +export function isNullish( + val: T, +): val is T extends null | undefined ? T : never { + return val === null || val === undefined; } export function isNumeric(value: string | number) { diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index b29c91fd28..a28e97acdc 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -17,11 +17,13 @@ export const ProtocolSmallestUnit = { /********* BASIC TYPES *********/ export type Domain = number; +export type ChainId = string | number; export type Address = string; export type AddressBytes32 = string; 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; +export type Numberish = number | string | bigint; // copied from node_modules/@ethersproject/bytes/src.ts/index.ts export type SignatureLike = diff --git a/typescript/utils/src/validation.ts b/typescript/utils/src/validation.ts index d5e85c43f3..1b3dd22899 100644 --- a/typescript/utils/src/validation.ts +++ b/typescript/utils/src/validation.ts @@ -1,4 +1,7 @@ -export function assert(predicate: any, errorMessage?: string) { +export function assert( + predicate: T, + errorMessage?: string, +): asserts predicate is NonNullable { if (!predicate) { throw new Error(errorMessage ?? 'Error'); } diff --git a/vectors/message.json b/vectors/message.json index 6a21a198c9..16644e9360 100644 --- a/vectors/message.json +++ b/vectors/message.json @@ -1 +1 @@ -[{"body":[18,52],"destination":2000,"id":"0x545b9ae16e93875efda786a09f3b78221d7f568f46a445fe4cd4a1e38096c576","nonce":0,"origin":1000,"recipient":"0x0000000000000000000000002222222222222222222222222222222222222222","sender":"0x0000000000000000000000001111111111111111111111111111111111111111","version":0}] \ No newline at end of file +[{"body":[18,52],"destination":2000,"id":"0xf8a66f8aadee751d842616fee0ed14a3ad6da1e13564920364ee0ad35a02703f","nonce":0,"origin":1000,"recipient":"0x0000000000000000000000002222222222222222222222222222222222222222","sender":"0x0000000000000000000000001111111111111111111111111111111111111111","version":3}] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 7bcc95324e..bef32c1d48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,10 +5,10 @@ __metadata: version: 8 cacheKey: 10 -"@adraffy/ens-normalize@npm:1.9.0": - version: 1.9.0 - resolution: "@adraffy/ens-normalize@npm:1.9.0" - checksum: 5257bbb45796720386a250ec2747f1c93b8fccd7063839ccda4aeb1b97ebec81caf5b435252dae1842c37175fe2e0ddcb89039f4ec1a4934fa4c6901a7f4992b +"@adraffy/ens-normalize@npm:1.10.0": + version: 1.10.0 + resolution: "@adraffy/ens-normalize@npm:1.10.0" + checksum: 5cdb5d2a9c9f8c0a71a7bb830967da0069cae1f1235cd41ae11147e4000f368f6958386e622cd4d52bf45c1ed3f8275056b387cba28902b83354e40ff323ecde languageName: node linkType: hard @@ -3191,33 +3191,14 @@ __metadata: languageName: node linkType: hard -"@eth-optimism/contracts-bedrock@npm:0.11.0": - version: 0.11.0 - resolution: "@eth-optimism/contracts-bedrock@npm:0.11.0" - dependencies: - "@eth-optimism/core-utils": "npm:^0.12.0" - "@openzeppelin/contracts": "npm:4.7.3" - "@openzeppelin/contracts-upgradeable": "npm:4.7.3" - ethers: "npm:^5.7.0" - hardhat: "npm:^2.9.6" - checksum: 26f2bf2fbaddc20c6f04f03855c9aecb569f0492c87be45e55a845659461332c7d2d0caa9fea8e3d3c6e4b612320388bfcca64c3bf106893417619addb6045fd - languageName: node - linkType: hard - -"@eth-optimism/contracts@npm:0.5.39": - version: 0.5.39 - resolution: "@eth-optimism/contracts@npm:0.5.39" - dependencies: - "@eth-optimism/core-utils": "npm:0.12.0" - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - peerDependencies: - ethers: ^5 - checksum: 941c254f5b101620afa38b23f315fbee9bae74be092f6571aa516662340b739bf504b38c4edadaafecf515d4bb0e4c84bf1b647f16b14f03c447e6c584025554 +"@eth-optimism/contracts-bedrock@npm:0.16.2": + version: 0.16.2 + resolution: "@eth-optimism/contracts-bedrock@npm:0.16.2" + checksum: 4708a5f0385e784c23bb40bc0c4321bac3ccc469ccba4491bb8ffbee267755bad5929215d1af923b372edb93f40bd8fb04d9a1e6caa4fb615776099d23688b9b languageName: node linkType: hard -"@eth-optimism/contracts@npm:^0.6.0": +"@eth-optimism/contracts@npm:0.6.0, @eth-optimism/contracts@npm:^0.6.0": version: 0.6.0 resolution: "@eth-optimism/contracts@npm:0.6.0" dependencies: @@ -3230,7 +3211,7 @@ __metadata: languageName: node linkType: hard -"@eth-optimism/core-utils@npm:0.12.0, @eth-optimism/core-utils@npm:^0.12.0": +"@eth-optimism/core-utils@npm:0.12.0": version: 0.12.0 resolution: "@eth-optimism/core-utils@npm:0.12.0" dependencies: @@ -3254,19 +3235,41 @@ __metadata: languageName: node linkType: hard -"@eth-optimism/sdk@npm:^1.7.0": - version: 1.8.0 - resolution: "@eth-optimism/sdk@npm:1.8.0" +"@eth-optimism/core-utils@npm:0.13.1": + version: 0.13.1 + resolution: "@eth-optimism/core-utils@npm:0.13.1" dependencies: - "@eth-optimism/contracts": "npm:0.5.39" - "@eth-optimism/contracts-bedrock": "npm:0.11.0" - "@eth-optimism/core-utils": "npm:0.12.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/abstract-provider": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/bytes": "npm:^5.7.0" + "@ethersproject/constants": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/keccak256": "npm:^5.7.0" + "@ethersproject/properties": "npm:^5.7.0" + "@ethersproject/rlp": "npm:^5.7.0" + "@ethersproject/web": "npm:^5.7.1" + chai: "npm:^4.3.9" + ethers: "npm:^5.7.2" + node-fetch: "npm:^2.6.7" + checksum: 7d9a3b94d05c3becce24562003032d6d2ddc4396e6420152ee3ad287a614ca513c53d43ecaeba5e238abb8bd85c352a42854a0f949df19cfb0219fc441e2da09 + languageName: node + linkType: hard + +"@eth-optimism/sdk@npm:^3.1.6": + version: 3.1.6 + resolution: "@eth-optimism/sdk@npm:3.1.6" + dependencies: + "@eth-optimism/contracts": "npm:0.6.0" + "@eth-optimism/contracts-bedrock": "npm:0.16.2" + "@eth-optimism/core-utils": "npm:0.13.1" lodash: "npm:^4.17.21" - merkletreejs: "npm:^0.2.27" + merkletreejs: "npm:^0.3.11" rlp: "npm:^2.2.7" peerDependencies: ethers: ^5 - checksum: 179bb561d30caca0f17affc8012fb4fd1ab30a566651f197111f3ad450f7decbf7ff212ba526ed95f02528694ff095d6e814bf642075a4e99c0e6de8dc322751 + checksum: 39ab8b94c7a4c4333ed31046de5a429529486b287a8080f7ad9f1a2d4c51d5f09a545931501c1b930578baca58cfc072626c460570b4bb6ba304f08061cb6f72 languageName: node linkType: hard @@ -4068,7 +4071,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:5.7.1": +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.1": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" dependencies: @@ -4232,8 +4235,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/sdk": "npm:3.1.10" - "@hyperlane-xyz/utils": "npm:3.1.10" + "@hyperlane-xyz/sdk": "npm:3.7.0" + "@hyperlane-xyz/utils": "npm:3.7.0" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -4246,6 +4249,7 @@ __metadata: eslint: "npm:^8.43.0" eslint-config-prettier: "npm:^8.8.0" ethers: "npm:^5.7.2" + latest-version: "npm:^8.0.0" mocha: "npm:^10.2.0" prettier: "npm:^2.8.8" terminal-link: "npm:^3.0.0" @@ -4258,12 +4262,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.1.10, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.7.0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:3.1.10" + "@hyperlane-xyz/utils": "npm:3.7.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts": "npm:^4.9.3" @@ -4290,12 +4294,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:3.1.10, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:3.7.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": "npm:3.1.10" - "@hyperlane-xyz/sdk": "npm:3.1.10" + "@hyperlane-xyz/core": "npm:3.7.0" + "@hyperlane-xyz/sdk": "npm:3.7.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -4336,13 +4340,14 @@ __metadata: "@aws-sdk/client-iam": "npm:^3.74.0" "@aws-sdk/client-kms": "npm:3.48.0" "@aws-sdk/client-s3": "npm:^3.74.0" - "@eth-optimism/sdk": "npm:^1.7.0" + "@cosmjs/amino": "npm:^0.31.3" + "@eth-optimism/sdk": "npm:^3.1.6" "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:3.1.10" - "@hyperlane-xyz/sdk": "npm:3.1.10" - "@hyperlane-xyz/utils": "npm:3.1.10" + "@hyperlane-xyz/helloworld": "npm:3.7.0" + "@hyperlane-xyz/sdk": "npm:3.7.0" + "@hyperlane-xyz/utils": "npm:3.7.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -4350,6 +4355,7 @@ __metadata: "@safe-global/protocol-kit": "npm:^1.2.0" "@solana/web3.js": "npm:^1.78.0" "@types/chai": "npm:^4.2.21" + "@types/json-stable-stringify": "npm:^1.0.36" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^16.9.1" "@types/prompts": "npm:^2.0.14" @@ -4362,6 +4368,7 @@ __metadata: ethereum-waffle: "npm:^4.0.10" ethers: "npm:^5.7.2" hardhat: "npm:^2.19.0" + json-stable-stringify: "npm:^1.1.1" mocha: "npm:^10.2.0" prettier: "npm:^2.8.8" prom-client: "npm:^14.0.1" @@ -4390,14 +4397,14 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:3.1.10, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.7.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.1.10" - "@hyperlane-xyz/utils": "npm:3.1.10" + "@hyperlane-xyz/core": "npm:3.7.0" + "@hyperlane-xyz/utils": "npm:3.7.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@solana/spl-token": "npm:^0.3.8" @@ -4409,7 +4416,7 @@ __metadata: "@types/sinon": "npm:^17.0.1" "@types/sinon-chai": "npm:^3.2.12" "@types/ws": "npm:^8.5.5" - "@wagmi/chains": "npm:^0.2.6" + "@wagmi/chains": "npm:^1.8.0" bignumber.js: "npm:^9.1.1" chai: "npm:^4.3.6" coingecko-api: "npm:^1.0.10" @@ -4426,7 +4433,8 @@ __metadata: sinon: "npm:^13.0.2" ts-node: "npm:^10.8.0" typescript: "npm:5.1.6" - viem: "npm:^1.3.1" + viem: "npm:^1.20.0" + yaml: "npm:^2.3.1" zod: "npm:^3.21.2" peerDependencies: "@ethersproject/abi": "*" @@ -4434,7 +4442,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:3.1.10, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:3.7.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -4816,12 +4824,12 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.0.0, @noble/curves@npm:~1.0.0": - version: 1.0.0 - resolution: "@noble/curves@npm:1.0.0" +"@noble/curves@npm:1.2.0, @noble/curves@npm:~1.2.0": + version: 1.2.0 + resolution: "@noble/curves@npm:1.2.0" dependencies: - "@noble/hashes": "npm:1.3.0" - checksum: 6db884e03b3f6c773317bcf4611bf1d9adb8084eab0bf6158407cc998c9c5dcb0560741bdd0aaca9c4393c9e8a3dcd7592b4148a6cfd561d0a00addb77a6129f + "@noble/hashes": "npm:1.3.2" + checksum: 94e02e9571a9fd42a3263362451849d2f54405cb3ce9fa7c45bc6b9b36dcd7d1d20e2e1e14cfded24937a13d82f1e60eefc4d7a14982ce0bc219a9fc0f51d1f9 languageName: node linkType: hard @@ -4841,13 +4849,6 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.0": - version: 1.3.0 - resolution: "@noble/hashes@npm:1.3.0" - checksum: 4680a71941c06ac897cc9eab9d229717d5af1147cea5e8cd4942190c817426ad3173ded750d897f58d764b869f9347d4fc3f6b3c16574541ac81906efa9ddc36 - languageName: node - linkType: hard - "@noble/hashes@npm:1.3.1, @noble/hashes@npm:^1.3.0, @noble/hashes@npm:~1.3.0": version: 1.3.1 resolution: "@noble/hashes@npm:1.3.1" @@ -4855,13 +4856,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.3.1": +"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.3.1": version: 1.3.2 resolution: "@noble/hashes@npm:1.3.2" checksum: 685f59d2d44d88e738114b71011d343a9f7dce9dfb0a121f1489132f9247baa60bc985e5ec6f3213d114fbd1e1168e7294644e46cbd0ce2eba37994f28eeb51b languageName: node linkType: hard +"@noble/hashes@npm:~1.3.2": + version: 1.3.3 + resolution: "@noble/hashes@npm:1.3.3" + checksum: 1025ddde4d24630e95c0818e63d2d54ee131b980fe113312d17ed7468bc18f54486ac86c907685759f8a7e13c2f9b9e83ec7b67d1cc20836f36b5e4a65bb102d + languageName: node + linkType: hard + "@noble/secp256k1@npm:1.5.5, @noble/secp256k1@npm:~1.5.2": version: 1.5.5 resolution: "@noble/secp256k1@npm:1.5.5" @@ -4911,20 +4919,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-block@npm:^4.0.0": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-block@npm:4.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - ethereum-cryptography: "npm:0.1.3" - checksum: cbdd37fddeeb3aa29dd750409fc4ce1b3ef5691d45d4dc0808706e99080e6f3f4ee4b95e3ce14fccfb91296513196cea84095c64eb740f81ef38f3d9ab0d2a21 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-blockchain@npm:7.0.2": version: 7.0.2 resolution: "@nomicfoundation/ethereumjs-blockchain@npm:7.0.2" @@ -4946,26 +4940,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-blockchain@npm:^6.0.0": - version: 6.0.0 - resolution: "@nomicfoundation/ethereumjs-blockchain@npm:6.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-ethash": "npm:^2.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - abstract-level: "npm:^1.0.3" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - level: "npm:^8.0.0" - lru-cache: "npm:^5.1.1" - memory-level: "npm:^1.0.0" - checksum: c380735f69182576694b3ffd5eff7a5e8e562efb5cbd96e5cf186c68a0592acfb0effe4551cdf4ad366f831d4b7dd28c3347bee8ca845ae2352b8e36cdcbb6cb - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-common@npm:4.0.2": version: 4.0.2 resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.2" @@ -4976,16 +4950,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-common@npm:^3.0.0": - version: 3.0.0 - resolution: "@nomicfoundation/ethereumjs-common@npm:3.0.0" - dependencies: - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - crc-32: "npm:^1.2.0" - checksum: d7012b0d05fba75e6ad2f54e60f777231896377a0a369af22d4868e4e260fb7e2586ac8d51c35b56ea2c1fa2e62dfef8669b653ed3e3b7816cfc38f2acf26090 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-ethash@npm:3.0.2": version: 3.0.2 resolution: "@nomicfoundation/ethereumjs-ethash@npm:3.0.2" @@ -5000,20 +4964,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-ethash@npm:^2.0.0": - version: 2.0.0 - resolution: "@nomicfoundation/ethereumjs-ethash@npm:2.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - abstract-level: "npm:^1.0.3" - bigint-crypto-utils: "npm:^3.0.23" - ethereum-cryptography: "npm:0.1.3" - checksum: dcabac814b7496a19824f6048157a095713afcb0676f4dd3d6172e579b31789dccd0f4417847c1901af3092be0021cc3e98f7f770a81766f505da93ff5930b00 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-evm@npm:2.0.2": version: 2.0.2 resolution: "@nomicfoundation/ethereumjs-evm@npm:2.0.2" @@ -5030,22 +4980,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-evm@npm:^1.0.0": - version: 1.0.0 - resolution: "@nomicfoundation/ethereumjs-evm@npm:1.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@types/async-eventemitter": "npm:^0.2.1" - async-eventemitter: "npm:^0.2.4" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - mcl-wasm: "npm:^0.7.1" - rustbn.js: "npm:~0.2.0" - checksum: 5a86ded335d74e63564d0b012c2e7807a1f8c9a2aca65a152fab5eb8200b7d2f23bc9fc31e26a3403a76eaba37ccf1bae4be81310da5e4619f8e1d63f1d0e3fd - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-rlp@npm:5.0.2": version: 5.0.2 resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.2" @@ -5055,15 +4989,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-rlp@npm:^4.0.0, @nomicfoundation/ethereumjs-rlp@npm:^4.0.0-beta.2": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-rlp@npm:4.0.0" - bin: - rlp: bin/rlp - checksum: 9e3e52876e408583cf3b19fd65979162eded9e1d02b699d4936a53bfba4155713084d0b37dbc938ec6a34e5bef61b9f2e3682684a86c6a29ea3900ac51113e92 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2": version: 2.0.2 resolution: "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2" @@ -5078,21 +5003,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-statemanager@npm:^1.0.0": - version: 1.0.0 - resolution: "@nomicfoundation/ethereumjs-statemanager@npm:1.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - functional-red-black-tree: "npm:^1.0.1" - checksum: faf7629ec3b5b494a955c9ab7a2e775c01796ad6b2441d10562407056b6a6e06ff751ac03ea131c3eddedc12189f1ae382be0cf2854fdce17f28ce62eb3c2f42 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-trie@npm:6.0.2": version: 6.0.2 resolution: "@nomicfoundation/ethereumjs-trie@npm:6.0.2" @@ -5106,18 +5016,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-trie@npm:^5.0.0": - version: 5.0.0 - resolution: "@nomicfoundation/ethereumjs-trie@npm:5.0.0" - dependencies: - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - ethereum-cryptography: "npm:0.1.3" - readable-stream: "npm:^3.6.0" - checksum: 709cfbb7be2c64208a3e712c27f0bc0580fc723d12340b211353beeddb886f68059170d51529650a090b821b6a21dbf63dc67499f23877e377fbc97295254b12 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-tx@npm:5.0.2": version: 5.0.2 resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.2" @@ -5132,18 +5030,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-tx@npm:^4.0.0": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-tx@npm:4.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - ethereum-cryptography: "npm:0.1.3" - checksum: 0833c6a4bfe5ad77a2931710ebe79d972d8e89eb59c9066bb0c7347cd6135a66bf0901c55e8c84827c1edad9200446ef0e5260a6026630552c9828e7f4f123a1 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-util@npm:9.0.2": version: 9.0.2 resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.2" @@ -5155,16 +5041,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-util@npm:^8.0.0": - version: 8.0.0 - resolution: "@nomicfoundation/ethereumjs-util@npm:8.0.0" - dependencies: - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0-beta.2" - ethereum-cryptography: "npm:0.1.3" - checksum: 8da64af39b83750347bf73fb30b169e1bf702cc26d6f94434a78521b569af9ebbd5c23d729e5e1ce38b22e354696af83f823bccc95f8a427c1baf722936d5404 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-vm@npm:7.0.2": version: 7.0.2 resolution: "@nomicfoundation/ethereumjs-vm@npm:7.0.2" @@ -5186,30 +5062,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-vm@npm:^6.0.0": - version: 6.0.0 - resolution: "@nomicfoundation/ethereumjs-vm@npm:6.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-blockchain": "npm:^6.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-evm": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-statemanager": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@types/async-eventemitter": "npm:^0.2.1" - async-eventemitter: "npm:^0.2.4" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - functional-red-black-tree: "npm:^1.0.1" - mcl-wasm: "npm:^0.7.1" - rustbn.js: "npm:~0.2.0" - checksum: 16c34baef9a8561d6d580164f258f03d8e0573b76cabb2373835e30ee684aef70d121c4d3a73f3fa5e82081a8b81f643326d11861b18f36d9dcf8e7cf157be96 - languageName: node - linkType: hard - "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0": version: 0.1.0 resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0" @@ -5382,13 +5234,6 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts-upgradeable@npm:4.7.3": - version: 4.7.3 - resolution: "@openzeppelin/contracts-upgradeable@npm:4.7.3" - checksum: 7c72ffeca867478b5aa8e8c7adb3d1ce114cfdc797ed4f3cd074788cf4da25d620ffffd624ac7e9d1223eecffeea9f7b79200ff70dc464cc828c470ccd12ddf1 - languageName: node - linkType: hard - "@openzeppelin/contracts-upgradeable@npm:^4.9.3, @openzeppelin/contracts-upgradeable@npm:^v4.9.3": version: 4.9.3 resolution: "@openzeppelin/contracts-upgradeable@npm:4.9.3" @@ -5396,13 +5241,6 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:4.7.3": - version: 4.7.3 - resolution: "@openzeppelin/contracts@npm:4.7.3" - checksum: 3d16ed8943938373ecc331c2ab83c3e8d0d89aed0c2a109aaa61ca6524b4c31cb5a81185c6f93ce9ee2dda685a4328fd85bd217929ae598f4be813d5d4cd1b78 - languageName: node - linkType: hard - "@openzeppelin/contracts@npm:^4.9.3": version: 4.9.3 resolution: "@openzeppelin/contracts@npm:4.9.3" @@ -5619,6 +5457,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.2": + version: 1.1.4 + resolution: "@scure/base@npm:1.1.4" + checksum: e735792dc5555f9d11b93fbaffbcf65177621a60d25d54d84439155611ee98b5f6fb631ca06ef0c57a65bc4e40277331b7364e96cf7f164908bec98b546f3442 + languageName: node + linkType: hard + "@scure/bip32@npm:1.0.1": version: 1.0.1 resolution: "@scure/bip32@npm:1.0.1" @@ -5630,14 +5475,14 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.3.0": - version: 1.3.0 - resolution: "@scure/bip32@npm:1.3.0" +"@scure/bip32@npm:1.3.2": + version: 1.3.2 + resolution: "@scure/bip32@npm:1.3.2" dependencies: - "@noble/curves": "npm:~1.0.0" - "@noble/hashes": "npm:~1.3.0" - "@scure/base": "npm:~1.1.0" - checksum: 1fabcc7f2215910b35980bfc455c03fc4ae7f848efed066fe3867960a8dfceb6141c932496434fc2cfbf385d270ff9efdfce2571992e4584103f82e45ac2103f + "@noble/curves": "npm:~1.2.0" + "@noble/hashes": "npm:~1.3.2" + "@scure/base": "npm:~1.1.2" + checksum: b90da28dfe75519496a85c97e77c9443734873910f32b8557762910a5c4e642290a462b0ed14fa42e0efed6acb9a7f6155ad5cb5d38d4ff87eb2de4760eb32a4 languageName: node linkType: hard @@ -5651,13 +5496,13 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.2.0": - version: 1.2.0 - resolution: "@scure/bip39@npm:1.2.0" +"@scure/bip39@npm:1.2.1": + version: 1.2.1 + resolution: "@scure/bip39@npm:1.2.1" dependencies: "@noble/hashes": "npm:~1.3.0" "@scure/base": "npm:~1.1.0" - checksum: 2a260eefea0b2658c5d3b2cb982479ef650552c3007e57f667b445943c79717eb923c1a104a664b4873bc210aeb59859bf890c3e7b47fb51ed5b94dc96f75105 + checksum: 2ea368bbed34d6b1701c20683bf465e147f231a9e37e639b8c82f585d6f978bb0f3855fca7ceff04954ae248b3e313f5d322d0210614fb7acb402739415aaf31 languageName: node linkType: hard @@ -6030,13 +5875,6 @@ __metadata: languageName: node linkType: hard -"@types/async-eventemitter@npm:^0.2.1": - version: 0.2.1 - resolution: "@types/async-eventemitter@npm:0.2.1" - checksum: 52f6a9c6773edec9bc8449273de8b08fca45ebbf1907c755cd67be9aca4f26988aebb6d0e461aecf01463b76f0e1b427f149a8ce54d27cec191702488c676f48 - languageName: node - linkType: hard - "@types/bn.js@npm:^4.11.3": version: 4.11.6 resolution: "@types/bn.js@npm:4.11.6" @@ -6166,6 +6004,13 @@ __metadata: languageName: node linkType: hard +"@types/json-stable-stringify@npm:^1.0.36": + version: 1.0.36 + resolution: "@types/json-stable-stringify@npm:1.0.36" + checksum: 765b07589e11a3896c3d06bb9e3a9be681e7edd95adf27370df0647a91bd2bfcfaf0e091fd4a13729343b388973f73f7e789d6cc62ab988240518a2d27c4a4e2 + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.1, @types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -6620,27 +6465,15 @@ __metadata: languageName: node linkType: hard -"@wagmi/chains@npm:1.6.0": - version: 1.6.0 - resolution: "@wagmi/chains@npm:1.6.0" +"@wagmi/chains@npm:^1.8.0": + version: 1.8.0 + resolution: "@wagmi/chains@npm:1.8.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 809954d65e0d32cfbf079f7f3df1ff6596d1da96187e177597bcaf0fa71532e7416186f3f7e94efe45cf529b1709a57b08e0e06b67b5529223221a6b82f1dc78 - languageName: node - linkType: hard - -"@wagmi/chains@npm:^0.2.6": - version: 0.2.6 - resolution: "@wagmi/chains@npm:0.2.6" - peerDependencies: - typescript: ">=4.9.4" - peerDependenciesMeta: - typescript: - optional: true - checksum: b56c6ec78fc077795b58757123bf04f387ecc7c3ee2088f9192de9d8e4e7b7f07bfaa8761ef8ecd180bb0a6276cf6827ce4b5958eb8d26b1ee7a90038d595ac6 + checksum: 8248419554a90c0d514acfc46f3a6f2090a282ff546b2488705e81fcdfaf197590e67a1fc62539383b4dd22ccafe9f16018cadad27acee098dc9d87b82f173e4 languageName: node linkType: hard @@ -6670,9 +6503,9 @@ __metadata: languageName: node linkType: hard -"abitype@npm:0.9.3": - version: 0.9.3 - resolution: "abitype@npm:0.9.3" +"abitype@npm:0.9.8": + version: 0.9.8 + resolution: "abitype@npm:0.9.8" peerDependencies: typescript: ">=5.0.4" zod: ^3 >=3.19.1 @@ -6681,16 +6514,7 @@ __metadata: optional: true zod: optional: true - checksum: 6a3f71b76b5d9b1fcda2796b46fdb89d3deaa7e89f64e530dff373ae50a29fd1b34a53bc4291ffc6b097baf400baaa5f374f5c2b53987c223e6dad38f379d348 - languageName: node - linkType: hard - -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 + checksum: 90940804839b1b65cb5b427d934db9c1cc899157d6091f281b1ce94d9c0c08b1ae946ab43e984e70c031e94c49355f6677475a7242ec60cae5457c074dcd40f9 languageName: node linkType: hard @@ -7907,6 +7731,21 @@ __metadata: languageName: node linkType: hard +"chai@npm:^4.3.9": + version: 4.3.10 + resolution: "chai@npm:4.3.10" + dependencies: + assertion-error: "npm:^1.1.0" + check-error: "npm:^1.0.3" + deep-eql: "npm:^4.1.3" + get-func-name: "npm:^2.0.2" + loupe: "npm:^2.3.6" + pathval: "npm:^1.1.1" + type-detect: "npm:^4.0.8" + checksum: 9e545fd60f5efee4f06f7ad62f7b1b142932b08fbb3454db69defd511e7c58771ce51843764212da1e129b2c9d1b029fbf5f98da030fe67a95a0853e8679524f + languageName: node + linkType: hard + "chalk@npm:^2.0.0, chalk@npm:^2.1.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -7956,6 +7795,15 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: "npm:^2.0.2" + checksum: e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399 + languageName: node + linkType: hard + "chokidar@npm:3.3.0": version: 3.3.0 resolution: "chokidar@npm:3.3.0" @@ -8564,10 +8412,10 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:^3.1.9-1": - version: 3.3.0 - resolution: "crypto-js@npm:3.3.0" - checksum: d7e11f3a387fb143be834e1a25ecf57ead6f5765e90fbf3aed9cead680cc38b1d241718768b7bfec448a843f569374ea5b5870ac7a8165e4bfa1915f0b00c89c +"crypto-js@npm:^4.2.0": + version: 4.2.0 + resolution: "crypto-js@npm:4.2.0" + checksum: c7bcc56a6e01c3c397e95aa4a74e4241321f04677f9a618a8f48a63b5781617248afb9adb0629824792e7ec20ca0d4241a49b6b2938ae6f973ec4efc5c53c924 languageName: node linkType: hard @@ -8736,6 +8584,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.3": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" + dependencies: + type-detect: "npm:^4.0.0" + checksum: 12ce93ae63de187e77b076d3d51bfc28b11f98910a22c18714cce112791195e86a94f97788180994614b14562a86c9763f67c69f785e4586f806b5df39bf9301 + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0, deep-extend@npm:~0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -9749,13 +9606,6 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 - languageName: node - linkType: hard - "eventemitter3@npm:4.0.4": version: 4.0.4 resolution: "eventemitter3@npm:4.0.4" @@ -10481,6 +10331,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": version: 1.1.2 resolution: "get-intrinsic@npm:1.1.2" @@ -10819,6 +10676,25 @@ __metadata: languageName: node linkType: hard +"got@npm:^13.0.0": + version: 13.0.0 + resolution: "got@npm:13.0.0" + dependencies: + "@sindresorhus/is": "npm:^5.2.0" + "@szmarczak/http-timer": "npm:^5.0.1" + cacheable-lookup: "npm:^7.0.0" + cacheable-request: "npm:^10.2.8" + decompress-response: "npm:^6.0.0" + form-data-encoder: "npm:^2.1.2" + get-stream: "npm:^6.0.1" + http2-wrapper: "npm:^2.1.10" + lowercase-keys: "npm:^3.0.0" + p-cancelable: "npm:^3.0.0" + responselike: "npm:^3.0.0" + checksum: 35ac9fe37daca3d0a4f90305d8e64626268ef5a42584f5bcb42eea3cb9bbeb691cf9041d5ea72133a7295d1291684789a3148ff89a95f3d3ce3d0ebb6fb2f680 + languageName: node + linkType: hard + "got@npm:^7.1.0": version: 7.1.0 resolution: "got@npm:7.1.0" @@ -10997,74 +10873,6 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.9.6": - version: 2.12.4 - resolution: "hardhat@npm:2.12.4" - dependencies: - "@ethersproject/abi": "npm:^5.1.2" - "@metamask/eth-sig-util": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-blockchain": "npm:^6.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-evm": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-statemanager": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@nomicfoundation/ethereumjs-vm": "npm:^6.0.0" - "@nomicfoundation/solidity-analyzer": "npm:^0.1.0" - "@sentry/node": "npm:^5.18.1" - "@types/bn.js": "npm:^5.1.0" - "@types/lru-cache": "npm:^5.1.0" - abort-controller: "npm:^3.0.0" - adm-zip: "npm:^0.4.16" - aggregate-error: "npm:^3.0.0" - ansi-escapes: "npm:^4.3.0" - chalk: "npm:^2.4.2" - chokidar: "npm:^3.4.0" - ci-info: "npm:^2.0.0" - debug: "npm:^4.1.1" - enquirer: "npm:^2.3.0" - env-paths: "npm:^2.2.0" - ethereum-cryptography: "npm:^1.0.3" - ethereumjs-abi: "npm:^0.6.8" - find-up: "npm:^2.1.0" - fp-ts: "npm:1.19.3" - fs-extra: "npm:^7.0.1" - glob: "npm:7.2.0" - immutable: "npm:^4.0.0-rc.12" - io-ts: "npm:1.10.4" - keccak: "npm:^3.0.2" - lodash: "npm:^4.17.11" - mnemonist: "npm:^0.38.0" - mocha: "npm:^10.0.0" - p-map: "npm:^4.0.0" - qs: "npm:^6.7.0" - raw-body: "npm:^2.4.1" - resolve: "npm:1.17.0" - semver: "npm:^6.3.0" - solc: "npm:0.7.3" - source-map-support: "npm:^0.5.13" - stacktrace-parser: "npm:^0.1.10" - tsort: "npm:0.0.1" - undici: "npm:^5.4.0" - uuid: "npm:^8.3.2" - ws: "npm:^7.4.6" - peerDependencies: - ts-node: "*" - typescript: "*" - peerDependenciesMeta: - ts-node: - optional: true - typescript: - optional: true - bin: - hardhat: internal/cli/cli.js - checksum: c92dd697b105fc3a81de4a1061f44e95b2c06169afde4704d1cd26e038693c5c9869b9be219342cd1e243796b3119f583c776c65abfb2bf53ed0d3f3b27ed612 - languageName: node - linkType: hard - "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -11941,21 +11749,21 @@ __metadata: languageName: node linkType: hard -"isomorphic-ws@npm:5.0.0": - version: 5.0.0 - resolution: "isomorphic-ws@npm:5.0.0" +"isomorphic-ws@npm:^4.0.1": + version: 4.0.1 + resolution: "isomorphic-ws@npm:4.0.1" peerDependencies: ws: "*" - checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + checksum: d7190eadefdc28bdb93d67b5f0c603385aaf87724fa2974abb382ac1ec9756ed2cfb27065cbe76122879c2d452e2982bc4314317f3d6c737ddda6c047328771a languageName: node linkType: hard -"isomorphic-ws@npm:^4.0.1": - version: 4.0.1 - resolution: "isomorphic-ws@npm:4.0.1" +"isows@npm:1.0.3": + version: 1.0.3 + resolution: "isows@npm:1.0.3" peerDependencies: ws: "*" - checksum: d7190eadefdc28bdb93d67b5f0c603385aaf87724fa2974abb382ac1ec9756ed2cfb27065cbe76122879c2d452e2982bc4314317f3d6c737ddda6c047328771a + checksum: 9cacd5cf59f67deb51e825580cd445ab1725ecb05a67c704050383fb772856f3cd5e7da8ad08f5a3bd2823680d77d099459d0c6a7037972a74d6429af61af440 languageName: node linkType: hard @@ -12135,6 +11943,18 @@ __metadata: languageName: node linkType: hard +"json-stable-stringify@npm:^1.1.1": + version: 1.1.1 + resolution: "json-stable-stringify@npm:1.1.1" + dependencies: + call-bind: "npm:^1.0.5" + isarray: "npm:^2.0.5" + jsonify: "npm:^0.0.1" + object-keys: "npm:^1.1.1" + checksum: 60853c1f63451319b5c7953465a555aa816cf84e60e3ca36b6c05225d8fdc4615127fb4ecb92f9f5ad880c552ab8cbae9a519f78b995e7788d6d89e57afafdeb + languageName: node + linkType: hard + "json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" @@ -12179,6 +11999,13 @@ __metadata: languageName: node linkType: hard +"jsonify@npm:^0.0.1": + version: 0.0.1 + resolution: "jsonify@npm:0.0.1" + checksum: 7b86b6f4518582ff1d8b7624ed6c6277affd5246445e864615dbdef843a4057ac58587684faf129ea111eeb80e01c15f0a4d9d03820eb3f3985fa67e81b12398 + languageName: node + linkType: hard + "jsonparse@npm:^1.2.0": version: 1.3.1 resolution: "jsonparse@npm:1.3.1" @@ -12295,6 +12122,15 @@ __metadata: languageName: node linkType: hard +"latest-version@npm:^8.0.0": + version: 8.0.0 + resolution: "latest-version@npm:8.0.0" + dependencies: + package-json: "npm:^9.0.0" + checksum: 8ef0ff0006a9df21a191991fbaee74b7bcd8f20d75dba0ac92932944a22625f1e76aadd33feb0ba1cfa45846a6238625ae603453189f71cf3c5c358989929aea + languageName: node + linkType: hard + "level-codec@npm:^9.0.0": version: 9.0.2 resolution: "level-codec@npm:9.0.2" @@ -12684,6 +12520,15 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^2.3.6": + version: 2.3.7 + resolution: "loupe@npm:2.3.7" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 635c8f0914c2ce7ecfe4e239fbaf0ce1d2c00e4246fafcc4ed000bfdb1b8f89d05db1a220054175cca631ebf3894872a26fffba0124477fcb562f78762848fb1 + languageName: node + linkType: hard + "lowercase-keys@npm:^1.0.0": version: 1.0.1 resolution: "lowercase-keys@npm:1.0.1" @@ -12917,16 +12762,16 @@ __metadata: languageName: node linkType: hard -"merkletreejs@npm:^0.2.27": - version: 0.2.32 - resolution: "merkletreejs@npm:0.2.32" +"merkletreejs@npm:^0.3.11": + version: 0.3.11 + resolution: "merkletreejs@npm:0.3.11" dependencies: bignumber.js: "npm:^9.0.1" buffer-reverse: "npm:^1.0.1" - crypto-js: "npm:^3.1.9-1" + crypto-js: "npm:^4.2.0" treeify: "npm:^1.1.0" web3-utils: "npm:^1.3.4" - checksum: 00c53a7fe9e87150b262b249f75c9e925641a628388d327f1b5fb4de9d146c95e58c67abe6771d5217b2555ebc3a9cc433ce958003f85dde58bda535225e853f + checksum: a93520ef768648d1e4ebd175182bd3d304270b8eb0700df5be99395fb3ad8805407bdaf3231d13b1649e87de799aa06d71c616db047ad039025764eb23b02244 languageName: node linkType: hard @@ -14142,6 +13987,18 @@ __metadata: languageName: node linkType: hard +"package-json@npm:^9.0.0": + version: 9.0.0 + resolution: "package-json@npm:9.0.0" + dependencies: + got: "npm:^13.0.0" + registry-auth-token: "npm:^5.0.2" + registry-url: "npm:^6.0.1" + semver: "npm:^7.5.4" + checksum: 7c3847b521b221a7f80264880e0c8b0f290796072771fb1c1d0dc36d0c59141e8eff6aa2b05056f8e85f07d872e2e68032378b674be4252311e3328b02df3e01 + languageName: node + linkType: hard + "pako@npm:^2.0.2": version: 2.1.0 resolution: "pako@npm:2.1.0" @@ -14620,7 +14477,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.7.0": +"qs@npm:^6.4.0": version: 6.10.5 resolution: "qs@npm:6.10.5" dependencies: @@ -14885,7 +14742,7 @@ __metadata: languageName: node linkType: hard -"registry-auth-token@npm:^5.0.1": +"registry-auth-token@npm:^5.0.1, registry-auth-token@npm:^5.0.2": version: 5.0.2 resolution: "registry-auth-token@npm:5.0.2" dependencies: @@ -14894,7 +14751,7 @@ __metadata: languageName: node linkType: hard -"registry-url@npm:^6.0.0": +"registry-url@npm:^6.0.0, registry-url@npm:^6.0.1": version: 6.0.1 resolution: "registry-url@npm:6.0.1" dependencies: @@ -16969,7 +16826,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.1.6, typescript@npm:^5.1.6": +"typescript@npm:5.1.6": version: 5.1.6 resolution: "typescript@npm:5.1.6" bin: @@ -16979,7 +16836,17 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.1.6#optional!builtin, typescript@patch:typescript@npm%3A^5.1.6#optional!builtin": +"typescript@npm:^5.1.6": + version: 5.3.2 + resolution: "typescript@npm:5.3.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 415e5fb6611f5713e460bad48039f00bcfdbde53a2f911727862d5aa9c5d5edd250059a419df382d8f031709e15a169c41eb62b6a401da5eec7ac0f4e359d6ac + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.1.6#optional!builtin": version: 5.1.6 resolution: "typescript@patch:typescript@npm%3A5.1.6#optional!builtin::version=5.1.6&hash=5da071" bin: @@ -16989,6 +16856,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.1.6#optional!builtin": + version: 5.3.2 + resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin::version=5.3.2&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 1b45cdfb577a78ae7a9a9d0b77a7b772142cb98ba05e4e5aefba7044a028ded885bcecef63166407a5986645cea816fe4986894336aacd5e791796ea79a6a7ed + languageName: node + linkType: hard + "typical@npm:^4.0.0": version: 4.0.0 resolution: "typical@npm:4.0.0" @@ -17290,25 +17167,24 @@ __metadata: languageName: node linkType: hard -"viem@npm:^1.3.1": - version: 1.3.1 - resolution: "viem@npm:1.3.1" - dependencies: - "@adraffy/ens-normalize": "npm:1.9.0" - "@noble/curves": "npm:1.0.0" - "@noble/hashes": "npm:1.3.0" - "@scure/bip32": "npm:1.3.0" - "@scure/bip39": "npm:1.2.0" - "@wagmi/chains": "npm:1.6.0" - abitype: "npm:0.9.3" - isomorphic-ws: "npm:5.0.0" - ws: "npm:8.12.0" +"viem@npm:^1.20.0": + version: 1.20.0 + resolution: "viem@npm:1.20.0" + dependencies: + "@adraffy/ens-normalize": "npm:1.10.0" + "@noble/curves": "npm:1.2.0" + "@noble/hashes": "npm:1.3.2" + "@scure/bip32": "npm:1.3.2" + "@scure/bip39": "npm:1.2.1" + abitype: "npm:0.9.8" + isows: "npm:1.0.3" + ws: "npm:8.13.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 073edf64453dd8bbce7bc6c6ac6a0281d6acc1e4ae7a770a01dba86c86e5a28ea5b466bb2f86833f3a822e699067ff23e1b6da1a272c8b9b7f1b7eb56eecc552 + checksum: f6f232e1b75085b2088e842a44d3e574afd3e603420cf65181d6fb0dec7942d0c4f4d3cdae8f7a43ddb9942dcb185443b1d5b7daec8a009a3cbab9436bea2164 languageName: node linkType: hard @@ -17941,9 +17817,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.12.0": - version: 8.12.0 - resolution: "ws@npm:8.12.0" +"ws@npm:8.13.0, ws@npm:^8.5.0": + version: 8.13.0 + resolution: "ws@npm:8.13.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -17952,7 +17828,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 325fbcf6bbed07350b82d7a5bdb43e8a4e81512973241c656c2119a37883a74fe49e7cac09646f9bfc28c517cd63f4111c78f5898bcdd25a3ec2cc4e59375331 + checksum: 1769532b6fdab9ff659f0b17810e7501831d34ecca23fd179ee64091dd93a51f42c59f6c7bb4c7a384b6c229aca8076fb312aa35626257c18081511ef62a161d languageName: node linkType: hard @@ -17997,21 +17873,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.5.0": - version: 8.13.0 - resolution: "ws@npm:8.13.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 1769532b6fdab9ff659f0b17810e7501831d34ecca23fd179ee64091dd93a51f42c59f6c7bb4c7a384b6c229aca8076fb312aa35626257c18081511ef62a161d - languageName: node - linkType: hard - "xhr-request-promise@npm:^0.1.2": version: 0.1.3 resolution: "xhr-request-promise@npm:0.1.3"